Beispiel #1
0
    def __init__(self,
                 settings=None,
                 custom_base_path=None,
                 sp_validation_only=False):
        """
        Initializes the settings:
        - Sets the paths of the different folders
        - Loads settings info from settings file or array/object provided

        :param settings: SAML Toolkit Settings
        :type settings: dict

        :param custom_base_path: Path where are stored the settings file and the cert folder
        :type custom_base_path: string

        :param sp_validation_only: Avoid the IdP validation
        :type sp_validation_only: boolean
        """
        self.__sp_validation_only = sp_validation_only
        self.__paths = {}
        self.__strict = False
        self.__debug = False
        self.__sp = {}
        self.__idp = {}
        self.__security = {}
        self.__contacts = {}
        self.__organization = {}
        self.__errors = []

        self.__load_paths(base_path=custom_base_path)
        self.__update_paths(settings)

        if settings is None:
            try:
                valid = self.__load_settings_from_file()
            except Exception as e:
                raise e
            if not valid:
                raise OneLogin_Saml2_Error(
                    'Invalid dict settings at the file: %s',
                    OneLogin_Saml2_Error.SETTINGS_INVALID,
                    ','.join(self.__errors))
        elif isinstance(settings, dict):
            if not self.__load_settings_from_dict(settings):
                raise OneLogin_Saml2_Error(
                    'Invalid dict settings: %s',
                    OneLogin_Saml2_Error.SETTINGS_INVALID,
                    ','.join(self.__errors))
        else:
            raise OneLogin_Saml2_Error(
                'Unsupported settings object',
                OneLogin_Saml2_Error.UNSUPPORTED_SETTINGS_OBJECT)

        self.format_idp_cert()
        if 'x509certMulti' in self.__idp:
            self.format_idp_cert_multi()
        self.format_sp_cert()
        if 'x509certNew' in self.__sp:
            self.format_sp_cert_new()
        self.format_sp_key()
Beispiel #2
0
    def process_response(self, request_id=None):
        """
        Process the SAML Response sent by the IdP.

        :param request_id: Is an optional argumen. Is the ID of the AuthNRequest sent by this SP to the IdP.
        :type request_id: string

        :raises: OneLogin_Saml2_Error.SAML_RESPONSE_NOT_FOUND, when a POST with a SAMLResponse is not found
        """
        self.__errors = []

        if 'post_data' in self.__request_data and 'SAMLResponse' in self.__request_data['post_data']:
            # AuthnResponse -- HTTP_POST Binding
            response = OneLogin_Saml2_Response(self.__settings, self.__request_data['post_data']['SAMLResponse'])

            if response.is_valid(self.__request_data, request_id):
                self.__attributes = response.get_attributes()
                self.__nameid = response.get_nameid()
                self.__session_index = response.get_session_index()
                self.__session_expiration = response.get_session_not_on_or_after()
                self.__authenticated = True

            else:
                self.__errors.append('invalid_response')
                self.__error_reason = response.get_error()

        else:
            self.__errors.append('invalid_binding')
            raise OneLogin_Saml2_Error(
                'SAML Response not found, Only supported HTTP_POST Binding',
                OneLogin_Saml2_Error.SAML_RESPONSE_NOT_FOUND
            )
Beispiel #3
0
    def process_slo(self, keep_local_session=False, request_id=None, delete_session_cb=None):
        """
        Process the SAML Logout Response / Logout Request sent by the IdP.

        :param keep_local_session: When false will destroy the local session, otherwise will destroy it
        :type keep_local_session: bool

        :param request_id: The ID of the LogoutRequest sent by this SP to the IdP
        :type request_id: string

        :returns: Redirection url
        """
        self.__errors = []

        get_data = 'get_data' in self.__request_data and self.__request_data['get_data']
        if get_data and 'SAMLResponse' in get_data:
            logout_response = OneLogin_Saml2_Logout_Response(self.__settings, get_data['SAMLResponse'])
            if not self.validate_response_signature(get_data):
                self.__errors.append('invalid_logout_response_signature')
                self.__errors.append('Signature validation failed. Logout Response rejected')
            elif not logout_response.is_valid(self.__request_data, request_id):
                self.__errors.append('invalid_logout_response')
                self.__error_reason = logout_response.get_error()
            elif logout_response.get_status() != OneLogin_Saml2_Constants.STATUS_SUCCESS:
                self.__errors.append('logout_not_success')
            elif not keep_local_session:
                OneLogin_Saml2_Utils.delete_local_session(delete_session_cb)

        elif get_data and 'SAMLRequest' in get_data:
            logout_request = OneLogin_Saml2_Logout_Request(self.__settings, get_data['SAMLRequest'])
            if not self.validate_request_signature(get_data):
                self.__errors.append("invalid_logout_request_signature")
                self.__errors.append('Signature validation failed. Logout Request rejected')
            elif not logout_request.is_valid(self.__request_data):
                self.__errors.append('invalid_logout_request')
                self.__error_reason = logout_request.get_error()
            else:
                if not keep_local_session:
                    OneLogin_Saml2_Utils.delete_local_session(delete_session_cb)

                in_response_to = logout_request.id
                response_builder = OneLogin_Saml2_Logout_Response(self.__settings)
                response_builder.build(in_response_to)
                logout_response = response_builder.get_response()

                parameters = {'SAMLResponse': logout_response}
                if 'RelayState' in self.__request_data['get_data']:
                    parameters['RelayState'] = self.__request_data['get_data']['RelayState']

                security = self.__settings.get_security_data()
                if security['logoutResponseSigned']:
                    self.add_response_signature(parameters, security['signatureAlgorithm'])

                return self.redirect_to(self.get_slo_url(), parameters)
        else:
            self.__errors.append('invalid_binding')
            raise OneLogin_Saml2_Error(
                'SAML LogoutRequest/LogoutResponse not found. Only supported HTTP_REDIRECT Binding',
                OneLogin_Saml2_Error.SAML_LOGOUTMESSAGE_NOT_FOUND
            )
Beispiel #4
0
    def __load_settings_from_file(self):
        """
        Loads settings info from the settings json file

        :returns: True if the settings info is valid
        :rtype: boolean
        """
        filename = self.get_base_path() + 'settings.json'

        if not exists(filename):
            raise OneLogin_Saml2_Error(
                'Settings file not found: %s',
                OneLogin_Saml2_Error.SETTINGS_FILE_NOT_FOUND, filename)

        # In the php toolkit instead of being a json file it is a php file and
        # it is directly included
        with open(filename, 'r') as json_data:
            settings = json.loads(json_data.read())

        advanced_filename = self.get_base_path() + 'advanced_settings.json'
        if exists(advanced_filename):
            with open(advanced_filename, 'r') as json_data:
                settings.update(json.loads(json_data.read()))  # Merge settings

        return self.__load_settings_from_dict(settings)
Beispiel #5
0
    def redirect(url, parameters={}, request_data={}):
        """
        Executes a redirection to the provided url (or return the target url).

        :param url: The target url
        :type: string

        :param parameters: Extra parameters to be passed as part of the url
        :type: dict

        :param request_data: The request as a dict
        :type: dict

        :returns: Url
        :rtype: string
        """
        assert isinstance(url, compat.str_type)
        assert isinstance(parameters, dict)

        if url.startswith('/'):
            url = '%s%s' % (
                OneLogin_Saml2_Utils.get_self_url_host(request_data), url)

        # Verify that the URL is to a http or https site.
        if re.search('^https?://', url) is None:
            raise OneLogin_Saml2_Error(
                'Redirect to invalid URL: ' + url,
                OneLogin_Saml2_Error.REDIRECT_INVALID_URL)

        # Add encoded parameters
        if url.find('?') < 0:
            param_prefix = '?'
        else:
            param_prefix = '&'

        for name, value in parameters.items():

            if value is None:
                param = OneLogin_Saml2_Utils.escape_url(name)
            elif isinstance(value, list):
                param = ''
                for val in value:
                    param += OneLogin_Saml2_Utils.escape_url(
                        name) + '[]=' + OneLogin_Saml2_Utils.escape_url(
                            val) + '&'
                if len(param) > 0:
                    param = param[0:-1]
            else:
                param = OneLogin_Saml2_Utils.escape_url(
                    name) + '=' + OneLogin_Saml2_Utils.escape_url(value)

            if param:
                url += param_prefix + param
                param_prefix = '&'

        return url
Beispiel #6
0
    def logout(self, return_to=None, name_id=None, session_index=None, nq=None):
        """
        Initiates the SLO process.

        :param return_to: Optional argument. The target URL the user should be redirected to after logout.
        :type return_to: string

        :param name_id: The NameID that will be set in the LogoutRequest.
        :type name_id: string

        :param session_index: SessionIndex that identifies the session of the user.
        :type session_index: string

        :param nq: IDP Name Qualifier
        :type: string

        :returns: Redirection url
        """
        slo_url = self.get_slo_url()
        if slo_url is None:
            raise OneLogin_Saml2_Error(
                'The IdP does not support Single Log Out',
                OneLogin_Saml2_Error.SAML_SINGLE_LOGOUT_NOT_SUPPORTED
            )

        if name_id is None and self.__nameid is not None:
            name_id = self.__nameid

        logout_request = OneLogin_Saml2_Logout_Request(
            self.__settings,
            name_id=name_id,
            session_index=session_index,
            nq=nq
        )

        parameters = {'SAMLRequest': logout_request.get_request()}
        if return_to is not None:
            parameters['RelayState'] = return_to
        else:
            parameters['RelayState'] = OneLogin_Saml2_Utils.get_self_url_no_query(self.__request_data)

        security = self.__settings.get_security_data()
        if security.get('logoutRequestSigned', False):
            self.add_request_signature(parameters, security['signatureAlgorithm'])
        return self.redirect_to(slo_url, parameters)
Beispiel #7
0
    def __build_signature(self, data, saml_type, sign_algorithm=OneLogin_Saml2_Constants.RSA_SHA1):
        """
        Builds the Signature
        :param data: The Request data
        :type data: dict

        :param saml_type: The target URL the user should be redirected to
        :type saml_type: string  SAMLRequest | SAMLResponse

        :param sign_algorithm: Signature algorithm method
        :type sign_algorithm: string
        """
        assert saml_type in ('SAMLRequest', 'SAMLResponse')
        key = self.get_settings().get_sp_key()

        if not key:
            raise OneLogin_Saml2_Error(
                "Trying to sign the %s but can't load the SP private key." % saml_type,
                OneLogin_Saml2_Error.SP_CERTS_NOT_FOUND
            )

        msg = self.__build_sign_query(data[saml_type],
                                      data.get('RelayState', None),
                                      sign_algorithm,
                                      saml_type)

        sign_algorithm_transform_map = {
            OneLogin_Saml2_Constants.DSA_SHA1: xmlsec.Transform.DSA_SHA1,
            OneLogin_Saml2_Constants.RSA_SHA1: xmlsec.Transform.RSA_SHA1,
            OneLogin_Saml2_Constants.RSA_SHA256: xmlsec.Transform.RSA_SHA256,
            OneLogin_Saml2_Constants.RSA_SHA384: xmlsec.Transform.RSA_SHA384,
            OneLogin_Saml2_Constants.RSA_SHA512: xmlsec.Transform.RSA_SHA512
        }
        sign_algorithm_transform = sign_algorithm_transform_map.get(sign_algorithm, xmlsec.Transform.RSA_SHA1)

        signature = OneLogin_Saml2_Utils.sign_binary(msg, key, sign_algorithm_transform, self.__settings.is_debug_active())
        data['Signature'] = OneLogin_Saml2_Utils.b64encode(signature)
        data['SigAlg'] = sign_algorithm
Beispiel #8
0
    def validate_node_sign(signature_node,
                           elem,
                           cert=None,
                           fingerprint=None,
                           fingerprintalg='sha1',
                           validatecert=False,
                           debug=False):
        """
        Validates a signature node.

        :param signature_node: The signature node
        :type: Node

        :param xml: The element we should validate
        :type: Document

        :param cert: The public cert
        :type: string

        :param fingerprint: The fingerprint of the public cert
        :type: string

        :param fingerprintalg: The algorithm used to build the fingerprint
        :type: string

        :param validatecert: If true, will verify the signature and if the cert is valid.
        :type: bool

        :param debug: Activate the xmlsec debug
        :type: bool

        :param raise_exceptions: Whether to return false on failure or raise an exception
        :type raise_exceptions: Boolean
        """
        if (cert is None or cert == '') and fingerprint:
            x509_certificate_nodes = OneLogin_Saml2_XML.query(
                signature_node,
                '//ds:Signature/ds:KeyInfo/ds:X509Data/ds:X509Certificate')
            if len(x509_certificate_nodes) > 0:
                x509_certificate_node = x509_certificate_nodes[0]
                x509_cert_value = OneLogin_Saml2_XML.element_text(
                    x509_certificate_node)
                x509_cert_value_formatted = OneLogin_Saml2_Utils.format_cert(
                    x509_cert_value)
                x509_fingerprint_value = OneLogin_Saml2_Utils.calculate_x509_fingerprint(
                    x509_cert_value_formatted, fingerprintalg)
                if fingerprint == x509_fingerprint_value:
                    cert = x509_cert_value_formatted

        if cert is None or cert == '':
            raise OneLogin_Saml2_Error(
                'Could not validate node signature: No certificate provided.',
                OneLogin_Saml2_Error.CERT_NOT_FOUND)

        # Check if Reference URI is empty
        reference_elem = OneLogin_Saml2_XML.query(signature_node,
                                                  '//ds:Reference')
        if len(reference_elem) > 0:
            if reference_elem[0].get('URI') == '':
                reference_elem[0].set(
                    'URI', '#%s' % signature_node.getparent().get('ID'))

        if validatecert:
            manager = xmlsec.KeysManager()
            manager.load_cert_from_memory(cert, xmlsec.KeyFormat.CERT_PEM,
                                          xmlsec.KeyDataType.TRUSTED)
            dsig_ctx = xmlsec.SignatureContext(manager)
        else:
            dsig_ctx = xmlsec.SignatureContext()
            dsig_ctx.key = xmlsec.Key.from_memory(cert,
                                                  xmlsec.KeyFormat.CERT_PEM,
                                                  None)

        dsig_ctx.set_enabled_key_data([xmlsec.KeyData.X509])

        try:
            dsig_ctx.verify(signature_node)
        except Exception as err:
            raise OneLogin_Saml2_ValidationError(
                'Signature validation failed. SAML Response rejected. %s',
                OneLogin_Saml2_ValidationError.INVALID_SIGNATURE, str(err))

        return True
Beispiel #9
0
    def get_sp_metadata(self):
        """
        Gets the SP metadata. The XML representation.
        :returns: SP metadata (xml)
        :rtype: string
        """
        metadata = OneLogin_Saml2_Metadata.builder(
            self.__sp, self.__security['authnRequestsSigned'],
            self.__security['wantAssertionsSigned'],
            self.__security['metadataValidUntil'],
            self.__security['metadataCacheDuration'], self.get_contacts(),
            self.get_organization())

        add_encryption = self.__security[
            'wantNameIdEncrypted'] or self.__security['wantAssertionsEncrypted']

        cert_new = self.get_sp_cert_new()
        metadata = OneLogin_Saml2_Metadata.add_x509_key_descriptors(
            metadata, cert_new, add_encryption)

        cert = self.get_sp_cert()
        metadata = OneLogin_Saml2_Metadata.add_x509_key_descriptors(
            metadata, cert, add_encryption)

        # Sign metadata
        if 'signMetadata' in self.__security and self.__security[
                'signMetadata'] is not False:
            if self.__security['signMetadata'] is True:
                # Use the SP's normal key to sign the metadata:
                if not cert:
                    raise OneLogin_Saml2_Error(
                        'Cannot sign metadata: missing SP public key certificate.',
                        OneLogin_Saml2_Error.PUBLIC_CERT_FILE_NOT_FOUND)
                cert_metadata = cert
                key_metadata = self.get_sp_key()
                if not key_metadata:
                    raise OneLogin_Saml2_Error(
                        'Cannot sign metadata: missing SP private key.',
                        OneLogin_Saml2_Error.PRIVATE_KEY_FILE_NOT_FOUND)
            else:
                # Use a custom key to sign the metadata:
                if ('keyFileName' not in self.__security['signMetadata'] or
                        'certFileName' not in self.__security['signMetadata']):
                    raise OneLogin_Saml2_Error(
                        'Invalid Setting: signMetadata value of the sp is not valid',
                        OneLogin_Saml2_Error.SETTINGS_INVALID_SYNTAX)
                key_file_name = self.__security['signMetadata']['keyFileName']
                cert_file_name = self.__security['signMetadata'][
                    'certFileName']
                key_metadata_file = self.__paths['cert'] + key_file_name
                cert_metadata_file = self.__paths['cert'] + cert_file_name

                try:
                    with open(key_metadata_file, 'r') as f_metadata_key:
                        key_metadata = f_metadata_key.read()
                except IOError:
                    raise OneLogin_Saml2_Error(
                        'Private key file not readable: %s',
                        OneLogin_Saml2_Error.PRIVATE_KEY_FILE_NOT_FOUND,
                        key_metadata_file)

                try:
                    with open(cert_metadata_file, 'r') as f_metadata_cert:
                        cert_metadata = f_metadata_cert.read()
                except IOError:
                    raise OneLogin_Saml2_Error(
                        'Public cert file not readable: %s',
                        OneLogin_Saml2_Error.PUBLIC_CERT_FILE_NOT_FOUND,
                        cert_metadata_file)

            signature_algorithm = self.__security['signatureAlgorithm']
            digest_algorithm = self.__security['digestAlgorithm']

            metadata = OneLogin_Saml2_Metadata.sign_metadata(
                metadata, key_metadata, cert_metadata, signature_algorithm,
                digest_algorithm)

        return metadata