providers/samlv2: remove SAMLv2 from master
This commit is contained in:
		| @ -1,11 +0,0 @@ | ||||
| """passbook saml provider app config""" | ||||
| from django.apps import AppConfig | ||||
|  | ||||
|  | ||||
| class PassbookProviderSAMLv2Config(AppConfig): | ||||
|     """passbook samlv2 provider app config""" | ||||
|  | ||||
|     name = "passbook.providers.samlv2" | ||||
|     label = "passbook_providers_samlv2" | ||||
|     verbose_name = "passbook Providers.SAMLv2" | ||||
|     mountpoint = "application/samlv2/" | ||||
| @ -1,15 +0,0 @@ | ||||
| """SAML-related constants""" | ||||
| NS_SAML_PROTOCOL = "urn:oasis:names:tc:SAML:2.0:protocol" | ||||
| NS_SAML_ASSERTION = "urn:oasis:names:tc:SAML:2.0:assertion" | ||||
| NS_SIGNATURE = "http://www.w3.org/2000/09/xmldsig#" | ||||
|  | ||||
| REQ_KEY_REQUEST = "SAMLRequest" | ||||
| REQ_KEY_SIGNATURE = "Signature" | ||||
|  | ||||
| SESSION_KEY = "passbook_saml_request" | ||||
|  | ||||
| SAML_ATTRIB_ACS_URL = "AssertionConsumerServiceURL" | ||||
| SAML_ATTRIB_DESTINATION = "Destination" | ||||
| SAML_ATTRIB_ID = "ID" | ||||
| SAML_ATTRIB_ISSUE_INSTANT = "IssueInstant" | ||||
| SAML_ATTRIB_PROTOCOL_BINDING = "ProtocolBinding" | ||||
| @ -1,83 +0,0 @@ | ||||
| """SAML Request Parse/builder""" | ||||
| from typing import TYPE_CHECKING, Optional | ||||
|  | ||||
| from defusedxml import ElementTree | ||||
| from signxml import XMLVerifier | ||||
|  | ||||
| from passbook.crypto.models import CertificateKeyPair | ||||
| from passbook.providers.samlv2.saml.constants import ( | ||||
|     NS_SAML_ASSERTION, | ||||
|     NS_SAML_PROTOCOL, | ||||
|     SAML_ATTRIB_ACS_URL, | ||||
|     SAML_ATTRIB_DESTINATION, | ||||
|     SAML_ATTRIB_ID, | ||||
|     SAML_ATTRIB_ISSUE_INSTANT, | ||||
|     SAML_ATTRIB_PROTOCOL_BINDING, | ||||
| ) | ||||
| from passbook.providers.samlv2.saml.utils import decode_base64_and_inflate | ||||
|  | ||||
| if TYPE_CHECKING: | ||||
|     from xml.etree.ElementTree import Element  # nosec | ||||
|  | ||||
|  | ||||
| # pylint: disable=too-many-instance-attributes | ||||
| class SAMLRequest: | ||||
|     """SAML Request data class, parse raw base64-encoded data, checks signature and more""" | ||||
|  | ||||
|     _root: "Element" | ||||
|  | ||||
|     acs_url: str | ||||
|     destination: str | ||||
|     id: str | ||||
|     issue_instant: str | ||||
|     protocol_binding: str | ||||
|  | ||||
|     issuer: str | ||||
|  | ||||
|     is_signed: bool | ||||
|     _detached_signature: str | ||||
|  | ||||
|     def __init__(self): | ||||
|         self.acs_url = "" | ||||
|         self.destination = "" | ||||
|         # pylint: disable=invalid-name | ||||
|         self.id = "" | ||||
|         self.issue_instant = "" | ||||
|         self.protocol_binding = "" | ||||
|  | ||||
|     @staticmethod | ||||
|     def parse(raw: str, detached_signature: Optional[str] = None) -> "SAMLRequest": | ||||
|         """Prase SAML request from raw string, which can be base64 encoded and deflated. | ||||
|         Optionally accepts a detached_signature, as from a REDIRECT request.""" | ||||
|         decoded_xml = decode_base64_and_inflate(raw) | ||||
|         root = ElementTree.fromstring(decoded_xml) | ||||
|         req = SAMLRequest() | ||||
|         req._root = root  # pylint: disable=protected-access | ||||
|         # Verify the root element's tag | ||||
|         _expected_tag = f"{{{NS_SAML_PROTOCOL}}}AuthnRequest" | ||||
|         if root.tag != _expected_tag: | ||||
|             raise ValueError( | ||||
|                 f"Invalid root tag, got '{root.tag}', expected '{_expected_tag}." | ||||
|             ) | ||||
|         req.acs_url = root.attrib[SAML_ATTRIB_ACS_URL] | ||||
|         req.destination = root.attrib[SAML_ATTRIB_DESTINATION] | ||||
|         req.id = root.attrib[SAML_ATTRIB_ID] | ||||
|         req.issue_instant = root.attrib[SAML_ATTRIB_ISSUE_INSTANT] | ||||
|         req.protocol_binding = root.attrib[SAML_ATTRIB_PROTOCOL_BINDING] | ||||
|         req.issuer = root.find(f"{{{NS_SAML_ASSERTION}}}Issuer").text | ||||
|         # Check if this Request is signed | ||||
|         if detached_signature: | ||||
|             # pylint: disable=protected-access | ||||
|             req._detached_signature = detached_signature | ||||
|         return req | ||||
|  | ||||
|     def verify_signature(self, keypair: CertificateKeyPair): | ||||
|         """Verify signature of SAML Request. | ||||
|         Raises `cryptography.exceptions.InvalidSignature` on validaton failure.""" | ||||
|         verifier = XMLVerifier() | ||||
|         if self._detached_signature: | ||||
|             verifier.verify( | ||||
|                 self._detached_signature, x509_cert=keypair.certificate_data | ||||
|             ) | ||||
|         else: | ||||
|             verifier.verify(self._root, x509_cert=keypair.certificate_data) | ||||
| @ -1,5 +0,0 @@ | ||||
| """SAML Provider logic""" | ||||
|  | ||||
|  | ||||
| class SAMLProvider: | ||||
|     """SAML Provider""" | ||||
| @ -1,24 +0,0 @@ | ||||
| """Wrappers to de/encode and de/inflate strings""" | ||||
| import base64 | ||||
| import zlib | ||||
|  | ||||
|  | ||||
| def decode_base64_and_inflate(encoded: str, encoding="utf-8") -> str: | ||||
|     """Base64 decode and ZLib decompress b64string""" | ||||
|     decoded_data = base64.b64decode(encoded) | ||||
|     try: | ||||
|         return zlib.decompress(decoded_data, -15).decode(encoding) | ||||
|     except zlib.error: | ||||
|         return decoded_data.decode(encoding) | ||||
|  | ||||
|  | ||||
| def deflate_and_base64_encode(inflated: bytes, encoding="utf-8"): | ||||
|     """Base64 and ZLib Compress b64string""" | ||||
|     zlibbed_str = zlib.compress(inflated) | ||||
|     compressed_string = zlibbed_str[2:-4] | ||||
|     return base64.b64encode(compressed_string).decode(encoding) | ||||
|  | ||||
|  | ||||
| def nice64(src): | ||||
|     """ Returns src base64-encoded and formatted nicely for our XML. """ | ||||
|     return base64.b64encode(src).decode("utf-8").replace("\n", "") | ||||
| @ -1,35 +0,0 @@ | ||||
| """passbook samlv2 URLs""" | ||||
| from django.urls import path | ||||
|  | ||||
| from passbook.providers.samlv2.views import authorize, idp_initiated, slo, sso | ||||
|  | ||||
| urlpatterns = [ | ||||
|     path( | ||||
|         "<slug:app_slug>/authorize/", | ||||
|         authorize.AuthorizeView.as_view(), | ||||
|         name="authorize", | ||||
|     ), | ||||
|     path( | ||||
|         "<slug:app_slug>/sso/redirect/", | ||||
|         sso.SAMLRedirectBindingView.as_view(), | ||||
|         name="sso-redirect", | ||||
|     ), | ||||
|     path( | ||||
|         "<slug:app_slug>/sso/post/", sso.SAMLPostBindingView.as_view(), name="sso-post", | ||||
|     ), | ||||
|     path( | ||||
|         "<slug:app_slug>/slo/redirect/", | ||||
|         slo.SAMLRedirectBindingView.as_view(), | ||||
|         name="slo-redirect", | ||||
|     ), | ||||
|     path( | ||||
|         "<slug:app_slug>/slo/redirect/", | ||||
|         slo.SAMLPostBindingView.as_view(), | ||||
|         name="slo-post", | ||||
|     ), | ||||
|     path( | ||||
|         "<slug:app_slug>/initiate/", | ||||
|         idp_initiated.IDPInitiatedView.as_view(), | ||||
|         name="initiate", | ||||
|     ), | ||||
| ] | ||||
| @ -1,6 +0,0 @@ | ||||
| """SAML Provider authorization view""" | ||||
| from django.views.generic import FormView | ||||
|  | ||||
|  | ||||
| class AuthorizeView(FormView): | ||||
|     """Authorization view""" | ||||
| @ -1,31 +0,0 @@ | ||||
| """SAML base views""" | ||||
| from typing import Optional | ||||
|  | ||||
| from django.http import HttpRequest, HttpResponse | ||||
| from django.shortcuts import get_object_or_404 | ||||
| from django.views import View | ||||
|  | ||||
| from passbook.core.models import Application | ||||
| from passbook.policies.mixins import PolicyAccessMixin | ||||
| from passbook.providers.samlv2.saml.constants import SESSION_KEY | ||||
| from passbook.providers.samlv2.saml.parser import SAMLRequest | ||||
|  | ||||
|  | ||||
| class BaseSAMLView(PolicyAccessMixin, View): | ||||
|     """Base SAML View to resolve app_slug""" | ||||
|  | ||||
|     application: Application | ||||
|  | ||||
|     def setup(self, request: HttpRequest, *args, **kwargs): | ||||
|         View.setup(self, request, *args, **kwargs) | ||||
|         self.application = self.get_application(self.kwargs.get("app_slug")) | ||||
|  | ||||
|     def get_application(self, app_slug: str) -> Optional[Application]: | ||||
|         """Return application or raise 404""" | ||||
|         return get_object_or_404(Application, slug=app_slug) | ||||
|  | ||||
|     def handle_saml_request(self, request: SAMLRequest) -> HttpResponse: | ||||
|         """Handle SAML Request""" | ||||
|         self.request.SESSION[SESSION_KEY] = request | ||||
|         if self.application.skip_authorization: | ||||
|             pass | ||||
| @ -1,6 +0,0 @@ | ||||
| """IDP-Initiated Views""" | ||||
| from django.views import View | ||||
|  | ||||
|  | ||||
| class IDPInitiatedView(View): | ||||
|     """IDP-initiated Handler""" | ||||
| @ -1,10 +0,0 @@ | ||||
| """Single Logout Views""" | ||||
| from django.views import View | ||||
|  | ||||
|  | ||||
| class SAMLPostBindingView(View): | ||||
|     """Handle SAML POST-type Requests""" | ||||
|  | ||||
|  | ||||
| class SAMLRedirectBindingView(View): | ||||
|     """Handle SAML Redirect-type Requests""" | ||||
| @ -1,41 +0,0 @@ | ||||
| """Single Signon Views""" | ||||
| from django.http import HttpRequest, HttpResponse, HttpResponseBadRequest | ||||
|  | ||||
| from passbook.providers.samlv2.saml.constants import REQ_KEY_REQUEST, REQ_KEY_SIGNATURE | ||||
| from passbook.providers.samlv2.saml.parser import SAMLRequest | ||||
| from passbook.providers.samlv2.views.base import BaseSAMLView | ||||
|  | ||||
| # SAML Authentication flow in passbook | ||||
| # - Parse and Verify SAML Request | ||||
| # - Check access to application (this is done after parsing as it might take a few seconds) | ||||
| # - Ask for user authorization (if required from Application) | ||||
| # - Log Access to audit log | ||||
| # - Create response with unique ID to protect against replay | ||||
|  | ||||
|  | ||||
| class SAMLPostBindingView(BaseSAMLView): | ||||
|     """Handle SAML POST-type Requests""" | ||||
|  | ||||
|     # pylint: disable=unused-argument | ||||
|     def post(self, request: HttpRequest, app_slug: str) -> HttpResponse: | ||||
|         """Handle POST Requests""" | ||||
|         if REQ_KEY_REQUEST not in request.POST: | ||||
|             return HttpResponseBadRequest() | ||||
|         raw_saml_request = request.POST.get(REQ_KEY_REQUEST) | ||||
|         detached_signature = request.POST.get(REQ_KEY_SIGNATURE, None) | ||||
|         srq = SAMLRequest.parse(raw_saml_request, detached_signature) | ||||
|         return self.handle_saml_request(srq) | ||||
|  | ||||
|  | ||||
| class SAMLRedirectBindingView(BaseSAMLView): | ||||
|     """Handle SAML Redirect-type Requests""" | ||||
|  | ||||
|     # pylint: disable=unused-argument | ||||
|     def get(self, request: HttpRequest, app_slug: str) -> HttpResponse: | ||||
|         """Handle GET Requests""" | ||||
|         if REQ_KEY_REQUEST not in request.GET: | ||||
|             return HttpResponseBadRequest() | ||||
|         raw_saml_request = request.GET.get(REQ_KEY_REQUEST) | ||||
|         detached_signature = request.GET.get(REQ_KEY_SIGNATURE, None) | ||||
|         srq = SAMLRequest.parse(raw_saml_request, detached_signature) | ||||
|         return self.handle_saml_request(srq) | ||||
| @ -92,7 +92,6 @@ INSTALLED_APPS = [ | ||||
|     "passbook.providers.oauth.apps.PassbookProviderOAuthConfig", | ||||
|     "passbook.providers.oidc.apps.PassbookProviderOIDCConfig", | ||||
|     "passbook.providers.saml.apps.PassbookProviderSAMLConfig", | ||||
|     "passbook.providers.samlv2.apps.PassbookProviderSAMLv2Config", | ||||
|     "passbook.recovery.apps.PassbookRecoveryConfig", | ||||
|     "passbook.sources.ldap.apps.PassbookSourceLDAPConfig", | ||||
|     "passbook.sources.oauth.apps.PassbookSourceOAuthConfig", | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 Jens Langhammer
					Jens Langhammer