flask_views.py 4.37 KB
Newer Older
1
from __future__ import absolute_import
ale's avatar
ale committed
2
import functools
3
import os
ale's avatar
ale committed
4
import sso
ale's avatar
ale committed
5
import urllib
6

7
from flask import request, session, abort, redirect, make_response, render_template, url_for, g, current_app
8 9

from . import exceptions
10
from . import registry
11
from . import xml_signing
ale's avatar
ale committed
12
from .app import saml_app
13 14 15 16 17


sso_cookie_name = 'SSO_SAML'


ale's avatar
ale committed
18
def init_app(app):
ale's avatar
ale committed
19
    app.register_blueprint(saml_app, url_prefix='/saml')
ale's avatar
ale committed
20 21

    saml_config = app.config['SAML']
ale's avatar
ale committed
22 23 24 25 26 27

    # Stick some immutable config objects in the app, so that
    # other functions can find them.
    saml_app.saml_certificate = xml_signing.load_certificate(saml_config['CERTIFICATE_FILE'])
    saml_app.saml_private_key = xml_signing.load_private_key(saml_config['PRIVATE_KEY_FILE'])

ale's avatar
ale committed
28
    saml_app.config = saml_config
ale's avatar
ale committed
29
    saml_app.login_server = saml_config['SSO_LOGIN_SERVER']
30
    saml_app.sso_service = saml_app.login_server + '/saml/'
ale's avatar
ale committed
31 32 33
    url_base = 'https://' + saml_app.sso_service
    saml_app.sso_url = url_base + 'login'
    saml_app.slo_url = url_base + 'logout'
ale's avatar
ale committed
34
    saml_app.issuer = 'https://' + saml_app.login_server
ale's avatar
ale committed
35 36 37 38 39 40 41
    with open(app.config['SSO_PUBLIC_KEY']) as fd:
        public_key = fd.read()
    saml_app.sso_verifier = sso.Verifier(
        public_key,
        saml_app.sso_service,
        app.config['SSO_DOMAIN'],
        [])
42 43


44 45 46 47
class NoCookieError(Exception):
    pass


48
def login_required(fn):
ale's avatar
ale committed
49
    @functools.wraps(fn)
50 51 52
    def _wrapper(*args, **kwargs):
        # Try to fetch the cookie.
        try:
53 54 55 56 57
            cookie = request.cookies.get(sso_cookie_name)
            if not cookie:
                raise NoCookieError('no cookie')
            current_app.logger.info('retrieved cookie: %s', cookie)
            g.sso_ticket = saml_app.sso_verifier.verify(str(cookie))
58 59 60 61
            # Cheat by looking up the email using the LoginService
            # private to the main app.
            g.user_email = current_app.login_service.auth.get_user_email(
                g.sso_ticket.user())
62
            return fn(*args, **kwargs)
63 64 65
        except (NoCookieError, TypeError, sso.Error) as e:
            current_app.logger.error('auth failed: %s', str(e))
            redir_url = 'https://%s/?%s' % (
ale's avatar
ale committed
66 67 68
                saml_app.login_server, urllib.urlencode({
                    's': saml_app.sso_service,
                    'd': request.url}))
69
            return redirect(redir_url)
ale's avatar
ale committed
70
    return _wrapper
71 72 73 74 75 76 77 78


@saml_app.route('/sso_login')
def sso_login():
    tkt_str = request.args['t']
    next_url = request.args['d']
    resp = redirect(next_url)
    resp.set_cookie(sso_cookie_name, tkt_str)
79
    current_app.logger.info('set sso cookie %s to %s', sso_cookie_name, tkt_str)
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
    return resp


@saml_app.route('/sso_logout')
def sso_logout():
    resp = make_response('OK')
    resp.set_cookie(sso_cookie_name, '', expires=0)
    return resp


@saml_app.route('/login', methods=('GET', 'POST'))
def login_begin():
    if request.method == 'POST':
        source = request.form
    else:
        source = request.args
    try:
        session['SAMLRequest'] = source['SAMLRequest']
    except KeyError:
        abort(400)
    session['RelayState'] = source.get('RelayState', '')
    return redirect(url_for('saml.login_process'))


@saml_app.route('/init/{resource}/{target}/')
@login_required
def login_init(resource, target):
    name, sp_config = metadata.get_config_for_resource(resource)
    proc = registry.get_processor(name, sp_config)

    try:
        linkdict = dict(metadata.get_links(sp_config))
        pattern = linkdict[resource]
    except KeyError:
        abort(400)
    url = pattern % target
    proc.init_deep_link(sp_config, url)
    return _generate_response(proc)


@saml_app.route('/login/process')
@login_required
def login_process():
123
    proc = registry.find_processor()
124 125 126 127 128 129 130 131 132 133 134
    return _generate_response(proc)


@saml_app.route('/logout')
def logout():
    pass


@saml_app.route('/metadata/xml/')
def descriptor():
    tv = {
ale's avatar
ale committed
135
        'entity_id': saml_app.issuer,
ale's avatar
ale committed
136 137
        'slo_url': saml_app.slo_url,
        'sso_url': saml_app.sso_url,
ale's avatar
ale committed
138
        'pubkey': saml_app.saml_certificate,
139
        }
ale's avatar
ale committed
140
    resp = make_response(render_template('saml/idpssodescriptor.xml', **tv))
141 142 143 144 145 146 147 148 149
    resp.headers['Content-Type'] = 'application/xml'
    return resp


def _generate_response(processor):
    try:
        tv = processor.generate_response()
    except exceptions.UserNotAuthorized:
        return render_template('saml/invalid_user.html')
150
    return render_template('saml/login.html', **tv)