Revert "*: providers and sources -> channels, PolicyModel to PolicyBindingModel that uses custom M2M through"
This reverts commit 7ed3ceb960.
			
			
This commit is contained in:
		
							
								
								
									
										0
									
								
								passbook/providers/samlv2/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								passbook/providers/samlv2/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										11
									
								
								passbook/providers/samlv2/apps.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								passbook/providers/samlv2/apps.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,11 @@
 | 
			
		||||
"""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/"
 | 
			
		||||
							
								
								
									
										0
									
								
								passbook/providers/samlv2/models.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								passbook/providers/samlv2/models.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										0
									
								
								passbook/providers/samlv2/saml/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								passbook/providers/samlv2/saml/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										15
									
								
								passbook/providers/samlv2/saml/constants.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								passbook/providers/samlv2/saml/constants.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,15 @@
 | 
			
		||||
"""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"
 | 
			
		||||
							
								
								
									
										83
									
								
								passbook/providers/samlv2/saml/parser.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								passbook/providers/samlv2/saml/parser.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,83 @@
 | 
			
		||||
"""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)
 | 
			
		||||
							
								
								
									
										5
									
								
								passbook/providers/samlv2/saml/provider.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								passbook/providers/samlv2/saml/provider.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,5 @@
 | 
			
		||||
"""SAML Provider logic"""
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SAMLProvider:
 | 
			
		||||
    """SAML Provider"""
 | 
			
		||||
							
								
								
									
										24
									
								
								passbook/providers/samlv2/saml/utils.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								passbook/providers/samlv2/saml/utils.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,24 @@
 | 
			
		||||
"""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", "")
 | 
			
		||||
							
								
								
									
										35
									
								
								passbook/providers/samlv2/urls.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								passbook/providers/samlv2/urls.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,35 @@
 | 
			
		||||
"""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",
 | 
			
		||||
    ),
 | 
			
		||||
]
 | 
			
		||||
							
								
								
									
										0
									
								
								passbook/providers/samlv2/views/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								passbook/providers/samlv2/views/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										6
									
								
								passbook/providers/samlv2/views/authorize.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								passbook/providers/samlv2/views/authorize.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,6 @@
 | 
			
		||||
"""SAML Provider authorization view"""
 | 
			
		||||
from django.views.generic import FormView
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AuthorizeView(FormView):
 | 
			
		||||
    """Authorization view"""
 | 
			
		||||
							
								
								
									
										31
									
								
								passbook/providers/samlv2/views/base.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								passbook/providers/samlv2/views/base.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,31 @@
 | 
			
		||||
"""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.core.views.access import AccessMixin
 | 
			
		||||
from passbook.providers.samlv2.saml.constants import SESSION_KEY
 | 
			
		||||
from passbook.providers.samlv2.saml.parser import SAMLRequest
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BaseSAMLView(AccessMixin, 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
 | 
			
		||||
							
								
								
									
										6
									
								
								passbook/providers/samlv2/views/idp_initiated.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								passbook/providers/samlv2/views/idp_initiated.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,6 @@
 | 
			
		||||
"""IDP-Initiated Views"""
 | 
			
		||||
from django.views import View
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class IDPInitiatedView(View):
 | 
			
		||||
    """IDP-initiated Handler"""
 | 
			
		||||
							
								
								
									
										10
									
								
								passbook/providers/samlv2/views/slo.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								passbook/providers/samlv2/views/slo.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,10 @@
 | 
			
		||||
"""Single Logout Views"""
 | 
			
		||||
from django.views import View
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SAMLPostBindingView(View):
 | 
			
		||||
    """Handle SAML POST-type Requests"""
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SAMLRedirectBindingView(View):
 | 
			
		||||
    """Handle SAML Redirect-type Requests"""
 | 
			
		||||
							
								
								
									
										41
									
								
								passbook/providers/samlv2/views/sso.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								passbook/providers/samlv2/views/sso.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,41 @@
 | 
			
		||||
"""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)
 | 
			
		||||
		Reference in New Issue
	
	Block a user