Project

General

Profile

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

oidc / ckanext / ozwillo_pyoidc / plugin.py @ 2bfcb919

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

    
11
from pylons import config
12

    
13
import conf
14
from oidc import create_client
15

    
16
plugin_config_prefix = 'ckanext.ozwillo_pyoidc.'
17

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

    
21
_CLIENTS = {}
22

    
23
class Clients(object):
24

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

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

    
47

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

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

    
65
        return map
66

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

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

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

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

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

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

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

    
115
class OpenidController(base.BaseController):
116

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

    
128
    def callback(self):
129
        g = model.Group.get(session['organization_id'])
130
        client = Clients.get(g)
131
        userinfo = client.callback(request.GET)
132
        locale = None
133
        log.info('Received userinfo: %s' % userinfo)
134

    
135
        if 'locale' in userinfo:
136
            locale = userinfo.get('locale', '')
137
            if '-' in locale:
138
                locale, country = locale.split('-')
139

    
140
        org_url = str(toolkit.url_for(host=request.host,
141
                                      controller="organization",
142
                                      action='read',
143
                                      id=g.name,
144
                                      locale=locale,
145
                                      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 client.app_admin or client.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(g)
213
            logout_url = client.end_session_endpoint
214

    
215
            redirect_uri = org_url + '/logout'
216

    
217
            if not hasattr(client, 'access_token'):
218
                self.sso(g.name)
219

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

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