3
from pkg_resources import resource_filename
4
from openid.server import server as openidserver
5
from openid import message as oidmessage
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
12
from aagidopenidgateway.webf import SessionGetter, NiceRequest
14
def gid_from_oid(oid):
15
gid = oid[8:].partition('/')[2].strip('/')
17
raise ValueError('no gid in: ' + oid)
20
def oid_from_gid(request, gid):
21
return 'http://{0}/{1}'.format(request.getHeader('host'), gid)
23
def serialize_check_id(authreq):
24
return authreq.message.toPostArgs(), authreq.op_endpoint
26
authreq.identity, authreq.return_to, authreq.trust_root,
27
authreq.immediate, authreq.assoc_handle, authreq.op_endpoint,
31
def unserialize_check_id(serialized):
32
return openidserver.CheckIDRequest.fromMessage(
33
oidmessage.Message.fromPostArgs(serialized[0]), serialized[1])
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)
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)
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))
51
class OpenIdEndpoint(Resource):
54
def __init__(self, openidserver, storage):
55
Resource.__init__(self)
56
self.oidserver = openidserver
57
self.storage = storage
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')
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')
75
def render_GET(self, request):
76
query = dict((k, v[0]) for k, v in request.args.iteritems())
78
oid_request = self.oidserver.decodeRequest(query)
80
if oid_request is None:
81
return request.redirect_page('/')
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))
89
return self.oid_response(
91
self.oidserver.handleRequest(oid_request))
93
render_POST = render_GET
95
class AuthPage(Resource, object):
96
def __init__(self, host, port, *args, **kwargs):
97
super(AuthPage, self).__init__()
100
self.token_page = TokenPage(*args, **kwargs)
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)
108
def getChild(self, path, request):
109
return self.token_page
111
class TokenPage(Resource):
112
def __init__(self, oid):
113
Resource.__init__(self)
116
def render_GET(self, request):
117
token = request.prepath[-1].replace('-', '').lower()
118
authname = log.callWithLogger(
120
self.oid.storage.use_token, request.getClientIP(), token)
122
request.session['user'] = authname
123
request.push_session()
124
return request.redirect_page('/user?')
126
return request.render_template('wrong.html')
128
class IdPage(Resource):
131
def render_GET(self, request):
132
gid = request.path.strip('/')
133
return request.render_template('idpage.html', gid=gid)
135
class UserPage(Resource, object):
138
def __init__(self, storage, oid):
139
super(UserPage, self).__init__()
140
self.storage = storage
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?')
149
return self.show_user_page(request)
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)
163
return self.error(request, 'Unknown action.')
165
def make_etag(self, request):
166
return '"{0}"'.format(time())
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(
176
def error(self, request, message):
177
request.toasts.error(message)
178
return request.redirect_page('/user?')
180
def complete(self, request):
182
for key, vals in request.args.items():
183
if key.startswith('submit-'):
185
return self.error(request, 'Multiple submit buttons used.')
188
return self.error(request, 'No submit button used.')
190
_, action, tr = submit.split('-', 2)
192
return self.error(request, 'Bad format: {0}'.format(submit))
193
authreqs = authreqs_from_session(request.session)
194
authreq = authreqs.get(tr)
196
return self.authenticate(request, authreq)
197
elif action == 'cancel':
198
return self.cancel(request, authreq)
200
return self.error(request, 'Unknown action.')
202
def authenticate(self, request, 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'])))
213
def cancel(self, request, 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))
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)
228
return self.error(request, 'Unknown action.')
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')
238
def remember(self, request, persist=True):
239
sess = request.session
240
sess['persist'] = persist
242
request.toasts.info('You will stay logged in for at least '
245
request.toasts.info('You will be logged out at the end of this '
247
request.push_session()
248
return request.redirect_page('/user?')
250
class Root(Resource):
251
def __init__(self, idpage, storage):
252
Resource.__init__(self)
254
self.storage = storage
256
def render_GET(self, request):
257
return request.render_template('frontpage.html')
259
def getChild(self, path, request):
262
if '@' in request.path:
264
return Resource.getChild(self, path, request)
266
class FileNoListings(File):
267
"""Disable directory listings"""
269
def directoryListing(self):
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)
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')))
286
site.session = session
287
site.requestFactory = NiceRequest