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.
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 '
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:'
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>'
160 def allowSidebar(fun
):
161 """Decorator that allows access if the sidebar is calling.
164 from functools
import wraps
167 def wrapper(self
, django_args
, *args
, **kwargs
):
168 if django_args
.get('SIDEBAR_CALLING'):
170 return fun(self
, django_args
, *args
, **kwargs
)
174 def denySidebar(fun
):
175 """Decorator that denies access if the sidebar is calling.
178 from functools
import wraps
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
)
188 def allowIfCheckPasses(checker_name
):
189 """Returns a decorator that allows access if the specified checker passes.
192 from functools
import wraps
195 """Decorator that allows access if the current user is a Developer.
199 def wrapper(self
, django_args
=None, *args
, **kwargs
):
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
)
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.
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
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 {}
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):
263 return (name
, (arg
if isinstance(arg
, list) else [arg
]))
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.
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
)
317 self
.doCheck(checker_name
, django_args
, args
)
318 self
.put(checker_name
, True)
320 except out_of_band
.Error
, e
:
321 self
.put(checker_name
, e
)
327 # re-raise the cached exception
330 def check(self
, use_cache
, checker_name
, django_args
, args
):
331 """Runs the checker, optionally using the cache.
335 self
.doCachedCheck(checker_name
, django_args
, args
)
337 self
.doCheck(checker_name
, django_args
, args
)
339 def setCurrentUser(self
, id, user
):
340 """Sets up everything for the current user.
346 def checkAccess(self
, access_type
, django_args
):
347 """Runs all the defined checks for the specified type.
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
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
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
)
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
388 roles: a list of roles to check
389 django_args: the django args that should be passed to doCheck
393 # we need to check manually, as we must return True!
394 self
.checkIsDeveloper(django_args
)
396 except out_of_band
.Error
:
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
405 except out_of_band
.Error
:
411 def checkMembership(self
, action
, prefix
, status
, django_args
):
412 """Checks whether the user has access to the specified status.
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
% {
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.
444 for checker_name
, args
in checks
:
446 self
.doCheck(checker_name
, django_args
, args
)
447 # one check passed, all is well
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
456 def allow(self
, django_args
):
457 """Never raises an alternate HTTP response. (an access no-op, basically).
460 django_args: a dictionary with django's arguments
465 def deny(self
, django_args
=None):
466 """Always raises an alternate HTTP response.
469 django_args: a dictionary with django's arguments
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.
484 django_args: a dictionary with django's arguments, not used
487 AccessViolationResponse:
488 * if no Google Account is even logged in
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.
500 django_args: a dictionary with django's arguments, not used
503 AccessViolationResponse:
504 * if a Google Account is currently logged in
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.
516 django_args: a dictionary with django's arguments, not used
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()
528 raise out_of_band
.LoginRequest(message_fmt
=DEF_NO_USER_LOGIN_MSG
)
530 if user_logic
.agreesToSiteToS(self
.user
):
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
)
541 def checkIsHost(self
, django_args
=None):
542 """Checks whether the current user has a role entity.
545 django_args: the keyword args from django, not used
551 return self
.checkHasActiveRole(django_args
, host_logic
)
554 def checkIsUserSelf(self
, django_args
, field_name
):
555 """Checks whether the specified user is the logged in user.
558 django_args: the keyword args from django, only field_name is used
563 if not field_name
in django_args
:
566 if self
.user
.link_id
== django_args
[field_name
]:
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.
575 django_args: a dictionary with django's arguments, not used
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
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.
599 django_args: a dictionary with django's arguments
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()
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.
618 django_args: a dictionary with django's arguments, not used
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
629 if user_logic
.isDeveloper(account
=self
.id, user
=self
.user
):
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
)
640 def _checkIsActive(self
, django_args
, logic
, fields
):
641 """Raises an alternate HTTP response if the entity is not active.
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
650 AccessViolationResponse:
651 * if no entity is found
652 * if the entity status is not active
657 fields
= dicts
.filter(django_args
, fields
)
658 fields
['status'] = 'active'
660 entity
= logic
.getForFields(fields
, unique
=True)
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
]
751 return self
.checkHasActiveRole(django_args
, logic
)
753 depth
= depths
.get(target_scope
, 0)
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
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
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'])
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
)
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')
814 'link_id': django_args
['link_id'],
815 'scope_path': django_args
['scope_path'],
817 'status': 'group_accepted',
820 entity
= request_logic
.getForFields(fields
, unique
=True)
822 if entity
and (entity
.scope
.status
not in ['invalid', 'inactive']):
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
)
842 'link_id': django_args
['link_id'],
843 'scope_path': django_args
['scope_path'],
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':
855 raise out_of_band
.AccessViolation(message_fmt
=DEF_SCOPE_INACTIVE_MSG
)
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
)
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
)
887 def checkIsActivePeriod(self
, django_args
, period_name
, key_name_arg
):
888 """Checks if the given period is active for the given program.
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
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
]
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
):
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')
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.
934 group_app_logic: A logic instance for the Group Application
937 self
.checkIsUser(django_args
)
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
)
953 raise out_of_band
.AccessViolation(message_fmt
=DEF_NOT_YOUR_ENTITY_MSG
)
956 def checkCanReviewGroupApp(self
, django_args
, group_app_logic
):
957 """Checks if the group_app in args is valid to be reviewed.
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
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
)
981 raise out_of_band
.AccessViolation(message_fmt
=DEF_REVIEW_COMPLETED_MSG
)
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.
989 django_args: a dictionary with django's arguments
992 AccessViolationResponse: if the required authorization is not met
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
)
1003 'applicant': self
.user
,
1004 'status': 'accepted'
1007 application
= app_logic
.getForFields(properties
, unique
=True)
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.
1018 django_args: a dictionary with django's arguments
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
,
1035 # check if the current user is already a student for this program
1036 student_role
= student_logic
.getForFields(filter, unique
=True)
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
1045 filter = {'user': user_entity
,
1048 mentor_roles
= mentor_logic
.getForFields(filter)
1050 role_list
+= mentor_roles
1052 org_admin_roles
= org_admin_logic
.getForFields(filter)
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
1067 def checkIsNotStudentForProgramInScope(self
, django_args
):
1068 """Checks if the current user is not a student for the given
1069 program in django_args.
1072 django_args: a dictionary with django's arguments
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']
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
,
1091 # check if the current user is already a student for this program
1092 student_role
= student_logic
.getForFields(filter, unique
=True)
1095 raise out_of_band
.AccessViolation(
1096 message_fmt
=DEF_ALREADY_PARTICIPATING_MSG
)
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.
1105 django_args: a dictionary with django's arguments
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
,
1122 student_role
= student_logic
.getForFields(filter=filter, unique
=True)
1125 raise out_of_band
.AccessViolation(
1126 message_fmt
=DEF_ALREADY_STUDENT_ROLE_MSG
)
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.
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
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
):
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):
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):
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):
1191 # no roles found, access denied
1192 raise out_of_band
.AccessViolation(
1193 message_fmt
=DEF_NEED_ROLE_MSG
)
1196 def checkCanStudentPropose(self
, django_args
, key_location
):
1197 """Checks if the program for this student accepts proposals.
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
]
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
,
1222 raise out_of_band
.AccessViolation(message_fmt
=DEF_PAGE_INACTIVE_MSG
)
1227 def checkIsStudent(self
, django_args
, key_location
, status
):
1228 """Checks if the current user is the given student.
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
]
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
)
1257 def checkIsMyEntity(self
, django_args
, logic
,
1258 field_name
='user', user
=False):
1259 """Checks whether the entity belongs to the user.
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
)
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
)
1282 raise out_of_band
.AccessViolation(message_fmt
=DEF_NOT_YOUR_ENTITY_MSG
)
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.
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.
1296 AccessViolationResponse: if the required authorization is not met
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
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
:
1310 # apparently it's not the user's role so check if managing this role is allowed
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
)
1321 'link_id': self
.user
.link_id
,
1322 'scope_path': django_args
['scope_path'],
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
)
1335 def checkIsDocumentReadable(self
, django_args
, key_name_field
=None):
1336 """Checks whether a document is readable.
1339 django_args: a dictionary with django's arguments
1343 key_name
= django_args
[key_name_field
]
1344 document
= document_logic
.getFromKeyName(key_name
)
1346 document
= document_logic
.getFromKeyFieldsOr404(django_args
)
1348 self
.checkMembership('read', document
.prefix
,
1349 document
.read_access
, django_args
)
1353 def checkIsDocumentWritable(self
, django_args
, key_name_field
=None):
1354 """Checks whether a document is writable.
1357 django_args: a dictionary with django's arguments
1361 key_name
= django_args
[key_name_field
]
1362 document
= document_logic
.getFromKeyName(key_name
)
1364 document
= document_logic
.getFromKeyFieldsOr404(django_args
)
1366 self
.checkMembership('write', document
.prefix
,
1367 document
.write_access
, django_args
)
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
)
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()
1408 for key
, value
in memberships
.iteritems():
1409 if self
.hasMembership(value
, django_args
):
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
)