Recipes

Integration with Flask Principal

Flask Principal allows you do manage your user authentication information and permissions. Flask SAML nicely by combining its own signal-based approach with that of Flask Principal. This recipe shows a basic implementation of that.

Note

Don’t forget to update the metadata URL to your SAML provider or this code won’t work. You might also need to update the port parameter depending on your SAML IdP configuration.

import flask
import flask_principal
import flask_saml

app = flask.Flask(__name__)
principals = flask_principal.Principal(app)
app.config.update({
    'SECRET_KEY': 'soverysecret',
    'SAML_METADATA_URL': 'https://metadata-url',
})
saml = flask_saml.FlaskSAML(app)

# Create a permission with a single Need, in this case a RoleNeed.
admin_permission = flask_principal.Permission(flask_principal.RoleNeed('admin'))

#
# Connect SAML & Principal
#

@flask_saml.saml_authenticated.connect_via(app)
def on_saml_authenticated(sender, subject, attributes, auth):
    # We have a logged in user, inform Flask-Principal
    flask_principal.identity_changed.send(
        flask.current_app._get_current_object(),
        identity=get_identity(),
    )


@flask_saml.saml_log_out.connect_via(app)
def on_saml_logout(sender):
    # Let Flask-Principal know the user is gone
    flask_principal.identity_changed.send(
        flask.current_app._get_current_object(),
        identity=get_identity(),
    )


# This provides the users' identity in the application
@principals.identity_loader
def get_identity():
    if 'saml' in flask.session:
        return flask_principal.Identity(flask.session['saml']['subject'])
    else:
        return flask_principal.AnonymousIdentity()


@flask_principal.identity_loaded.connect_via(app)
def on_identity_loaded(sender, identity):
    # If authenticated, you're an admin - yay!
    if not isinstance(identity, flask_principal.AnonymousIdentity):
        identity.provides.add(flask_principal.RoleNeed('admin'))


# protect a view with a principal for that need
@app.route('/admin')
@admin_permission.require()
def do_admin_index():
    return flask.Response('Only if you are an admin')

# this time protect with a context manager
@app.route('/articles')
def do_articles():
    with admin_permission.require():
        return flask.Response('Only if you are admin')


@app.errorhandler(flask_principal.PermissionDenied)
def handle_permission_denied(error):
    deny = 'Permission Denied', 403
    redirect = flask.redirect(flask.url_for('login', next=flask.request.url))
    if isinstance(flask.g.identity, flask_principal.AnonymousIdentity):
        return redirect
    else:
        return deny


if __name__ == '__main__':
    app.run(port=8000, debug=True)