~epsy/+junk/aagidopenidgateway

« back to all changes in this revision

Viewing changes to aagidopenidgateway/web.py

  • Committer: Yann Kaiser
  • Date: 2013-09-22 22:06:34 UTC
  • Revision ID: kaiser.yann@gmail.com-20130922220634-1f6yuskoc5feuv8i
initial import

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
from time import time
 
2
 
 
3
from pkg_resources import resource_filename
 
4
from openid.server import server as openidserver
 
5
from openid import message as oidmessage
 
6
 
 
7
from twisted.web.resource import Resource, NoResource
 
8
from twisted.web.server import Site
 
9
from twisted.web.static import File
 
10
from twisted.python import log
 
11
 
 
12
from aagidopenidgateway.webf import SessionGetter, NiceRequest
 
13
 
 
14
def gid_from_oid(oid):
 
15
    gid = oid[8:].partition('/')[2].strip('/')
 
16
    if not gid:
 
17
        raise ValueError('no gid in: ' + oid)
 
18
    return gid
 
19
 
 
20
def oid_from_gid(request, gid):
 
21
    return 'http://{0}/{1}'.format(request.getHeader('host'), gid)
 
22
 
 
23
def serialize_check_id(authreq):
 
24
    return authreq.message.toPostArgs(), authreq.op_endpoint
 
25
    return (
 
26
        authreq.identity, authreq.return_to, authreq.trust_root,
 
27
        authreq.immediate, authreq.assoc_handle, authreq.op_endpoint,
 
28
        authreq.claimed_id
 
29
        )
 
30
 
 
31
def unserialize_check_id(serialized):
 
32
    return openidserver.CheckIDRequest.fromMessage(
 
33
        oidmessage.Message.fromPostArgs(serialized[0]), serialized[1])
 
34
 
 
35
def expire_check_ids(serialized):
 
36
    oldest_time = time() - REQUEST_EXPIRY
 
37
    for key, (time_created, value) in serialized.items():
 
38
        if time_created > oldest_time:
 
39
            yield key, (time_created, value)
 
40
 
 
41
REQUEST_EXPIRY = 60 * 30
 
42
def unserialize_check_ids(serialized):
 
43
    for key, (time_created, value) in serialized.items():
 
44
        yield key, unserialize_check_id(value)
 
45
 
 
46
def authreqs_from_session(sess):
 
47
    valid = dict(expire_check_ids(sess.get('authreqs', {})))
 
48
    sess['authreqs'] = valid
 
49
    return dict(unserialize_check_ids(valid))
 
50
 
 
51
class OpenIdEndpoint(Resource):
 
52
    isLeaf = True
 
53
 
 
54
    def __init__(self, openidserver, storage):
 
55
        Resource.__init__(self)
 
56
        self.oidserver = openidserver
 
57
        self.storage = storage
 
58
 
 
59
    def start_auth(self, request, oid_request, error=None):
 
60
        sess = request.session
 
61
        authreqs = dict(expire_check_ids(sess.get('authreqs', {})))
 
62
        authreqs[oid_request.trust_root] = (
 
63
            time(), serialize_check_id(oid_request))
 
64
        sess['authreqs'] = authreqs
 
65
        request.push_session()
 
66
        return request.redirect_page('/user?' if 'user' in sess else '/auth')
 
67
 
 
68
    def oid_response(self, request, oid_response):
 
69
        encoded = self.oidserver.encodeResponse(oid_response)
 
70
        request.setResponseCode(encoded.code)
 
71
        for header, val in encoded.headers.iteritems():
 
72
            request.setHeader(header.encode('utf_8'), val.encode('utf_8'))
 
73
        return encoded.body.encode('utf-8')
 
74
 
 
75
    def render_GET(self, request):
 
76
        query = dict((k, v[0]) for k, v in request.args.iteritems())
 
77
 
 
78
        oid_request = self.oidserver.decodeRequest(query)
 
79
 
 
80
        if oid_request is None:
 
81
            return request.redirect_page('/')
 
82
 
 
83
        if oid_request.mode == 'checkid_setup':
 
84
            return self.start_auth(request, oid_request)
 
85
        if oid_request.mode == 'checkid_immediate':
 
86
            return self.oid_response(
 
87
                request, oid_request.answer(False))
 
88
        else:
 
89
            return self.oid_response(
 
90
                request,
 
91
                self.oidserver.handleRequest(oid_request))
 
92
 
 
93
    render_POST = render_GET
 
94
 
 
95
class AuthPage(Resource, object):
 
96
    def __init__(self, host, port, *args, **kwargs):
 
97
        super(AuthPage, self).__init__()
 
98
        self.host = host
 
99
        self.port = port
 
100
        self.token_page = TokenPage(*args, **kwargs)
 
101
 
 
102
    def render_GET(self, request):
 
103
        if 'user' in request.session:
 
104
            return request.redirect_page('/user?')
 
105
        return request.render_template('instructions.html',
 
106
                      host=self.host, port=self.port)
 
107
 
 
108
    def getChild(self, path, request):
 
109
        return self.token_page
 
110
 
 
111
class TokenPage(Resource):
 
112
    def __init__(self, oid):
 
113
        Resource.__init__(self)
 
114
        self.oid = oid
 
115
 
 
116
    def render_GET(self, request):
 
117
        token = request.prepath[-1].replace('-', '').lower()
 
118
        authname = log.callWithLogger(
 
119
            self.oid.storage,
 
120
            self.oid.storage.use_token, request.getClientIP(), token)
 
121
        if authname:
 
122
            request.session['user'] = authname
 
123
            request.push_session()
 
124
            return request.redirect_page('/user?')
 
125
        else:
 
126
            return request.render_template('wrong.html')
 
127
 
 
128
class IdPage(Resource):
 
129
    isLeaf = True
 
130
 
 
131
    def render_GET(self, request):
 
132
        gid = request.path.strip('/')
 
133
        return request.render_template('idpage.html', gid=gid)
 
134
 
 
135
class UserPage(Resource, object):
 
136
    isLeaf = True
 
137
 
 
138
    def __init__(self, storage, oid):
 
139
        super(UserPage, self).__init__()
 
140
        self.storage = storage
 
141
        self.oid = oid
 
142
 
 
143
    def render_GET(self, request):
 
144
        if 'user' not in request.session:
 
145
            return request.redirect_page('/auth')
 
146
        elif request.postpath:
 
147
            return request.redirect_page('/user?')
 
148
        else:
 
149
            return self.show_user_page(request)
 
150
 
 
151
    def render_POST(self, request):
 
152
        action, = request.postpath
 
153
        sess = request.session
 
154
        if not request.csrf.check():
 
155
            return request.redirect_page('/user?')
 
156
        elif 'user' not in sess:
 
157
            return request.redirect_page('/auth')
 
158
        elif action == 'complete':
 
159
            return self.complete(request)
 
160
        elif action == 'account':
 
161
            return self.account(request)
 
162
        else:
 
163
            return self.error(request, 'Unknown action.')
 
164
 
 
165
    def make_etag(self, request):
 
166
        return '"{0}"'.format(time())
 
167
 
 
168
    def show_user_page(self, request):
 
169
        authreqs = authreqs_from_session(request.session)
 
170
        request.setETag(self.make_etag(request))
 
171
        return request.render_template(
 
172
            'user.html',
 
173
            requests=authreqs
 
174
            )
 
175
 
 
176
    def error(self, request, message):
 
177
        request.toasts.error(message)
 
178
        return request.redirect_page('/user?')
 
179
 
 
180
    def complete(self, request):
 
181
        submit = None
 
182
        for key, vals in request.args.items():
 
183
            if key.startswith('submit-'):
 
184
                if submit:
 
185
                    return self.error(request, 'Multiple submit buttons used.')
 
186
                submit = key
 
187
        if submit is None:
 
188
            return self.error(request, 'No submit button used.')
 
189
        try:
 
190
            _, action, tr = submit.split('-', 2)
 
191
        except ValueError:
 
192
            return self.error(request, 'Bad format: {0}'.format(submit))
 
193
        authreqs = authreqs_from_session(request.session)
 
194
        authreq = authreqs.get(tr)
 
195
        if action == 'auth':
 
196
            return self.authenticate(request, authreq)
 
197
        elif action == 'cancel':
 
198
            return self.cancel(request, authreq)
 
199
        else:
 
200
            return self.error(request, 'Unknown action.')
 
201
 
 
202
    def authenticate(self, request, authreq):
 
203
        if not authreq:
 
204
            return self.error(request, 'Expired or invalid auth request.')
 
205
        sess = request.session
 
206
        sess['authreqs'].pop(authreq.trust_root, None)
 
207
        request.push_session()
 
208
        log.msg('authenticated {0} from {1} to {2}'.format(
 
209
            sess['user'], request.getClientIP(), authreq.trust_root))
 
210
        return self.oid.oid_response(request, authreq.answer(
 
211
            True, identity=oid_from_gid(request, sess['user'])))
 
212
 
 
213
    def cancel(self, request, authreq):
 
214
        if not authreq:
 
215
            return self.show_user_page(request)
 
216
        request.session['authreqs'].pop(authreq.trust_root, None)
 
217
        request.push_session()
 
218
        return self.oid.oid_response(request, authreq.answer(False))
 
219
 
 
220
    def account(self, request):
 
221
        if 'logout' in request.args:
 
222
            return self.logout(request)
 
223
        elif 'remember' in request.args:
 
224
            return self.remember(request)
 
225
        elif 'forget' in request.args:
 
226
            return self.remember(request, persist=False)
 
227
        else:
 
228
            return self.error(request, 'Unknown action.')
 
229
 
 
230
    def logout(self, request):
 
231
        sess = request.session
 
232
        sess.pop('user', None)
 
233
        sess.pop('csrf', None)
 
234
        request.toasts.info('Logged out succesfully.')
 
235
        request.push_session()
 
236
        return request.redirect_page('/auth')
 
237
 
 
238
    def remember(self, request, persist=True):
 
239
        sess = request.session
 
240
        sess['persist'] = persist
 
241
        if persist:
 
242
            request.toasts.info('You will stay logged in for at least '
 
243
                                '31 days.')
 
244
        else:
 
245
            request.toasts.info('You will be logged out at the end of this '
 
246
                                'browser session.')
 
247
        request.push_session()
 
248
        return request.redirect_page('/user?')
 
249
 
 
250
class Root(Resource):
 
251
    def __init__(self, idpage, storage):
 
252
        Resource.__init__(self)
 
253
        self.idpage = idpage
 
254
        self.storage = storage
 
255
 
 
256
    def render_GET(self, request):
 
257
        return request.render_template('frontpage.html')
 
258
 
 
259
    def getChild(self, path, request):
 
260
        if not path:
 
261
            return self
 
262
        if '@' in request.path:
 
263
            return self.idpage
 
264
        return Resource.getChild(self, path, request)
 
265
 
 
266
class FileNoListings(File):
 
267
    """Disable directory listings"""
 
268
 
 
269
    def directoryListing(self):
 
270
        return NoResource()
 
271
 
 
272
def make_site(oidstore, storage, arma_host, arma_port, address, serializer):
 
273
    oidserver = openidserver.Server(
 
274
        oidstore, 'http://{0}/openid'.format(address))
 
275
    session = SessionGetter(serializer)
 
276
 
 
277
    root = Root(IdPage(), storage)
 
278
    oid = OpenIdEndpoint(oidserver, storage)
 
279
    root.putChild('openid', oid)
 
280
    root.putChild('auth', AuthPage(arma_host, arma_port, oid))
 
281
    root.putChild('user', UserPage(storage, oid))
 
282
    root.putChild('static',
 
283
        FileNoListings(resource_filename(__name__, 'static')))
 
284
 
 
285
    site = Site(root)
 
286
    site.session = session
 
287
    site.requestFactory = NiceRequest
 
288
    return site
 
289