diff --git a/app.py b/app.py new file mode 100644 index 0000000..ce58ada --- /dev/null +++ b/app.py @@ -0,0 +1,545 @@ +# from flask import Flask, request, redirect, session, render_template, url_for, make_response +# from flask_cors import CORS +# from saml2 import BINDING_HTTP_POST, BINDING_HTTP_REDIRECT, saml +# from saml2.config import Config as Saml2Config +# from saml2.client import Saml2Client +# from saml2.saml import NameID +# import logging +# import secrets +# import base64 +# import zlib +# import xmltodict +# import json +# from urllib.parse import urlparse, parse_qs, quote +# from cryptography.hazmat.primitives import hashes +# from cryptography.hazmat.primitives.asymmetric import padding +# from cryptography.hazmat.primitives import serialization +# from lxml import etree + +# app = Flask(__name__) +# app.secret_key = secrets.token_hex(16) +# CORS(app) + +# logging.basicConfig(level=logging.INFO) + +# # Load configuration +# with open('config.json') as config_file: +# config_data = json.load(config_file) + +# # Temporary storage for SAML data +# saml_storage = { +# 'saml_request_id': '', +# 'saml_request': '', +# 'decoded_saml_request': '' +# } + +# def saml_client(): +# config = Saml2Config() +# config.load({ +# 'entityid': config_data['entityid'], +# 'service': { +# 'sp': { +# 'name': 'SAML SP', +# 'endpoints': { +# 'assertion_consumer_service': [ +# (config_data['acs_url'], BINDING_HTTP_POST), +# ], +# 'single_logout_service': [ +# (config_data['sp_slo_url'], BINDING_HTTP_REDIRECT), +# (config_data['sp_slo_url'], BINDING_HTTP_POST), +# ], +# }, +# 'required_attributes': config_data['required_attributes'], +# 'optional_attributes': config_data['optional_attributes'], +# 'authn_requests_signed': config_data['authn_requests_signed'], +# 'want_assertions_signed': config_data['want_assertions_signed'], +# 'want_response_signed': config_data['want_response_signed'], +# 'return_addresses': [ +# config_data['sp_slo_url'], +# config_data['idp_slo_url'] +# ], +# }, +# }, +# 'metadata': { +# 'local': [config_data['idp_metadata_file']], +# }, +# 'key_file': config_data['key_file'], +# 'cert_file': config_data['cert_file'], +# 'allow_unknown_attributes': config_data['allow_unknown_attributes'], +# 'debug': config_data['debug'], +# }) +# return Saml2Client(config) + + +# def pretty_print_xml(xml_string): +# try: +# xml_dict = xmltodict.parse(xml_string) +# return xmltodict.unparse(xml_dict, pretty=True) +# except Exception as e: +# logging.error(f"Error parsing XML: {e}") +# return xml_string + +# def sign_logout_request(logout_request, key_file): +# with open(key_file, 'rb') as key_file_data: +# private_key = serialization.load_pem_private_key( +# key_file_data.read(), +# password=None +# ) + +# # Parse the XML +# root = etree.fromstring(logout_request.encode('utf-8')) + +# # Generate a digest of the entire XML string +# digest = hashes.Hash(hashes.SHA1()) +# digest.update(logout_request.encode('utf-8')) +# digest_value = digest.finalize() + +# # Sign the digest +# signature = private_key.sign( +# digest_value, +# padding.PKCS1v15(), +# hashes.SHA1() +# ) + +# # Create the Signature element +# signature_value = base64.b64encode(signature).decode('utf-8') +# signature_element = etree.Element('{http://www.w3.org/2000/09/xmldsig#}SignatureValue') +# signature_element.text = signature_value + +# # Append the Signature element to the XML +# root.append(signature_element) + +# # Convert the XML back to a string +# signed_xml_string = etree.tostring(root, pretty_print=True, xml_declaration=True, encoding='UTF-8').decode('utf-8') + +# return signed_xml_string + +# @app.route('/') +# def index(): +# saml_request = saml_storage['saml_request'] +# decoded_saml_request = saml_storage['decoded_saml_request'] +# return render_template('index.html', saml_request=saml_request, decoded_saml_request=decoded_saml_request) + +# @app.route('/generate_saml_request', methods=['POST']) +# def generate_saml_request(): +# client = saml_client() +# reqid, info = client.prepare_for_authenticate() +# location_header = dict(info['headers'])['Location'] +# parsed_url = urlparse(location_header) +# saml_request = parse_qs(parsed_url.query)['SAMLRequest'][0] + +# # Decode and decompress SAML Request +# decoded_saml_request = base64.b64decode(saml_request) +# decompressed_saml_request = zlib.decompress(decoded_saml_request, -15).decode('utf-8') +# pretty_saml_request = pretty_print_xml(decompressed_saml_request) + +# # Store in temporary storage +# saml_storage['saml_request_id'] = reqid +# saml_storage['saml_request'] = saml_request +# saml_storage['decoded_saml_request'] = pretty_saml_request + +# return redirect('/') + +# @app.route('/sso', methods=['POST']) +# def sso(): +# saml_request = request.form['saml_request'] +# encoded_saml_request = quote(saml_request) # Ensure the SAML request is URL encoded +# redirect_url = f"{config_data['redirect_url']}?SAMLRequest={encoded_saml_request}" +# return redirect(redirect_url) + +# @app.route('/acs', methods=['POST']) +# def acs(): +# client = saml_client() +# saml_response = request.form['SAMLResponse'] +# try: +# outstanding = {saml_storage['saml_request_id']: 'example'} +# authn_response = client.parse_authn_request_response( +# saml_response, BINDING_HTTP_POST, outstanding=outstanding +# ) +# session['user_info'] = authn_response.get_identity() + +# # Extract session_index from AuthnStatement +# session_index = None +# for statement in authn_response.assertion.authn_statement: +# session_index = statement.session_index +# if session_index: +# break + +# if not session_index: +# raise ValueError("No session index found in AuthnStatement") + +# # Store name_id and session_index as strings +# session['name_id'] = str(authn_response.name_id) +# session['session_index'] = session_index + +# # Decode SAML Response +# decoded_saml_response = base64.b64decode(saml_response).decode('utf-8') +# pretty_saml_response = pretty_print_xml(decoded_saml_response) + +# logging.info("User successfully logged in") + +# return render_template('response.html', saml_response=saml_response, pretty_saml_response=pretty_saml_response, user_info=session['user_info']) +# except Exception as e: +# logging.error(f"Error processing SAML response: {e}") +# return f"Error processing SAML response: {e}", 500 + +# @app.route('/logout', methods=['POST']) +# def logout(): +# client = saml_client() +# session_index = session.get('session_index') +# name_id_str = session.get('name_id') + +# if 'entityid' not in config_data: +# logging.error("Missing 'entityid' in configuration data") +# return redirect(url_for('index')) + +# issuer_entity_id = config_data['entityid'] + +# if session_index and name_id_str: +# logging.info(f"Initiating logout for session_index: {session_index}, name_id: {name_id_str}") +# try: +# name_id = NameID(text=name_id_str, format=saml.NAMEID_FORMAT_EMAILADDRESS) + +# # Prepare the logout request +# logout_request_id, logout_request = client.create_logout_request( +# name_id=name_id, +# session_indexes=[session_index], +# destination=config_data['idp_slo_url'], +# issuer_entity_id=issuer_entity_id +# ) + +# logout_request_str = str(logout_request) +# logging.info(f"Logout request before signing: {logout_request_str}") + +# # Sign the logout request using cryptography +# signed_logout_request_str = sign_logout_request(logout_request_str, client.config.key_file) +# logging.info(f"Signed LogoutRequest: {signed_logout_request_str}") + +# # Encode the SAML request (deflate + base64) +# deflated_logout_request = zlib.compress(signed_logout_request_str.encode('utf-8'))[2:-4] +# saml_request_encoded = base64.b64encode(deflated_logout_request).decode('utf-8') +# logging.info(f"Deflated LogoutRequest: {deflated_logout_request}") +# logging.info(f"Base64 Encoded SAMLRequest: {saml_request_encoded}") + +# # Ensure the SAML request is URL encoded +# encoded_saml_request = quote(saml_request_encoded) +# logging.info(f"URL Encoded SAMLRequest: {encoded_saml_request}") + +# # Formulate the logout URL +# logout_url = f"{config_data['idp_slo_url']}?SAMLRequest={encoded_saml_request}" + +# # Clear the session and SAML storage +# session.clear() +# clear_saml_storage() + +# # Clear cookies +# response = make_response(redirect(logout_url)) +# response.set_cookie('session', '', expires=0) +# for key in request.cookies.keys(): +# response.set_cookie(key, '', expires=0) + +# logging.info("User successfully logged out") +# return response +# except Exception as e: +# logging.error(f"Error creating logout request: {e}") +# logging.error(f"Error details: {e.__class__.__name__}, {str(e)}") +# return f"Error creating logout request: {e}", 500 + +# # In case of missing session data, clear and redirect +# session.clear() +# clear_saml_storage() + +# # Clear cookies +# response = make_response(redirect(url_for('index'))) +# response.set_cookie('session', '', expires=0) +# for key in request.cookies.keys(): +# response.set_cookie(key, '', expires=0) + +# logging.info("Session and cookies cleared, redirected to index") +# return response + +# @app.route('/slo', methods=['POST']) +# def slo(): +# response = request.form.get('SAMLResponse') +# if response: +# try: +# client = saml_client() +# logout_response = client.parse_logout_request_response( +# response, BINDING_HTTP_POST +# ) +# if logout_response: +# # Clear the session and redirect to index +# session.clear() +# clear_saml_storage() +# logging.info("Single logout successful, session cleared") +# return redirect(url_for('index')) +# except Exception as e: +# logging.error(f"Error processing SLO response: {e}") +# return f"Error processing SLO response: {e}", 500 + +# logging.error("Invalid SLO response") +# return "Invalid SLO response", 400 + +# def clear_saml_storage(): +# saml_storage['saml_request_id'] = '' +# saml_storage['saml_request'] = '' +# saml_storage['decoded_saml_request'] = '' + +# if __name__ == '__main__': +# app.run(debug=True) + + +from flask import Flask, request, redirect, session, render_template, url_for, make_response +from flask_cors import CORS +from saml2 import BINDING_HTTP_POST, BINDING_HTTP_REDIRECT, saml +from saml2.config import Config as Saml2Config +from saml2.client import Saml2Client +from saml2.saml import NameID +import logging +import secrets +import base64 +import zlib +import xmltodict +import json +from urllib.parse import urlparse, parse_qs, quote +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric import padding +from cryptography.hazmat.primitives import serialization +from lxml import etree + +app = Flask(__name__) +app.secret_key = secrets.token_hex(16) +CORS(app) + +logging.basicConfig(level=logging.INFO) + +# Load configuration +with open('config.json') as config_file: + config_data = json.load(config_file) + +# Temporary storage for SAML data +saml_storage = { + 'saml_request_id': '', + 'saml_request': '', + 'decoded_saml_request': '' +} + +def saml_client(): + config = Saml2Config() + config.load({ + 'entityid': config_data['entityid'], + 'service': { + 'sp': { + 'name': 'SAML SP', + 'endpoints': { + 'assertion_consumer_service': [ + (config_data['acs_url'], BINDING_HTTP_POST), + ], + 'single_logout_service': [ + (config_data['sp_slo_url'], BINDING_HTTP_REDIRECT), + (config_data['sp_slo_url'], BINDING_HTTP_POST), + ], + }, + 'required_attributes': config_data['required_attributes'], + 'optional_attributes': config_data['optional_attributes'], + 'authn_requests_signed': config_data['authn_requests_signed'], + 'want_assertions_signed': config_data['want_assertions_signed'], + 'want_response_signed': config_data['want_response_signed'], + 'return_addresses': [ + config_data['sp_slo_url'], + config_data['idp_slo_url'] + ], + }, + }, + 'metadata': { + 'local': [config_data['idp_metadata_file']], + }, + 'key_file': config_data['key_file'], + 'cert_file': config_data['cert_file'], + 'allow_unknown_attributes': config_data['allow_unknown_attributes'], + 'debug': config_data['debug'], + }) + return Saml2Client(config) + +def pretty_print_xml(xml_string): + try: + xml_dict = xmltodict.parse(xml_string) + return xmltodict.unparse(xml_dict, pretty=True) + except Exception as e: + logging.error(f"Error parsing XML: {e}") + return xml_string + +def sign_logout_request(logout_request, key_file): + with open(key_file, 'rb') as key_file_data: + private_key = serialization.load_pem_private_key(key_file_data.read(), password=None) + + # Parse the XML + root = etree.fromstring(logout_request.encode('utf-8')) + + # Generate a digest of the entire XML string + digest = hashes.Hash(hashes.SHA1()) + digest.update(logout_request.encode('utf-8')) + digest_value = digest.finalize() + + # Sign the digest + signature = private_key.sign(digest_value, padding.PKCS1v15(), hashes.SHA1()) + + # Create the Signature element + signature_value = base64.b64encode(signature).decode('utf-8') + signature_element = etree.Element('{http://www.w3.org/2000/09/xmldsig#}SignatureValue') + signature_element.text = signature_value + + # Append the Signature element to the XML + root.append(signature_element) + + # Convert the XML back to a string + signed_xml_string = etree.tostring(root, pretty_print=True, xml_declaration=True, encoding='UTF-8').decode('utf-8') + + return signed_xml_string + +@app.route('/') +def index(): + saml_request = saml_storage['saml_request'] + decoded_saml_request = saml_storage['decoded_saml_request'] + return render_template('index.html', saml_request=saml_request, decoded_saml_request=decoded_saml_request) + +@app.route('/generate_saml_request', methods=['POST']) +def generate_saml_request(): + client = saml_client() + reqid, info = client.prepare_for_authenticate() + location_header = dict(info['headers'])['Location'] + parsed_url = urlparse(location_header) + saml_request = parse_qs(parsed_url.query)['SAMLRequest'][0] + + # Decode and decompress SAML Request + decoded_saml_request = base64.b64decode(saml_request) + decompressed_saml_request = zlib.decompress(decoded_saml_request, -15).decode('utf-8') + pretty_saml_request = pretty_print_xml(decompressed_saml_request) + + # Store in temporary storage + saml_storage['saml_request_id'] = reqid + saml_storage['saml_request'] = saml_request + saml_storage['decoded_saml_request'] = pretty_saml_request + + return redirect('/') + +@app.route('/sso', methods=['POST']) +def sso(): + saml_request = request.form['saml_request'] + encoded_saml_request = quote(saml_request) # Ensure the SAML request is URL encoded + redirect_url = f"{config_data['redirect_url']}?SAMLRequest={encoded_saml_request}" + return redirect(redirect_url) + +@app.route('/acs', methods=['POST']) +def acs(): + client = saml_client() + saml_response = request.form['SAMLResponse'] + try: + outstanding = {saml_storage['saml_request_id']: 'example'} + authn_response = client.parse_authn_request_response( + saml_response, BINDING_HTTP_POST, outstanding=outstanding + ) + session['user_info'] = authn_response.get_identity() + + # Extract session_index from AuthnStatement + session_index = None + for statement in authn_response.assertion.authn_statement: + session_index = statement.session_index + if session_index: + break + + if not session_index: + raise ValueError("No session index found in AuthnStatement") + + # Store name_id and session_index as strings + session['name_id'] = str(authn_response.name_id) + session['session_index'] = session_index + + # Decode SAML Response + decoded_saml_response = base64.b64decode(saml_response).decode('utf-8') + pretty_saml_response = pretty_print_xml(decoded_saml_response) + + logging.info("User successfully logged in") + + return render_template('response.html', saml_response=saml_response, pretty_saml_response=pretty_saml_response, user_info=session['user_info']) + except Exception as e: + logging.error(f"Error processing SAML response: {e}") + return f"Error processing SAML response: {e}", 500 + +@app.route('/logout', methods=['POST']) +def logout(): + client = saml_client() + session_index = session.get('session_index') + name_id_str = session.get('name_id') + + if not session_index or not name_id_str: + session.clear() + clear_saml_storage() + return redirect(url_for('index')) + + try: + name_id = NameID(text=name_id_str, format=saml.NAMEID_FORMAT_EMAILADDRESS) + + # Prepare the logout request + logout_request_id, logout_request = client.create_logout_request( + name_id=name_id, + session_indexes=[session_index], + destination=config_data['idp_slo_url'], + issuer_entity_id=config_data['entityid'] + ) + + # Sign the logout request + signed_logout_request_str = sign_logout_request(str(logout_request), client.config.key_file) + + # Encode the SAML request (deflate + base64) + deflated_logout_request = zlib.compress(signed_logout_request_str.encode('utf-8'))[2:-4] + saml_request_encoded = base64.b64encode(deflated_logout_request).decode('utf-8') + + # Ensure the SAML request is URL encoded + encoded_saml_request = quote(saml_request_encoded) + + # Formulate the logout URL + logout_url = f"{config_data['idp_slo_url']}?SAMLRequest={encoded_saml_request}" + + # Clear the session and SAML storage + session.clear() + clear_saml_storage() + + # Clear cookies + response = make_response(redirect(logout_url)) + response.set_cookie('session', '', expires=0) + for key in request.cookies.keys(): + response.set_cookie(key, '', expires=0) + + logging.info("User successfully logged out") + return response + except Exception as e: + logging.error(f"Error creating logout request: {e}") + return f"Error creating logout request: {e}", 500 + +@app.route('/slo', methods=['POST']) +def slo(): + response = request.form.get('SAMLResponse') + if not response: + return "Invalid SLO response", 400 + + try: + client = saml_client() + logout_response = client.parse_logout_request_response(response, BINDING_HTTP_POST) + if logout_response: + # Clear the session and redirect to index + session.clear() + clear_saml_storage() + logging.info("Single logout successful, session cleared") + return redirect(url_for('index')) + except Exception as e: + logging.error(f"Error processing SLO response: {e}") + return f"Error processing SLO response: {e}", 500 + +def clear_saml_storage(): + saml_storage['saml_request_id'] = '' + saml_storage['saml_request'] = '' + saml_storage['decoded_saml_request'] = '' + +if __name__ == '__main__': + app.run(debug=True) diff --git a/config.json b/config.json new file mode 100644 index 0000000..0dce258 --- /dev/null +++ b/config.json @@ -0,0 +1,18 @@ +{ + "entityid": "https://zitadel-test.sp/metadata", + "acs_url": "http://127.0.0.1:5000/acs", + "idp_slo_url": "https://my-instance-xtzfbc.zitadel.cloud/saml/v2/SLO", + "sp_slo_url": "http://127.0.0.1:5000/slo", + "redirect_url": "https://my-instance-xtzfbc.zitadel.cloud/saml/v2/SSO", + "idp_metadata_file": "idp_metadata.xml", + "key_file": "sp-key.pem", + "cert_file": "sp-cert.pem", + "xmlsec_binary": "/opt/homebrew/bin/xmlsec1", + "required_attributes": ["uid"], + "optional_attributes": ["mail"], + "authn_requests_signed": true, + "want_assertions_signed": true, + "want_response_signed": false, + "allow_unknown_attributes": true, + "debug": 1 +} diff --git a/idp_metadata.xml b/idp_metadata.xml new file mode 100644 index 0000000..2faad4b --- /dev/null +++ b/idp_metadata.xml @@ -0,0 +1,2 @@ + +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urn:oasis:names:tc:SAML:2.0:profiles:attribute:basicurn:oasis:names:tc:SAML:2.0:nameid-format:persistenthttps://my-instance-xtzfbc.zitadel.cloud/saml/v2/metadata IDP signingMIIFITCCAwmgAwIBAgIBUTANBgkqhkiG9w0BAQsFADAsMRAwDgYDVQQKEwdaSVRBREVMMRgwFgYDVQQDEw9aSVRBREVMIFNBTUwgQ0EwHhcNMjQwNTIwMTIxNjQzWhcNMjUwNTIwMTgxNjQzWjAyMRAwDgYDVQQKEwdaSVRBREVMMR4wHAYDVQQDExVaSVRBREVMIFNBTUwgcmVzcG9uc2UwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDaVZkfAG7xhg/rEkBMzvOI4asj49utf9oM/EzpLHs0EPPMB5CLEfKnUkd9YMkG9M7bhPI2ApQSOCnlpKIumUtA/7r8ft2v0pJjq8eQ/EIMCXaFQnt0YKK3E1BgTBSbrGvRRwgeSzy9vziDzEy3j9Vk/65vXNj9qHWRF7cUlAqa9/WP+atQIbCl/j4SiDO/6gCqIeoxK17agz5sIoxhYIs95QLmxlqKdSSe+ldZUVyMwP7HN3OoMa7LZzjCJsaMMHEZ2mIVPWxpDD9KDQahgVYtPgtXGXF8RSTzPJlJIXCplxel6RlsTcAaOU2uMse767Hj4iWis206AeTIKOE8sqvtq7avDYGuLWq7FR0oR/3JWnXeRKseyPeLWBe+ThzfU959yaopjYW58f7178QZ6WGUfkUgBE4WYiWMrntaKROYOa4lgrJLe0UXUT5V5X++5DVmN2Ai4qT7ZyJNP+cH9PzpBB3+sgns8k5TljPkEpuYp/8b0uTGkPUNx5cNBw7usxcK8IB2dbFkAVNiLeSJddJ1zhgcfcFfJ03qOnjtmwmPz7oLgHHT+INTISHnJt+QAzH797UFls7gzPR7UNjTzR0oa6iFaaQZbNPZfv6+OK7SyFzWF4tjzSxCP3nNx3NLrP0aLMcPIIAEEXCSdpLXwAMqzgO7WhHTuO5qLlpKKXEOkwIDAQABo0gwRjAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwIwHwYDVR0jBBgwFoAUHOzVG4W7yv6Aqp1HwrfzLZurxRowDQYJKoZIhvcNAQELBQADggIBAGtVUkvatE2BIn9/W2EHgWJDjjsrJfB+6UklmgwsV/WxZTaK35qkDUIJrGDtpbcYRpILJLHKJ06KmuFh6ko4XbS7FGZj4rQNa85C0CAsCZBcSV+I0Hm6DYFuXfMdV7jEEZ8u8HUFPhAin2+kPgvqI1dpdzW9VGoiLNA3tfAWNoKD4O4OkrAaZASX2UToy/JLHBOKLHF/azi71yxwD2yb7zVZt3G+SecFn03/rKcz7sDGZ511Yba827RpJKkV7aJefdWZDbdemul5UCZnCsGyubtBNPmJ+qZXRLSQ0cDQ1NZZ3exWm9BpBKoPe4PPX+OdmP9LwzhFq8I+Dc4WIKAbaq2i7+dmftuPbAU+Cl1bn1kkdR3cnqh/Vs8VJdXaZJmdViIsZo6nm2Tv8uYya2hOPPy6I8DQyRGGlWUmwEik5CGaaFlujoOzq8dgnbmZFVamrB3o78uak3u0sSZ5Y1AAXTeQF94LxcXlD1fUuzzCywBslkeBYZE1kBt2NqdPAdB2CJ8CDycS30l3ebckXL9tOSjzsas0z4forcGcIbBhchXjU/acdYEMFt5w+I7Z74MvMAbKyMghidTdScfVpkxHgWO5p1t/r0fQQUOpfAIF71P3v5IZQdq4IbYIG0hgY0KkHvETm1rSUNLQf6pQV1+I/OTiyf0bjfHhDjRuwuGw4lRnurn:oasis:names:tc:SAML:2.0:nameid-format:persistenturn:oasis:names:tc:SAML:2.0:profiles:attribute:basichttps://my-instance-xtzfbc.zitadel.cloud/saml/v2/metadata IDP signingMIIFITCCAwmgAwIBAgIBUTANBgkqhkiG9w0BAQsFADAsMRAwDgYDVQQKEwdaSVRBREVMMRgwFgYDVQQDEw9aSVRBREVMIFNBTUwgQ0EwHhcNMjQwNTIwMTIxNjQzWhcNMjUwNTIwMTgxNjQzWjAyMRAwDgYDVQQKEwdaSVRBREVMMR4wHAYDVQQDExVaSVRBREVMIFNBTUwgcmVzcG9uc2UwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDaVZkfAG7xhg/rEkBMzvOI4asj49utf9oM/EzpLHs0EPPMB5CLEfKnUkd9YMkG9M7bhPI2ApQSOCnlpKIumUtA/7r8ft2v0pJjq8eQ/EIMCXaFQnt0YKK3E1BgTBSbrGvRRwgeSzy9vziDzEy3j9Vk/65vXNj9qHWRF7cUlAqa9/WP+atQIbCl/j4SiDO/6gCqIeoxK17agz5sIoxhYIs95QLmxlqKdSSe+ldZUVyMwP7HN3OoMa7LZzjCJsaMMHEZ2mIVPWxpDD9KDQahgVYtPgtXGXF8RSTzPJlJIXCplxel6RlsTcAaOU2uMse767Hj4iWis206AeTIKOE8sqvtq7avDYGuLWq7FR0oR/3JWnXeRKseyPeLWBe+ThzfU959yaopjYW58f7178QZ6WGUfkUgBE4WYiWMrntaKROYOa4lgrJLe0UXUT5V5X++5DVmN2Ai4qT7ZyJNP+cH9PzpBB3+sgns8k5TljPkEpuYp/8b0uTGkPUNx5cNBw7usxcK8IB2dbFkAVNiLeSJddJ1zhgcfcFfJ03qOnjtmwmPz7oLgHHT+INTISHnJt+QAzH797UFls7gzPR7UNjTzR0oa6iFaaQZbNPZfv6+OK7SyFzWF4tjzSxCP3nNx3NLrP0aLMcPIIAEEXCSdpLXwAMqzgO7WhHTuO5qLlpKKXEOkwIDAQABo0gwRjAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwIwHwYDVR0jBBgwFoAUHOzVG4W7yv6Aqp1HwrfzLZurxRowDQYJKoZIhvcNAQELBQADggIBAGtVUkvatE2BIn9/W2EHgWJDjjsrJfB+6UklmgwsV/WxZTaK35qkDUIJrGDtpbcYRpILJLHKJ06KmuFh6ko4XbS7FGZj4rQNa85C0CAsCZBcSV+I0Hm6DYFuXfMdV7jEEZ8u8HUFPhAin2+kPgvqI1dpdzW9VGoiLNA3tfAWNoKD4O4OkrAaZASX2UToy/JLHBOKLHF/azi71yxwD2yb7zVZt3G+SecFn03/rKcz7sDGZ511Yba827RpJKkV7aJefdWZDbdemul5UCZnCsGyubtBNPmJ+qZXRLSQ0cDQ1NZZ3exWm9BpBKoPe4PPX+OdmP9LwzhFq8I+Dc4WIKAbaq2i7+dmftuPbAU+Cl1bn1kkdR3cnqh/Vs8VJdXaZJmdViIsZo6nm2Tv8uYya2hOPPy6I8DQyRGGlWUmwEik5CGaaFlujoOzq8dgnbmZFVamrB3o78uak3u0sSZ5Y1AAXTeQF94LxcXlD1fUuzzCywBslkeBYZE1kBt2NqdPAdB2CJ8CDycS30l3ebckXL9tOSjzsas0z4forcGcIbBhchXjU/acdYEMFt5w+I7Z74MvMAbKyMghidTdScfVpkxHgWO5p1t/r0fQQUOpfAIF71P3v5IZQdq4IbYIG0hgY0KkHvETm1rSUNLQf6pQV1+I/OTiyf0bjfHhDjRuwuGw4lRn \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..84245c4 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,6 @@ +Flask==2.2.2 +Flask-CORS==3.0.10 +pysaml2==6.5.1 +xmltodict==0.12.0 +cryptography==41.0.0 +lxml==4.9.2 diff --git a/sp-cert.pem b/sp-cert.pem new file mode 100644 index 0000000..ca743e4 --- /dev/null +++ b/sp-cert.pem @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDtzCCAp+gAwIBAgIUfITRQGueqcK1fkd3dbNuJGQ9WSowDQYJKoZIhvcNAQEL +BQAwazELMAkGA1UEBhMCTEsxEjAQBgNVBAgMCVNyaSBMYW5rYTEQMA4GA1UEBwwH +Q29sb21ibzEQMA4GA1UECgwHWklUQURFTDEkMCIGCSqGSIb3DQEJARYVZGFrc2hp +dGhhQHppdGFkZWwuY29tMB4XDTI0MDUyMTA3MzUzNVoXDTI1MDUyMTA3MzUzNVow +azELMAkGA1UEBhMCTEsxEjAQBgNVBAgMCVNyaSBMYW5rYTEQMA4GA1UEBwwHQ29s +b21ibzEQMA4GA1UECgwHWklUQURFTDEkMCIGCSqGSIb3DQEJARYVZGFrc2hpdGhh +QHppdGFkZWwuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlaOA +bdE53gM2j/kBmy/UFCCjy8YsVyUUSx23CfXs0T4IQEZyRWrgCchxFBGN+rZF7ZZt +oeV7u4vwlXqzCdLBftIpoMO/tvQ8wEe8pUFaldXOhRrEYbwUqpSQHF61kBf7Lqfb +LBCw+v13BfKn05siHmTB/6k4EHEoIytyaOkcJk8e+DvW9m5zuB4LCA2iCrrUZ//M +sPPDkZ8pkC+VfBkBkm36td93QjNvkXNghoKj8SmgGfP9/83/2LH0qIRhysC3R0kf +T+ARtTZXkB4ooBnma52Pnxc4aVHMfaYRxCx+V/qzJK5j+Z05GuJQydrgXXsdIhD9 +S1yF5XuaJcxOYOtRlQIDAQABo1MwUTAdBgNVHQ4EFgQUAaW3LdD3frG3uiRQevwq +NJYQ9WUwHwYDVR0jBBgwFoAUAaW3LdD3frG3uiRQevwqNJYQ9WUwDwYDVR0TAQH/ +BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAFSPTNAo26fNq6TMoZiMkvh/cfhEI +t7ybc+mB5WbaJWx0E38TNtolnvKvgXDGDdOHClWHWfoe8eA0ZhZlndfsZ7Um6g4l +W53m7X9L9sefwDYui8YIoKeRZ6ANokffIIajntYsmgWbwTQ04DFsxXk8t6mKIovW +qw2OyEZMuGMwbJamknaJj2z8pxYP80dWrq/Roq5gEoCA0jNBCr+uIhhWfv5FkJ6t +52Y3Xa1bkrtAIOVQptEo6ehK1a55x/v5arWkFydUVZGRzoucUsGd+O34c5VaGfuX +F8d0Pr24mdiPsAwF0cVjgLH4SzH2xHtIs4iL9ymvSGhZhdmD1rsIlInkrQ== +-----END CERTIFICATE----- diff --git a/sp-csr.pem b/sp-csr.pem new file mode 100644 index 0000000..c126b5d --- /dev/null +++ b/sp-csr.pem @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICsDCCAZgCAQAwazELMAkGA1UEBhMCTEsxEjAQBgNVBAgMCVNyaSBMYW5rYTEQ +MA4GA1UEBwwHQ29sb21ibzEQMA4GA1UECgwHWklUQURFTDEkMCIGCSqGSIb3DQEJ +ARYVZGFrc2hpdGhhQHppdGFkZWwuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAlaOAbdE53gM2j/kBmy/UFCCjy8YsVyUUSx23CfXs0T4IQEZyRWrg +CchxFBGN+rZF7ZZtoeV7u4vwlXqzCdLBftIpoMO/tvQ8wEe8pUFaldXOhRrEYbwU +qpSQHF61kBf7LqfbLBCw+v13BfKn05siHmTB/6k4EHEoIytyaOkcJk8e+DvW9m5z +uB4LCA2iCrrUZ//MsPPDkZ8pkC+VfBkBkm36td93QjNvkXNghoKj8SmgGfP9/83/ +2LH0qIRhysC3R0kfT+ARtTZXkB4ooBnma52Pnxc4aVHMfaYRxCx+V/qzJK5j+Z05 +GuJQydrgXXsdIhD9S1yF5XuaJcxOYOtRlQIDAQABoAAwDQYJKoZIhvcNAQELBQAD +ggEBAE/Jxs1+RqO0DNGE0w4iQ5G1lY5mkn2kCzCEjjzjc6NoFQ5+qLNlmtEXf3ui +GNUiPCbUVfLlxXElEdFCzj3J4xZxnex14W63eFrpY4RRcExmR9Du+Yra95hNE+8R +kLVoLyPqCbbDS69p0j6XSd9uNqXWiahCSc3RBNmHetXTjgsY8qUcvoB2FsJ+KGQk +T4bp1avu34VxnIfBqxR6GKqN3l3S6xofVNqXpAnOJVH10t9zwnaCmccfwOExKQZR +Xv6q5agIIbuiP1esaWdpLa9vP0oRtWjwQLP8WaVMjNUJoPStOiSo/w3RNxB26j5l +Dgw8NxIjv3db0FBkSPUsIOmy9io= +-----END CERTIFICATE REQUEST----- diff --git a/sp-key.pem b/sp-key.pem new file mode 100644 index 0000000..e36c036 --- /dev/null +++ b/sp-key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCVo4Bt0TneAzaP ++QGbL9QUIKPLxixXJRRLHbcJ9ezRPghARnJFauAJyHEUEY36tkXtlm2h5Xu7i/CV +erMJ0sF+0imgw7+29DzAR7ylQVqV1c6FGsRhvBSqlJAcXrWQF/sup9ssELD6/XcF +8qfTmyIeZMH/qTgQcSgjK3Jo6RwmTx74O9b2bnO4HgsIDaIKutRn/8yw88ORnymQ +L5V8GQGSbfq133dCM2+Rc2CGgqPxKaAZ8/3/zf/YsfSohGHKwLdHSR9P4BG1NleQ +HiigGeZrnY+fFzhpUcx9phHELH5X+rMkrmP5nTka4lDJ2uBdex0iEP1LXIXle5ol +zE5g61GVAgMBAAECggEABXt78soPgP5RV/BrQ776jRHUq0KwXWSXrz7Z9dMp4R3d +zVKnaiESlaX5DdKs07D5s+aaHEXdOCki4EWI3LgsUnpE5l9CajB3L1ZEtGTfrdpF +AQrStuZeQ94O8sy95/19vIWPpp6Mu81ghUb3XId0NkF1YbZwV7RFoH0KUCSsqgyX +fr2SCkA4clmY1yr2tXollGQQolhYGQ55mBN+kIH+OzTQ4RwWjUW10DHJCmqpc1Bh +efuyhCn+0cPSXfyEu5WFcRTq+yEtp9HLsy6T419/5tWqY7QfQQZvXykyZxdfAPw/ +n6tTsFR1lFpLg8VkdJTFZmZrbsdrAiI444AvsHr4WwKBgQDMRJ3jiOLbbmqVHbvI +aiTZBlwqldm5cCpsENr+One5atYkk/gRpy1GIsc8fJeFMr7u5WcKS8wUqxN1Er+s +Vg0r7FJFXcfz1H0+IwvDU9o2CwmTbbK9TuscaBzoIaqVZOU1fW0qWdDOZ0hoL5JT +jIQBKf2ofKJv8z0nMSFSUA4ywwKBgQC7iRSFymGxwFnvRXz4BQkEOgaixseu83mC +5RD9puD4nisb8WrnRIdyuIxNCzKUL5aWeCjkJtgjDNcm0ykTpwTpjUVWMWh/WRmI +ddEgoW1S9zuFksEwh5Q0C5+psdeGSkJ+RRNiSD0kjnKXZ/r43ZYDsfudsQy8uRU3 +sfL8FID0xwKBgF0QyirXvBwjo3XK9TpredxTNHzGh0sgmoNf7pazxsZ4sZY6wGQy +mcMBp2qcjWBHbYxkglzDifjEtPm7EA8RqbCgXB90idxCrMk5qGtW8e17e+JqsWvk +3+OyX7E3XYPxUyjau3j3MNZJYkjtm6prM91f50zzdAKzfOI1VSwugzmHAoGBALB2 +2NzcBvmfqgDAPkuTUVyszTm2GofBiwFkUoybzu2ix6XPkWx0y49joIGOm9VcwjJs +du3Yr2Cr4HgkEm7vpmuuBySH8XCDgscpNdOikqCTC2sxIKBts0MV/PNM6STwE4mF +riu5Fe+kKqfVrA8pUtO/UqibxQSAlDqIM8CH+qBZAoGBAJ1q8v7eQUfCtF1wP0Ob +RrxlY7ys90SqB8hu6kETNeKiWHPFUq4ZSEihlRGJ7XIsF/2bowoWJj9hmtNPhJl9 +oUnEz2Y/73ToJlsxQUfdJyMBxb7Z4F2THZvNSVFH+TWdeTpEu0ujJFgCgXLw6liG +tlOSUnVy2KoACyAhDEgFmhYU +-----END PRIVATE KEY----- diff --git a/sp_metadata.xml b/sp_metadata.xml new file mode 100644 index 0000000..f9c910c --- /dev/null +++ b/sp_metadata.xml @@ -0,0 +1,23 @@ + + + + + + + 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 + + + + + + urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress + + +