1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 """
23 Decorators for use with OMERO.web applications.
24 """
25
26 import logging
27
28 from django.http import Http404, HttpResponse, HttpResponseRedirect, HttpResponseForbidden
29
30 from django.conf import settings
31 from django.utils.http import urlencode
32 from functools import update_wrapper
33 from django.utils import simplejson
34 from django.core.urlresolvers import reverse
35 from django.core import template_loader
36 from django.template import RequestContext
37 from django.core.cache import cache
38
39 from omeroweb.connector import Connector
40
41 logger = logging.getLogger(__name__)
42
44 """Extension of L{HttpResponse} which closes the OMERO connection."""
45
56
57
59 """
60 OMERO.web specific extension of the Django login_required() decorator,
61 https://docs.djangoproject.com/en/dev/topics/auth/, which is responsible
62 for ensuring a valid L{omero.gateway.BlitzGateway} connection. Is
63 configurable by various options.
64 """
65
66 - def __init__(self, useragent='OMERO.web', isAdmin=False,
67 isGroupOwner=False, doConnectionCleanup=True, omero_group='-1',
68 allowPublic=None):
69 """
70 Initialises the decorator.
71 """
72 self.useragent = useragent
73 self.isAdmin = isAdmin
74 self.isGroupOwner = isGroupOwner
75 self.doConnectionCleanup = doConnectionCleanup
76 self.omero_group = omero_group
77 self.allowPublic = allowPublic
78
79
80
81
87
89 """The URL that should be redirected to if not logged in."""
90 return reverse(settings.LOGIN_VIEW)
91 login_url = property(get_login_url)
92
101
115
117 """Called whenever the user is not logged in."""
118 if request.is_ajax():
119 logger.debug('Request is Ajax, returning HTTP 403.')
120 return HttpResponseForbidden()
121 args = {'url': url}
122 logger.debug('Request is not Ajax, redirecting to %s' % self.login_url)
123 return HttpResponseRedirect('%s?%s' % (self.login_url, urlencode(args)))
124
126 """
127 Called whenever the users is successfully logged in.
128 Sets the 'omero.group' option if specified in the constructor
129 """
130 if self.omero_group is not None:
131 conn.SERVICE_OPTS.setOmeroGroup(self.omero_group)
132
134 """Called whenever a share connection is successfully prepared."""
135 pass
136
138 """
139 If we have been requested to by the isAdmin flag, verify the user
140 is an admin and raise an exception if they are not.
141 """
142 if self.isAdmin and not conn.isAdmin():
143 raise Http404
144
146 """
147 If we have been requested to by the isGroupOwner flag, verify the user
148 is the owner of the provided group. If no group is provided the user's
149 active session group ownership will be verified.
150 """
151 if not self.isGroupOwner:
152 return
153 if gid is not None:
154 if not conn.isLeader(gid):
155 raise Http404
156 else:
157 if not conn.isLeader():
158 raise Http404
159
161 """
162 Verifies that the URL for the resource being requested falls within
163 the scope of the OMERO.webpublic URL filter.
164 """
165 if settings.PUBLIC_ENABLED:
166 if not hasattr(settings, 'PUBLIC_USER'):
167 logger.warn('OMERO.webpublic enabled but public user ' \
168 '(omero.web.public.user) not set, disabling ' \
169 'OMERO.webpublic.')
170 settings.PUBLIC_ENABLED = False
171 return False
172 if not hasattr(settings, 'PUBLIC_PASSWORD'):
173 logger.warn('OMERO.webpublic enabled but public user ' \
174 'password (omero.web.public.password) not set, ' \
175 'disabling OMERO.webpublic.')
176 settings.PUBLIC_ENABLED = False
177 return False
178 if self.allowPublic is None:
179 return settings.PUBLIC_URL_FILTER.search(request.path) is not None
180 return self.allowPublic
181 return False
182
184 """
185 Returns the current cached OMERO.webpublic connector or None if
186 nothing has been cached.
187 """
188 if not settings.PUBLIC_CACHE_ENABLED:
189 return
190 return cache.get(settings.PUBLIC_CACHE_KEY)
191
200
249
251 """
252 Prepares an authenticated Blitz connection wrapper (from
253 L{omero.gateway}) for use with a view function.
254 """
255
256
257 session = request.session
258 request = request.REQUEST
259 is_secure = request.get('ssl', False)
260 logger.debug('Is SSL? %s' % is_secure)
261 connector = session.get('connector', None)
262 logger.debug('Connector: %s' % connector)
263
264 if server_id is None:
265
266
267
268 if connector is not None:
269 server_id = connector.server_id
270 else:
271 try:
272 server_id = request['server']
273 except:
274 logger.debug('No Server ID available.')
275 return None
276
277
278
279 try:
280 omero_session_key = request['bsession']
281 connector = Connector(server_id, is_secure)
282 except KeyError:
283
284 pass
285 else:
286
287
288 logger.debug('Have OMERO session key %s, attempting to join...' % \
289 omero_session_key)
290 connector.user_id = None
291 connector.omero_session_key = omero_session_key
292 connection = connector.join_connection(self.useragent)
293 session['connector'] = connector
294 return connection
295
296
297
298 username = None
299 password = None
300 try:
301 username = request['username']
302 password = request['password']
303 except KeyError:
304 if connector is None:
305 logger.debug('No username or password in request, exiting.')
306
307
308
309 return None
310
311 if username is not None and password is not None:
312
313
314
315
316 logger.debug('Creating connection with username and password...')
317 connector = Connector(server_id, is_secure)
318 connection = connector.create_connection(
319 self.useragent, username, password)
320 session['connector'] = connector
321 return connection
322
323 logger.debug('Django session connector: %r' % connector)
324 if connector is not None:
325
326
327 connection = connector.join_connection(self.useragent)
328 if connection is not None:
329 logger.debug('Connector valid, session successfully joined.')
330 return connection
331
332
333
334 logger.debug('Connector is no longer valid, destroying...')
335 del session['connector']
336 return None
337
338 session['connector'] = connector
339 return connection
340
342 """
343 Tries to prepare a logged in connection, then calls function and
344 returns the result.
345 """
346 def wrapped(request, *args, **kwargs):
347 url = request.REQUEST.get('url')
348 if url is None or len(url) == 0:
349 url = request.get_full_path()
350
351 doConnectionCleanup = False
352
353 conn = kwargs.get('conn', None)
354 error = None
355 server_id = kwargs.get('server_id', None)
356
357
358
359 if conn is None:
360 doConnectionCleanup = ctx.doConnectionCleanup
361 logger.debug('Connection not provided, attempting to get one.')
362 try:
363 conn = ctx.get_connection(server_id, request)
364 except Exception, x:
365 logger.error('Error retrieving connection.', exc_info=True)
366 error = str(x)
367 else:
368
369 if conn is None:
370 return ctx.on_not_logged_in(request, url, error)
371 else:
372 ctx.on_logged_in(request, conn)
373 ctx.verify_is_admin(conn)
374 ctx.verify_is_group_owner(conn, kwargs.get('gid'))
375
376 share_id = kwargs.get('share_id')
377 conn_share = ctx.prepare_share_connection(request, conn, share_id)
378 if conn_share is not None:
379 ctx.on_share_connection_prepared(request, conn_share)
380 kwargs['conn'] = conn_share
381 else:
382 kwargs['conn'] = conn
383
384
385 kwargs['url'] = url
386
387 retval = f(request, *args, **kwargs)
388 try:
389 logger.debug('Doing connection cleanup? %s' % \
390 doConnectionCleanup)
391 if doConnectionCleanup:
392 if conn is not None and conn.c is not None:
393 for v in conn._proxies.values():
394 v.close()
395 conn.c.closeSession()
396 except:
397 logger.warn('Failed to clean up connection.', exc_info=True)
398 return retval
399 return update_wrapper(wrapped, f)
400
401
403 """
404 This decorator handles the rendering of view methods to HttpResponse. It expects
405 that wrapped view methods return a dict. This allows:
406 - The template to be specified in the method arguments OR within the view method itself
407 - The dict to be returned as json if required
408 - The request is passed to the template context, as required by some tags etc
409 - A hook is provided for adding additional data to the context, from the L{omero.gateway.BlitzGateway}
410 or from the request.
411 """
412
413
414
415
421
422 - def prepare_context(self, request, context, *args, **kwargs):
423 """ Hook for adding additional data to the context dict """
424 pass
425
426
428 """ Here we wrap the view method f and return the wrapped method """
429
430 def wrapper(request, *args, **kwargs):
431 """ Wrapper calls the view function, processes the result and returns HttpResponse """
432
433
434 context = f(request, *args, **kwargs)
435
436
437 if isinstance(context, HttpResponse):
438 return context
439
440
441 template = 'template' in context and context['template'] or None
442 template = kwargs.get('template', template)
443 logger.debug("Rendering template: %s" % template)
444
445
446 if template is None or template == 'json':
447 json_data = simplejson.dumps(context)
448 return HttpResponse(json_data, mimetype='application/javascript')
449 else:
450
451 ctx.prepare_context(request, context, *args, **kwargs)
452 t = template_loader.get_template(template)
453 c = RequestContext(request, context)
454 return HttpResponse(t.render(c))
455 return update_wrapper(wrapper, f)
456