def process(): saml_response = request.form['SAMLResponse'] assertion_data = XMLVerifier().verify(base64.b64decode(saml_response), x509_cert=cert).signed_xml if assertion_data.find( '{urn:oasis:names:tc:SAML:2.0:assertion}LogoutRequest' ) is not None: response = make_response('You have been logged out') response.set_cookie('auth_session', '', expires=0) return response username = assertion_data.find('{urn:oasis:names:tc:SAML:2.0:assertion}Assertion')\ .find('{urn:oasis:names:tc:SAML:2.0:assertion}Subject')\ .find('{urn:oasis:names:tc:SAML:2.0:assertion}NameID').text key = 'secret' expiry = int(time.time()) + timeout signature = hashlib.sha256( f'{username}|{expiry}|{key}'.encode('UTF-8')).hexdigest() if 'RelayState' in request.form: return_path = request.form['RelayState'] response = redirect(return_path, 302) else: response = redirect('/', 302) response.set_cookie('auth_session', f'{username}|{expiry}|{signature}') return response
def authenticate(input_xml, ca_pem_file): # Check signature and retrieve the XML that's guaranteed to be signed. signed_xml = XMLVerifier().verify(input_xml, require_x509=True, ca_pem_file=ca_pem_file).signed_xml # Respect expiry boundaries given in SAML. # Apparently the datetimes have 7 digits of microseconds when normally # they should be 6 when parsed. We'll leave them out by only using the # first 19 characters. conds_xml = signed_xml.find( './/{urn:oasis:names:tc:SAML:2.0:assertion}Conditions') time_limit_lower = datetime.strptime(conds_xml.attrib['NotBefore'][:19], '%Y-%m-%dT%H:%M:%S') time_limit_upper = datetime.strptime(conds_xml.attrib['NotOnOrAfter'][:19], '%Y-%m-%dT%H:%M:%S') now = datetime.now() if time_limit_lower > now or time_limit_upper < now: raise SamlException('Remote authentication expired') # Find the assertion ID for our records. This is not needed for # functionality's sake and is only kept for the hypothetical scenario # where a particular authentication needs to be matched with records on # the identity provider's side. assertion_id = signed_xml.find( './/{urn:oasis:names:tc:SAML:2.0:assertion}Assertion').attrib['ID'] # Translate SAML attributes into a handy dictionary. attributes = {} for attribute in signed_xml.findall( './/{urn:oasis:names:tc:SAML:2.0:assertion}Attribute'): attributes[attribute.attrib['Name']] = attribute.find( './/{urn:oasis:names:tc:SAML:2.0:assertion}AttributeValue').text return assertion_id, attributes