def __build_signature(self, saml_data, relay_state, saml_type, sign_algorithm=OneLogin_Saml2_Constants.RSA_SHA1): """ Builds the Signature :param saml_data: The SAML Data :type saml_data: string :param relay_state: The target URL the user should be redirected to :type relay_state: string :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'] # Load the key into the xmlsec context key = self.__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) xmlsec.initialize() dsig_ctx = xmlsec.DSigCtx() dsig_ctx.signKey = xmlsec.Key.loadMemory(key, xmlsec.KeyDataFormatPem, None) saml_data_str = '%s=%s' % (saml_type, quote_plus(saml_data)) relay_state_str = 'RelayState=%s' % quote_plus(relay_state) alg_str = 'SigAlg=%s' % quote_plus(sign_algorithm) sign_data = [saml_data_str, relay_state_str, alg_str] msg = '&'.join(sign_data) # Sign the metadata with our private key. sign_algorithm_transform_map = { OneLogin_Saml2_Constants.DSA_SHA1: xmlsec.TransformDsaSha1, OneLogin_Saml2_Constants.RSA_SHA1: xmlsec.TransformRsaSha1, OneLogin_Saml2_Constants.RSA_SHA256: xmlsec.TransformRsaSha256, OneLogin_Saml2_Constants.RSA_SHA384: xmlsec.TransformRsaSha384, OneLogin_Saml2_Constants.RSA_SHA512: xmlsec.TransformRsaSha512 } sign_algorithm_transform = sign_algorithm_transform_map.get( sign_algorithm, xmlsec.TransformRsaSha1) signature = dsig_ctx.signBinary(str(msg), sign_algorithm_transform) return b64encode(signature)
def logout(self, return_to=None, name_id=None, session_index=None, nq=None, name_id_format=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 :param name_id_format: The NameID Format that will be set in the LogoutRequest. :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 if name_id_format is None and self.__nameid_format is not None: name_id_format = self.__nameid_format logout_request = OneLogin_Saml2_Logout_Request( self.__settings, name_id=name_id, session_index=session_index, nq=nq, name_id_format=name_id_format ) self.__last_request = logout_request.get_xml() self.__last_request_id = logout_request.id saml_request = logout_request.get_request() 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): parameters['SigAlg'] = security['signatureAlgorithm'] parameters['Signature'] = self.build_request_signature(saml_request, parameters['RelayState'], security['signatureAlgorithm']) return self.redirect_to(slo_url, parameters)
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 = [] if 'get_data' in self.__request_data and 'SAMLResponse' in self.__request_data['get_data']: logout_response = OneLogin_Saml2_Logout_Response(self.__settings, self.__request_data['get_data']['SAMLResponse']) if 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' in self.__request_data and 'SAMLRequest' in self.__request_data['get_data']: logout_request = OneLogin_Saml2_Logout_Request(self.__settings, self.__request_data['get_data']['SAMLRequest']) if 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 'logoutResponseSigned' in security and security['logoutResponseSigned']: parameters['SigAlg'] = security['signatureAlgorithm'] parameters['Signature'] = self.build_response_signature(logout_response, parameters.get('RelayState', None), 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 )
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, basestring) 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 = quote_plus(name) elif isinstance(value, list): param = '' for val in value: param += quote_plus(name) + '[]=' + quote_plus(val) + '&' if len(param) > 0: param = param[0:-1] else: param = quote_plus(name) + '=' + quote_plus(value) if param: url += param_prefix + param param_prefix = '&' return url
def get_nameid_data(request, key=None): """ Gets the NameID Data of the the Logout Request :param request: Logout Request Message :type request: string|DOMDocument :param key: The SP key :type key: string :return: Name ID Data (Value, Format, NameQualifier, SPNameQualifier) :rtype: dict """ if isinstance(request, etree._Element): elem = request else: if isinstance(request, Document): request = request.toxml() elem = fromstring(request, forbid_dtd=True) name_id = None encrypted_entries = OneLogin_Saml2_Utils.query( elem, '/samlp:LogoutRequest/saml:EncryptedID') if len(encrypted_entries) == 1: if key is None: raise OneLogin_Saml2_Error( 'Private Key is required in order to decrypt the NameID, check settings', OneLogin_Saml2_Error.PRIVATE_KEY_NOT_FOUND) encrypted_data_nodes = OneLogin_Saml2_Utils.query( elem, '/samlp:LogoutRequest/saml:EncryptedID/xenc:EncryptedData') if len(encrypted_data_nodes) == 1: encrypted_data = encrypted_data_nodes[0] name_id = OneLogin_Saml2_Utils.decrypt_element( encrypted_data, key) else: entries = OneLogin_Saml2_Utils.query( elem, '/samlp:LogoutRequest/saml:NameID') if len(entries) == 1: name_id = entries[0] if name_id is None: raise OneLogin_Saml2_ValidationError( 'NameID not found in the Logout Request', OneLogin_Saml2_ValidationError.NO_NAMEID) name_id_data = {'Value': OneLogin_Saml2_Utils.element_text(name_id)} for attr in ['Format', 'SPNameQualifier', 'NameQualifier']: if attr in name_id.attrib.keys(): name_id_data[attr] = name_id.attrib[attr] return name_id_data
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
def process_response(self, request_id=None): """ Process the SAML Response sent by the IdP. :param request_id: Is an optional argument. 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 = [] self.__error_reason = None 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']) self.__last_response = response.get_xml_document() if response.is_valid(self.__request_data, request_id): self.__attributes = response.get_attributes() self.__nameid = response.get_nameid() self.__nameid_format = response.get_nameid_format() self.__session_index = response.get_session_index() self.__session_expiration = response.get_session_not_on_or_after( ) self.__last_message_id = response.get_id() self.__last_assertion_id = response.get_assertion_id() self.__last_authn_contexts = response.get_authn_contexts() self.__last_assertion_not_on_or_after = response.get_assertion_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)
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 """ error_callback_method = None if debug: error_callback_method = print_xmlsec_errors xmlsec.set_error_callback(error_callback_method) xmlsec.addIDs(elem, ["ID"]) if (cert is None or cert == '') and fingerprint: x509_certificate_nodes = OneLogin_Saml2_Utils.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 = x509_certificate_node.text x509_fingerprint_value = OneLogin_Saml2_Utils.calculate_x509_fingerprint( x509_cert_value, fingerprintalg) if fingerprint == x509_fingerprint_value: cert = OneLogin_Saml2_Utils.format_cert(x509_cert_value) # Check if Reference URI is empty # reference_elem = OneLogin_Saml2_Utils.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 cert is None or cert == '': raise OneLogin_Saml2_Error( 'Could not validate node signature: No certificate provided.', OneLogin_Saml2_Error.CERT_NOT_FOUND) file_cert = OneLogin_Saml2_Utils.write_temp_file(cert) if validatecert: mngr = xmlsec.KeysMngr() mngr.loadCert(file_cert.name, xmlsec.KeyDataFormatCertPem, xmlsec.KeyDataTypeTrusted) dsig_ctx = xmlsec.DSigCtx(mngr) else: dsig_ctx = xmlsec.DSigCtx() dsig_ctx.signKey = xmlsec.Key.load(file_cert.name, xmlsec.KeyDataFormatCertPem, None) file_cert.close() dsig_ctx.setEnabledKeyData([xmlsec.KeyDataX509]) 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, err.__str__()) return True
def __decrypt_assertion(self, dom): """ Decrypts the Assertion :raises: Exception if no private key available :param dom: Encrypted Assertion :type dom: Element :returns: Decrypted Assertion :rtype: Element """ key = self.__settings.get_sp_key() debug = self.__settings.is_debug_active() if not key: raise OneLogin_Saml2_Error( 'No private key available to decrypt the assertion, check settings', OneLogin_Saml2_Error.PRIVATE_KEY_NOT_FOUND) encrypted_assertion_nodes = OneLogin_Saml2_Utils.query( dom, '/samlp:Response/saml:EncryptedAssertion') if encrypted_assertion_nodes: encrypted_data_nodes = OneLogin_Saml2_Utils.query( encrypted_assertion_nodes[0], '//saml:EncryptedAssertion/xenc:EncryptedData') if encrypted_data_nodes: keyinfo = OneLogin_Saml2_Utils.query( encrypted_assertion_nodes[0], '//saml:EncryptedAssertion/xenc:EncryptedData/ds:KeyInfo') if not keyinfo: raise OneLogin_Saml2_ValidationError( 'No KeyInfo present, invalid Assertion', OneLogin_Saml2_ValidationError. KEYINFO_NOT_FOUND_IN_ENCRYPTED_DATA) keyinfo = keyinfo[0] children = keyinfo.getchildren() if not children: raise OneLogin_Saml2_ValidationError( 'KeyInfo has no children nodes, invalid Assertion', OneLogin_Saml2_ValidationError. CHILDREN_NODE_NOT_FOUND_IN_KEYINFO) for child in children: if 'RetrievalMethod' in child.tag: if child.attrib[ 'Type'] != 'http://www.w3.org/2001/04/xmlenc#EncryptedKey': raise OneLogin_Saml2_ValidationError( 'Unsupported Retrieval Method found', OneLogin_Saml2_ValidationError. UNSUPPORTED_RETRIEVAL_METHOD) uri = child.attrib['URI'] if not uri.startswith('#'): break uri = uri.split('#')[1] encrypted_key = OneLogin_Saml2_Utils.query( encrypted_assertion_nodes[0], './xenc:EncryptedKey[@Id="' + uri + '"]') if encrypted_key: keyinfo.append(encrypted_key[0]) encrypted_data = encrypted_data_nodes[0] decrypted = OneLogin_Saml2_Utils.decrypt_element( encrypted_data, key, debug=debug, inplace=True) dom.replace(encrypted_assertion_nodes[0], decrypted) return dom
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
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
def runTest(self): exception = OneLogin_Saml2_Error('test') self.assertEqual(exception.message, 'test')
def get_sp_metadata(self): """ Gets the SP metadata. The XML representation. :returns: SP metadata (xml) :rtype: string """ metadata = MetaDataBuilder( self._OneLogin_Saml2_Settings__sp, self._OneLogin_Saml2_Settings__security['authnRequestsSigned'], self._OneLogin_Saml2_Settings__security['wantAssertionsSigned'], self._OneLogin_Saml2_Settings__security['metadataValidUntil'], self._OneLogin_Saml2_Settings__security['metadataCacheDuration'], self.get_contacts(), self.get_organization()) ##### SP SPid patch ## mapping xml tag tagMapping = { "md0:AssertionConsumerService": 'assertionConsumerService', "md0:AttributeConsumingService": 'attributeConsumingService', "md0:ServiceName": 'serviceName', "md0:ServiceDescription": 'serviceDescription', "md0:RequestedAttribute": 'requestedAttributes', "md1:AttributeValue": 'attributeValue' } ## edit meta data ns = { 'md0': OneLogin_Saml2_Constants.NS_MD, 'md1': OneLogin_Saml2_Constants.NS_SAML } xml.etree.ElementTree.register_namespace( 'md0', OneLogin_Saml2_Constants.NS_MD) xml.etree.ElementTree.register_namespace( 'md1', OneLogin_Saml2_Constants.NS_SAML) parsedMetadata = xml.etree.ElementTree.fromstring(metadata) SPSSODescriptor = parsedMetadata.find('md0:SPSSODescriptor', ns) if not self._OneLogin_Saml2_Settings__security[ 'putMetadataCacheDuration']: parsedMetadata.attrib.pop('cacheDuration') if not self._OneLogin_Saml2_Settings__security['putMetadataValidUntil']: parsedMetadata.attrib.pop('validUntil') ## fix AssertionConsumerService index 0 assertionConsumerService = SPSSODescriptor.find( 'md0:AssertionConsumerService', ns) assertionConsumerService.attrib['index'] = '0' assertionConsumerService.set('isDefault', 'true') ## fix AttributeConsumingService index 0 attributeConsumingService = SPSSODescriptor.find( 'md0:AttributeConsumingService', ns) attributeConsumingService.attrib['index'] = '0' attributeConsumingService.find('md0:ServiceName', ns).attrib[ '{http://www.w3.org/XML/1998/namespace}lang'] = self._OneLogin_Saml2_Settings__sp[ 'lang'] attributeConsumingService.find('md0:ServiceDescription', ns).attrib[ '{http://www.w3.org/XML/1998/namespace}lang'] = self._OneLogin_Saml2_Settings__sp[ 'lang'] ## add other AssertionConsumerService try: for index, value1 in enumerate(self._OneLogin_Saml2_Settings__sp[ 'otherAssertionConsumerService']): #indexValue = str(index+1) tag = "md0:AssertionConsumerService" element = xml.etree.ElementTree.Element( tag, attrib={ 'Location': value1[tagMapping[tag]]['url'], 'Binding': value1[tagMapping[tag]]['binding'], 'index': value1[tagMapping[tag]]['index'] }) SPSSODescriptor.insert(index + 3, element) tag1 = "md0:AttributeConsumingService" element1 = xml.etree.ElementTree.Element( tag1, attrib={'index': value1[tagMapping[tag]]['index']}) SPSSODescriptor.append(element1) tag2 = "md0:ServiceName" element2 = xml.etree.ElementTree.Element( tag2, attrib={ 'xml:lang': self._OneLogin_Saml2_Settings__sp['lang'] }) element2.text = value1[tagMapping[tag]][tagMapping[tag1]][ tagMapping[tag2]] element1.append(element2) tag2 = "md0:ServiceDescription" element2 = xml.etree.ElementTree.Element( tag2, attrib={ 'xml:lang': self._OneLogin_Saml2_Settings__sp['lang'] }) element2.text = value1[tagMapping[tag]][tagMapping[tag1]][ tagMapping[tag2]] element1.append(element2) tag3 = "md0:RequestedAttribute" for index2, value2 in enumerate(value1[tagMapping[tag]][ tagMapping[tag1]][tagMapping[tag3]]): if value2['isRequired']: attrRequired = "true" else: attrRequired = "false" attributi = { 'Name': value2['name'], 'FriendlyName': value2['friendlyName'], 'isRequired': attrRequired } element3 = xml.etree.ElementTree.Element(tag3, attrib=attributi) element1.append(element3) for index3, value3 in enumerate( value1[tagMapping[tag]][tagMapping[tag1]][ tagMapping[tag3]][index2]['attributeValue']): xml.etree.ElementTree.Element(tag3, attrib=attributi) tag4 = "md1:AttributeValue" element4 = xml.etree.ElementTree.Element(tag4) element4.text = value3 element3.append(element4) except: pass ## add other singleLogoutService try: for index, value1 in enumerate( self. _OneLogin_Saml2_Settings__sp['othersSingleLogoutService']): #indexValue = str(index+1) tag = "md0:SingleLogoutService" element = xml.etree.ElementTree.Element(tag, attrib={ 'Location': value1['url'], 'Binding': value1['binding'] }) SPSSODescriptor.insert(index + 1, element) except: pass metadata = xml.etree.ElementTree.tostring(parsedMetadata, encoding="unicode") #### add_encryption = self._OneLogin_Saml2_Settings__security[ 'wantNameIdEncrypted'] or self._OneLogin_Saml2_Settings__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._OneLogin_Saml2_Settings__security and self._OneLogin_Saml2_Settings__security[ 'signMetadata'] is not False: if self._OneLogin_Saml2_Settings__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._OneLogin_Saml2_Settings__security['signMetadata'] or 'certFileName' not in self. _OneLogin_Saml2_Settings__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._OneLogin_Saml2_Settings__security[ 'signMetadata']['keyFileName'] cert_file_name = self._OneLogin_Saml2_Settings__security[ 'signMetadata']['certFileName'] key_metadata_file = self._OneLogin_Saml2_Settings__paths[ 'cert'] + key_file_name cert_metadata_file = self._OneLogin_Saml2_Settings__paths[ 'cert'] + cert_file_name try: with open(key_metadata_file, 'rb') as f_metadata_key: key_metadata = f_metadata_key.read().decode("utf-8") 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, 'rb') as f_metadata_cert: cert_metadata = f_metadata_cert.read().decode("utf-8") 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._OneLogin_Saml2_Settings__security[ 'signatureAlgorithm'] digest_algorithm = self._OneLogin_Saml2_Settings__security[ 'digestAlgorithm'] #metadata = OneLogin_Saml2_Metadata.sign_metadata(metadata, key_metadata, cert_metadata, signature_algorithm, digest_algorithm) metadata = AddSign(metadata, key_metadata, cert_metadata, False, signature_algorithm, digest_algorithm) return metadata
def is_valid(self, request_data, request_id=None, raise_exceptions=False): """ Determines if the SAML LogoutResponse is valid :param request_id: The ID of the LogoutRequest sent by this SP to the IdP :type request_id: string :param raise_exceptions: Whether to return false on failure or raise an exception :type raise_exceptions: Boolean :return: Returns if the SAML LogoutResponse is or not valid :rtype: boolean """ self.__error = None lowercase_urlencoding = False try: idp_data = self.__settings.get_idp_data() idp_entity_id = idp_data['entityId'] get_data = request_data['get_data'] if 'lowercase_urlencoding' in request_data.keys(): lowercase_urlencoding = request_data['lowercase_urlencoding'] if self.__settings.is_strict(): res = OneLogin_Saml2_Utils.validate_xml(self.document, 'saml-schema-protocol-2.0.xsd', self.__settings.is_debug_active()) if not isinstance(res, Document): raise OneLogin_Saml2_ValidationError( 'Invalid SAML Logout Response. Not match the saml-schema-protocol-2.0.xsd', OneLogin_Saml2_ValidationError.INVALID_XML_FORMAT ) security = self.__settings.get_security_data() # Check if the InResponseTo of the Logout Response matches the ID of the Logout Request (requestId) if provided if request_id is not None and self.document.documentElement.hasAttribute('InResponseTo'): in_response_to = self.document.documentElement.getAttribute('InResponseTo') if request_id != in_response_to: raise OneLogin_Saml2_ValidationError( 'The InResponseTo of the Logout Response: %s, does not match the ID of the Logout request sent by the SP: %s' % (in_response_to, request_id), OneLogin_Saml2_ValidationError.WRONG_INRESPONSETO ) # Check issuer issuer = self.get_issuer() if issuer is not None and issuer != idp_entity_id: raise OneLogin_Saml2_ValidationError( 'Invalid issuer in the Logout Request', OneLogin_Saml2_ValidationError.WRONG_ISSUER ) current_url = OneLogin_Saml2_Utils.get_self_url_no_query(request_data) # Check destination if self.document.documentElement.hasAttribute('Destination'): destination = self.document.documentElement.getAttribute('Destination') if destination != '': if current_url not in destination: raise OneLogin_Saml2_ValidationError( 'The LogoutResponse was received at %s instead of %s' % (current_url, destination), OneLogin_Saml2_ValidationError.WRONG_DESTINATION ) if security['wantMessagesSigned']: if security['wantValidMessageSignature']: if 'Signature' not in get_data: raise OneLogin_Saml2_ValidationError( 'The Message of the Logout Response is not signed and the SP require it', OneLogin_Saml2_ValidationError.NO_SIGNED_MESSAGE ) if 'Signature' in get_data: if 'SigAlg' not in get_data: sign_alg = OneLogin_Saml2_Constants.RSA_SHA1 else: sign_alg = get_data['SigAlg'] signed_query = 'SAMLResponse=%s' % OneLogin_Saml2_Utils.get_encoded_parameter(get_data, 'SAMLResponse', lowercase_urlencoding=lowercase_urlencoding) if 'RelayState' in get_data: signed_query = '%s&RelayState=%s' % (signed_query, OneLogin_Saml2_Utils.get_encoded_parameter(get_data, 'RelayState', lowercase_urlencoding=lowercase_urlencoding)) signed_query = '%s&SigAlg=%s' % (signed_query, OneLogin_Saml2_Utils.get_encoded_parameter(get_data, 'SigAlg', OneLogin_Saml2_Constants.RSA_SHA1, lowercase_urlencoding=lowercase_urlencoding)) exists_x509cert = 'x509cert' in idp_data and idp_data['x509cert'] exists_multix509sign = 'x509certMulti' in idp_data and \ 'signing' in idp_data['x509certMulti'] and \ idp_data['x509certMulti']['signing'] if not (exists_x509cert or exists_multix509sign): raise OneLogin_Saml2_Error( 'In order to validate the sign on the Logout Response, the x509cert of the IdP is required', OneLogin_Saml2_Error.CERT_NOT_FOUND ) if exists_multix509sign: for cert in idp_data['x509certMulti']['signing']: if OneLogin_Saml2_Utils.validate_binary_sign(signed_query, b64decode(get_data['Signature']), cert, sign_alg): return True raise OneLogin_Saml2_ValidationError( 'Signature validation failed. Logout Response rejected', OneLogin_Saml2_ValidationError.INVALID_SIGNATURE ) else: cert = idp_data['x509cert'] if not OneLogin_Saml2_Utils.validate_binary_sign(signed_query, b64decode(get_data['Signature']), cert, sign_alg): raise OneLogin_Saml2_ValidationError( 'Signature validation failed. Logout Response rejected', OneLogin_Saml2_ValidationError.INVALID_SIGNATURE ) return True # pylint: disable=R0801 except Exception as err: self.__error = err.__str__() debug = self.__settings.is_debug_active() if debug: print err.__str__() if raise_exceptions: raise err return False
def __init__(self, settings, force_authn=False, is_passive=False, set_nameid_policy=True): """ Constructs the AuthnRequest object. :param settings: OSetting data :type return_to: OneLogin_Saml2_Settings :param force_authn: Optional argument. When true the AuthNReuqest will set the ForceAuthn='true'. :type force_authn: bool :param is_passive: Optional argument. When true the AuthNReuqest will set the Ispassive='true'. :type is_passive: bool :param set_nameid_policy: Optional argument. When true the AuthNReuqest will set a nameIdPolicy element. :type set_nameid_policy: bool """ self.__settings = settings sp_data = self.__settings.get_sp_data() idp_data = self.__settings.get_idp_data() security = self.__settings.get_security_data() uid = OneLogin_Saml2_Utils.generate_unique_id() self.__id = uid issue_instant = OneLogin_Saml2_Utils.parse_time_to_SAML(OneLogin_Saml2_Utils.now()) destination = idp_data['singleSignOnService']['url'] provider_name_str = '' organization_data = settings.get_organization() if isinstance(organization_data, dict) and organization_data: langs = organization_data.keys() if 'en-US' in langs: lang = 'en-US' else: lang = langs[0] if 'displayname' in organization_data[lang] and organization_data[lang]['displayname'] is not None: provider_name_str = "\n" + ' ProviderName="%s"' % organization_data[lang]['displayname'] force_authn_str = '' if force_authn is True: force_authn_str = "\n" + ' ForceAuthn="true"' is_passive_str = '' if is_passive is True: is_passive_str = "\n" + ' IsPassive="true"' nameid_policy_str = '' if set_nameid_policy: name_id_policy_format = sp_data['NameIDFormat'] if 'wantNameIdEncrypted' in security and security['wantNameIdEncrypted']: name_id_policy_format = OneLogin_Saml2_Constants.NAMEID_ENCRYPTED nameid_policy_str = """ <samlp:NameIDPolicy Format="%s" AllowCreate="true" />""" % name_id_policy_format requested_authn_context_str = '' if 'requestedAuthnContext' in security.keys() and security['requestedAuthnContext'] is not False: authn_comparison = 'exact' if 'requestedAuthnContextComparison' in security.keys(): authn_comparison = security['requestedAuthnContextComparison'] if security['requestedAuthnContext'] is True: requested_authn_context_str = "\n" + """ <samlp:RequestedAuthnContext Comparison="%s"> <saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml:AuthnContextClassRef> </samlp:RequestedAuthnContext>""" % authn_comparison else: requested_authn_context_str = "\n" + ' <samlp:RequestedAuthnContext Comparison="%s">' % authn_comparison for authn_context in security['requestedAuthnContext']: requested_authn_context_str += '<saml:AuthnContextClassRef>%s</saml:AuthnContextClassRef>' % authn_context requested_authn_context_str += ' </samlp:RequestedAuthnContext>' attr_consuming_service_str = '' if 'attributeConsumingService' in sp_data and sp_data['attributeConsumingService']: attr_consuming_service_str = 'AttributeConsumingServiceIndex="1"' request = """<samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="%(id)s" Version="2.0"%(provider_name)s%(force_authn_str)s%(is_passive_str)s IssueInstant="%(issue_instant)s" Destination="%(destination)s" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" AssertionConsumerServiceURL="%(assertion_url)s" %(attr_consuming_service_str)s> <saml:Issuer>%(entity_id)s</saml:Issuer>%(nameid_policy_str)s%(requested_authn_context_str)s </samlp:AuthnRequest>""" % \ { 'id': uid, 'provider_name': provider_name_str, 'force_authn_str': force_authn_str, 'is_passive_str': is_passive_str, 'issue_instant': issue_instant, 'destination': destination, 'assertion_url': sp_data['assertionConsumerService']['url'], 'entity_id': sp_data['entityId'], 'nameid_policy_str': nameid_policy_str, 'requested_authn_context_str': requested_authn_context_str, 'attr_consuming_service_str': attr_consuming_service_str } #from https://github.com/onelogin/python-saml/pull/78. credit to @tachang # Only the urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST binding gets the enveloped signature if settings.get_idp_data()['singleSignOnService'].get('binding', None) == 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST' and \ security['authnRequestsSigned'] is True: log.debug("Generating AuthnRequest using urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST binding") if 'signatureAlgorithm' in security: key = settings.get_sp_key() if not key: raise OneLogin_Saml2_Error("Attempt to sign the AuthnRequest but unable to load the SP private key") cert = settings.get_sp_cert() if not key: raise OneLogin_Saml2_Error("Attempt to sign the AuthnRequest but unable to load the SP cert") doc = parseString(request) security_algo = security['signatureAlgorithm'] digest_method_algo = security['digestMethodAlgorithm'] self.__authn_request = OneLogin_Saml2_Utils.add_sign_with_id(doc, uid, key, cert, sign_algorithm=security_algo, digest_algorithm=digest_method_algo, debug=False) log.debug("Generated AuthnRequest: {}".format(self.__authn_request)) else: self.__authn_request = request log.debug("Generated AuthnRequest: {}".format(self.__authn_request)) else: self.__authn_request = request
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 """ 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)) self.__add_default_values() elif isinstance(settings, str): try: valid = self.__load_settings_from_file(settings) 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)) self.__add_default_values() 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() self.format_sp_cert() if 'x509certNew' in self.__sp: self.format_sp_cert_new() self.format_sp_key() if 'x509certMulti' in self.__idp: self.format_idp_cert_multi()
def runTest(self): exception = OneLogin_Saml2_Error('test') self.assertEqual(str(exception), 'test')
def is_valid(self, request_data, raise_exceptions=False): """ Checks if the Logout Request received is valid :param request_data: Request Data :type request_data: dict :param raise_exceptions: Whether to return false on failure or raise an exception :type raise_exceptions: Boolean :return: If the Logout Request is or not valid :rtype: boolean """ self.__error = None lowercase_urlencoding = False try: dom = fromstring(self.__logout_request, forbid_dtd=True) idp_data = self.__settings.get_idp_data() idp_entity_id = idp_data['entityId'] if 'get_data' in request_data.keys(): get_data = request_data['get_data'] else: get_data = {} if 'lowercase_urlencoding' in request_data.keys(): lowercase_urlencoding = request_data['lowercase_urlencoding'] if self.__settings.is_strict(): res = OneLogin_Saml2_Utils.validate_xml( dom, 'saml-schema-protocol-2.0.xsd', self.__settings.is_debug_active()) if not isinstance(res, Document): raise OneLogin_Saml2_ValidationError( 'Invalid SAML Logout Request. Not match the saml-schema-protocol-2.0.xsd', OneLogin_Saml2_ValidationError.INVALID_XML_FORMAT) security = self.__settings.get_security_data() current_url = OneLogin_Saml2_Utils.get_self_url_no_query( request_data) # Check NotOnOrAfter if dom.get('NotOnOrAfter', None): na = OneLogin_Saml2_Utils.parse_SAML_to_time( dom.get('NotOnOrAfter')) if na <= OneLogin_Saml2_Utils.now(): raise OneLogin_Saml2_ValidationError( 'Could not validate timestamp: expired. Check system clock.', OneLogin_Saml2_ValidationError.RESPONSE_EXPIRED) # Check destination destination = dom.get('Destination') if destination: if not OneLogin_Saml2_Utils.normalize_url( url=destination).startswith( OneLogin_Saml2_Utils.normalize_url( url=current_url)): raise Exception( 'The LogoutRequest was received at ' '%(currentURL)s instead of %(destination)s' % { 'currentURL': current_url, 'destination': destination, }, OneLogin_Saml2_ValidationError.WRONG_DESTINATION) # Check issuer issuer = OneLogin_Saml2_Logout_Request.get_issuer(dom) if issuer is not None and issuer != idp_entity_id: raise OneLogin_Saml2_ValidationError( 'Invalid issuer in the Logout Request (expected %(idpEntityId)s, got %(issuer)s)' % { 'idpEntityId': idp_entity_id, 'issuer': issuer }, OneLogin_Saml2_ValidationError.WRONG_ISSUER) if security['wantMessagesSigned']: if 'Signature' not in get_data: raise OneLogin_Saml2_ValidationError( 'The Message of the Logout Request is not signed and the SP require it', OneLogin_Saml2_ValidationError.NO_SIGNED_MESSAGE) if 'Signature' in get_data: if 'SigAlg' not in get_data: sign_alg = OneLogin_Saml2_Constants.RSA_SHA1 else: sign_alg = get_data['SigAlg'] signed_query = 'SAMLRequest=%s' % OneLogin_Saml2_Utils.get_encoded_parameter( get_data, 'SAMLRequest', lowercase_urlencoding=lowercase_urlencoding) if 'RelayState' in get_data: signed_query = '%s&RelayState=%s' % ( signed_query, OneLogin_Saml2_Utils.get_encoded_parameter( get_data, 'RelayState', lowercase_urlencoding=lowercase_urlencoding)) signed_query = '%s&SigAlg=%s' % ( signed_query, OneLogin_Saml2_Utils.get_encoded_parameter( get_data, 'SigAlg', OneLogin_Saml2_Constants.RSA_SHA1, lowercase_urlencoding=lowercase_urlencoding)) exists_x509cert = 'x509cert' in idp_data and idp_data[ 'x509cert'] exists_multix509sign = 'x509certMulti' in idp_data and \ 'signing' in idp_data['x509certMulti'] and \ idp_data['x509certMulti']['signing'] if not (exists_x509cert or exists_multix509sign): raise OneLogin_Saml2_Error( 'In order to validate the sign on the Logout Request, the x509cert of the IdP is required', OneLogin_Saml2_Error.CERT_NOT_FOUND) if exists_multix509sign: for cert in idp_data['x509certMulti']['signing']: if OneLogin_Saml2_Utils.validate_binary_sign( signed_query, b64decode(get_data['Signature']), cert, sign_alg): return True raise OneLogin_Saml2_ValidationError( 'Signature validation failed. Logout Request rejected', OneLogin_Saml2_ValidationError.INVALID_SIGNATURE) else: cert = idp_data['x509cert'] if not OneLogin_Saml2_Utils.validate_binary_sign( signed_query, b64decode(get_data['Signature']), cert, sign_alg): raise OneLogin_Saml2_ValidationError( 'Signature validation failed. Logout Request rejected', OneLogin_Saml2_ValidationError.INVALID_SIGNATURE) return True except Exception as err: # pylint: disable=R0801sign_alg self.__error = err.__str__() debug = self.__settings.is_debug_active() if debug: print(err.__str__()) if raise_exceptions: raise err return False