Project

General

Profile

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

oidc / ckanext / ozwillo_pyoidc / plugin.py @ e5e6f14a

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
_CLIENTS = {}
23

    
24
class Clients(object):
25

    
26
    @classmethod
27
    def get(cls, g):
28
        global _CLIENTS
29
        if g.id in _CLIENTS:
30
            return _CLIENTS.get(g.id)
31
        client = cls().get_client(g)
32
        _CLIENTS.update({g.id: client})
33
        return client
34

    
35
    def get_client(self, g):
36
        params = conf.CLIENT.copy()
37
        params['client_registration'].update({
38
            'client_id': g._extras['client_id'].value,
39
            'client_secret': g._extras['client_secret'].value,
40
            'redirect_uris': [url_for(host=request.host,
41
                                      controller=plugin_controller,
42
                                      action='callback',
43
                                      id=g.name,
44
                                      qualified=True)]
45
        })
46
        return create_client(**params)
47

    
48

    
49
class OzwilloPyoidcPlugin(plugins.SingletonPlugin):
50
    plugins.implements(plugins.IConfigurer)
51
    plugins.implements(plugins.IRoutes)
52
    plugins.implements(plugins.IAuthenticator, inherit=True)
53

    
54
    def before_map(self, map):
55
        map.connect('/organization/{id:.*}/sso',
56
                    controller=plugin_controller,
57
                    action='sso')
58
        map.connect('/organization/{id:.*}/callback',
59
                    controller=plugin_controller,
60
                    action='callback')
61
        map.connect('/user/slo',
62
                    controller=plugin_controller,
63
                    action='slo')
64
        map.redirect('/organization/{id:.*}/logout', '/user/_logout')
65

    
66
        return map
67

    
68
    def after_map(self, map):
69
        return map
70

    
71
    def identify(self):
72
        user = session.get('user')
73
        if user and not toolkit.c.userobj:
74
            userobj = model.User.get(user)
75
            toolkit.c.user = userobj.name
76
            toolkit.c.userobj = userobj
77

    
78
    def login(self):
79
        for cookie in request.cookies:
80
            value = request.cookies.get(cookie)
81
            response.set_cookie(cookie, value, secure=True, httponly=True)
82

    
83
        if 'organization_id' in session:
84
            g = model.Group.get(session['organization_id'])
85
            client = Clients.get(g)
86
            url, ht_args = client.create_authn_request(conf.ACR_VALUES)
87
            if ht_args:
88
                toolkit.request.headers.update(ht_args)
89
            redirect_to(url)
90
        else:
91
            redirect_to('/')
92

    
93
    def logout(self):
94
        log.info('Logging out user: %s' % session['user'])
95
        session['user'] = None
96
        session.save()
97
        g = model.Group.get(session['organization_id'])
98
        for cookie in request.cookies:
99
            response.delete_cookie(cookie)
100
        if g:
101
            org_url = toolkit.url_for(host=request.host,
102
                                      controller='organization',
103
                                      action='read',
104
                                      id=g.name,
105
                                      qualified=True)
106

    
107
            redirect_to(str(org_url))
108
        else:
109
            redirect_to('/')
110

    
111
    def update_config(self, config_):
112
        toolkit.add_template_directory(config_, 'templates')
113
        toolkit.add_public_directory(config_, 'public')
114
        toolkit.add_resource('fanstatic', 'ozwillo_pyoidc')
115

    
116
class OpenidController(base.BaseController):
117

    
118
    def sso(self, id):
119
        log.info('SSO for organization "%s"' % id)
120
        session['organization_id'] = id
121
        session.save()
122
        log.info('redirecting to login page')
123
        login_url = toolkit.url_for(host=request.host,
124
                                    controller='user',
125
                                    action='login',
126
                                    qualified=True)
127
        redirect_to(login_url)
128

    
129
    def callback(self):
130
        g = model.Group.get(session['organization_id'])
131
        client = Clients.get(g)
132
        org_url = str(toolkit.url_for(controller="organization",
133
                                      action='read',
134
                                      id=g.name))
135
        try:
136
            userinfo = client.callback(request.GET)
137
        except OIDCError, e:
138
            flash_error('Login failed')
139
            redirect_to(org_url, qualified=True)
140
        locale = None
141
        log.info('Received userinfo: %s' % userinfo)
142

    
143
        if 'locale' in userinfo:
144
            locale = userinfo.get('locale', '')
145
            if '-' in locale:
146
                locale, country = locale.split('-')
147

    
148
        org_url = str(toolkit.url_for(org_url, locale=locale, qualified=True))
149
        if 'sub' in userinfo:
150

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

    
170
                    member_create_context = {
171
                        'model': model,
172
                        'user': userobj.name,
173
                        'ignore_auth': True,
174
                        'session': session
175
                    }
176

    
177
                    member_create(member_create_context, member_dict)
178

    
179
            if 'given_name' in userinfo:
180
                userobj.fullname = userinfo['given_name']
181
            if 'family_name' in userinfo:
182
                userobj.fullname += ' ' + userinfo['family_name']
183
            userobj.save()
184

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

    
192
            session['user'] = userobj.id
193
            session.save()
194

    
195
        redirect_to(org_url)
196

    
197

    
198
    def slo(self):
199
        """
200
        Revokes the delivered access token. Logs out the user
201
        """
202

    
203
        if not request.referer or request.host not in request.referer:
204
            redirect_to('/')
205

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

    
214
        if toolkit.c.user:
215
            client = Clients.get(g)
216
            logout_url = client.end_session_endpoint
217

    
218
            redirect_uri = org_url + '/logout'
219

    
220
            if not hasattr(client, 'access_token'):
221
                self.sso(g.name)
222

    
223
            # revoke the access token
224
            headers = {'Content-Type': 'application/x-www-form-urlencoded'}
225
            data = 'token=' + client.access_token
226
            data += '&token_type_hint=access_token'
227
            client.http_request(client.revocation_endpoint, 'POST',
228
                                data=data, headers=headers)
229

    
230
            # redirect to IDP logout
231
            logout_url += '?id_token_hint=%s&' % client.id_token
232
            logout_url += 'post_logout_redirect_uri=%s' % redirect_uri
233
            redirect_to(str(logout_url))
234
        redirect_to(org_url)
(4-4/4)