Skip to content

Commit b7c3d90

Browse files
rodumaniChangje Jeong
authored andcommitted
Implement demo-bottle
1 parent fa67f9e commit b7c3d90

File tree

8 files changed

+357
-0
lines changed

8 files changed

+357
-0
lines changed

demo-bottle/index.py

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
import os
2+
3+
from bottle import Bottle, run, redirect, request, response, ServerAdapter, jinja2_view
4+
from beaker.middleware import SessionMiddleware
5+
6+
from urlparse import urlparse
7+
8+
from onelogin.saml2.auth import OneLogin_Saml2_Auth
9+
from onelogin.saml2.utils import OneLogin_Saml2_Utils
10+
11+
12+
app = Bottle(__name__)
13+
app.config['SECRET_KEY'] = 'onelogindemopytoolkit'
14+
app.config['SAML_PATH'] = os.path.join(os.path.dirname(__file__), 'saml')
15+
16+
17+
session_opts = {
18+
'session.type': 'file',
19+
'session.cookie_expires': 300,
20+
'session.data_dir': './.data',
21+
'session.auto': True
22+
}
23+
24+
25+
def init_saml_auth(req):
26+
auth = OneLogin_Saml2_Auth(req, custom_base_path=app.config['SAML_PATH'])
27+
return auth
28+
29+
30+
def prepare_bottle_request(req):
31+
url_data = urlparse(req.url)
32+
return {
33+
'http_host': req.get_header('host'),
34+
'server_port': url_data.port,
35+
'script_name': req.fullpath,
36+
'get_data': req.query,
37+
'post_data': req.forms,
38+
'https': 'on' if req.urlparts.scheme == 'https' else 'off'
39+
}
40+
41+
42+
@app.route('/acs', method='POST')
43+
@jinja2_view('index.html', template_lookup=['templates'])
44+
def index():
45+
req = prepare_bottle_request(request)
46+
auth = init_saml_auth(req)
47+
paint_logout = False
48+
attributes = False
49+
50+
session = request.environ['beaker.session']
51+
52+
auth.process_response()
53+
errors = auth.get_errors()
54+
not_auth_warn = not auth.is_authenticated()
55+
if len(errors) == 0:
56+
session['samlUserdata'] = auth.get_attributes()
57+
session['samlNameId'] = auth.get_nameid()
58+
session['samlSessionIndex'] = auth.get_session_index()
59+
self_url = OneLogin_Saml2_Utils.get_self_url(req)
60+
if 'RelayState' in request.forms and self_url != request.forms['RelayState']:
61+
return redirect(request.forms['RelayState'])
62+
63+
if 'samlUserdata' in session:
64+
paint_logout = True
65+
if len(session['samlUserdata']) > 0:
66+
attributes = session['samlUserdata'].items()
67+
68+
return {
69+
'errors':errors,
70+
'not_auth_warn':not_auth_warn,
71+
'attributes':attributes,
72+
'paint_logout':paint_logout
73+
}
74+
75+
76+
@app.route('/', method='GET')
77+
@jinja2_view('index.html', template_lookup=['templates'])
78+
def index():
79+
req = prepare_bottle_request(request)
80+
auth = init_saml_auth(req)
81+
errors = []
82+
not_auth_warn = False
83+
success_slo = False
84+
attributes = False
85+
paint_logout = False
86+
87+
session = request.environ['beaker.session']
88+
89+
if 'sso' in request.query:
90+
return_to = '{0}://{1}/'.format(request.urlparts.scheme, request.get_header('host'))
91+
return redirect(auth.login(return_to))
92+
elif 'sso2' in request.query:
93+
return_to = '{0}://{1}/attrs/'.format(request.urlparts.scheme, request.get_header('host'))
94+
return redirect(auth.login(return_to))
95+
elif 'slo' in request.query:
96+
name_id = None
97+
session_index = None
98+
if 'samlNameId' in session:
99+
name_id = session['samlNameId']
100+
if 'samlSessionIndex' in session:
101+
session_index = session['samlSessionIndex']
102+
103+
return redirect(auth.logout(name_id=name_id, session_index=session_index))
104+
elif 'sls' in request.query:
105+
dscb = lambda: session.clear()
106+
url = auth.process_slo(delete_session_cb=dscb)
107+
errors = auth.get_errors()
108+
if len(errors) == 0:
109+
if url is not None:
110+
return redirect(url)
111+
else:
112+
success_slo = True
113+
114+
if 'samlUserdata' in session:
115+
paint_logout = True
116+
if len(session['samlUserdata']) > 0:
117+
attributes = session['samlUserdata'].items()
118+
119+
return {
120+
'errors':errors,
121+
'not_auth_warn':not_auth_warn,
122+
'success_slo':success_slo,
123+
'attributes':attributes,
124+
'paint_logout':paint_logout
125+
}
126+
127+
128+
@app.route('/attrs/')
129+
@jinja2_view('attrs.html', template_lookup=['templates'])
130+
def attrs():
131+
paint_logout = False
132+
attributes = False
133+
session = request.environ['beaker.session']
134+
135+
if 'samlUserdata' in session:
136+
paint_logout = True
137+
if len(session['samlUserdata']) > 0:
138+
attributes = session['samlUserdata'].items()
139+
140+
return {'paint_logout':paint_logout,
141+
'attributes':attributes}
142+
143+
144+
@app.route('/metadata/')
145+
def metadata():
146+
req = prepare_bottle_request(request)
147+
auth = init_saml_auth(req)
148+
settings = auth.get_settings()
149+
metadata = settings.get_sp_metadata()
150+
errors = settings.validate_metadata(metadata)
151+
152+
if len(errors) == 0:
153+
response.status = 200
154+
response.set_header('Content-Type', 'text/xml')
155+
return metadata
156+
else:
157+
response.status = 500
158+
return ','.join(errors)
159+
160+
161+
class SSLPasteServer(ServerAdapter):
162+
def run(self, handler):
163+
from paste import httpserver
164+
165+
server = httpserver.serve(handler, '0.0.0.0', '8000', ssl_pem='local.pem', start_loop=False)
166+
try:
167+
server.serve_forever()
168+
finally:
169+
server.server_close()
170+
171+
172+
if __name__ == "__main__":
173+
# To run HTTPS
174+
#run(SessionMiddleware(app, config=session_opts), host='0.0.0.0', port=8000, debug=True, reloader=True, server=SSLPasteServer)
175+
176+
# To run HTTP
177+
run(SessionMiddleware(app, config=session_opts), host='0.0.0.0', port=8000, debug=True, reloader=True, server='paste')

demo-bottle/requirements.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
bottle==0.12.8
2+
beaker==1.6.4
3+
paste==1.7.5.1
4+
jinja2==2.7.3
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"security": {
3+
"nameIdEncrypted": false,
4+
"authnRequestsSigned": false,
5+
"logoutRequestSigned": false,
6+
"logoutResponseSigned": false,
7+
"signMetadata": false,
8+
"wantMessagesSigned": false,
9+
"wantAssertionsSigned": false,
10+
"wantNameIdEncrypted": false
11+
},
12+
"contactPerson": {
13+
"technical": {
14+
"givenName": "technical_name",
15+
"emailAddress": "technical@example.com"
16+
},
17+
"support": {
18+
"givenName": "support_name",
19+
"emailAddress": "support@example.com"
20+
}
21+
},
22+
"organization": {
23+
"en-US": {
24+
"name": "sp_test",
25+
"displayname": "SP test",
26+
"url": "http://sp.example.com"
27+
}
28+
}
29+
}

demo-bottle/saml/certs/README

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
Take care of this folder that could contain private key. Be sure that this folder never is published.
2+
3+
Onelogin Python Toolkit expects that certs for the SP could be stored in this folder as:
4+
5+
* sp.key Private Key
6+
* sp.cert Public cert
7+
8+
Also you can use other cert to sign the metadata of the SP using the:
9+
10+
* metadata.key
11+
* metadata.cert

demo-bottle/saml/settings.json

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"strict": true,
3+
"debug": true,
4+
"sp": {
5+
"entityId": "https://<sp_domain>/metadata/",
6+
"assertionConsumerService": {
7+
"url": "https://<sp_domain>/?acs",
8+
"binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
9+
},
10+
"singleLogoutService": {
11+
"url": "https://<sp_domain>/?sls",
12+
"binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
13+
},
14+
"NameIDFormat": "urn:oasis:names:tc:SAML:2.0:nameid-format:unspecified",
15+
"x509cert": "",
16+
"privateKey": ""
17+
},
18+
"idp": {
19+
"entityId": "https://app.onelogin.com/saml/metadata/<onelogin_connector_id>",
20+
"singleSignOnService": {
21+
"url": "https://app.onelogin.com/trust/saml2/http-post/sso/<onelogin_connector_id>",
22+
"binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
23+
},
24+
"singleLogoutService": {
25+
"url": "https://app.onelogin.com/trust/saml2/http-redirect/slo/<onelogin_connector_id>",
26+
"binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
27+
},
28+
"x509cert": "<onelogin_connector_cert>"
29+
}
30+
}

demo-bottle/templates/attrs.html

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{% extends "base.html" %}
2+
3+
{% block content %}
4+
5+
{% if paint_logout %}
6+
{% if attributes %}
7+
<p>You have the following attributes:</p>
8+
<table class="table table-striped">
9+
<thead>
10+
<th>Name</th><th>Values</th>
11+
</thead>
12+
<tbody>
13+
{% for attr in attributes %}
14+
<tr><td>{{ attr.0 }}</td>
15+
<td><ul class="list-unstyled">
16+
{% for val in attr.1 %}
17+
<li>{{ val }}</li>
18+
{% endfor %}
19+
</ul></td></tr>
20+
{% endfor %}
21+
</tbody>
22+
</table>
23+
{% else %}
24+
<div class="alert alert-danger" role="alert">You don't have any attributes</div>
25+
{% endif %}
26+
<a href="/?slo" class="btn btn-danger">Logout</a>
27+
{% else %}
28+
<a href="/?sso2" class="btn btn-primary">Login and access again to this page</a>
29+
{% endif %}
30+
31+
{% endblock %}

demo-bottle/templates/base.html

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8">
5+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
6+
<meta name="viewport" content="width=device-width, initial-scale=1">
7+
8+
<title>A Python SAML Toolkit by OneLogin demo</title>
9+
10+
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
11+
12+
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
13+
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
14+
<!--[if lt IE 9]>
15+
<script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
16+
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
17+
<![endif]-->
18+
</head>
19+
<body>
20+
<div class="container">
21+
<h1>A Python SAML Toolkit by OneLogin demo</h1>
22+
23+
{% block content %}{% endblock %}
24+
</div>
25+
</body>
26+
</html>

demo-bottle/templates/index.html

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
{% extends "base.html" %}
2+
3+
{% block content %}
4+
5+
{% if errors %}
6+
<div class="alert alert-danger" role="alert">
7+
<strong>Errors:</strong>
8+
<ul class="list-unstyled">
9+
{% for err in errors %}
10+
<li>{{err}}</li>
11+
{% endfor %}
12+
</ul>
13+
</div>
14+
{% endif %}
15+
16+
{% if not_auth_warn %}
17+
<div class="alert alert-danger" role="alert">Not authenticated</div>
18+
{% endif %}
19+
20+
{% if success_slo %}
21+
<div class="alert alert-success" role="alert">Successfully logged out</div>
22+
{% endif %}
23+
24+
{% if paint_logout %}
25+
{% if attributes %}
26+
<table class="table table-striped">
27+
<thead>
28+
<th>Name</th><th>Values</th>
29+
</thead>
30+
<tbody>
31+
{% for attr in attributes %}
32+
<tr><td>{{ attr.0 }}</td>
33+
<td><ul class="list-unstyled">
34+
{% for val in attr.1 %}
35+
<li>{{ val }}</li>
36+
{% endfor %}
37+
</ul></td></tr>
38+
{% endfor %}
39+
</tbody>
40+
</table>
41+
{% else %}
42+
<div class="alert alert-danger" role="alert">You don't have any attributes</div>
43+
{% endif %}
44+
<a href="/?slo" class="btn btn-danger">Logout</a>
45+
{% else %}
46+
<a href="/?sso" class="btn btn-primary">Login</a> <a href="?sso2" class="btn btn-info">Login and access to attrs page</a>
47+
{% endif %}
48+
49+
{% endblock %}

0 commit comments

Comments
 (0)