Fix bug in checkDocumentPick of soc.views.helper.access module, which cause KeyError...
[Melange.git] / app / soc / views / helper / access.py
blob069df042efb9e92e0c36e71087c0e10e526de73c
1 #!/usr/bin/python2.5
3 # Copyright 2008 the Melange authors.
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
17 """Access control helper.
19 The functions in this module can be used to check access control
20 related requirements. When the specified required conditions are not
21 met, an exception is raised. This exception contains a views that
22 either prompts for authentication, or informs the user that they
23 do not meet the required criteria.
24 """
26 __authors__ = [
27 '"Todd Larsen" <tlarsen@google.com>',
28 '"Sverre Rabbelier" <sverre@rabbelier.nl>',
29 '"Lennard de Rijk" <ljvderijk@gmail.com>',
30 '"Pawel Solyga" <pawel.solyga@gmail.com>',
34 from google.appengine.api import users
35 from google.appengine.api import memcache
37 from django.core import urlresolvers
38 from django.utils.translation import ugettext
40 from soc.logic import accounts
41 from soc.logic import dicts
42 from soc.logic import rights as rights_logic
43 from soc.logic.helper import timeline as timeline_helper
44 from soc.logic.models.club_admin import logic as club_admin_logic
45 from soc.logic.models.club_member import logic as club_member_logic
46 from soc.logic.models.document import logic as document_logic
47 from soc.logic.models.host import logic as host_logic
48 from soc.logic.models.mentor import logic as mentor_logic
49 from soc.logic.models.notification import logic as notification_logic
50 from soc.logic.models.org_admin import logic as org_admin_logic
51 from soc.logic.models.organization import logic as org_logic
52 from soc.logic.models.program import logic as program_logic
53 from soc.logic.models.request import logic as request_logic
54 from soc.logic.models.role import logic as role_logic
55 from soc.logic.models.site import logic as site_logic
56 from soc.logic.models.sponsor import logic as sponsor_logic
57 from soc.logic.models.student import logic as student_logic
58 from soc.logic.models.student_proposal import logic as student_proposal_logic
59 from soc.logic.models.timeline import logic as timeline_logic
60 from soc.logic.models.user import logic as user_logic
61 from soc.views.helper import redirects
62 from soc.views import helper
63 from soc.views import out_of_band
66 DEF_NO_USER_LOGIN_MSG= ugettext(
67 'Please create <a href="/user/create_profile">User Profile</a>'
68 ' in order to view this page.')
70 DEF_AGREE_TO_TOS_MSG_FMT = ugettext(
71 'You must agree to the <a href="%(tos_link)s">site-wide Terms of'
72 ' Service</a> in your <a href="/user/edit_profile">User Profile</a>'
73 ' in order to view this page.')
75 DEF_DEV_LOGOUT_LOGIN_MSG_FMT = ugettext(
76 'Please <a href="%%(sign_out)s">sign out</a>'
77 ' and <a href="%%(sign_in)s">sign in</a>'
78 ' again as %(role)s to view this page.')
80 DEF_NEED_MEMBERSHIP_MSG_FMT = ugettext(
81 'You need to be in the %(status)s group to %(action)s'
82 ' documents in the %(prefix)s prefix.')
84 DEF_NEED_ROLE_MSG = ugettext(
85 'You do not have the required role.')
87 DEF_NOT_YOUR_ENTITY_MSG = ugettext(
88 'This entity does not belong to you.')
90 DEF_NO_ACTIVE_ENTITY_MSG = ugettext(
91 'There is no such active entity.')
93 DEF_NO_ACTIVE_GROUP_MSG = ugettext(
94 'There is no such active group.')
96 DEF_NO_ACTIVE_ROLE_MSG = ugettext(
97 'There is no such active role.')
99 DEF_ALREADY_PARTICIPATING_MSG = ugettext(
100 'You cannot become a student because you are already participating '
101 'in this program.')
103 DEF_ALREADY_STUDENT_ROLE_MSG = ugettext(
104 'You cannot become a Mentor or Organization Admin because you already are '
105 'a student in this program.')
107 DEF_NO_ACTIVE_PROGRAM_MSG = ugettext(
108 'There is no such active program.')
110 DEF_NO_REQUEST_MSG = ugettext(
111 'There is no accepted request that would allow you to visit this page.')
113 DEF_NO_APPLICATION_MSG = ugettext(
114 'There is no application that would allow you to visit this page.')
116 DEF_NEED_PICK_ARGS_MSG = ugettext(
117 'The "continue" and "field" args are not both present.')
119 DEF_REVIEW_COMPLETED_MSG = ugettext(
120 'This Application can not be reviewed anymore (it has been completed or rejected).')
122 DEF_REQUEST_COMPLETED_MSG = ugettext(
123 'This request cannot be accepted (it is either completed or denied).')
125 DEF_SCOPE_INACTIVE_MSG = ugettext(
126 'The scope for this request is not active.')
128 DEF_SIGN_UP_AS_STUDENT_MSG = ugettext(
129 'You need to sign up as a Student first.')
131 DEF_NO_LIST_ACCESS_MSG = ugettext(
132 'You do not have the required rights to list documents for this scope and prefix.')
134 DEF_PAGE_DENIED_MSG = ugettext(
135 'Access to this page has been restricted.')
137 DEF_PREFIX_NOT_IN_ARGS_MSG = ugettext(
138 'A required GET url argument ("prefix") was not specified.')
140 DEF_PAGE_INACTIVE_MSG = ugettext(
141 'This page is inactive at this time.')
143 DEF_LOGOUT_MSG_FMT = ugettext(
144 'Please <a href="%(sign_out)s">sign out</a> in order to view this page.')
146 DEF_GROUP_NOT_FOUND_MSG = ugettext(
147 'The requested Group can not be found.')
149 DEF_USER_ACCOUNT_INVALID_MSG_FMT = ugettext(
150 'The <b><i>%(email)s</i></b> account cannot be used with this site, for'
151 ' one or more of the following reasons:'
152 '<ul>'
153 ' <li>the account is invalid</li>'
154 ' <li>the account is already attached to a User profile and cannot be'
155 ' used to create another one</li>'
156 ' <li>the account is a former account that cannot be used again</li>'
157 '</ul>')
160 def allowSidebar(fun):
161 """Decorator that allows access if the sidebar is calling.
164 from functools import wraps
166 @wraps(fun)
167 def wrapper(self, django_args, *args, **kwargs):
168 if django_args.get('SIDEBAR_CALLING'):
169 return
170 return fun(self, django_args, *args, **kwargs)
171 return wrapper
174 def denySidebar(fun):
175 """Decorator that denies access if the sidebar is calling.
178 from functools import wraps
180 @wraps(fun)
181 def wrapper(self, django_args, *args, **kwargs):
182 if django_args.get('SIDEBAR_CALLING'):
183 raise out_of_band.Error("Sidebar Calling")
184 return fun(self, django_args, *args, **kwargs)
185 return wrapper
188 def allowIfCheckPasses(checker_name):
189 """Returns a decorator that allows access if the specified checker passes.
192 from functools import wraps
194 def decorator(fun):
195 """Decorator that allows access if the current user is a Developer.
198 @wraps(fun)
199 def wrapper(self, django_args=None, *args, **kwargs):
200 try:
201 # if the check passes we allow access regardless
202 return self.doCheck(checker_name, django_args, [])
203 except out_of_band.Error:
204 # otherwise we run the original check
205 return fun(self, django_args, *args, **kwargs)
206 return wrapper
208 return decorator
211 allowDeveloper = allowIfCheckPasses('checkIsDeveloper')
214 class Checker(object):
216 The __setitem__() and __getitem__() methods are overloaded to DTRT
217 when adding new access rights, and retrieving them, so use these
218 rather then modifying rights directly if so desired.
221 MEMBERSHIP = {
222 'anyone': 'allow',
223 'club_admin': ('checkHasActiveRoleForScope', club_admin_logic),
224 'club_member': ('checkHasActiveRoleForScope', club_member_logic),
225 'host': ('checkHasDocumentAccess', [host_logic, 'sponsor']),
226 'org_admin': ('checkHasDocumentAccess', [org_admin_logic, 'org']),
227 'org_mentor': ('checkHasDocumentAccess', [mentor_logic, 'org']),
228 'org_student': ('checkHasDocumentAccess', [student_logic, 'org']),
229 'user': 'checkIsUser',
230 'user_self': ('checkIsUserSelf', 'scope_path'),
233 #: the depths of various scopes to other scopes
234 # the 0 entries are not used, and are for clarity purposes only
235 SCOPE_DEPTH = {
236 'site': None,
237 'sponsor': (sponsor_logic, {'sponsor': 0}),
238 'program': (program_logic, {'sponsor': 1, 'program': 0}),
239 'org': (org_logic, {'sponsor': 2, 'program': 1, 'org': 0}),
242 def __init__(self, params):
243 """Adopts base.rights as rights if base is set.
246 base = params.get('rights') if params else None
247 self.rights = base.rights if base else {}
248 self.id = None
249 self.user = None
251 def normalizeChecker(self, checker):
252 """Normalizes the checker to a pre-defined format.
254 The result is guaranteed to be a list of 2-tuples, the first element is a
255 checker (iff there is an checker with the specified name), the second
256 element is a list of arguments that should be passed to the checker when
257 calling it in addition to the standard django_args.
260 # Be nice an repack so that it is always a list with tuples
261 if isinstance(checker, tuple):
262 name, arg = checker
263 return (name, (arg if isinstance(arg, list) else [arg]))
264 else:
265 return (checker, [])
267 def __setitem__(self, key, value):
268 """Sets a value only if no old value exists.
271 oldvalue = self.rights.get(key)
272 self.rights[key] = oldvalue if oldvalue else value
274 def __getitem__(self, key):
275 """Retrieves and normalizes the right checkers.
278 return [self.normalizeChecker(i) for i in self.rights.get(key, [])]
280 def key(self, checker_name):
281 """Returns the key for the specified checker for the current user.
284 return "%s.%s" % (self.id, checker_name)
286 def put(self, checker_name, value):
287 """Puts the result for the specified checker in the cache.
290 retention = 30
292 memcache_key = self.key(checker_name)
293 memcache.add(memcache_key, value, retention)
295 def get(self, checker_name):
296 """Retrieves the result for the specified checker from cache.
299 memcache_key = self.key(checker_name)
300 return memcache.get(memcache_key)
302 def doCheck(self, checker_name, django_args, args):
303 """Runs the specified checker with the specified arguments.
306 checker = getattr(self, checker_name)
307 checker(django_args, *args)
309 def doCachedCheck(self, checker_name, django_args, args):
310 """Retrieves from cache or runs the specified checker.
313 cached = self.get(checker_name)
315 if cached is None:
316 try:
317 self.doCheck(checker_name, django_args, args)
318 self.put(checker_name, True)
319 return
320 except out_of_band.Error, e:
321 self.put(checker_name, e)
322 raise
324 if cached is True:
325 return
327 # re-raise the cached exception
328 raise cached
330 def check(self, use_cache, checker_name, django_args, args):
331 """Runs the checker, optionally using the cache.
334 if use_cache:
335 self.doCachedCheck(checker_name, django_args, args)
336 else:
337 self.doCheck(checker_name, django_args, args)
339 def setCurrentUser(self, id, user):
340 """Sets up everything for the current user.
343 self.id = id
344 self.user = user
346 def checkAccess(self, access_type, django_args):
347 """Runs all the defined checks for the specified type.
349 Args:
350 access_type: the type of request (such as 'list' or 'edit')
351 rights: a dictionary containing access check functions
352 django_args: a dictionary with django's arguments
354 Rights usage:
355 The rights dictionary is used to check if the current user is allowed
356 to view the page specified. The functions defined in this dictionary
357 are always called with the provided django_args dictionary as argument. On any
358 request, regardless of what type, the functions in the 'any_access' value
359 are called. If the specified type is not in the rights dictionary, all
360 the functions in the 'unspecified' value are called. When the specified
361 type _is_ in the rights dictionary, all the functions in that access_type's
362 value are called.
365 use_cache = django_args.get('SIDEBAR_CALLING')
367 # Call each access checker
368 for checker_name, args in self['any_access']:
369 self.check(use_cache, checker_name, django_args, args)
371 if access_type not in self.rights:
372 # No checks defined, so do the 'generic' checks and bail out
373 for checker_name, args in self['unspecified']:
374 self.check(use_cache, checker_name, django_args, args)
375 return
377 for checker_name, args in self[access_type]:
378 self.check(use_cache, checker_name, django_args, args)
380 def hasMembership(self, roles, django_args):
381 """Checks whether the user has access to any of the specified roles.
383 Makes use of self.MEMBERSHIP, which defines checkers specific to
384 document access, as such this method should only be used when checking
385 document access.
387 Args:
388 roles: a list of roles to check
389 django_args: the django args that should be passed to doCheck
392 try:
393 # we need to check manually, as we must return True!
394 self.checkIsDeveloper(django_args)
395 return True
396 except out_of_band.Error:
397 pass
399 for role in roles:
400 try:
401 checker_name, args = self.normalizeChecker(self.MEMBERSHIP[role])
402 self.doCheck(checker_name, django_args, args)
403 # the check passed, we can stop now
404 return True
405 except out_of_band.Error:
406 continue
408 return False
410 @allowDeveloper
411 def checkMembership(self, action, prefix, status, django_args):
412 """Checks whether the user has access to the specified status.
414 Args:
415 action: the action that was performed (e.g., 'read')
416 prefix: the prefix, determines what access set is used
417 status: the access status (e.g., 'public')
418 django_args: the django args to pass on to the checkers
421 checker = rights_logic.Checker(prefix)
422 roles = checker.getMembership(status)
424 message_fmt = DEF_NEED_MEMBERSHIP_MSG_FMT % {
425 'action': action,
426 'prefix': prefix,
427 'status': status,
430 # try to see if they belong to any of the roles, if not, raise an
431 # access violation for the specified action, prefix and status.
432 if not self.hasMembership(roles, django_args):
433 raise out_of_band.AccessViolation(message_fmt)
435 def checkHasAny(self, django_args, checks):
436 """Checks if any of the checks passes.
438 If none of the specified checks passes, the exception that the first of the
439 checks raised is reraised.
442 first = None
444 for checker_name, args in checks:
445 try:
446 self.doCheck(checker_name, django_args, args)
447 # one check passed, all is well
448 return
449 except out_of_band.Error, e:
450 # store the first esception
451 first = first if first else e
453 # none passed, re-raise the first exception
454 raise first
456 def allow(self, django_args):
457 """Never raises an alternate HTTP response. (an access no-op, basically).
459 Args:
460 django_args: a dictionary with django's arguments
463 return
465 def deny(self, django_args=None):
466 """Always raises an alternate HTTP response.
468 Args:
469 django_args: a dictionary with django's arguments
471 Raises:
472 always raises AccessViolationResponse if called
475 context = django_args.get('context', {})
476 context['title'] = 'Access denied'
478 raise out_of_band.AccessViolation(DEF_PAGE_DENIED_MSG, context=context)
480 def checkIsLoggedIn(self, django_args=None):
481 """Raises an alternate HTTP response if Google Account is not logged in.
483 Args:
484 django_args: a dictionary with django's arguments, not used
486 Raises:
487 AccessViolationResponse:
488 * if no Google Account is even logged in
491 if self.id:
492 return
494 raise out_of_band.LoginRequest()
496 def checkNotLoggedIn(self, django_args=None):
497 """Raises an alternate HTTP response if Google Account is logged in.
499 Args:
500 django_args: a dictionary with django's arguments, not used
502 Raises:
503 AccessViolationResponse:
504 * if a Google Account is currently logged in
507 if not self.id:
508 return
510 raise out_of_band.LoginRequest(message_fmt=DEF_LOGOUT_MSG_FMT)
512 def checkIsUser(self, django_args=None):
513 """Raises an alternate HTTP response if Google Account has no User entity.
515 Args:
516 django_args: a dictionary with django's arguments, not used
518 Raises:
519 AccessViolationResponse:
520 * if no User exists for the logged-in Google Account, or
521 * if no Google Account is logged in at all
522 * if User has not agreed to the site-wide ToS, if one exists
525 self.checkIsLoggedIn()
527 if not self.user:
528 raise out_of_band.LoginRequest(message_fmt=DEF_NO_USER_LOGIN_MSG)
530 if user_logic.agreesToSiteToS(self.user):
531 return
533 # Would not reach this point of site-wide ToS did not exist, since
534 # agreesToSiteToS() call above always returns True if no ToS is in effect.
535 login_msg_fmt = DEF_AGREE_TO_TOS_MSG_FMT % {
536 'tos_link': redirects.getToSRedirect(site_logic.getSingleton())}
538 raise out_of_band.LoginRequest(message_fmt=login_msg_fmt)
540 @allowDeveloper
541 def checkIsHost(self, django_args=None):
542 """Checks whether the current user has a role entity.
544 Args:
545 django_args: the keyword args from django, not used
548 if not django_args:
549 django_args = {}
551 return self.checkHasActiveRole(django_args, host_logic)
553 @allowDeveloper
554 def checkIsUserSelf(self, django_args, field_name):
555 """Checks whether the specified user is the logged in user.
557 Args:
558 django_args: the keyword args from django, only field_name is used
561 self.checkIsUser()
563 if not field_name in django_args:
564 self.deny()
566 if self.user.link_id == django_args[field_name]:
567 return
569 raise out_of_band.AccessViolation(DEF_NOT_YOUR_ENTITY_MSG)
571 def checkIsUnusedAccount(self, django_args=None):
572 """Raises an alternate HTTP response if Google Account has a User entity.
574 Args:
575 django_args: a dictionary with django's arguments, not used
577 Raises:
578 AccessViolationResponse:
579 * if a User exists for the logged-in Google Account, or
580 * if a User has this Gooogle Account in their formerAccounts list
583 self.checkIsLoggedIn()
585 if not self.user and not user_logic.isFormerAccount(self.id):
586 # this account has not been used yet
587 return
589 message_fmt = DEF_USER_ACCOUNT_INVALID_MSG_FMT % {
590 'email' : self.id.email()
593 raise out_of_band.LoginRequest(message_fmt=message_fmt)
595 def checkHasUserEntity(self, django_args=None):
596 """Raises an alternate HTTP response if Google Account has no User entity.
598 Args:
599 django_args: a dictionary with django's arguments
601 Raises:
602 AccessViolationResponse:
603 * if no User exists for the logged-in Google Account, or
604 * if no Google Account is logged in at all
607 self.checkIsLoggedIn()
609 if self.user:
610 return
612 raise out_of_band.LoginRequest(message_fmt=DEF_NO_USER_LOGIN_MSG)
614 def checkIsDeveloper(self, django_args=None):
615 """Raises an alternate HTTP response if Google Account is not a Developer.
617 Args:
618 django_args: a dictionary with django's arguments, not used
620 Raises:
621 AccessViolationResponse:
622 * if User is not a Developer, or
623 * if no User exists for the logged-in Google Account, or
624 * if no Google Account is logged in at all
627 self.checkIsUser()
629 if user_logic.isDeveloper(account=self.id, user=self.user):
630 return
632 login_message_fmt = DEF_DEV_LOGOUT_LOGIN_MSG_FMT % {
633 'role': 'a Site Developer ',
636 raise out_of_band.LoginRequest(message_fmt=login_message_fmt)
638 @allowDeveloper
639 @denySidebar
640 def _checkIsActive(self, django_args, logic, fields):
641 """Raises an alternate HTTP response if the entity is not active.
643 Args:
644 django_args: a dictionary with django's arguments
645 logic: the logic that should be used to look up the entity
646 fields: the name of the fields that should be copied verbatim
647 from the django_args as filter
649 Raises:
650 AccessViolationResponse:
651 * if no entity is found
652 * if the entity status is not active
655 self.checkIsUser()
657 fields = dicts.filter(django_args, fields)
658 fields['status'] = 'active'
660 entity = logic.getForFields(fields, unique=True)
662 if entity:
663 return
665 raise out_of_band.AccessViolation(message_fmt=DEF_NO_ACTIVE_ENTITY_MSG)
667 def checkGroupIsActiveForScopeAndLinkId(self, django_args, logic):
668 """Checks that the specified group is active.
670 Only group where both the link_id and the scope_path match the value
671 of the link_id and the scope_path from the django_args are considered.
674 fields = ['scope_path', 'link_id']
675 self._checkIsActive(django_args, logic, fields)
677 def checkGroupIsActiveForLinkId(self, django_args, logic):
678 """Checks that the specified group is active.
680 Only group where the link_id matches the value of the link_id
681 from the django_args are considered.
684 self._checkIsActive(django_args, logic, ['link_id'])
686 def checkHasActiveRole(self, django_args, logic):
687 """Checks that the user has the specified active role.
690 django_args = django_args.copy()
691 django_args['user'] = self.user
692 self._checkIsActive(django_args, logic, ['user'])
694 def _checkHasActiveRoleFor(self, django_args, logic, field_name):
695 """Checks that the user has the specified active role.
697 Only roles where the field as specified by field_name matches the
698 scope_path from the django_args are considered.
701 fields = [field_name, 'user']
702 django_args = django_args.copy()
703 django_args['user'] = self.user
704 self._checkIsActive(django_args, logic, fields)
706 def checkHasActiveRoleForKeyFieldsAsScope(self, django_args, logic):
710 key_fields = "%(scope_path)s/%(link_id)s" % django_args
711 new_args = {'scope_path': key_fields}
712 self._checkHasActiveRoleFor(new_args, logic, 'scope_path')
714 def checkHasActiveRoleForScope(self, django_args, logic):
715 """Checks that the user has the specified active role.
717 Only roles where the scope_path matches the scope_path from the
718 django_args are considered.
721 self._checkHasActiveRoleFor(django_args, logic, 'scope_path')
723 def checkHasActiveRoleForLinkId(self, django_args, logic):
724 """Checks that the user has the specified active role.
726 Only roles where the link_id matches the link_id from the
727 django_args are considered.
730 self._checkHasActiveRoleFor(django_args, logic, 'link_id')
732 def checkHasActiveRoleForLinkIdAsScope(self, django_args, logic):
733 """Checks that the user has the specified active role.
735 Only roles where the scope_path matches the link_id from the
736 django_args are considered.
739 django_args = django_args.copy()
740 django_args['scope_path'] = django_args['link_id']
741 self._checkHasActiveRoleFor(django_args, logic, 'scope_path')
743 def checkHasDocumentAccess(self, django_args, logic, target_scope):
744 """Checks that the user has access to the specified document scope.
747 prefix = django_args['prefix']
748 if self.SCOPE_DEPTH.get(prefix):
749 scope_logic, depths = self.SCOPE_DEPTH[prefix]
750 else:
751 return self.checkHasActiveRole(django_args, logic)
753 depth = depths.get(target_scope, 0)
755 # nothing to do
756 if not (scope_logic and depth):
757 return self.checkHasActiveRoleForScope(django_args, logic)
759 # we don't want to modify the original django args
760 django_args = django_args.copy()
762 entity = scope_logic.getFromKeyName(django_args['scope_path'])
764 # cannot have access to the specified scope if it is invalid
765 if not entity:
766 raise out_of_band.AccessViolation(message_fmt=DEF_NO_ACTIVE_ENTITY_MSG)
768 # walk up the scope to where we need to be
769 for _ in range(depth):
770 entity = entity.scope
772 django_args['scope_path'] = entity.key().name()
774 self.checkHasActiveRoleForScope(django_args, logic)
776 def checkSeeded(self, django_args, checker_name, *args):
777 """Wrapper to update the django_args with the contens of seed first.
780 django_args.update(django_args.get('seed', {}))
781 self.doCheck(checker_name, django_args, args)
783 def checkCanMakeRequestToGroup(self, django_args, group_logic):
784 """Raises an alternate HTTP response if the specified group is not in an
785 active status.
787 Args:
788 group_logic: Logic module for the type of group which the request is for
791 self.checkIsUser(django_args)
793 group_entity = role_logic.getGroupEntityFromScopePath(
794 group_logic.logic, django_args['scope_path'])
796 if not group_entity:
797 raise out_of_band.Error(DEF_GROUP_NOT_FOUND_MSG, status=404)
799 if group_entity.status != 'active':
800 # tell the user that this group is not active
801 raise out_of_band.AccessViolation(message_fmt=DEF_NO_ACTIVE_GROUP_MSG)
803 return
805 def checkCanCreateFromRequest(self, django_args, role_name):
806 """Raises an alternate HTTP response if the specified request does not exist
807 or if it's status is not group_accepted. Also when the group this request
808 is from is in an inactive or invalid status access will be denied.
811 self.checkIsUserSelf(django_args, 'link_id')
813 fields = {
814 'link_id': django_args['link_id'],
815 'scope_path': django_args['scope_path'],
816 'role': role_name,
817 'status': 'group_accepted',
820 entity = request_logic.getForFields(fields, unique=True)
822 if entity and (entity.scope.status not in ['invalid', 'inactive']):
823 return
825 raise out_of_band.AccessViolation(message_fmt=DEF_NO_REQUEST_MSG)
827 def checkIsMyGroupAcceptedRequest(self, django_args):
828 """Checks whether the user can accept the specified request.
831 self.checkCanCreateFromRequest(django_args, django_args['role'])
833 def checkCanProcessRequest(self, django_args, role_name):
834 """Raises an alternate HTTP response if the specified request does not exist
835 or if it's status is completed or denied. Also Raises an alternate HTTP response
836 whenever the group in the request is not active.
839 self.checkIsUser(django_args)
841 fields = {
842 'link_id': django_args['link_id'],
843 'scope_path': django_args['scope_path'],
844 'role': role_name,
847 request_entity = request_logic.getFromKeyFieldsOr404(fields)
849 if request_entity.status in ['completed', 'denied']:
850 raise out_of_band.AccessViolation(message_fmt=DEF_REQUEST_COMPLETED_MSG)
852 if request_entity.scope.status == 'active':
853 return
855 raise out_of_band.AccessViolation(message_fmt=DEF_SCOPE_INACTIVE_MSG)
857 @allowDeveloper
858 @denySidebar
859 def checkIsHostForProgram(self, django_args):
860 """Checks if the user is a host for the specified program.
863 program = program_logic.getFromKeyFields(django_args)
865 if not program or program.status == 'invalid':
866 raise out_of_band.AccessViolation(message_fmt=DEF_NO_ACTIVE_PROGRAM_MSG)
868 new_args = {'scope_path': program.scope_path }
869 self.checkHasActiveRoleForScope(new_args, host_logic)
871 @allowDeveloper
872 @denySidebar
873 def checkIsHostForProgramInScope(self, django_args):
874 """Checks if the user is a host for the specified program.
877 program = program_logic.getFromKeyName(django_args['scope_path'])
879 if not program or program.status == 'invalid':
880 raise out_of_band.AccessViolation(message_fmt=DEF_NO_ACTIVE_PROGRAM_MSG)
882 django_args = {'scope_path': program.scope_path}
883 self.checkHasActiveRoleForScope(django_args, host_logic)
885 @allowDeveloper
886 @denySidebar
887 def checkIsActivePeriod(self, django_args, period_name, key_name_arg):
888 """Checks if the given period is active for the given program.
890 Args:
891 django_args: a dictionary with django's arguments.
892 period_name: the name of the period which is checked.
893 key_name_arg: the entry in django_args that specifies the given program
894 keyname. If none is given the key_name is constructed from django_args
895 itself.
897 Raises:
898 AccessViolationResponse:
899 * if no active Program is found
900 * if the period is not active
903 if key_name_arg and key_name_arg in django_args:
904 key_name = django_args[key_name_arg]
905 else:
906 key_name = program_logic.getKeyNameFromFields(django_args)
908 program_entity = program_logic.getFromKeyName(key_name)
910 if not program_entity or (
911 program_entity.status in ['inactive', 'invalid']):
912 raise out_of_band.AccessViolation(message_fmt=DEF_SCOPE_INACTIVE_MSG)
914 if timeline_helper.isActivePeriod(program_entity.timeline, period_name):
915 return
917 raise out_of_band.AccessViolation(message_fmt=DEF_PAGE_INACTIVE_MSG)
919 def checkCanCreateOrgApp(self, django_args, period_name):
920 """Checks to see if the program in the scope_path is accepting org apps
923 if 'seed' in django_args:
924 return self.checkIsActivePeriod(django_args['seed'],
925 period_name, 'scope_path')
926 else:
927 return
929 @allowDeveloper
930 def checkCanEditGroupApp(self, django_args, group_app_logic):
931 """Checks if the group_app in args is valid to be edited by the current user.
933 Args:
934 group_app_logic: A logic instance for the Group Application
937 self.checkIsUser(django_args)
939 fields = {
940 'link_id': django_args['link_id'],
941 'applicant': self.user,
942 'status' : ['needs review', 'rejected']
945 if 'scope_path' in django_args:
946 fields['scope_path'] = django_args['scope_path']
948 entity = group_app_logic.getForFields(fields)
950 if entity:
951 return
953 raise out_of_band.AccessViolation(message_fmt=DEF_NOT_YOUR_ENTITY_MSG)
955 @allowSidebar
956 def checkCanReviewGroupApp(self, django_args, group_app_logic):
957 """Checks if the group_app in args is valid to be reviewed.
959 Args:
960 group_app_logic: A logic instance for the Group Application
963 if 'link_id' not in django_args:
964 # calling review overview, so we can't check a specified entity
965 return
967 fields = {
968 'link_id': django_args['link_id'],
969 'status': ['needs review', 'accepted', 'rejected', 'ignored',
970 'pre-accepted', 'pre-rejected']
973 if 'scope_path' in django_args:
974 fields['scope_path'] = django_args['scope_path']
976 entity = group_app_logic.getForFields(fields)
978 if entity:
979 return
981 raise out_of_band.AccessViolation(message_fmt=DEF_REVIEW_COMPLETED_MSG)
983 @allowDeveloper
984 def checkIsApplicationAccepted(self, django_args, app_logic):
985 """Returns an alternate HTTP response if Google Account has no Club App
986 entity for the specified Club.
988 Args:
989 django_args: a dictionary with django's arguments
991 Raises:
992 AccessViolationResponse: if the required authorization is not met
994 Returns:
995 None if Club App exists for the specified program, or a subclass
996 of django.http.HttpResponse which contains the alternate response
997 should be returned by the calling view.
1000 self.checkIsUser(django_args)
1002 properties = {
1003 'applicant': self.user,
1004 'status': 'accepted'
1007 application = app_logic.getForFields(properties, unique=True)
1009 if application:
1010 return
1012 raise out_of_band.AccessViolation(message_fmt=DEF_NO_APPLICATION_MSG)
1014 def checkIsNotParticipatingInProgramInScope(self, django_args):
1015 """Checks if the current user has no roles for the given program in django_args.
1017 Args:
1018 django_args: a dictionary with django's arguments
1020 Raises:
1021 AccessViolationResponse: if the current user has a student, mentor or
1022 org admin role for the given program.
1025 if not django_args.get('scope_path'):
1026 raise out_of_band.AccessViolation(message_fmt=DEF_PAGE_DENIED_MSG)
1028 program_entity = program_logic.getFromKeyName(django_args['scope_path'])
1029 user_entity = user_logic.getForCurrentAccount()
1031 filter = {'user': user_entity,
1032 'scope': program_entity,
1033 'status': 'active'}
1035 # check if the current user is already a student for this program
1036 student_role = student_logic.getForFields(filter, unique=True)
1038 if student_role:
1039 raise out_of_band.AccessViolation(
1040 message_fmt=DEF_ALREADY_PARTICIPATING_MSG)
1042 # fill the role_list with all the mentor and org admin roles for this user
1043 role_list = []
1045 filter = {'user': user_entity,
1046 'status': 'active'}
1048 mentor_roles = mentor_logic.getForFields(filter)
1049 if mentor_roles:
1050 role_list += mentor_roles
1052 org_admin_roles = org_admin_logic.getForFields(filter)
1053 if org_admin_roles:
1054 role_list += org_admin_roles
1056 # check if the user has a role for the retrieved program
1057 for role in role_list:
1059 if role.program.key() == program_entity.key():
1060 # the current user has a role for the given program
1061 raise out_of_band.AccessViolation(
1062 message_fmt=DEF_ALREADY_PARTICIPATING_MSG)
1064 # no roles found, access granted
1065 return
1067 def checkIsNotStudentForProgramInScope(self, django_args):
1068 """Checks if the current user is not a student for the given
1069 program in django_args.
1071 Args:
1072 django_args: a dictionary with django's arguments
1074 Raises:
1075 AccessViolationResponse: if the current user has a student
1076 role for the given program.
1079 if django_args.get('seed'):
1080 key_name = django_args['seed']['scope_path']
1081 else:
1082 key_name = django_args['scope_path']
1084 program_entity = program_logic.getFromKeyName(key_name)
1085 user_entity = user_logic.getForCurrentAccount()
1087 filter = {'user': user_entity,
1088 'scope': program_entity,
1089 'status': 'active'}
1091 # check if the current user is already a student for this program
1092 student_role = student_logic.getForFields(filter, unique=True)
1094 if student_role:
1095 raise out_of_band.AccessViolation(
1096 message_fmt=DEF_ALREADY_PARTICIPATING_MSG)
1098 return
1100 def checkIsNotStudentForProgramOfOrg(self, django_args):
1101 """Checks if the current user has no active Student role for the program
1102 that the organization in the scope_path is participating in.
1104 Args:
1105 django_args: a dictionary with django's arguments
1107 Raises:
1108 AccessViolationResponse: if the current user is a student for the
1109 program the organization is in.
1112 if not django_args.get('scope_path'):
1113 raise out_of_band.AccessViolation(message_fmt=DEF_PAGE_DENIED_MSG)
1115 org_entity = org_logic.getFromKeyName(django_args['scope_path'])
1116 user_entity = user_logic.getForCurrentAccount()
1118 filter = {'scope': org_entity.scope,
1119 'user': user_entity,
1120 'status': 'active'}
1122 student_role = student_logic.getForFields(filter=filter, unique=True)
1124 if student_role:
1125 raise out_of_band.AccessViolation(
1126 message_fmt=DEF_ALREADY_STUDENT_ROLE_MSG)
1128 return
1130 @allowDeveloper
1131 def checkRoleAndStatusForStudentProposal(self, django_args, allowed_roles,
1132 role_status, proposal_status):
1133 """Checks if the current user has access to the given proposal.
1135 Args:
1136 django_args: a dictionary with django's arguments
1137 allowed_roles: list with names for the roles allowed to pass access check
1138 role_status: list with states allowed for the role
1139 proposal_status: a list with states allowed for the proposal
1141 Raises:
1142 AccessViolationResponse:
1143 - If there is no proposal found
1144 - If the proposal is not in one of the required states.
1145 - If the user does not have any ofe the required roles
1148 self.checkIsUser(django_args)
1150 # bail out with 404 if no proposal is found
1151 proposal_entity = student_proposal_logic.getFromKeyFieldsOr404(django_args)
1153 if not proposal_entity.status in proposal_status:
1154 # this proposal can not be accessed at the moment
1155 raise out_of_band.AccessViolation(
1156 message_fmt=DEF_NO_ACTIVE_ENTITY_MSG)
1158 user_entity = self.user
1160 if 'proposer' in allowed_roles:
1161 # check if this proposal belongs to the current user
1162 student_entity = proposal_entity.scope
1163 if (user_entity.key() == student_entity.user.key()) and (
1164 student_entity.status in role_status):
1165 return
1167 filter = {'user': user_entity,
1168 'status': role_status}
1170 if 'host' in allowed_roles:
1171 # check if the current user is a host for this proposal's program
1172 filter['scope'] = proposal_entity.program
1174 if host_logic.getForFields(filter, unique=True):
1175 return
1177 if 'org_admin' in allowed_roles:
1178 # check if the current user is an admin for this proposal's org
1179 filter['scope'] = proposal_entity.org
1181 if org_admin_logic.getForFields(filter, unique=True):
1182 return
1184 if 'mentor' in allowed_roles:
1185 # check if the current user is a mentor for this proposal's org
1186 filter['scope'] = proposal_entity.org
1188 if mentor_logic.getForFields(filter, unique=True):
1189 return
1191 # no roles found, access denied
1192 raise out_of_band.AccessViolation(
1193 message_fmt=DEF_NEED_ROLE_MSG)
1195 @allowDeveloper
1196 def checkCanStudentPropose(self, django_args, key_location):
1197 """Checks if the program for this student accepts proposals.
1199 Args:
1200 django_args: a dictionary with django's arguments
1201 key_location: the key for django_args in which the key_name
1202 from the student is stored
1205 self.checkIsUser(django_args)
1207 if django_args.get('seed'):
1208 key_name = django_args['seed'][key_location]
1209 else:
1210 key_name = django_args[key_location]
1212 student_entity = student_logic.getFromKeyName(key_name)
1214 if not student_entity or student_entity.status == 'invalid':
1215 raise out_of_band.AccessViolation(
1216 message_fmt=DEF_SIGN_UP_AS_STUDENT_MSG)
1218 program_entity = student_entity.scope
1220 if not timeline_helper.isActivePeriod(program_entity.timeline,
1221 'student_signup'):
1222 raise out_of_band.AccessViolation(message_fmt=DEF_PAGE_INACTIVE_MSG)
1224 return
1226 @allowDeveloper
1227 def checkIsStudent(self, django_args, key_location, status):
1228 """Checks if the current user is the given student.
1230 Args:
1231 django_args: a dictionary with django's arguments
1232 key_location: the key for django_args in which the key_name
1233 from the student is stored
1234 status: the allowed status for the student
1237 self.checkIsUser(django_args)
1239 if 'seed' in django_args:
1240 key_name = django_args['seed'][key_location]
1241 else:
1242 key_name = django_args[key_location]
1244 student_entity = student_logic.getFromKeyName(key_name)
1246 if not student_entity or student_entity.status not in status:
1247 raise out_of_band.AccessViolation(
1248 message_fmt=DEF_SIGN_UP_AS_STUDENT_MSG)
1250 if student_entity.user.key() != self.user.key():
1251 # this is not the page for the current user
1252 self.deny(django_args)
1254 return
1256 @allowDeveloper
1257 def checkIsMyEntity(self, django_args, logic,
1258 field_name='user', user=False):
1259 """Checks whether the entity belongs to the user.
1261 Args:
1262 logic: the logic that should be used to fetch the entity
1263 field_name: the name of the field the entity uses to store it's owner
1264 user: whether the entity stores the user's key name, or a reference
1267 self.checkIsUser(django_args)
1269 fields = {
1270 'link_id': django_args['link_id'],
1271 field_name: self.user if user else self.user.key().name()
1274 if 'scope_path' in django_args:
1275 fields['scope_path'] = django_args['scope_path']
1277 entity = logic.getForFields(fields)
1279 if entity:
1280 return
1282 raise out_of_band.AccessViolation(message_fmt=DEF_NOT_YOUR_ENTITY_MSG)
1284 @allowDeveloper
1285 @denySidebar
1286 def checkIsAllowedToManageRole(self, django_args, role_logic, manage_role_logic):
1287 """Returns an alternate HTTP response if the user is not allowed to manage
1288 the role given in args.
1290 Args:
1291 role_logic: determines the logic for the role in args.
1292 manage_role_logic: determines the logic for the role which is allowed
1293 to manage this role.
1295 Raises:
1296 AccessViolationResponse: if the required authorization is not met
1298 Returns:
1299 None if the given role is active and belongs to the current user.
1300 None if the current User has an active role (from manage_role_logic)
1301 that belongs to the same scope as the role that needs to be managed
1304 try:
1305 # check if it is my role the user's own role
1306 self.checkHasActiveRoleForScope(django_args, role_logic)
1307 except out_of_band.Error:
1308 pass
1310 # apparently it's not the user's role so check if managing this role is allowed
1311 fields = {
1312 'link_id': django_args['link_id'],
1313 'scope_path': django_args['scope_path'],
1316 role_entity = role_logic.getFromKeyFieldsOr404(fields)
1317 if role_entity.status != 'active':
1318 raise out_of_band.AccessViolation(message_fmt=DEF_NO_ACTIVE_ROLE_MSG)
1320 fields = {
1321 'link_id': self.user.link_id,
1322 'scope_path': django_args['scope_path'],
1323 'status': 'active'
1326 manage_entity = manage_role_logic.getForFields(fields, unique=True)
1328 if not manage_entity:
1329 raise out_of_band.AccessViolation(message_fmt=DEF_NOT_YOUR_ENTITY_MSG)
1331 return
1333 @allowSidebar
1334 @allowDeveloper
1335 def checkIsDocumentReadable(self, django_args, key_name_field=None):
1336 """Checks whether a document is readable.
1338 Args:
1339 django_args: a dictionary with django's arguments
1342 if key_name_field:
1343 key_name = django_args[key_name_field]
1344 document = document_logic.getFromKeyName(key_name)
1345 else:
1346 document = document_logic.getFromKeyFieldsOr404(django_args)
1348 self.checkMembership('read', document.prefix,
1349 document.read_access, django_args)
1351 @denySidebar
1352 @allowDeveloper
1353 def checkIsDocumentWritable(self, django_args, key_name_field=None):
1354 """Checks whether a document is writable.
1356 Args:
1357 django_args: a dictionary with django's arguments
1360 if key_name_field:
1361 key_name = django_args[key_name_field]
1362 document = document_logic.getFromKeyName(key_name)
1363 else:
1364 document = document_logic.getFromKeyFieldsOr404(django_args)
1366 self.checkMembership('write', document.prefix,
1367 document.write_access, django_args)
1369 @allowDeveloper
1370 def checkDocumentList(self, django_args):
1371 """Checks whether the user is allowed to list documents.
1374 filter = django_args['filter']
1376 prefix = filter['prefix']
1377 scope_path = filter['scope_path']
1379 checker = rights_logic.Checker(prefix)
1380 roles = checker.getMembership('list')
1382 if not self.hasMembership(roles, filter):
1383 raise out_of_band.AccessViolation(message_fmt=DEF_NO_LIST_ACCESS_MSG)
1385 @allowDeveloper
1386 def checkDocumentPick(self, django_args):
1387 """Checks whether the user has access to the specified pick url.
1389 Will update the 'read_access' field of django_args['GET'].
1392 get_args = django_args['GET']
1394 # make mutable in order to inject the proper read_access filter
1395 mutable = get_args._mutable
1396 get_args._mutable = True
1398 if 'prefix' not in get_args:
1399 raise out_of_band.AccessViolation(message_fmt=DEF_PREFIX_NOT_IN_ARGS_MSG)
1401 prefix = get_args['prefix']
1402 django_args['prefix'] = prefix
1404 checker = rights_logic.Checker(prefix)
1405 memberships = checker.getMemberships()
1407 roles = []
1408 for key, value in memberships.iteritems():
1409 if self.hasMembership(value, django_args):
1410 roles.append(key)
1412 if not roles:
1413 roles = ['deny']
1415 get_args.setlist('read_access', roles)
1416 get_args._mutable = mutable
1418 def checkCanEditTimeline(self, django_args):
1419 """Checks whether this program's timeline may be edited.
1422 time_line_keyname = timeline_logic.getKeyFieldsFromFields(django_args)
1423 timeline_entity = timeline_logic.getFromKeyName(time_line_keyname)
1425 if not timeline_entity:
1426 # timeline does not exists so deny
1427 self.deny(django_args)
1429 fields = program_logic.getKeyFieldsFromFields(django_args)
1430 self.checkIsHostForProgram(fields)