The BottleSaml.SamlSP module implements a SAML Service Provider class for the Bottle web framework.
This module Implements a SAML v2 Service Provider (SP)
-
Creates SAMLRequests, validates SAMLResponses, and adds SAML assertions into to session data.
-
Establishes an Assertion Control Service (ACS) endpoint '/saml/acs'
-
Makes available SAML assertions are to other middleware and views as a Python
dict
maintained in the user session data. -
Utilizes BottleSessions to provide simple access to the SAML protocol provided assertion data.
-
Uses @HENNGE's excellent minisaml implementation.
-
Complies with Bottle Plugin API v2.
BottleSaml is the name of the module, SamlSP is the bottle plug-in class implementing the SP functionality. My testing has been tested Azure AD and SimpleSamlPHP IdPs.
pypi installation:
# python3 -m pip install BottleSaml
This will load required modules, including bottle, BottleSession and minisaml, and their dependencies.
Adding SamlSP to a bottle app:
# Imports
from bottle import Bottle
from BottleSessions import BottleSessions
from BottleSaml import SamlSP
# import SAML configuration (see below)
from config import saml_config, cache_config
# Create a Bottle application context
app = Bottle()
# Install session middleware
sess = BottleSessions(app, session_backing=cache_config)
saml = SamlSP(app,sess=sess, saml_config=saml_config) # SAML Service Provider
The saml_config
is a Python dict
containing the necessary information to use the SAML IdP (and a few configuration parameters). The parameters needed for configuring saml_config
are discussed later. The SamlSP class is discussed here, cover some other features, such as login hooks.
# require_login decorator initiates SAML IdP login
@app.route('/login')
@saml.require_login
def hello_user():
return f"Hello {request.session['username']}"
# is_authenticated indicates login status
@app.route('/')
def index():
if saml.is_authenticated:
return "You are authenticated"
else:
return "You need to login first"
# simple way to logout (withou a SAML Logout)
@app.route('/logout')
def logout_view():
request.session.clear()
return 'OK'
app.run(port=8000, debug=True, reloader=True)
The saved attributes and values are accessible to views and other middleware in the users session. For instance, you can pre-populate a form with the authenticated user's name, department, email, and other data you acquire from the IdP.
Using SAML provided user data
@app.route('/whoami')
@saml.login_required
def whoami():
return request.session['username']
@app.routes('/allattributes')
@saml.login_required
def allattrs():
return request.session['attributes']
@app.route('/admins')
@saml.login_required
def admins_only():
if 'sysadmin' in request.session['attributes']['groups]:
return 'Welcome to admins only'
else:
return 'Restricted access - admins only'
To simplify authentication and authorization of views, a companion module, BottleAuth middleware relegating these tasks to middleware, and providing both route-based and path prefix based authorization mechanisms.
Example using SamlSP with BotAuth:
@app.route('/admins', authz={'groups':'sysadmins'})
def admins_only():
return 'Welcome to admins only...'
for simpler authentication and authorization mechanisms.
The attributes you have available and saved in your session from the SAML authorizaiton process depend on:
- The SAML assertions provided by the IdP. This is set by the IdP configuration. We can't save something we never received. This is all on the IdP end.
- The attributes listed in the
assertions
key in the saml_config options. Thislist
is set by you, and filters the assertions you care about from the IdP'sSAMLResponse
. - If an assertion name matches an attribute name in the config, the value is added to the users
session['attributes']
dict with the name as the key. - If no
assertions
list is provided in saml_config, the only attribute added to your session will beusername
, set from thename_id
in theSAMLResponse
. - Multi-value assertions (such as you might find in a
group
ormember_of
parameter) are stored as a Pythonlist
of values.
This is the structure used by SamlSP to save attributes is presented here: avoid namespace collisions with view or middleware session data.
SamlSP session data layout:
{'username': <username>}
{'attributes':{
'attr1': value1, # attribute/value pairs
'attr2': value2. # created from SAML
... # assertions
'attrN': valueN,
'_SAML' : { # SamlSP's metadata
'issuer' : <IdP entity id>
'requestid' : <saml 'on behalf of'>
'audience' : <saml response audience>
'expires' : <unix time until reauth required>
}
}
{'request_id': <request-id>} # temporary during login
...
Configuration information required to set-up the SAML authentication, and parameters to tune BottleSaml is formed into a dict
passed as the saml_config=
argument to SamlSP()
. Typically this is imported from a config.py
as in the example above.
Collect the necessary information from the IdP (much available from it's metadata) and to select the behavior appropriate for your application.
This isn't difficult, but it can confusing if you are unfamiliar with SAML.
SamlSP config file paramters:
parameter | type | default | description |
---|---|---|---|
saml_endpoint | URL | required | IdP's SAML auth endpoint |
spid | URI | required | Our apps's entity id |
issuer | URI | required | IdP's issuer identity |
force_reauth | Bool | False | True will request IdP full login |
acs_url | URL | required | URL of our Assertion Control Service (ACS) endpoint |
user_attr | string | name_id | SAML assertion providing username attribute |
assertions | [string] | [] | A list of SAMLRespons assertions to collect |
idp_ok | Bool | True | Permit IdP initiated logon |
auth_duration | int | 3600 | Seconds between SAML credentials renewal |
certificate | string | required | The IdP's public certificate for signing verification |
- In most cases the "URI's" will be "URL's".
- The SPID must be configured with the IdP. This is how the IdP knows who we are.
- The
acs_url
needs to match what was configured for the app on the IdP. - If
assertions
is missing or an empty list, only theusername
will be placed in the session.
- It is important to configure BottleSessions to meet the needs of your app. Considertions include the type of caching your app needs, cookie and cache TTL and cookie name name. Consult the BottleSessions Documentation for more information.