def testIsInvalidDestination(self): """ Tests the is_valid method of the OneLogin_Saml2_LogoutRequest Case Invalid Destination """ request_data = { 'http_host': 'example.com', 'script_name': 'index.html' } request = self.file_contents( join(self.data_path, 'logout_requests', 'logout_request.xml')) settings = OneLogin_Saml2_Settings(self.loadSettingsJSON()) logout_request = OneLogin_Saml2_Logout_Request( settings, OneLogin_Saml2_Utils.b64encode(request)) self.assertTrue(logout_request.is_valid(request_data)) settings.set_strict(True) logout_request2 = OneLogin_Saml2_Logout_Request( settings, OneLogin_Saml2_Utils.b64encode(request)) with self.assertRaisesRegexp(Exception, 'The LogoutRequest was received at'): logout_request2.is_valid(request_data, raise_exceptions=True) dom = parseString(request) dom.documentElement.setAttribute('Destination', None) logout_request3 = OneLogin_Saml2_Logout_Request( settings, OneLogin_Saml2_Utils.b64encode(dom.toxml())) self.assertTrue(logout_request3.is_valid(request_data)) dom.documentElement.removeAttribute('Destination') logout_request4 = OneLogin_Saml2_Logout_Request( settings, OneLogin_Saml2_Utils.b64encode(dom.toxml())) self.assertTrue(logout_request4.is_valid(request_data))
def testIsInvalidIssuer(self): """ Tests the is_valid method of the OneLogin_Saml2_LogoutRequest Case Invalid Issuer """ request = self.file_contents( join(self.data_path, 'logout_requests', 'invalids', 'invalid_issuer.xml')) request_data = { 'http_host': 'example.com', 'script_name': 'index.html' } current_url = OneLogin_Saml2_Utils.get_self_url_no_query(request_data) request = request.replace( 'http://stuff.com/endpoints/endpoints/sls.php', current_url) settings = OneLogin_Saml2_Settings(self.loadSettingsJSON()) logout_request = OneLogin_Saml2_Logout_Request( settings, OneLogin_Saml2_Utils.b64encode(request)) self.assertTrue(logout_request.is_valid(request_data)) settings.set_strict(True) logout_request2 = OneLogin_Saml2_Logout_Request( settings, OneLogin_Saml2_Utils.b64encode(request)) with self.assertRaisesRegex(Exception, 'Invalid issuer in the Logout Request'): logout_request2.is_valid(request_data, raise_exceptions=True)
def testIsInvalidDestination(self): """ Tests the is_valid method of the OneLogin_Saml2_LogoutRequest Case Invalid Destination """ request_data = { 'http_host': 'example.com', 'script_name': 'index.html' } request = self.file_contents(join(self.data_path, 'logout_requests', 'logout_request.xml')) settings = OneLogin_Saml2_Settings(self.loadSettingsJSON()) logout_request = OneLogin_Saml2_Logout_Request(settings, OneLogin_Saml2_Utils.b64encode(request)) self.assertTrue(logout_request.is_valid(request_data)) settings.set_strict(True) try: logout_request2 = OneLogin_Saml2_Logout_Request(settings, OneLogin_Saml2_Utils.b64encode(request)) valid = logout_request2.is_valid(request_data) self.assertFalse(valid) except Exception as e: self.assertIn('The LogoutRequest was received at', str(e)) dom = parseString(request) dom.documentElement.setAttribute('Destination', None) logout_request3 = OneLogin_Saml2_Logout_Request(settings, OneLogin_Saml2_Utils.b64encode(dom.toxml())) self.assertTrue(logout_request3.is_valid(request_data)) dom.documentElement.removeAttribute('Destination') logout_request4 = OneLogin_Saml2_Logout_Request(settings, OneLogin_Saml2_Utils.b64encode(dom.toxml())) self.assertTrue(logout_request4.is_valid(request_data))
def testIsValid(self): """ Tests the is_valid method of the OneLogin_Saml2_LogoutRequest """ request_data = { 'http_host': 'example.com', 'script_name': 'index.html' } request = self.file_contents(join(self.data_path, 'logout_requests', 'logout_request.xml')) settings = OneLogin_Saml2_Settings(self.loadSettingsJSON()) logout_request = OneLogin_Saml2_Logout_Request(settings, OneLogin_Saml2_Utils.b64encode(request)) self.assertTrue(logout_request.is_valid(request_data)) settings.set_strict(True) logout_request2 = OneLogin_Saml2_Logout_Request(settings, OneLogin_Saml2_Utils.b64encode(request)) self.assertFalse(logout_request2.is_valid(request_data)) settings.set_strict(False) dom = parseString(request) logout_request3 = OneLogin_Saml2_Logout_Request(settings, OneLogin_Saml2_Utils.b64encode(dom.toxml())) self.assertTrue(logout_request3.is_valid(request_data)) settings.set_strict(True) logout_request4 = OneLogin_Saml2_Logout_Request(settings, OneLogin_Saml2_Utils.b64encode(dom.toxml())) self.assertFalse(logout_request4.is_valid(request_data)) current_url = OneLogin_Saml2_Utils.get_self_url_no_query(request_data) request = request.replace('http://stuff.com/endpoints/endpoints/sls.php', current_url) logout_request5 = OneLogin_Saml2_Logout_Request(settings, OneLogin_Saml2_Utils.b64encode(request)) self.assertTrue(logout_request5.is_valid(request_data))
def testIsValid(self): """ Tests the is_valid method of the OneLogin_Saml2_LogoutRequest """ request_data = { 'http_host': 'example.com', 'script_name': 'index.html' } request = self.file_contents(join(self.data_path, 'logout_requests', 'logout_request.xml')) settings = OneLogin_Saml2_Settings(self.loadSettingsJSON()) logout_request = OneLogin_Saml2_Logout_Request(settings, OneLogin_Saml2_Utils.b64encode(request)) self.assertTrue(logout_request.is_valid(request_data)) settings.set_strict(True) logout_request2 = OneLogin_Saml2_Logout_Request(settings, OneLogin_Saml2_Utils.b64encode(request)) self.assertFalse(logout_request2.is_valid(request_data)) settings.set_strict(False) dom = parseString(request) logout_request3 = OneLogin_Saml2_Logout_Request(settings, OneLogin_Saml2_Utils.b64encode(dom.toxml())) self.assertTrue(logout_request3.is_valid(request_data)) settings.set_strict(True) logout_request4 = OneLogin_Saml2_Logout_Request(settings, OneLogin_Saml2_Utils.b64encode(dom.toxml())) self.assertFalse(logout_request4.is_valid(request_data)) current_url = OneLogin_Saml2_Utils.get_self_url_no_query(request_data) request = request.replace('http://stuff.com/endpoints/endpoints/sls.php', current_url) logout_request5 = OneLogin_Saml2_Logout_Request(settings, OneLogin_Saml2_Utils.b64encode(request)) self.assertTrue(logout_request5.is_valid(request_data))
def testIsInvalidNotOnOrAfter(self): """ Tests the is_valid method of the OneLogin_Saml2_LogoutRequest Case Invalid NotOnOrAfter """ request_data = { 'http_host': 'example.com', 'script_name': 'index.html' } request = self.file_contents( join(self.data_path, 'logout_requests', 'invalids', 'not_after_failed.xml')) current_url = OneLogin_Saml2_Utils.get_self_url_no_query(request_data) request = request.replace( 'http://stuff.com/endpoints/endpoints/sls.php', current_url) settings = OneLogin_Saml2_Settings(self.loadSettingsJSON()) logout_request = OneLogin_Saml2_Logout_Request( settings, OneLogin_Saml2_Utils.b64encode(request)) self.assertTrue(logout_request.is_valid(request_data)) settings.set_strict(True) try: logout_request2 = OneLogin_Saml2_Logout_Request( settings, OneLogin_Saml2_Utils.b64encode(request)) valid = logout_request2.is_valid(request_data) self.assertFalse(valid) except Exception as e: self.assertIn('Timing issues (please check your clock settings)', str(e))
def testIsInvalidNotOnOrAfter(self): """ Tests the is_valid method of the OneLogin_Saml2_LogoutRequest Case Invalid NotOnOrAfter """ request_data = { 'http_host': 'example.com', 'script_name': 'index.html' } request = self.file_contents( join(self.data_path, 'logout_requests', 'invalids', 'not_after_failed.xml')) current_url = OneLogin_Saml2_Utils.get_self_url_no_query(request_data) request = request.replace( 'http://stuff.com/endpoints/endpoints/sls.php', current_url) settings = OneLogin_Saml2_Settings(self.loadSettingsJSON()) logout_request = OneLogin_Saml2_Logout_Request( settings, OneLogin_Saml2_Utils.b64encode(request)) self.assertTrue(logout_request.is_valid(request_data)) settings.set_strict(True) logout_request2 = OneLogin_Saml2_Logout_Request( settings, OneLogin_Saml2_Utils.b64encode(request)) with self.assertRaisesRegexp( Exception, 'Could not validate timestamp: expired. Check system clock.'): logout_request2.is_valid(request_data, raise_exceptions=True)
def testResponseAndAssertionSigned(self): """ Tests the getNameId method of the OneLogin_Saml2_Response Case valid signed response, signed assertion """ settings = OneLogin_Saml2_Settings(self.loadSettingsJSON()) message = self.file_contents(join(self.data_path, 'responses', 'simple_saml_php.xml')) response = OneLogin_Saml2_Response(settings, OneLogin_Saml2_Utils.b64encode(message)) self.assertEqual('*****@*****.**', response.get_nameid())
def testResponseAndAssertionSigned(self): """ Tests the getNameId method of the OneLogin_Saml2_Response Case valid signed response, signed assertion """ settings = OneLogin_Saml2_Settings(self.loadSettingsJSON()) message = self.file_contents( join(self.data_path, 'responses', 'simple_saml_php.xml')) response = OneLogin_Saml2_Response( settings, OneLogin_Saml2_Utils.b64encode(message)) self.assertEqual('*****@*****.**', response.get_nameid())
def _sign_saml_request(request, saml_auth, saml_security): sign_algorithm_transform_map = { OneLogin_Saml2_Constants.DSA_SHA1: xmlsec.constants.TransformDsaSha1, OneLogin_Saml2_Constants.RSA_SHA1: xmlsec.constants.TransformRsaSha1, OneLogin_Saml2_Constants.RSA_SHA256: xmlsec.constants.TransformRsaSha256, OneLogin_Saml2_Constants.RSA_SHA384: xmlsec.constants.TransformRsaSha384, OneLogin_Saml2_Constants.RSA_SHA512: xmlsec.constants.TransformRsaSha512 } digest_algorithm_transform_map = { OneLogin_Saml2_Constants.SHA1: xmlsec.constants.TransformSha1, OneLogin_Saml2_Constants.SHA256: xmlsec.constants.TransformSha256, OneLogin_Saml2_Constants.SHA384: xmlsec.constants.TransformSha384, OneLogin_Saml2_Constants.SHA512: xmlsec.constants.TransformSha512 } # request_root = etree.fromstring(request) xmlsec.tree.add_ids(request_root, ["ID"]) signature_node = xmlsec.template.create( request_root, xmlsec.constants.TransformExclC14N, sign_algorithm_transform_map.get( saml_security.get("signatureAlgorithm", OneLogin_Saml2_Constants.RSA_SHA1), xmlsec.constants.TransformRsaSha1)) request_root.insert(1, signature_node) reference_node = xmlsec.template.add_reference( signature_node, digest_algorithm_transform_map.get( saml_security.get("digestAlgorithm", OneLogin_Saml2_Constants.SHA1), xmlsec.constants.TransformSha1), uri=f"#{request_root.get('ID')}") xmlsec.template.add_transform(reference_node, xmlsec.constants.TransformEnveloped) xmlsec.template.add_transform(reference_node, xmlsec.constants.TransformExclC14N) xmlsec.template.add_x509_data( xmlsec.template.ensure_key_info(signature_node)) signature_ctx = xmlsec.SignatureContext() signature_ctx.key = xmlsec.Key.from_memory( saml_auth.get_settings().get_sp_key(), xmlsec.constants.KeyDataFormatPem) signature_ctx.key.load_cert_from_memory( saml_auth.get_settings().get_sp_cert(), format=xmlsec.constants.KeyDataFormatPem) signature_ctx.sign(signature_node) request = OneLogin_Saml2_Utils.b64encode(etree.tostring(request_root)) return request
def get_request(self, deflate=True): """ Returns unsigned AuthnRequest. :param deflate: It makes the deflate process optional :type: bool :return: AuthnRequest maybe deflated and base64 encoded :rtype: str object """ if deflate: request = OneLogin_Saml2_Utils.deflate_and_base64_encode(self.__authn_request) else: request = OneLogin_Saml2_Utils.b64encode(self.__authn_request) return request
def get_response(self, deflate=True): """ Returns a Logout Response object. :param deflate: It makes the deflate process optional :type: bool :return: Logout Response maybe deflated and base64 encoded :rtype: string """ if deflate: response = OneLogin_Saml2_Utils.deflate_and_base64_encode(self.__logout_response) else: response = OneLogin_Saml2_Utils.b64encode(self.__logout_response) return response
def get_request(self, deflate=True): """ Returns the Logout Request deflated, base64encoded :param deflate: It makes the deflate process optional :type: bool :return: Logout Request maybe deflated and base64 encoded :rtype: str object """ if deflate: request = OneLogin_Saml2_Utils.deflate_and_base64_encode(self.__logout_request) else: request = OneLogin_Saml2_Utils.b64encode(self.__logout_request) return request
def get_response(self, deflate=True): """ Returns a Logout Response object. :param deflate: It makes the deflate process optional :type: bool :return: Logout Response maybe deflated and base64 encoded :rtype: string """ if deflate: response = OneLogin_Saml2_Utils.deflate_and_base64_encode(self.__logout_response) else: response = OneLogin_Saml2_Utils.b64encode(self.__logout_response) return response
def testIsInvalidIssuer(self): """ Tests the is_valid method of the OneLogin_Saml2_LogoutRequest Case Invalid Issuer """ request = self.file_contents(join(self.data_path, 'logout_requests', 'invalids', 'invalid_issuer.xml')) request_data = { 'http_host': 'example.com', 'script_name': 'index.html' } current_url = OneLogin_Saml2_Utils.get_self_url_no_query(request_data) request = request.replace('http://stuff.com/endpoints/endpoints/sls.php', current_url) settings = OneLogin_Saml2_Settings(self.loadSettingsJSON()) logout_request = OneLogin_Saml2_Logout_Request(settings, OneLogin_Saml2_Utils.b64encode(request)) self.assertTrue(logout_request.is_valid(request_data)) settings.set_strict(True) try: logout_request2 = OneLogin_Saml2_Logout_Request(settings, OneLogin_Saml2_Utils.b64encode(request)) valid = logout_request2.is_valid(request_data) self.assertFalse(valid) except Exception as e: self.assertIn('Invalid issuer in the Logout Request', str(e))
def testIsValidRaisesExceptionWhenRaisesArgumentIsTrue(self): request = OneLogin_Saml2_Utils.b64encode('<xml>invalid</xml>') request_data = { 'http_host': 'example.com', 'script_name': 'index.html', } settings = OneLogin_Saml2_Settings(self.loadSettingsJSON()) settings.set_strict(True) logout_request = OneLogin_Saml2_Logout_Request(settings, request) self.assertFalse(logout_request.is_valid(request_data)) with self.assertRaises(Exception): logout_request.is_valid(request_data, raise_exceptions=True)
def testGetXML(self): """ Tests that we can get the logout request XML directly without going through intermediate steps """ request = self.file_contents(join(self.data_path, 'logout_requests', 'logout_request.xml')) settings = OneLogin_Saml2_Settings(self.loadSettingsJSON()) logout_request_generated = OneLogin_Saml2_Logout_Request(settings) expectedFragment = ( 'Destination="http://idp.example.com/SingleLogoutService.php">\n' ' <saml:Issuer>http://stuff.com/endpoints/metadata.php</saml:Issuer>\n' ' <saml:NameID SPNameQualifier="http://stuff.com/endpoints/metadata.php" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">http://idp.example.com/</saml:NameID>\n' ' \n</samlp:LogoutRequest>' ) self.assertIn(expectedFragment, logout_request_generated.get_xml()) logout_request_processed = OneLogin_Saml2_Logout_Request(settings, OneLogin_Saml2_Utils.b64encode(request)) self.assertEqual(request, logout_request_processed.get_xml())
def testIsInvalidXML(self): """ Tests the is_valid method of the OneLogin_Saml2_LogoutRequest Case Invalid XML """ request = OneLogin_Saml2_Utils.b64encode('<xml>invalid</xml>') request_data = { 'http_host': 'example.com', 'script_name': 'index.html', } settings = OneLogin_Saml2_Settings(self.loadSettingsJSON()) logout_request = OneLogin_Saml2_Logout_Request(settings, request) self.assertTrue(logout_request.is_valid(request_data)) settings.set_strict(True) logout_request2 = OneLogin_Saml2_Logout_Request(settings, request) self.assertFalse(logout_request2.is_valid(request_data))
def testIsInvalidXML(self): """ Tests the is_valid method of the OneLogin_Saml2_LogoutRequest Case Invalid XML """ request = OneLogin_Saml2_Utils.b64encode('<xml>invalid</xml>') request_data = { 'http_host': 'example.com', 'script_name': 'index.html', } settings = OneLogin_Saml2_Settings(self.loadSettingsJSON()) logout_request = OneLogin_Saml2_Logout_Request(settings, request) self.assertTrue(logout_request.is_valid(request_data)) settings.set_strict(True) logout_request2 = OneLogin_Saml2_Logout_Request(settings, request) self.assertFalse(logout_request2.is_valid(request_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.PRIVATE_KEY_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 __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 formatError(self, response_obj, srelay, url): try: # build response fprm try: with open( os.path.join(globalsObj.modules_basedir, globalsObj.easyspid_responseFormPath), 'rb') as myfile: response_form = myfile.read().decode("utf-8") except: with open(globalsObj.easyspid_responseFormPath, 'rb') as myfile: response_form = myfile.read().decode("utf-8") new_response_obj = ResponseObj(httpcode=200, ID=response_obj.id) new_response_obj.setError('200') new_response_obj.result = response_obj.result response_form = response_form.replace("%URLTARGET%", url) response_form = response_form.replace("%RELAYSTATE%", srelay) response_form = response_form.replace( "%RESPONSE%", OneLogin_Saml2_Utils.b64encode(response_obj.jsonWrite())) self.postTo = response_form return new_response_obj except Exception as inst: response_obj = ResponseObj(httpcode=500) response_obj.setError('500') logging.getLogger( type(self).__module__ + "." + type(self).__qualname__).error( 'Exception', exc_info=True) return new_response_obj
def processResponse(self, chkTime=True, checkInResponseTo=True): try: # get response and Relay state responsePost = self.get_argument('SAMLResponse') srelayPost = self.get_argument('RelayState') # decode saml response #response = responsePost self.response = responsePost try: self.response = OneLogin_Saml2_Utils.decode_base64_and_inflate( responsePost) except Exception: try: self.response = OneLogin_Saml2_Utils.b64decode( responsePost) except Exception: pass # try: # #response = OneLogin_Saml2_Utils.b64decode(responsePost) # self.response = OneLogin_Saml2_Utils.b64decode(responsePost) # except Exception: # pass ## parse XML and make some check ns = { 'md0': OneLogin_Saml2_Constants.NS_SAMLP, 'md1': OneLogin_Saml2_Constants.NS_SAML } parsedResponse = xml.etree.ElementTree.fromstring(self.response) self.inResponseTo = parsedResponse.get('InResponseTo') self.ResponseID = parsedResponse.get('ID') issuer = self.issuer = parsedResponse.find("md1:Issuer", ns) if issuer is None: response_obj = ResponseObj(httpcode=401) response_obj.setError('easyspid118') return response_obj #spByInResponseTo = None # try to get sp searching a corresponding request and raise error if checkInResponseTo is True # inResponseChk = waitFuture(asyncio.run_coroutine_threadsafe(self.dbobjSaml.execute_statment("chk_idAssertion('%s')" % # inResponseTo), globalsObj.ioloop)) # if inResponseChk['error'] == 0 and inResponseChk['result'] is not None: # spByInResponseTo = inResponseChk['result'][0]['cod_sp'] # # elif checkInResponseTo and inResponseChk['error'] == 0 and inResponseChk['result'] == None: # response_obj = ResponseObj(httpcode=404, ID = ResponseID) # response_obj.setError('easyspid120') # response_obj.setResult(response = str(response, 'utf-8')) # return response_obj # # elif inResponseChk['error'] > 0: # response_obj = ResponseObj(httpcode=500) # response_obj.setError('easyspid105') # response_obj.setResult(inResponseChk['result']) # return response_obj # try to get sp searching a corresponding request and raise error if checkInResponseTo is True spByInResponseTo = self.chkExistsReq(checkInResponseTo) ### check StatusCode to find errors firstChk = easyspid.lib.utils.validateAssertion( str(self.response, 'utf-8'), None, None) if not firstChk['chkStatus']: #get errors codes samlErrors = waitFuture( asyncio.run_coroutine_threadsafe( getResponseError(parsedResponse, sp=spByInResponseTo, namespace=ns), globalsObj.ioloop)) if samlErrors['error'] == '0': response_obj = ResponseObj(httpcode=400, ID=self.ResponseID) response_obj.setError('easyspid121') response_obj.setResult(response=str( self.response, 'utf-8'), format='json', samlErrors=samlErrors['status']) return self.formatError(response_obj, srelayPost, samlErrors['service']) elif samlErrors['error'] == 'easyspid114': response_obj = ResponseObj(httpcode=404) response_obj.setError('easyspid114') return response_obj else: response_obj = ResponseObj(httpcode=500) response_obj.setError('500') response_obj.setResult(samlErrors['error']) return response_obj #decode Relay state #srelay = srelayPost self.srelay = srelayPost try: self.srelay = OneLogin_Saml2_Utils.decode_base64_and_inflate( srelayPost) except Exception: try: self.srelay = OneLogin_Saml2_Utils.b64decode(srelayPost) except Exception: pass #self.srelay = OneLogin_Saml2_Utils.b64decode(srelayPost) #pass # try: # #srelay = OneLogin_Saml2_Utils.b64decode(srelayPost) # self.srelay = OneLogin_Saml2_Utils.b64decode(srelayPost) # except Exception: # pass ## get sp by ID #ns = {'md0': OneLogin_Saml2_Constants.NS_SAMLP, 'md1': OneLogin_Saml2_Constants.NS_SAML} #parsedResponse = xml.etree.ElementTree.fromstring(response) #issuer = self.issuer = parsedResponse.find("md1:Issuer", ns) #inResponseTo = parsedResponse.get('InResponseTo') #get audience audience = self.audience = parsedResponse.find( 'md1:Assertion/md1:Conditions/md1:AudienceRestriction/md1:Audience', ns) if audience is None: response_obj = ResponseObj(httpcode=401) response_obj.setError('easyspid118') return response_obj # if issuer is None or audience is None: # response_obj = ResponseObj(httpcode=401) # response_obj.setError('easyspid118') # return response_obj #task1 = asyncio.run_coroutine_threadsafe(self.dbobjSaml.execute_statment("chk_idAssertion('%s')" % # inResponseTo), globalsObj.ioloop) # task2 = asyncio.run_coroutine_threadsafe(self.dbobjSaml.execute_statment("get_provider_byentityid(%s, '%s')" % # ('True', '{'+(self.issuer.text.strip())+'}')), globalsObj.ioloop) #task3 = asyncio.run_coroutine_threadsafe(self.dbobjSaml.execute_statment("get_provider_byentityid(%s, '%s')" % # ('True', '{'+(audience.text.strip())+'}')), globalsObj.ioloop) #assert not task1.done() #inResponseChk = task1.result() #inResponseChk = waitFuture(task1) #audienceChk = waitFuture(task3) #spByAudience = None #spByInResponseTo = None #if inResponseChk['error'] == 0 and inResponseChk['result'] is not None: # spByInResponseTo = inResponseChk['result'][0]['cod_sp'] # if audienceChk['error'] == 0 and audienceChk['result'] is not None: # spByAudience = audienceChk['result'][0]['cod_provider'] #check audinece # if spByAudience is None: # response_obj = ResponseObj(httpcode=404) # response_obj.setError('easyspid115') # return response_obj # get sp by audience spByAudience = self.getSpByAudience() #check inresponseTo and spByAudience == spByInResponseTo if checkInResponseTo and spByAudience == spByInResponseTo: sp = spByAudience elif checkInResponseTo and spByAudience != spByInResponseTo: response_obj = ResponseObj(httpcode=401) response_obj.setError('easyspid110') return response_obj sp = spByAudience # get service by sp and relay_state try: task = asyncio.run_coroutine_threadsafe( self.dbobjSaml.execute_statment( "get_service(%s, '%s', '%s')" % ('True', str(self.srelay), sp)), globalsObj.ioloop) #assert not task.done() #service = task.result() service = waitFuture(task) if service['error'] == 0 and service['result'] is not None: # costruisci il routing self.routing = dict() self.routing['url'] = service['result'][0]['url'] self.routing['relaystate'] = self.srelay self.routing['format'] = service['result'][0]['format'] elif service['error'] > 0 or service['result'] is None: response_obj = ResponseObj(httpcode=500, debugMessage=service['result']) response_obj.setError("easyspid111") return response_obj except Exception: pass # get IdP # idpEntityId = waitFuture(task2) # # if idpEntityId['error'] == 0 and idpEntityId['result'] is not None: # idp_metadata = idpEntityId['result'][0]['xml'] # idp = idpEntityId['result'][0]['cod_provider'] # # elif idpEntityId['error'] == 0 and idpEntityId['result'] is None: # response_obj = ResponseObj(httpcode=404) # response_obj.setError('easyspid103') # return response_obj # # elif idpEntityId['error'] > 0: # response_obj = ResponseObj(httpcode=500, debugMessage=idpEntityId['result']) # response_obj.setError("easyspid105") # return response_obj # get IdP and metadata (idp_metadata, idp) = self.getIdentyIdp() # get sp settings task = asyncio.run_coroutine_threadsafe( easyspid.lib.easyspid.spSettings(sp, idp, close=True), globalsObj.ioloop) sp_settings = waitFuture(task) if sp_settings['error'] == 0 and sp_settings['result'] != None: ## insert response into DB task = asyncio.run_coroutine_threadsafe( self.dbobjSaml.execute_statment( "write_assertion('%s', '%s', '%s', '%s')" % (str(self.response, 'utf-8').replace( "'", "''"), sp, idp, self.remote_ip)), globalsObj.ioloop) wrtAuthn = waitFuture(task) if wrtAuthn['error'] == 0: if self.routing['format'] == 'saml': return self.passthrough() task = asyncio.run_coroutine_threadsafe( self.dbobjJwt.execute_statment( "get_token_by_cod('%s')" % (wrtAuthn['result'][0]['cod_token'])), globalsObj.ioloop) #assert not task.done() #jwt = task.result() jwt = waitFuture(task) else: response_obj = ResponseObj(httpcode=500, debugMessage=wrtAuthn['result']) response_obj.setError("easyspid105") logging.getLogger( type(self).__module__ + "." + type(self).__qualname__).error('Exception', exc_info=True) return response_obj # create settings OneLogin dict #settings = sp_settings['result'] prvdSettings = Saml2_Settings(sp_settings['result']) chk = easyspid.lib.utils.validateAssertion( str(self.response, 'utf-8'), sp_settings['result']['idp']['x509cert_fingerprint'], sp_settings['result']['idp']['x509cert_fingerprintalg']) chk['issuer'] = issuer.text.strip() chk['audience'] = audience.text.strip() if not chk['chkStatus']: response_obj = ResponseObj(httpcode=401) response_obj.setError('easyspid107') return response_obj elif not chk['schemaValidate']: response_obj = ResponseObj(httpcode=401) response_obj.setError('easyspid104') response_obj.setResult(responseValidate=chk) return response_obj elif not chk['signCheck']: response_obj = ResponseObj(httpcode=401) response_obj.setError('easyspid106') response_obj.setResult(responseValidate=chk) return response_obj elif not chk[ 'certAllowed'] and globalsObj.easyspid_checkCertificateAllowed: response_obj = ResponseObj(httpcode=401) response_obj.setError('easyspid116') response_obj.setResult(responseValidate=chk) return response_obj elif not chk[ 'certValidity'] and globalsObj.easyspid_checkCertificateValidity: response_obj = ResponseObj(httpcode=401) response_obj.setError('easyspid117') response_obj.setResult(responseValidate=chk) return response_obj elif chkTime and not chk['chkTime']: response_obj = ResponseObj(httpcode=401) response_obj.setError('easyspid108') return response_obj #get all attributes attributes = chk['serviceAttributes'] attributes_tmp = dict() for key in attributes: attributes_tmp[key] = attributes[key][0] attributes = attributes_tmp # build response form try: with open( os.path.join(globalsObj.modules_basedir, globalsObj.easyspid_responseFormPath), 'rb') as myfile: response_form = myfile.read().decode("utf-8") except: with open(globalsObj.easyspid_responseFormPath, 'rb') as myfile: response_form = myfile.read().decode("utf-8") response_obj = ResponseObj( httpcode=200, ID=wrtAuthn['result'][0]['ID_assertion']) response_obj.setError('200') response_obj.setResult(attributes=attributes, jwt=jwt['result'][0]['token'], responseValidate=chk, response=str(self.response, 'utf-8'), format='json') response_form = response_form.replace("%URLTARGET%", self.routing['url']) response_form = response_form.replace("%RELAYSTATE%", srelayPost) response_form = response_form.replace( "%RESPONSE%", OneLogin_Saml2_Utils.b64encode(response_obj.jsonWrite())) self.postTo = response_form elif sp_settings['error'] == 0 and sp_settings['result'] == None: response_obj = ResponseObj(httpcode=404) response_obj.setError('easyspid114') elif sp_settings['error'] > 0: #response_obj = sp_settings['result'] response_obj = sp_settings except goExit as e: return e.expression except tornado.web.MissingArgumentError as error: response_obj = ResponseObj(debugMessage=error.log_message, httpcode=error.status_code, devMessage=error.log_message) response_obj.setError(str(error.status_code)) logging.getLogger( type(self).__module__ + "." + type(self).__qualname__).error( '%s' % error, exc_info=True) except Exception as inst: response_obj = ResponseObj(httpcode=500) response_obj.setError('500') logging.getLogger( type(self).__module__ + "." + type(self).__qualname__).error( 'Exception', exc_info=True) return response_obj
def loginAuthnReq(self, sp_settings, idp_metadata, attributeIndex, binding, srelay_cod): try: if binding == 'redirect': authn_request = authnreqBuildhandler.buildAthnReq( self, sp_settings, attributeIndex, signed=False) elif binding == 'post': authn_request = authnreqBuildhandler.buildAthnReq( self, sp_settings, attributeIndex, signed=True) bindingMap = { 'redirect': OneLogin_Saml2_Constants.BINDING_HTTP_REDIRECT, 'post': OneLogin_Saml2_Constants.BINDING_HTTP_POST } if (sp_settings['error'] == 0 and sp_settings['result'] is not None and idp_metadata.error.code == '200' and authn_request.error.code == '200'): sp = sp_settings['result']['sp']['cod_sp'] idp = sp_settings['result']['idp']['cod_idp'] # get relay state task = asyncio.run_coroutine_threadsafe( self.dbobjSaml.execute_statment( "get_service(%s, '%s', '%s')" % ('True', str(srelay_cod), sp)), globalsObj.ioloop) #assert not task.done() #srelay = task.result() srelay = waitFuture(task) if srelay['error'] == 0 and srelay['result'] is None: response_obj = ResponseObj(httpcode=404) response_obj.setError('easyspid113') return response_obj elif srelay['error'] > 0: response_obj = ResponseObj( httpcode=500, debugMessage=sp_settings['result']) response_obj.setError("easyspid105") return response_obj #if (sp_settings['error'] == 0 and sp_settings['result'] is not None #and idp_metadata.error.code == '200' and authn_request.error.code == '200'): idp_data = OneLogin_Saml2_IdPMetadataParser.parse( idp_metadata.result.metadata, required_sso_binding=bindingMap[binding], required_slo_binding=bindingMap[binding]) idp_settings = idp_data['idp'] # fake authn_request req = { "http_host": "", "script_name": "", "server_port": "", "get_data": "", "post_data": "" } settings = sp_settings['result'] if 'entityId' in idp_settings: settings['idp']['entityId'] = idp_settings['entityId'] if 'singleLogoutService' in idp_settings: settings['idp']['singleLogoutService'] = idp_settings[ 'singleLogoutService'] if 'singleSignOnService' in idp_settings: settings['idp']['singleSignOnService'] = idp_settings[ 'singleSignOnService'] if 'x509cert' in idp_settings: settings['idp']['x509cert'] = idp_settings['x509cert'] auth = OneLogin_Saml2_Auth(req, sp_settings['result']) spSettings = Saml2_Settings(sp_settings['result']) sign_alg = ( spSettings.get_security_data())['signatureAlgorithm'] # build login message # redirect binding if binding == 'redirect': saml_request = OneLogin_Saml2_Utils.deflate_and_base64_encode( authn_request.result.authnrequest) parameters = {'SAMLRequest': saml_request} parameters['RelayState'] = srelay['result'][0][ 'relay_state'] auth.add_request_signature(parameters, sign_alg) redirectLocation = auth.redirect_to( auth.get_sso_url(), parameters) response_obj = ResponseObj(httpcode=200) response_obj.setError('200') response_obj.setResult(redirectTo=redirectLocation, jwt=authn_request.result.jwt) # POST binding elif binding == 'post': saml_request_signed = OneLogin_Saml2_Utils.b64encode( authn_request.result.authnrequest) relay_state = OneLogin_Saml2_Utils.b64encode( srelay['result'][0]['relay_state']) idpsso = idp_settings['singleSignOnService']['url'] try: with open( os.path.join(globalsObj.modules_basedir, globalsObj.easyspid_postFormPath), 'rb') as myfile: post_form = myfile.read().decode("utf-8").replace( '\n', '') except: with open(globalsObj.easyspid_postFormPath, 'rb') as myfile: post_form = myfile.read().decode("utf-8").replace( '\n', '') post_form = post_form.replace("%IDPSSO%", idpsso) post_form = post_form.replace("%AUTHNREQUEST%", saml_request_signed) post_form = post_form.replace("%RELAYSTATE%", relay_state) response_obj = ResponseObj(httpcode=200) response_obj.setError('200') response_obj.setResult(postTo=post_form, jwt=authn_request.result.jwt) elif sp_settings['error'] == 0 and sp_settings['result'] is None: response_obj = ResponseObj(httpcode=404) response_obj.setError('easyspid101') elif sp_settings['error'] > 0: response_obj = ResponseObj(httpcode=500, debugMessage=sp_settings['result']) response_obj.setError("easyspid105") except tornado.web.MissingArgumentError as error: response_obj = ResponseObj(debugMessage=error.log_message, httpcode=error.status_code, devMessage=error.log_message) response_obj.setError(str(error.status_code)) logging.getLogger( type(self).__module__ + "." + type(self).__qualname__).error( '%s' % error, exc_info=True) except Exception as inst: response_obj = ResponseObj(httpcode=500) response_obj.setError('500') logging.getLogger( type(self).__module__ + "." + type(self).__qualname__).error( 'Exception', exc_info=True) return response_obj
def test_full_login_process(self): """Asserts the nominal login process works.""" sso_location = "http://testserver/account/saml/local-accepting-idp/sso/" entity_descriptor_list = [ generate_idp_metadata( entity_id=sso_location, sso_location=sso_location, ui_info_display_names=format_mdui_display_name( "Local accepting IdP"), ), ] # 1/ Select Idp in the provider list with mock.patch("urllib.request.urlopen") as urlopen_mock: class UrlOpenMock: """Mockin object for the urlopen""" def read(self): """Allow object to be read several times.""" return generate_idp_federation_metadata( entity_descriptor_list=entity_descriptor_list, ).encode("utf-8") urlopen_mock.return_value = UrlOpenMock() response = self.client.get( reverse("account:saml_fer_idp_choice"), ) self.assertContains( response, f'action="{reverse("account:social:begin", args=("saml_fer",))}"', ) self.assertContains(response, "local-accepting-idp") response = self.client.get( f'{reverse("account:social:begin", args=("saml_fer",))}?idp=local-accepting-idp', ) self.assertEqual(response.status_code, 302) self.assertTrue(response["Location"].startswith( "http://testserver/account/saml/local-accepting-idp/sso/?SAMLRequest=" )) # 2/ Fake the redirection to the SSO response = self.client.get( f'{reverse("account:social:begin", args=("saml_fer",))}?idp=local-accepting-idp', follow=False, ) # 3/ Generate SAML response using SAML request query_values = parse_qs(urlparse(response["Location"]).query) saml_request = query_values["SAMLRequest"] saml_relay_state = query_values["RelayState"] readable_saml_request = OneLogin_Saml2_Utils.decode_base64_and_inflate( saml_request, ) saml_request = OneLogin_Saml2_XML.to_etree(readable_saml_request) saml_acs_url = saml_request.get("AssertionConsumerServiceURL") request_id = saml_request.get("ID") auth_response = OneLogin_Saml2_Utils.b64encode( generate_auth_response( request_id, saml_acs_url, issuer= "http://testserver/account/saml/local-accepting-idp/sso/", )) # 4/ POST the data to our endpoint response = self.client.post( saml_acs_url, data={ "RelayState": saml_relay_state, "SAMLResponse": auth_response, }, ) self.assertEqual(response.status_code, 302) self.assertEqual(response["Location"], "/") # Assert the user is authenticated user = auth_get_user(self.client) self.assertTrue(user.is_authenticated) # Assert the user has an organization organization_access = user.organization_accesses.select_related( "organization").get() # also assert there is only one organization self.assertEqual(organization_access.role, STUDENT) self.assertEqual(organization_access.organization.name, "OrganizationDName")