Initial revision
This commit is contained in:
commit
2e1ca9292a
132
app.py
Normal file
132
app.py
Normal file
@ -0,0 +1,132 @@
|
||||
import json
|
||||
import os
|
||||
|
||||
from flask import Flask, request, redirect, session, make_response, render_template
|
||||
from onelogin.saml2.auth import OneLogin_Saml2_Auth
|
||||
from onelogin.saml2.utils import OneLogin_Saml2_Utils
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config['SAML_PATH'] = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'config')
|
||||
app.config['SECRET_KEY'] = 'onelogindemopytoolkit'
|
||||
|
||||
def init_saml_auth(req):
|
||||
auth = OneLogin_Saml2_Auth(req, custom_base_path=app.config['SAML_PATH'])
|
||||
return auth
|
||||
|
||||
|
||||
def prepare_flask_request(request):
|
||||
url_data = request.args.copy()
|
||||
url_data.update(request.form)
|
||||
return {
|
||||
'https': 'on' if request.scheme == 'https' else 'off',
|
||||
'http_host': request.host,
|
||||
'server_port': request.host.split(':')[1] if ':' in request.host else '443' if request.scheme == 'https' else '80',
|
||||
'script_name': request.path,
|
||||
'get_data': url_data,
|
||||
'post_data': request.form.copy()
|
||||
}
|
||||
|
||||
|
||||
@app.route("/", methods=['GET', 'POST'])
|
||||
def index():
|
||||
req = prepare_flask_request(request)
|
||||
auth = init_saml_auth(req)
|
||||
errors = []
|
||||
error_reason = None
|
||||
not_auth_warn = False
|
||||
success_slo = False
|
||||
attributes = False
|
||||
paint_logout = False
|
||||
if 'sso' in request.args:
|
||||
return redirect(auth.login())
|
||||
# If AuthNRequest ID need to be stored in order to later validate it, do instead
|
||||
# sso_built_url = auth.login()
|
||||
# request.session['AuthNRequestID'] = auth.get_last_request_id()
|
||||
# return redirect(sso_built_url)
|
||||
elif 'slo' in request.args:
|
||||
name_id = session_index = name_id_format = name_id_nq = name_id_spnq = None
|
||||
if 'samlNameId' in session:
|
||||
name_id = session['samlNameId']
|
||||
if 'samlSessionIndex' in session:
|
||||
session_index = session['samlSessionIndex']
|
||||
if 'samlNameIdFormat' in session:
|
||||
name_id_format = session['samlNameIdFormat']
|
||||
if 'samlNameIdNameQualifier' in session:
|
||||
name_id_nq = session['samlNameIdNameQualifier']
|
||||
if 'samlNameIdSPNameQualifier' in session:
|
||||
name_id_spnq = session['samlNameIdSPNameQualifier']
|
||||
return redirect(auth.logout(name_id=name_id, session_index=session_index, nq=name_id_nq, name_id_format=name_id_format, spnq=name_id_spnq))
|
||||
elif 'acs' in request.args:
|
||||
request_id = None
|
||||
if 'AuthNRequestID' in session:
|
||||
request_id = session['AuthNRequestID']
|
||||
auth.process_response(request_id=request_id)
|
||||
errors = auth.get_errors()
|
||||
not_auth_warn = not auth.is_authenticated()
|
||||
if len(errors) == 0:
|
||||
if 'AuthNRequestID' in session:
|
||||
del session['AuthNRequestID']
|
||||
session['samlUserdata'] = auth.get_attributes()
|
||||
session['samlNameId'] = auth.get_nameid()
|
||||
session['samlNameIdFormat'] = auth.get_nameid_format()
|
||||
session['samlNameIdNameQualifier'] = auth.get_nameid_nq()
|
||||
session['samlNameIdSPNameQualifier'] = auth.get_nameid_spnq()
|
||||
session['samlSessionIndex'] = auth.get_session_index()
|
||||
self_url = OneLogin_Saml2_Utils.get_self_url(req)
|
||||
if 'RelayState' in request.form and self_url != request.form['RelayState']:
|
||||
# To avoid 'Open Redirect' attacks, before execute the redirection confirm
|
||||
# the value of the request.form['RelayState'] is a trusted URL.
|
||||
return redirect(auth.redirect_to(request.form['RelayState']))
|
||||
elif auth.get_settings().is_debug_active():
|
||||
error_reason = auth.get_last_error_reason()
|
||||
elif 'sls' in request.args:
|
||||
request_id = None
|
||||
if 'LogoutRequestID' in session:
|
||||
request_id = session['LogoutRequestID']
|
||||
dscb = lambda: session.clear()
|
||||
url = auth.process_slo(request_id=request_id, delete_session_cb=dscb)
|
||||
errors = auth.get_errors()
|
||||
if len(errors) == 0:
|
||||
if url is not None:
|
||||
# To avoid 'Open Redirect' attacks, before execute the redirection confirm
|
||||
# the value of the url is a trusted URL.
|
||||
return redirect(url)
|
||||
else:
|
||||
success_slo = True
|
||||
elif auth.get_settings().is_debug_active():
|
||||
error_reason = auth.get_last_error_reason()
|
||||
|
||||
if 'samlUserdata' in session:
|
||||
paint_logout = True
|
||||
if len(session['samlUserdata']) > 0:
|
||||
attributes = session['samlUserdata'].items()
|
||||
|
||||
return render_template(
|
||||
'index.html',
|
||||
errors=errors,
|
||||
error_reason=error_reason,
|
||||
not_auth_warn=not_auth_warn,
|
||||
success_slo=success_slo,
|
||||
attributes=attributes,
|
||||
paint_logout=paint_logout
|
||||
)
|
||||
|
||||
|
||||
@app.route('/metadata')
|
||||
def metadata():
|
||||
req = prepare_flask_request(request)
|
||||
auth = init_saml_auth(req)
|
||||
settings = auth.get_settings()
|
||||
metadata = settings.get_sp_metadata()
|
||||
errors = settings.validate_metadata(metadata)
|
||||
|
||||
if len(errors) == 0:
|
||||
resp = make_response(metadata, 200)
|
||||
resp.headers['Content-Type'] = 'text/xml'
|
||||
else:
|
||||
resp = make_response(', '.join(errors), 500)
|
||||
return resp
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(debug=True)
|
63
config/advanced_settings.json
Normal file
63
config/advanced_settings.json
Normal file
@ -0,0 +1,63 @@
|
||||
{
|
||||
"security": {
|
||||
|
||||
"authnRequestsSigned": true,
|
||||
|
||||
"logoutRequestSigned": true,
|
||||
|
||||
"logoutResponseSigned": false,
|
||||
|
||||
"signMetadata": false,
|
||||
|
||||
"wantMessagesSigned": false,
|
||||
|
||||
"wantAssertionsSigned": true,
|
||||
|
||||
"wantAssertionsEncrypted": false,
|
||||
|
||||
"wantNameId": true,
|
||||
|
||||
"wantNameIdEncrypted": false,
|
||||
|
||||
"wantAttributeStatement": true,
|
||||
|
||||
"requestedAuthnContext": true,
|
||||
|
||||
"requestedAuthnContextComparison": "exact",
|
||||
|
||||
"failOnAuthnContextMismatch": false,
|
||||
|
||||
"metadataValidUntil": null,
|
||||
|
||||
"metadataCacheDuration": null,
|
||||
|
||||
"allowSingleLabelDomains": false,
|
||||
|
||||
"signatureAlgorithm": "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
|
||||
|
||||
"digestAlgorithm": "http://www.w3.org/2001/04/xmlenc#sha256",
|
||||
|
||||
"allowRepeatAttributeName": false,
|
||||
|
||||
"rejectDeprecatedAlgorithm": true
|
||||
},
|
||||
|
||||
"contactPerson": {
|
||||
"technical": {
|
||||
"givenName": "Kriss",
|
||||
"emailAddress": "kriss@vilanet.fr"
|
||||
},
|
||||
"support": {
|
||||
"givenName": "Kriss",
|
||||
"emailAddress": "kriss@vilanet.fr"
|
||||
}
|
||||
},
|
||||
|
||||
"organization": {
|
||||
"en-US": {
|
||||
"name": "client-saml",
|
||||
"displayname": "Test Client SAML",
|
||||
"url": "http://localhost:5000"
|
||||
}
|
||||
}
|
||||
}
|
29
config/settings.json
Normal file
29
config/settings.json
Normal file
@ -0,0 +1,29 @@
|
||||
{
|
||||
"strict": true,
|
||||
"debug": true,
|
||||
"sp": {
|
||||
"entityId": "http://localhost:5000/metadata",
|
||||
"assertionConsumerService": {
|
||||
"url": "http://localhost:5000/?acs",
|
||||
"binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
|
||||
},
|
||||
"singleLogoutService": {
|
||||
"url": "http://localhost:5000/?sls",
|
||||
"binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
|
||||
},
|
||||
"x509cert": "MIIDIzCCAgugAwIBAgIUO3lW/e6/coBwiHOJzygNpSopoJMwDQYJKoZIhvcNAQELBQAwITELMAkGA1UEBhMCRlIxEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0yNDA2MTUxNjA5NTNaFw0yNDA3MTUxNjA5NTNaMCExCzAJBgNVBAYTAkZSMRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCVPpupF3onnm+H08x8fpL7AQk07iViukdcILT35gobz9WryP5txhp8VAcoCQqpPQmrnuY1txUZ3fCpnVA1+5FPUZ0o54DXDuaL/0oxeV7yOXvtwkgq6DsXzNIosLf1gwXbCN/f+xhbBRVkOxS+eKGKWG5CtkqyECw+k87rqolqz/6CK0D+M1aYjmLhilSIsnUzX/FfdPm5ntLHlU3K3RhvVb3TPi9CJgW5XnvAxvzMAWtzpNVDmbZ78kfekWR6+oWbr0icX73O/ubz/9hREWaxsvDtWQRFGy+xRHwd+fMdT4gemO6ImBKxdKl1YgIkqEwFRp4WhW0C\nyzr602j6+yODAgMBAAGjUzBRMB0GA1UdDgQWBBQE33DzxteoD18rR0hDK2FOGexgPjAfBgNVHSMEGDAWgBQE33DzxteoD18rR0hDK2FOGexgPjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCCEopUP4FTtvAL8k/w/Hu9HW5pwGlRivUsBP4Z4VvXLCyWy1lQdDRedIR9KOBTHwIhmgZadzPo/qTQd/9xlkX3G+DDkeEXDL/mdyW3tElGuHeQaMD+w3SCXMwOIYdUJteJS0lLVVg1KhSz95BkOU54lt2yVlQFms5DbAWMqEAUNfZLtr2ZmFHuCHLxEOxpuPCdY8VLkQWl0mwF3YlrSLAQTdgJbVu/wIkYtq49GOp+weHCHI64fhnKvODU/uNdS7WynCAweqd59noDlGApOhvTQNPz53Gpo2LdEICIovEHgB0X+RYWv8f3dIqIprX8tVkWTGo4bbpFLuZ1Q7uGupz9",
|
||||
"privateKey": "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCVPpupF3onnm+H08x8fpL7AQk07iViukdcILT35gobz9WryP5txhp8VAcoCQqpPQmrnuY1txUZ3fCpnVA1+5FPUZ0o54DXDuaL/0oxeV7yOXvtwkgq6DsXzNIosLf1gwXbCN/f+xhbBRVkOxS+eKGKWG5CtkqyECw+k87rqolqz/6CK0D+M1aYjmLhilSIsnUzX/FfdPm5ntLHlU3K3RhvVb3TPi9CJgW5XnvAxvzMAWtzpNVDmbZ78kfekWR6+oWbr0icX73O/ubz/9hREWaxsvDtWQRFGy+xRHwd+fMdT4gemO6ImBKxdKl1YgIkqEwFRp4WhW0Cyzr602j6+yODAgMBAAECggEAEN5oOrcIHBVI1unmGYY29774uu5V9HBIo42g1so+B0G5DXck5uR21umqe2h2N4XKI4dMzvJ4JJnU218IE2nxVS1M3bQ4+FXbUiSTTr9Ar5E2xDtq68qe70Q71tSOnmXmUgkRQOBBMvyCm/cfk53P6HKxV8IB9FlmntYXE8UNTjCzCKBpOc0lbCWKHufPv/77vdmN7fOEwpQSsoll8IfJLzN7r2KWpr8uOzRlDupxQGT806U9EgPqjj5HNfhJ/d7pr4kDbudCMmy11MtDOqyub+3LB5tv7e+UqvgcGbaF0dZ2/YyTNrjHB3zus4nKIUTlTAxG/oaU38AJlH1BvFZqwQKBgQDIjdMbmasms2zEMZYvIkFCodtFzyxMgLOKU646SpysMCFRyGG3eAEA8j2bN+OGbAYKSjFoTKHrTYzDb0uleBMZ7xX5e0eRGqStPZb8LBXp/ufYfHp4gXYHzCpZ+n2kx5F+WiF378v27FfLk2NoczYCuF4X5JvysgTvj8S6Ts6zwwKBgQC+gV3WEjcr9mC70qvOuz5Id7oA0mXsjKU4o1CoDsjojuBF/zSGQi0Ar76ZDgd8Jup0eeLSKeRVjV86pDNPWAcKaHmeex2P/OD/uK/yuzdFc31SEopFbDEZ6fK3PBZ1M7VneakDOg1zspcUgVTsolNjRh1hsIkhEj368gA2G7WVQQKBgAhEwKVsqn/H+f4ExVpgISysG6w/JGZrD/vuA0rn9JmsylLi3hSAYBo34o5ZuYm7PmyCLpNMRYi8A8ey+P1ze+Yf01ob2RGEdbGmzmjLMIQbPFfSmgIJ5GHh6wUWrMN0bu00rhiRzGj7yYrdIsYVqe5mx4pYpI1XBZkS5luAEEmdAoGAE0Z1nx5StMEGApsLRSyO3bg3erPPGkMUyIlFtOtiCp3CNXLf9qGlegdOKqBPw5EQcd6PQ6J3duyJ8R4CDwoiFDyD6bQdRp9YiKdALjghHIbV7ELx+Jo80ZlpNH8A6rTjqueVYT0zdTxhqvJ3DEZUV5wVhvfcuBrnaIep28+r7MECgYEAveoBzdWVX6mOWuxZp8ASl0z694jjrWAWqWRa3hvVSxTSDbbE9IWMniNHGrm4FzDBvkgdb4Zglvyz4TSpX6o2UoVpq6Pzg5y4kMRoH1+Rk0jUpvIRMNyLCkS9wrB7EOnDYQwJnOInMGdUYkLgPbEfz2yczQVwt1F9EJOPBg668rA="
|
||||
},
|
||||
"idp": {
|
||||
"entityId": "https://id.vilanet.fr/realms/vilanet",
|
||||
"singleSignOnService": {
|
||||
"url": "https://id.vilanet.fr/realms/vilanet/protocol/saml",
|
||||
"binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
|
||||
},
|
||||
"singleLogoutService": {
|
||||
"url": "https://id.vilanet.fr/realms/vilanet/protocol/saml",
|
||||
"binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
|
||||
},
|
||||
"x509cert": "MIICnTCCAYUCBgGPz1WjUzANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAd2aWxhbmV0MB4XDTI0MDUzMTE1NDU0N1oXDTM0MDUzMTE1NDcyN1owEjEQMA4GA1UEAwwHdmlsYW5ldDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKC6k5iYxxu9F/ms6G5JYsIapewpqNCd4hr2DXcEfXrI1Et6gNxDNrHRwoYl4sG/6C1V8x1woynwEXe7AOH5f5Piwr2lvWq6Hl3NgPKBlUwIyCB9rjXVkHJxxCrMPwi4Isxuvw8LLMNYCAhx5rFKuo7lwNWMFPAmbAkb5TZqByG4kQvXT5aavtgSN3NdPOSTKSZOnqx3MY+j3b9nWbIhQ2EIfgj9pEe3FEknis0kQVUwUWIL68QdDg4AECiBOjgIStE+P9d7VLiWpu6azMgUxIcxNTW+ZQrL1yScXt+IOs6TvSILv0EnHX4Qj0kS1Uvs5tAwSXPznY9vSM980G51btECAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAEbEtGGc6hfb6fEAPuu7tAg4wq2NGTVFiw9D5+kupz4R877DumIKExe1VHZyDuThPWDGAQ9n93BXkllyzkqwWpgzPGQggVQ88L1hIyg1cC9y1SAlJ7ZRMd74URTlV5sa2mYkMaPULsxO7hIFtEAdgeJEuvmZGEjmEOKG4JYZLBkqa5PqyfABKI/QcIWgxrHxEZXqqWSTJ/hYEiHQ9eZq8H5mUTmhAvQ0J8y6E2VOGrAnNgeFjxedJnTIBK+Lx3SE4+lJ9qLBoZRWcPu7aqZV7DmLpewH4nt/Q4CDm/5RgdblQUR3lUEH8VRzWR+2TbOliqlAkWxvoa9Cb6s6Mdtohig=="
|
||||
}
|
||||
}
|
2
requirements.txt
Normal file
2
requirements.txt
Normal file
@ -0,0 +1,2 @@
|
||||
python3-saml
|
||||
flask
|
26
templates/base.html
Normal file
26
templates/base.html
Normal file
@ -0,0 +1,26 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<title>A Python SAML Toolkit demo</title>
|
||||
|
||||
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
|
||||
|
||||
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
|
||||
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
|
||||
<!--[if lt IE 9]>
|
||||
<script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
|
||||
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
|
||||
<![endif]-->
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>A Python SAML Toolkit demo</h1>
|
||||
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
54
templates/index.html
Normal file
54
templates/index.html
Normal file
@ -0,0 +1,54 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% if errors %}
|
||||
<div class="alert alert-danger" role="alert">
|
||||
<strong>Errors:</strong>
|
||||
<ul class="list-unstyled">
|
||||
{% for err in errors %}
|
||||
<li>{{err}}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% if error_reason %}
|
||||
<span>{{error_reason}}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if not_auth_warn %}
|
||||
<div class="alert alert-danger" role="alert">Not authenticated</div>
|
||||
{% endif %}
|
||||
|
||||
{% if success_slo %}
|
||||
<div class="alert alert-success" role="alert">Successfully logged out</div>
|
||||
{% endif %}
|
||||
|
||||
{% if paint_logout %}
|
||||
{% if attributes %}
|
||||
<p>You have the following attributes:</p>
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<th>Name</th><th>Values</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for attr in attributes %}
|
||||
<tr><td>{{ attr.0 }}</td>
|
||||
<td><ul class="list-unstyled">
|
||||
{% for val in attr.1 %}
|
||||
<li>{{ val }}</li>
|
||||
{% endfor %}
|
||||
</ul></td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<div class="alert alert-danger" role="alert">You don't have any attributes</div>
|
||||
{% endif %}
|
||||
<a href="?slo" class="btn btn-danger">Logout</a>
|
||||
{% else %}
|
||||
<a href="?sso" class="btn btn-primary">Login</a>
|
||||
{% endif %}
|
||||
<a href="/metadata" class="btn btn-info">Metadata</a>
|
||||
|
||||
{% endblock %}
|
Loading…
Reference in New Issue
Block a user