Project

General

Profile

Download (7.98 KB) Statistics
| Branch: | Tag: | Revision:

oidc / ckanext / ozwillo_pyoidc / plugin.py @ 34457e9c

1
import logging
2
from routes import redirect_to, url_for
3

    
4
import ckan.plugins as plugins
5
import ckan.plugins.toolkit as toolkit
6
from ckan.common import session, c, request, response
7
from ckan import model
8
from ckan.logic.action.create import user_create, member_create
9
import ckan.lib.base as base
10
from ckan.lib.helpers import flash_error
11

    
12
from pylons import config
13

    
14
import conf
15
from oidc import create_client, OIDCError
16

    
17
plugin_config_prefix = 'ckanext.ozwillo_pyoidc.'
18

    
19
log = logging.getLogger(__name__)
20
plugin_controller = __name__ + ':OpenidController'
21

    
22

    
23
class Clients(object):
24

    
25
    @classmethod
26
    def get_client(cls, g):
27
        params = conf.CLIENT.copy()
28
        params['client_registration'].update({
29
            'client_id': g._extras['client_id'].value,
30
            'client_secret': g._extras['client_secret'].value,
31
            'redirect_uris': [url_for(host=request.host,
32
                                      controller=plugin_controller,
33
                                      action='callback',
34
                                      id=g.name,
35
                                      qualified=True)]
36
        })
37
        return create_client(**params)
38

    
39

    
40
class OzwilloPyoidcPlugin(plugins.SingletonPlugin):
41
    plugins.implements(plugins.IConfigurer)
42
    plugins.implements(plugins.IRoutes)
43
    plugins.implements(plugins.IAuthenticator, inherit=True)
44

    
45
    def before_map(self, map):
46
        map.connect('/organization/{id:.*}/sso',
47
                    controller=plugin_controller,
48
                    action='sso')
49
        map.connect('/organization/{id:.*}/callback',
50
                    controller=plugin_controller,
51
                    action='callback')
52
        map.connect('/user/slo',
53
                    controller=plugin_controller,
54
                    action='slo')
55
        map.redirect('/organization/{id:.*}/logout', '/user/_logout')
56

    
57
        return map
58

    
59
    def after_map(self, map):
60
        return map
61

    
62
    def identify(self):
63
        user = session.get('user')
64
        if user and not toolkit.c.userobj:
65
            userobj = model.User.get(user)
66
            toolkit.c.user = userobj.name
67
            toolkit.c.userobj = userobj
68

    
69
    def login(self):
70
        for cookie in request.cookies:
71
            value = request.cookies.get(cookie)
72
            response.set_cookie(cookie, value, secure=True, httponly=True)
73

    
74
        if 'organization_id' in session:
75
            g = model.Group.get(session['organization_id'])
76
            client = Clients.get_client(g)
77
            url, ht_args, state = client.create_authn_request(conf.ACR_VALUES)
78
            session['state'] = state
79
            session.save()
80
            if ht_args:
81
                toolkit.request.headers.update(ht_args)
82
            redirect_to(url)
83
        else:
84
            redirect_to('/')
85

    
86
    def logout(self):
87
        log.info('Logging out user: %s' % session['user'])
88
        session['user'] = None
89
        session.save()
90
        g = model.Group.get(session['organization_id'])
91
        for cookie in request.cookies:
92
            response.delete_cookie(cookie)
93
        if g:
94
            org_url = toolkit.url_for(host=request.host,
95
                                      controller='organization',
96
                                      action='read',
97
                                      id=g.name,
98
                                      qualified=True)
99

    
100
            redirect_to(str(org_url))
101
        else:
102
            redirect_to('/')
103

    
104
    def update_config(self, config_):
105
        toolkit.add_template_directory(config_, 'templates')
106
        toolkit.add_public_directory(config_, 'public')
107
        toolkit.add_resource('fanstatic', 'ozwillo_pyoidc')
108

    
109
class OpenidController(base.BaseController):
110

    
111
    def sso(self, id):
112
        log.info('SSO for organization "%s"' % id)
113
        session['organization_id'] = id
114
        session.save()
115
        log.info('redirecting to login page')
116
        login_url = toolkit.url_for(host=request.host,
117
                                    controller='user',
118
                                    action='login',
119
                                    qualified=True)
120
        redirect_to(login_url)
121

    
122
    def callback(self):
123
        g = model.Group.get(session['organization_id'])
124
        client = Clients.get_client(g)
125
        org_url = str(toolkit.url_for(controller="organization",
126
                                      action='read',
127
                                      id=g.name))
128
        try:
129
            userinfo, app_admin, app_user, access_token, id_token \
130
                = client.callback(session['state'], request.GET)
131
            session['access_token'] = access_token
132
            session['id_token'] = id_token
133
            session.save()
134
        except OIDCError, e:
135
            flash_error('Login failed')
136
            redirect_to(org_url, qualified=True)
137
        locale = None
138
        log.info('Received userinfo: %s' % userinfo)
139

    
140
        if 'locale' in userinfo:
141
            locale = userinfo.get('locale', '')
142
            if '-' in locale:
143
                locale, country = locale.split('-')
144

    
145
        org_url = str(toolkit.url_for(org_url, locale=locale, qualified=True))
146
        if 'sub' in userinfo:
147

    
148
            userobj = model.User.get(userinfo['sub'])
149
            if not userobj:
150
                user_dict = {'id': userinfo['sub'],
151
                             'name': userinfo['sub'].replace('-', ''),
152
                             'email': userinfo['email'],
153
                             'password': userinfo['sub']
154
                             }
155
                context = {'ignore_auth': True, 'model': model,
156
                           'session': model.Session}
157
                user_create(context, user_dict)
158
                userobj = model.User.get(userinfo['sub'])
159
                if app_admin or app_user:
160
                    member_dict = {
161
                        'id': g.id,
162
                        'object': userinfo['sub'],
163
                        'object_type': 'user',
164
                        'capacity': 'admin',
165
                    }
166

    
167
                    member_create_context = {
168
                        'model': model,
169
                        'user': userobj.name,
170
                        'ignore_auth': True,
171
                        'session': session
172
                    }
173

    
174
                    member_create(member_create_context, member_dict)
175

    
176
            if 'given_name' in userinfo:
177
                userobj.fullname = userinfo['given_name']
178
            if 'family_name' in userinfo:
179
                userobj.fullname += ' ' + userinfo['family_name']
180
            userobj.save()
181

    
182
            if 'nickname' in userinfo:
183
                userobj.name = userinfo['nickname']
184
            try:
185
                userobj.save()
186
            except Exception, e:
187
                log.warning('Error while saving user name: %s' % e)
188

    
189
            session['user'] = userobj.id
190
            session.save()
191

    
192
        redirect_to(org_url)
193

    
194

    
195
    def slo(self):
196
        """
197
        Revokes the delivered access token. Logs out the user
198
        """
199

    
200
        if not request.referer or request.host not in request.referer:
201
            redirect_to('/')
202

    
203
        g = model.Group.get(session['organization_id'])
204
        org_url = url_for(host=request.host,
205
                          controller='organization',
206
                          action='read',
207
                          id=g.name,
208
                          qualified=True)
209
        org_url = str(org_url)
210

    
211
        if toolkit.c.user:
212
            client = Clients.get_client(g)
213
            logout_url = client.end_session_endpoint
214

    
215
            redirect_uri = org_url + '/logout'
216

    
217
            # revoke the access token
218
            headers = {'Content-Type': 'application/x-www-form-urlencoded'}
219
            data = 'token=' + session.get('access_token')
220
            data += '&token_type_hint=access_token'
221
            client.http_request(client.revocation_endpoint, 'POST',
222
                                data=data, headers=headers)
223

    
224
            # redirect to IDP logout
225
            logout_url += '?id_token_hint=%s&' % session.get('id_token')
226
            logout_url += 'post_logout_redirect_uri=%s' % redirect_uri
227
            redirect_to(str(logout_url))
228
        redirect_to(org_url)
(4-4/4)