Do not rely on a backup_admin being set
[Melange.git] / app / soc / views / helper / access.py
blobedc76d84c00ad0fd890ed8c913acd69028a12584
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 memcache
36 from django.utils.translation import ugettext
38 from soc.logic import dicts
39 from soc.logic import rights as rights_logic
40 from soc.logic.helper import timeline as timeline_helper
41 from soc.logic.models.club_admin import logic as club_admin_logic
42 from soc.logic.models.club_member import logic as club_member_logic
43 from soc.logic.models.document import logic as document_logic
44 from soc.logic.models.host import logic as host_logic
45 from soc.logic.models.mentor import logic as mentor_logic
46 from soc.logic.models.org_admin import logic as org_admin_logic
47 from soc.logic.models.organization import logic as org_logic
48 from soc.logic.models.program import logic as program_logic
49 from soc.logic.models.request import logic as request_logic
50 from soc.logic.models.role import logic as role_logic
51 from soc.logic.models.site import logic as site_logic
52 from soc.logic.models.sponsor import logic as sponsor_logic
53 from soc.logic.models.student import logic as student_logic
54 from soc.logic.models.student_project import logic as student_project_logic
55 from soc.logic.models.student_proposal import logic as student_proposal_logic
56 from soc.logic.models.timeline import logic as timeline_logic
57 from soc.logic.models.user import logic as user_logic
58 from soc.views.helper import redirects
59 from soc.views import out_of_band
62 DEF_NO_USER_LOGIN_MSG = ugettext(
63 'Please create <a href="/user/create_profile">User Profile</a>'
64 ' in order to view this page.')
66 DEF_AGREE_TO_TOS_MSG_FMT = ugettext(
67 'You must agree to the <a href="%(tos_link)s">site-wide Terms of'
68 ' Service</a> in your <a href="/user/edit_profile">User Profile</a>'
69 ' in order to view this page.')
71 DEF_DEV_LOGOUT_LOGIN_MSG_FMT = ugettext(
72 'Please <a href="%%(sign_out)s">sign out</a>'
73 ' and <a href="%%(sign_in)s">sign in</a>'
74 ' again as %(role)s to view this page.')
76 DEF_NEED_MEMBERSHIP_MSG_FMT = ugettext(
77 'You need to be in the %(status)s group to %(action)s'
78 ' documents in the %(prefix)s prefix.')
80 DEF_NEED_ROLE_MSG = ugettext(
81 'You do not have the required role.')
83 DEF_NOT_YOUR_ENTITY_MSG = ugettext(
84 'This entity does not belong to you.')
86 DEF_NO_ACTIVE_ENTITY_MSG = ugettext(
87 'There is no such active entity.')
89 DEF_NO_ACTIVE_GROUP_MSG = ugettext(
90 'There is no such active group.')
92 DEF_NO_ACTIVE_ROLE_MSG = ugettext(
93 'There is no such active role.')
95 DEF_ALREADY_PARTICIPATING_MSG = ugettext(
96 'You cannot become a Student because you are already participating '
97 'in this program.')
99 DEF_ALREADY_STUDENT_ROLE_MSG = ugettext(
100 'You cannot become a Mentor or Organization Admin because you already are '
101 'a Student in this program.')
103 DEF_NO_ACTIVE_PROGRAM_MSG = ugettext(
104 'There is no such active program.')
106 DEF_NO_REQUEST_MSG = ugettext(
107 'There is no accepted request that would allow you to visit this page. '
108 'Perhaps you already accepted this request?')
110 DEF_NO_APPLICATION_MSG = ugettext(
111 'There is no application that would allow you to visit this page.')
113 DEF_NEED_PICK_ARGS_MSG = ugettext(
114 'The "continue" and "field" args are not both present.')
116 DEF_REVIEW_COMPLETED_MSG = ugettext('This Application can not be reviewed '
117 'anymore (it has been completed or rejected).')
119 DEF_REQUEST_COMPLETED_MSG = ugettext(
120 'This request cannot be accepted (it is either completed or denied).')
122 DEF_SCOPE_INACTIVE_MSG = ugettext(
123 'The scope for this request is not active.')
125 DEF_SIGN_UP_AS_STUDENT_MSG = ugettext(
126 'You need to sign up as a Student first.')
128 DEF_MAX_PROPOSALS_REACHED = ugettext(
129 'You have reached the maximum number of Proposals allowed '
130 'for this program.')
132 DEF_NO_LIST_ACCESS_MSG = ugettext('You do not have the required rights to '
133 'list documents for this scope and prefix.')
135 DEF_PAGE_DENIED_MSG = ugettext(
136 'Access to this page has been restricted.')
138 DEF_PREFIX_NOT_IN_ARGS_MSG = ugettext(
139 'A required GET url argument ("prefix") was not specified.')
141 DEF_PAGE_INACTIVE_MSG = ugettext(
142 'This page is inactive at this time.')
144 DEF_LOGOUT_MSG_FMT = ugettext(
145 'Please <a href="%(sign_out)s">sign out</a> in order to view this page.')
147 DEF_GROUP_NOT_FOUND_MSG = ugettext(
148 'The requested Group can not be found.')
150 DEF_USER_ACCOUNT_INVALID_MSG_FMT = ugettext(
151 'The <b><i>%(email)s</i></b> account cannot be used with this site, for'
152 ' one or more of the following reasons:'
153 '<ul>'
154 ' <li>the account is invalid</li>'
155 ' <li>the account is already attached to a User profile and cannot be'
156 ' used to create another one</li>'
157 ' <li>the account is a former account that cannot be used again</li>'
158 '</ul>')
161 def allowSidebar(fun):
162 """Decorator that allows access if the sidebar is calling.
165 from functools import wraps
167 @wraps(fun)
168 def wrapper(self, django_args, *args, **kwargs):
169 """Decorator wrapper method.
171 if django_args.get('SIDEBAR_CALLING'):
172 return
173 return fun(self, django_args, *args, **kwargs)
174 return wrapper
177 def denySidebar(fun):
178 """Decorator that denies access if the sidebar is calling.
181 from functools import wraps
183 @wraps(fun)
184 def wrapper(self, django_args, *args, **kwargs):
185 """Decorator wrapper method.
187 if django_args.get('SIDEBAR_CALLING'):
188 raise out_of_band.Error("Sidebar Calling")
189 return fun(self, django_args, *args, **kwargs)
190 return wrapper
193 def allowIfCheckPasses(checker_name):
194 """Returns a decorator that allows access if the specified checker passes.
197 from functools import wraps
199 def decorator(fun):
200 """Decorator that allows access if the current user is a Developer.
203 @wraps(fun)
204 def wrapper(self, django_args=None, *args, **kwargs):
205 """Decorator wrapper method.
207 try:
208 # if the check passes we allow access regardless
209 return self.doCheck(checker_name, django_args, [])
210 except out_of_band.Error:
211 # otherwise we run the original check
212 return fun(self, django_args, *args, **kwargs)
213 return wrapper
215 return decorator
217 # pylint: disable-msg=C0103
218 allowDeveloper = allowIfCheckPasses('checkIsDeveloper')
221 class Checker(object):
223 The __setitem__() and __getitem__() methods are overloaded to DTRT
224 when adding new access rights, and retrieving them, so use these
225 rather then modifying rights directly if so desired.
228 MEMBERSHIP = {
229 'anyone': 'allow',
230 'club_admin': ('checkHasActiveRoleForScope', club_admin_logic),
231 'club_member': ('checkHasActiveRoleForScope', club_member_logic),
232 'host': ('checkHasDocumentAccess', [host_logic, 'sponsor']),
233 'org_admin': ('checkHasDocumentAccess', [org_admin_logic, 'org']),
234 'org_mentor': ('checkHasDocumentAccess', [mentor_logic, 'org']),
235 'org_student': ('checkHasDocumentAccess', [student_logic, 'org']),
236 'user': 'checkIsUser',
237 'user_self': ('checkIsUserSelf', 'scope_path'),
240 #: the depths of various scopes to other scopes
241 # the 0 entries are not used, and are for clarity purposes only
242 SCOPE_DEPTH = {
243 'site': None,
244 'sponsor': (sponsor_logic, {'sponsor': 0}),
245 'program': (program_logic, {'sponsor': 1, 'program': 0}),
246 'org': (org_logic, {'sponsor': 2, 'program': 1, 'org': 0}),
249 def __init__(self, params):
250 """Adopts base.rights as rights if base is set.
253 base = params.get('rights') if params else None
254 self.rights = base.rights if base else {}
255 self.id = None
256 self.user = None
258 def normalizeChecker(self, checker):
259 """Normalizes the checker to a pre-defined format.
261 The result is guaranteed to be a list of 2-tuples, the first element is a
262 checker (iff there is an checker with the specified name), the second
263 element is a list of arguments that should be passed to the checker when
264 calling it in addition to the standard django_args.
267 # Be nice an repack so that it is always a list with tuples
268 if isinstance(checker, tuple):
269 name, arg = checker
270 return (name, (arg if isinstance(arg, list) else [arg]))
271 else:
272 return (checker, [])
274 def __setitem__(self, key, value):
275 """Sets a value only if no old value exists.
278 oldvalue = self.rights.get(key)
279 self.rights[key] = oldvalue if oldvalue else value
281 def __getitem__(self, key):
282 """Retrieves and normalizes the right checkers.
285 return [self.normalizeChecker(i) for i in self.rights.get(key, [])]
287 def key(self, checker_name):
288 """Returns the key for the specified checker for the current user.
291 return "%s.%s" % (self.id, checker_name)
293 def put(self, checker_name, value):
294 """Puts the result for the specified checker in the cache.
297 retention = 30
299 memcache_key = self.key(checker_name)
300 memcache.add(memcache_key, value, retention)
302 def get(self, checker_name):
303 """Retrieves the result for the specified checker from cache.
306 memcache_key = self.key(checker_name)
307 return memcache.get(memcache_key)
309 def doCheck(self, checker_name, django_args, args):
310 """Runs the specified checker with the specified arguments.
313 checker = getattr(self, checker_name)
314 checker(django_args, *args)
316 def doCachedCheck(self, checker_name, django_args, args):
317 """Retrieves from cache or runs the specified checker.
320 cached = self.get(checker_name)
322 if cached is None:
323 try:
324 self.doCheck(checker_name, django_args, args)
325 self.put(checker_name, True)
326 return
327 except out_of_band.Error, exception:
328 self.put(checker_name, exception)
329 raise
331 if cached is True:
332 return
334 # re-raise the cached exception
335 raise cached
337 def check(self, use_cache, checker_name, django_args, args):
338 """Runs the checker, optionally using the cache.
341 if use_cache:
342 self.doCachedCheck(checker_name, django_args, args)
343 else:
344 self.doCheck(checker_name, django_args, args)
346 def setCurrentUser(self, id, user):
347 """Sets up everything for the current user.
350 self.id = id
351 self.user = user
353 def checkAccess(self, access_type, django_args):
354 """Runs all the defined checks for the specified type.
356 Args:
357 access_type: the type of request (such as 'list' or 'edit')
358 rights: a dictionary containing access check functions
359 django_args: a dictionary with django's arguments
361 Rights usage:
362 The rights dictionary is used to check if the current user is allowed
363 to view the page specified. The functions defined in this dictionary
364 are always called with the provided django_args dictionary as argument. On any
365 request, regardless of what type, the functions in the 'any_access' value
366 are called. If the specified type is not in the rights dictionary, all
367 the functions in the 'unspecified' value are called. When the specified
368 type _is_ in the rights dictionary, all the functions in that access_type's
369 value are called.
372 use_cache = django_args.get('SIDEBAR_CALLING')
374 # Call each access checker
375 for checker_name, args in self['any_access']:
376 self.check(use_cache, checker_name, django_args, args)
378 if access_type not in self.rights:
379 # No checks defined, so do the 'generic' checks and bail out
380 for checker_name, args in self['unspecified']:
381 self.check(use_cache, checker_name, django_args, args)
382 return
384 for checker_name, args in self[access_type]:
385 self.check(use_cache, checker_name, django_args, args)
387 def hasMembership(self, roles, django_args):
388 """Checks whether the user has access to any of the specified roles.
390 Makes use of self.MEMBERSHIP, which defines checkers specific to
391 document access, as such this method should only be used when checking
392 document access.
394 Args:
395 roles: a list of roles to check
396 django_args: the django args that should be passed to doCheck
399 try:
400 # we need to check manually, as we must return True!
401 self.checkIsDeveloper(django_args)
402 return True
403 except out_of_band.Error:
404 pass
406 for role in roles:
407 try:
408 checker_name, args = self.normalizeChecker(self.MEMBERSHIP[role])
409 self.doCheck(checker_name, django_args, args)
410 # the check passed, we can stop now
411 return True
412 except out_of_band.Error:
413 continue
415 return False
417 @allowDeveloper
418 def checkMembership(self, action, prefix, status, django_args):
419 """Checks whether the user has access to the specified status.
421 Args:
422 action: the action that was performed (e.g., 'read')
423 prefix: the prefix, determines what access set is used
424 status: the access status (e.g., 'public')
425 django_args: the django args to pass on to the checkers
428 checker = rights_logic.Checker(prefix)
429 roles = checker.getMembership(status)
431 message_fmt = DEF_NEED_MEMBERSHIP_MSG_FMT % {
432 'action': action,
433 'prefix': prefix,
434 'status': status,
437 # try to see if they belong to any of the roles, if not, raise an
438 # access violation for the specified action, prefix and status.
439 if not self.hasMembership(roles, django_args):
440 raise out_of_band.AccessViolation(message_fmt)
442 def checkHasAny(self, django_args, checks):
443 """Checks if any of the checks passes.
445 If none of the specified checks passes, the exception that the first of the
446 checks raised is reraised.
449 first = None
451 for checker_name, args in checks:
452 try:
453 self.doCheck(checker_name, django_args, args)
454 # one check passed, all is well
455 return
456 except out_of_band.Error, exception:
457 # store the first esception
458 first = first if first else exception
460 # none passed, re-raise the first exception
461 # pylint: disable-msg=W0706
462 raise first
464 def allow(self, django_args):
465 """Never raises an alternate HTTP response. (an access no-op, basically).
467 Args:
468 django_args: a dictionary with django's arguments
471 return
473 def deny(self, django_args=None):
474 """Always raises an alternate HTTP response.
476 Args:
477 django_args: a dictionary with django's arguments
479 Raises:
480 always raises AccessViolationResponse if called
483 context = django_args.get('context', {})
484 context['title'] = 'Access denied'
486 raise out_of_band.AccessViolation(DEF_PAGE_DENIED_MSG, context=context)
488 def checkIsLoggedIn(self, django_args=None):
489 """Raises an alternate HTTP response if Google Account is not logged in.
491 Args:
492 django_args: a dictionary with django's arguments, not used
494 Raises:
495 AccessViolationResponse:
496 * if no Google Account is even logged in
499 if self.id:
500 return
502 raise out_of_band.LoginRequest()
504 def checkNotLoggedIn(self, django_args=None):
505 """Raises an alternate HTTP response if Google Account is logged in.
507 Args:
508 django_args: a dictionary with django's arguments, not used
510 Raises:
511 AccessViolationResponse:
512 * if a Google Account is currently logged in
515 if not self.id:
516 return
518 raise out_of_band.LoginRequest(message_fmt=DEF_LOGOUT_MSG_FMT)
520 def checkIsUser(self, django_args=None):
521 """Raises an alternate HTTP response if Google Account has no User entity.
523 Args:
524 django_args: a dictionary with django's arguments, not used
526 Raises:
527 AccessViolationResponse:
528 * if no User exists for the logged-in Google Account, or
529 * if no Google Account is logged in at all
530 * if User has not agreed to the site-wide ToS, if one exists
533 self.checkIsLoggedIn()
535 if not self.user:
536 raise out_of_band.LoginRequest(message_fmt=DEF_NO_USER_LOGIN_MSG)
538 if user_logic.agreesToSiteToS(self.user):
539 return
541 # Would not reach this point of site-wide ToS did not exist, since
542 # agreesToSiteToS() call above always returns True if no ToS is in effect.
543 login_msg_fmt = DEF_AGREE_TO_TOS_MSG_FMT % {
544 'tos_link': redirects.getToSRedirect(site_logic.getSingleton())}
546 raise out_of_band.LoginRequest(message_fmt=login_msg_fmt)
548 @allowDeveloper
549 def checkIsHost(self, django_args=None):
550 """Checks whether the current user has a role entity.
552 Args:
553 django_args: the keyword args from django, not used
556 if not django_args:
557 django_args = {}
559 return self.checkHasActiveRole(django_args, host_logic)
561 @allowDeveloper
562 def checkIsUserSelf(self, django_args, field_name):
563 """Checks whether the specified user is the logged in user.
565 Args:
566 django_args: the keyword args from django, only field_name is used
569 self.checkIsUser()
571 if not field_name in django_args:
572 self.deny()
574 if self.user.link_id == django_args[field_name]:
575 return
577 raise out_of_band.AccessViolation(DEF_NOT_YOUR_ENTITY_MSG)
579 def checkIsUnusedAccount(self, django_args=None):
580 """Raises an alternate HTTP response if Google Account has a User entity.
582 Args:
583 django_args: a dictionary with django's arguments, not used
585 Raises:
586 AccessViolationResponse:
587 * if a User exists for the logged-in Google Account, or
588 * if a User has this Gooogle Account in their formerAccounts list
591 self.checkIsLoggedIn()
593 fields = {'account': self.id}
594 user_entity = user_logic.getForFields(fields, unique=True)
596 if not user_entity and not user_logic.isFormerAccount(self.id):
597 # this account has not been used yet
598 return
600 message_fmt = DEF_USER_ACCOUNT_INVALID_MSG_FMT % {
601 'email' : self.id.email()
604 raise out_of_band.LoginRequest(message_fmt=message_fmt)
606 def checkHasUserEntity(self, django_args=None):
607 """Raises an alternate HTTP response if Google Account has no User entity.
609 Args:
610 django_args: a dictionary with django's arguments
612 Raises:
613 AccessViolationResponse:
614 * if no User exists for the logged-in Google Account, or
615 * if no Google Account is logged in at all
618 self.checkIsLoggedIn()
620 if self.user:
621 return
623 raise out_of_band.LoginRequest(message_fmt=DEF_NO_USER_LOGIN_MSG)
625 def checkIsDeveloper(self, django_args=None):
626 """Raises an alternate HTTP response if Google Account is not a Developer.
628 Args:
629 django_args: a dictionary with django's arguments, not used
631 Raises:
632 AccessViolationResponse:
633 * if User is not a Developer, or
634 * if no User exists for the logged-in Google Account, or
635 * if no Google Account is logged in at all
638 self.checkIsUser()
640 if user_logic.isDeveloper(account=self.id, user=self.user):
641 return
643 login_message_fmt = DEF_DEV_LOGOUT_LOGIN_MSG_FMT % {
644 'role': 'a Site Developer ',
647 raise out_of_band.LoginRequest(message_fmt=login_message_fmt)
649 @allowDeveloper
650 @denySidebar
651 def _checkIsActive(self, django_args, logic, fields):
652 """Raises an alternate HTTP response if the entity is not active.
654 Args:
655 django_args: a dictionary with django's arguments
656 logic: the logic that should be used to look up the entity
657 fields: the name of the fields that should be copied verbatim
658 from the django_args as filter
660 Raises:
661 AccessViolationResponse:
662 * if no entity is found
663 * if the entity status is not active
666 self.checkIsUser()
668 fields = dicts.filter(django_args, fields)
669 fields['status'] = 'active'
671 entity = logic.getForFields(fields, unique=True)
673 if entity:
674 return
676 raise out_of_band.AccessViolation(message_fmt=DEF_NO_ACTIVE_ENTITY_MSG)
678 def checkGroupIsActiveForScopeAndLinkId(self, django_args, logic):
679 """Checks that the specified group is active.
681 Only group where both the link_id and the scope_path match the value
682 of the link_id and the scope_path from the django_args are considered.
684 Args:
685 django_args: a dictionary with django's arguments
686 logic: the logic that should be used to look up the entity
689 fields = ['scope_path', 'link_id']
690 self._checkIsActive(django_args, logic, fields)
692 def checkGroupIsActiveForLinkId(self, django_args, logic):
693 """Checks that the specified group is active.
695 Only group where the link_id matches the value of the link_id
696 from the django_args are considered.
698 Args:
699 django_args: a dictionary with django's arguments
700 logic: the logic that should be used to look up the entity
703 self._checkIsActive(django_args, logic, ['link_id'])
705 def checkHasActiveRole(self, django_args, logic):
706 """Checks that the user has the specified active role.
708 Args:
709 django_args: a dictionary with django's arguments
710 logic: the logic that should be used to look up the entity
713 django_args = django_args.copy()
714 django_args['user'] = self.user
715 self._checkIsActive(django_args, logic, ['user'])
717 def _checkHasActiveRoleFor(self, django_args, logic, field_name):
718 """Checks that the user has the specified active role.
720 Only roles where the field as specified by field_name matches the
721 scope_path from the django_args are considered.
723 Args:
724 django_args: a dictionary with django's arguments
725 logic: the logic that should be used to look up the entity
728 fields = [field_name, 'user']
729 django_args = django_args.copy()
730 django_args['user'] = self.user
731 self._checkIsActive(django_args, logic, fields)
733 def checkHasActiveRoleForKeyFieldsAsScope(self, django_args, logic):
734 """Checks that the user has the specified active role.
736 Args:
737 django_args: a dictionary with django's arguments
738 logic: the logic that should be used to look up the entity
741 key_fields = "%(scope_path)s/%(link_id)s" % django_args
742 new_args = {'scope_path': key_fields}
743 self._checkHasActiveRoleFor(new_args, logic, 'scope_path')
745 def checkHasActiveRoleForScope(self, django_args, logic):
746 """Checks that the user has the specified active role.
748 Only roles where the scope_path matches the scope_path from the
749 django_args are considered.
751 Args:
752 django_args: a dictionary with django's arguments
753 logic: the logic that should be used to look up the entity
756 self._checkHasActiveRoleFor(django_args, logic, 'scope_path')
758 def checkHasActiveRoleForLinkId(self, django_args, logic):
759 """Checks that the user has the specified active role.
761 Only roles where the link_id matches the link_id from the
762 django_args are considered.
764 Args:
765 django_args: a dictionary with django's arguments
766 logic: the logic that should be used to look up the entity
769 self._checkHasActiveRoleFor(django_args, logic, 'link_id')
771 def checkHasActiveRoleForLinkIdAsScope(self, django_args, logic):
772 """Checks that the user has the specified active role.
774 Only roles where the scope_path matches the link_id from the
775 django_args are considered.
777 Args:
778 django_args: a dictionary with django's arguments
779 logic: the logic that should be used to look up the entity
782 django_args = django_args.copy()
783 django_args['scope_path'] = django_args['link_id']
784 self._checkHasActiveRoleFor(django_args, logic, 'scope_path')
786 def checkHasDocumentAccess(self, django_args, logic, target_scope):
787 """Checks that the user has access to the specified document scope.
789 Args:
790 django_args: a dictionary with django's arguments
791 logic: the logic that should be used to look up the entity
794 prefix = django_args['prefix']
795 if self.SCOPE_DEPTH.get(prefix):
796 scope_logic, depths = self.SCOPE_DEPTH[prefix]
797 else:
798 return self.checkHasActiveRole(django_args, logic)
800 depth = depths.get(target_scope, 0)
802 # nothing to do
803 if not (scope_logic and depth):
804 return self.checkHasActiveRoleForScope(django_args, logic)
806 # we don't want to modify the original django args
807 django_args = django_args.copy()
809 entity = scope_logic.getFromKeyName(django_args['scope_path'])
811 # cannot have access to the specified scope if it is invalid
812 if not entity:
813 raise out_of_band.AccessViolation(message_fmt=DEF_NO_ACTIVE_ENTITY_MSG)
815 # walk up the scope to where we need to be
816 for _ in range(depth):
817 entity = entity.scope
819 django_args['scope_path'] = entity.key().id_or_name()
821 self.checkHasActiveRoleForScope(django_args, logic)
823 def checkSeeded(self, django_args, checker_name, *args):
824 """Wrapper to update the django_args with the contens of seed first.
827 django_args.update(django_args.get('seed', {}))
828 self.doCheck(checker_name, django_args, args)
830 def checkCanMakeRequestToGroup(self, django_args, group_logic):
831 """Raises an alternate HTTP response if the specified group is not in an
832 active status.
834 Args:
835 django_args: a dictionary with django's arguments
836 group_logic: Logic module for the type of group which the request is for
839 self.checkIsUser(django_args)
841 group_entity = role_logic.getGroupEntityFromScopePath(
842 group_logic.logic, django_args['scope_path'])
844 if not group_entity:
845 raise out_of_band.Error(DEF_GROUP_NOT_FOUND_MSG, status=404)
847 if group_entity.status != 'active':
848 # tell the user that this group is not active
849 raise out_of_band.AccessViolation(message_fmt=DEF_NO_ACTIVE_GROUP_MSG)
851 return
853 def checkCanCreateFromRequest(self, django_args, role_name):
854 """Raises an alternate HTTP response if the specified request does not exist
855 or if it's status is not group_accepted. Also when the group this request
856 is from is in an inactive or invalid status access will be denied.
858 Args:
859 django_args: a dictionary with django's arguments
860 role_name: name of the role
863 self.checkIsUserSelf(django_args, 'link_id')
865 fields = {
866 'link_id': django_args['link_id'],
867 'scope_path': django_args['scope_path'],
868 'role': role_name,
869 'status': 'group_accepted',
872 entity = request_logic.getForFields(fields, unique=True)
874 if entity and (entity.scope.status not in ['invalid', 'inactive']):
875 return
877 raise out_of_band.AccessViolation(message_fmt=DEF_NO_REQUEST_MSG)
879 def checkIsMyGroupAcceptedRequest(self, django_args):
880 """Checks whether the user can accept the specified request.
882 Args:
883 django_args: a dictionary with django's arguments
886 self.checkCanCreateFromRequest(django_args, django_args['role'])
888 def checkCanProcessRequest(self, django_args, role_name):
889 """Raises an alternate HTTP response if the specified request does not exist
890 or if it's status is completed or denied. Also Raises an alternate HTTP response
891 whenever the group in the request is not active.
893 Args:
894 django_args: a dictionary with django's arguments
895 role_name: name of the role
898 self.checkIsUser(django_args)
900 fields = {
901 'link_id': django_args['link_id'],
902 'scope_path': django_args['scope_path'],
903 'role': role_name,
906 request_entity = request_logic.getFromKeyFieldsOr404(fields)
908 if request_entity.status in ['completed', 'denied']:
909 raise out_of_band.AccessViolation(message_fmt=DEF_REQUEST_COMPLETED_MSG)
911 if request_entity.scope.status == 'active':
912 return
914 raise out_of_band.AccessViolation(message_fmt=DEF_SCOPE_INACTIVE_MSG)
916 @allowDeveloper
917 @denySidebar
918 def checkIsHostForProgram(self, django_args):
919 """Checks if the user is a host for the specified program.
921 Args:
922 django_args: a dictionary with django's arguments
925 program = program_logic.getFromKeyFields(django_args)
927 if not program or program.status == 'invalid':
928 raise out_of_band.AccessViolation(message_fmt=DEF_NO_ACTIVE_PROGRAM_MSG)
930 new_args = {'scope_path': program.scope_path }
931 self.checkHasActiveRoleForScope(new_args, host_logic)
933 @allowDeveloper
934 @denySidebar
935 def checkIsHostForProgramInScope(self, django_args):
936 """Checks if the user is a host for the specified program.
938 Args:
939 django_args: a dictionary with django's arguments
942 program = program_logic.getFromKeyName(django_args['scope_path'])
944 if not program or program.status == 'invalid':
945 raise out_of_band.AccessViolation(message_fmt=DEF_NO_ACTIVE_PROGRAM_MSG)
947 django_args = {'scope_path': program.scope_path}
948 self.checkHasActiveRoleForScope(django_args, host_logic)
950 @allowDeveloper
951 @denySidebar
952 def checkIsActivePeriod(self, django_args, period_name, key_name_arg):
953 """Checks if the given period is active for the given program.
955 Args:
956 django_args: a dictionary with django's arguments
957 period_name: the name of the period which is checked
958 key_name_arg: the entry in django_args that specifies the given program
959 keyname. If none is given the key_name is constructed from django_args
960 itself.
962 Raises:
963 AccessViolationResponse:
964 * if no active Program is found
965 * if the period is not active
968 if key_name_arg and key_name_arg in django_args:
969 key_name = django_args[key_name_arg]
970 else:
971 key_name = program_logic.getKeyNameFromFields(django_args)
973 program_entity = program_logic.getFromKeyName(key_name)
975 if not program_entity or (
976 program_entity.status in ['inactive', 'invalid']):
977 raise out_of_band.AccessViolation(message_fmt=DEF_SCOPE_INACTIVE_MSG)
979 if timeline_helper.isActivePeriod(program_entity.timeline, period_name):
980 return
982 raise out_of_band.AccessViolation(message_fmt=DEF_PAGE_INACTIVE_MSG)
984 @allowDeveloper
985 @denySidebar
986 def checkisAfterEvent(self, django_args, event_name, key_name_arg):
987 """Checks if the given event has taken place for the given program.
989 Args:
990 django_args: a dictionary with django's arguments
991 event_name: the name of the event which is checked
992 key_name_arg: the entry in django_args that specifies the given program
993 keyname. If none is given the key_name is constructed from django_args
994 itself.
996 Raises:
997 AccessViolationResponse:
998 * if no active Program is found
999 * if the event has not taken place yet
1002 if key_name_arg and key_name_arg in django_args:
1003 key_name = django_args[key_name_arg]
1004 else:
1005 key_name = program_logic.getKeyNameFromFields(django_args)
1007 program_entity = program_logic.getFromKeyName(key_name)
1009 if not program_entity or (
1010 program_entity.status in ['inactive', 'invalid']):
1011 raise out_of_band.AccessViolation(message_fmt=DEF_SCOPE_INACTIVE_MSG)
1013 if timeline_helper.isAfterEvent(program_entity.timeline, event_name):
1014 return
1016 raise out_of_band.AccessViolation(message_fmt=DEF_PAGE_INACTIVE_MSG)
1018 def checkCanCreateOrgApp(self, django_args, period_name):
1019 """Checks to see if the program in the scope_path is accepting org apps
1021 Args:
1022 django_args: a dictionary with django's arguments
1023 period_name: the name of the period which is checked
1026 if 'seed' in django_args:
1027 return self.checkIsActivePeriod(django_args['seed'],
1028 period_name, 'scope_path')
1029 else:
1030 return
1032 @allowDeveloper
1033 def checkCanEditGroupApp(self, django_args, group_app_logic):
1034 """Checks if the group_app in args is valid to be edited by
1035 the current user.
1037 Args:
1038 django_args: a dictionary with django's arguments
1039 group_app_logic: A logic instance for the Group Application
1042 self.checkIsUser(django_args)
1044 fields = {
1045 'link_id': django_args['link_id'],
1046 'applicant': self.user,
1047 'status' : ['needs review', 'rejected']
1050 if 'scope_path' in django_args:
1051 fields['scope_path'] = django_args['scope_path']
1053 entity = group_app_logic.getForFields(fields)
1055 if entity:
1056 return
1058 del fields['applicant']
1059 fields['backup_admin'] = self.user
1061 entity = group_app_logic.getForFields(fields)
1063 if entity:
1064 return
1066 raise out_of_band.AccessViolation(message_fmt=DEF_NOT_YOUR_ENTITY_MSG)
1068 @allowSidebar
1069 def checkCanReviewGroupApp(self, django_args, group_app_logic):
1070 """Checks if the group_app in args is valid to be reviewed.
1072 Args:
1073 django_args: a dictionary with django's arguments
1074 group_app_logic: A logic instance for the Group Application
1077 if 'link_id' not in django_args:
1078 # calling review overview, so we can't check a specified entity
1079 return
1081 fields = {
1082 'link_id': django_args['link_id'],
1083 'status': ['needs review', 'accepted', 'rejected', 'ignored',
1084 'pre-accepted', 'pre-rejected']
1087 if 'scope_path' in django_args:
1088 fields['scope_path'] = django_args['scope_path']
1090 entity = group_app_logic.getForFields(fields)
1092 if entity:
1093 return
1095 raise out_of_band.AccessViolation(message_fmt=DEF_REVIEW_COMPLETED_MSG)
1097 @allowDeveloper
1098 def checkIsApplicationAccepted(self, django_args, app_logic):
1099 """Returns an alternate HTTP response if Google Account has no accepted
1100 Group Application entity for the specified arguments.
1102 Args:
1103 django_args: a dictionary with django's arguments
1105 Raises:
1106 AccessViolationResponse: if the required authorization is not met
1108 Returns:
1109 None if the Accepted Group App exists for the specified program, or a subclass
1110 of django.http.HttpResponse which contains the alternate response
1111 should be returned by the calling view.
1114 self.checkIsUser(django_args)
1116 application = app_logic.getFromKeyFieldsOr404(django_args)
1117 applicant = application.applicant.key()
1118 backup_admin = application.backup_admin
1119 backup_admin = backup_admin.key() if backup_admin else None
1120 user = self.user.key()
1122 # check if the application is accepted and the applicant is the current user
1123 if application.status == 'accepted' and (applicant == user or
1124 backup_admin == user):
1125 return
1127 raise out_of_band.AccessViolation(message_fmt=DEF_NO_APPLICATION_MSG)
1129 def checkIsNotParticipatingInProgramInScope(self, django_args):
1130 """Checks if the current user has no roles for the given
1131 program in django_args.
1133 Args:
1134 django_args: a dictionary with django's arguments
1136 Raises:
1137 AccessViolationResponse: if the current user has a student, mentor or
1138 org admin role for the given program.
1141 if not django_args.get('scope_path'):
1142 raise out_of_band.AccessViolation(message_fmt=DEF_PAGE_DENIED_MSG)
1144 program_entity = program_logic.getFromKeyNameOr404(
1145 django_args['scope_path'])
1146 user_entity = user_logic.getForCurrentAccount()
1148 filter = {'user': user_entity,
1149 'scope': program_entity,
1150 'status': 'active'}
1152 # check if the current user is already a student for this program
1153 student_role = student_logic.getForFields(filter, unique=True)
1155 if student_role:
1156 raise out_of_band.AccessViolation(
1157 message_fmt=DEF_ALREADY_PARTICIPATING_MSG)
1159 # fill the role_list with all the mentor and org admin roles for this user
1160 # role_list = []
1162 filter = {'user': user_entity,
1163 'program': program_entity,
1164 'status': 'active'}
1166 mentor_role = mentor_logic.getForFields(filter, unique=True)
1167 if mentor_role:
1168 # the current user has a role for the given program
1169 raise out_of_band.AccessViolation(
1170 message_fmt=DEF_ALREADY_PARTICIPATING_MSG)
1172 org_admin_role = org_admin_logic.getForFields(filter, unique=True)
1173 if org_admin_role:
1174 # the current user has a role for the given program
1175 raise out_of_band.AccessViolation(
1176 message_fmt=DEF_ALREADY_PARTICIPATING_MSG)
1178 # no roles found, access granted
1179 return
1181 def checkIsNotStudentForProgramInScope(self, django_args):
1182 """Checks if the current user is not a student for the given
1183 program in django_args.
1185 Args:
1186 django_args: a dictionary with django's arguments
1188 Raises:
1189 AccessViolationResponse: if the current user has a student
1190 role for the given program.
1193 if django_args.get('seed'):
1194 key_name = django_args['seed']['scope_path']
1195 else:
1196 key_name = django_args['scope_path']
1198 program_entity = program_logic.getFromKeyNameOr404(key_name)
1199 user_entity = user_logic.getForCurrentAccount()
1201 filter = {'user': user_entity,
1202 'scope': program_entity,
1203 'status': 'active'}
1205 # check if the current user is already a student for this program
1206 student_role = student_logic.getForFields(filter, unique=True)
1208 if student_role:
1209 raise out_of_band.AccessViolation(
1210 message_fmt=DEF_ALREADY_STUDENT_ROLE_MSG)
1212 return
1214 def checkIsNotStudentForProgramOfOrg(self, django_args):
1215 """Checks if the current user has no active Student role for the program
1216 that the organization in the scope_path is participating in.
1218 Args:
1219 django_args: a dictionary with django's arguments
1221 Raises:
1222 AccessViolationResponse: if the current user is a student for the
1223 program the organization is in.
1226 if not django_args.get('scope_path'):
1227 raise out_of_band.AccessViolation(message_fmt=DEF_PAGE_DENIED_MSG)
1229 org_entity = org_logic.getFromKeyNameOr404(django_args['scope_path'])
1230 user_entity = user_logic.getForCurrentAccount()
1232 filter = {'scope': org_entity.scope,
1233 'user': user_entity,
1234 'status': 'active'}
1236 student_role = student_logic.getForFields(filter=filter, unique=True)
1238 if student_role:
1239 raise out_of_band.AccessViolation(
1240 message_fmt=DEF_ALREADY_STUDENT_ROLE_MSG)
1242 return
1244 @allowDeveloper
1245 def checkRoleAndStatusForStudentProposal(self, django_args, allowed_roles,
1246 role_status, proposal_status):
1247 """Checks if the current user has access to the given proposal.
1249 Args:
1250 django_args: a dictionary with django's arguments
1251 allowed_roles: list with names for the roles allowed to pass access check
1252 role_status: list with states allowed for the role
1253 proposal_status: a list with states allowed for the proposal
1255 Raises:
1256 AccessViolationResponse:
1257 - If there is no proposal found
1258 - If the proposal is not in one of the required states.
1259 - If the user does not have any ofe the required roles
1262 self.checkIsUser(django_args)
1264 # bail out with 404 if no proposal is found
1265 proposal_entity = student_proposal_logic.getFromKeyFieldsOr404(django_args)
1267 if not proposal_entity.status in proposal_status:
1268 # this proposal can not be accessed at the moment
1269 raise out_of_band.AccessViolation(
1270 message_fmt=DEF_NO_ACTIVE_ENTITY_MSG)
1272 user_entity = self.user
1274 if 'proposer' in allowed_roles:
1275 # check if this proposal belongs to the current user
1276 student_entity = proposal_entity.scope
1277 if (user_entity.key() == student_entity.user.key()) and (
1278 student_entity.status in role_status):
1279 return
1281 filter = {'user': user_entity,
1282 'status': role_status}
1284 if 'host' in allowed_roles:
1285 # check if the current user is a host for this proposal's program
1286 filter['scope'] = proposal_entity.program
1288 if host_logic.getForFields(filter, unique=True):
1289 return
1291 if 'org_admin' in allowed_roles:
1292 # check if the current user is an admin for this proposal's org
1293 filter['scope'] = proposal_entity.org
1295 if org_admin_logic.getForFields(filter, unique=True):
1296 return
1298 if 'mentor' in allowed_roles:
1299 # check if the current user is a mentor for this proposal's org
1300 filter['scope'] = proposal_entity.org
1302 if mentor_logic.getForFields(filter, unique=True):
1303 return
1305 # no roles found, access denied
1306 raise out_of_band.AccessViolation(
1307 message_fmt=DEF_NEED_ROLE_MSG)
1309 @allowDeveloper
1310 def checkCanStudentPropose(self, django_args, key_location, check_limit):
1311 """Checks if the program for this student accepts proposals.
1313 Args:
1314 django_args: a dictionary with django's arguments
1315 key_location: the key for django_args in which the key_name
1316 from the student is stored
1317 check_limit: iff true checks if the student reached the apps_tasks_limit
1318 for the given program.
1321 self.checkIsUser(django_args)
1323 if django_args.get('seed'):
1324 key_name = django_args['seed'][key_location]
1325 else:
1326 key_name = django_args[key_location]
1328 student_entity = student_logic.getFromKeyName(key_name)
1330 if not student_entity or student_entity.status == 'invalid':
1331 raise out_of_band.AccessViolation(
1332 message_fmt=DEF_SIGN_UP_AS_STUDENT_MSG)
1334 program_entity = student_entity.scope
1336 if not timeline_helper.isActivePeriod(program_entity.timeline,
1337 'student_signup'):
1338 raise out_of_band.AccessViolation(message_fmt=DEF_PAGE_INACTIVE_MSG)
1340 if check_limit:
1341 # count all studentproposals by the student
1342 fields = {'scope': student_entity}
1343 proposal_query = student_proposal_logic.getQueryForFields(fields)
1345 if proposal_query.count() >= program_entity.apps_tasks_limit:
1346 # too many proposals access denied
1347 raise out_of_band.AccessViolation(message_fmt=DEF_MAX_PROPOSALS_REACHED)
1349 return
1351 @allowDeveloper
1352 def checkIsStudent(self, django_args, key_location, status):
1353 """Checks if the current user is the given student.
1355 Args:
1356 django_args: a dictionary with django's arguments
1357 key_location: the key for django_args in which the key_name
1358 from the student is stored
1359 status: the allowed status for the student
1362 self.checkIsUser(django_args)
1364 if 'seed' in django_args:
1365 key_name = django_args['seed'][key_location]
1366 else:
1367 key_name = django_args[key_location]
1369 student_entity = student_logic.getFromKeyName(key_name)
1371 if not student_entity or student_entity.status not in status:
1372 raise out_of_band.AccessViolation(
1373 message_fmt=DEF_SIGN_UP_AS_STUDENT_MSG)
1375 if student_entity.user.key() != self.user.key():
1376 # this is not the page for the current user
1377 self.deny(django_args)
1379 return
1381 @allowDeveloper
1382 def checkIsMyStudentProject(self, django_args):
1383 """Checks whether the project belongs to the current user.
1385 Args:
1386 django_args: a dictionary with django's arguments
1388 Raises:
1389 AccessViolationResponse:
1390 - If there is no project found
1391 - If the project does not belong to the current user
1394 self.checkIsUser()
1396 project_entity = student_project_logic.getFromKeyFieldsOr404(django_args)
1398 if project_entity.student.user.key() != self.user.key():
1399 raise out_of_band.AccessViolation(
1400 message_fmt=DEF_NOT_YOUR_ENTITY_MSG)
1402 return
1404 @allowDeveloper
1405 def checkStudentProjectHasStatus(self, django_args, allowed_status):
1406 """Checks whether the Project has one of the given statuses.
1408 Args:
1409 django_args: a dictionary with django's arguments
1410 allowed_status: list with the allowed statusses for the entity
1412 Raises:
1413 AccessViolationResponse:
1414 - If there is no project found
1415 - If the project is not in the requested status
1418 project_entity = student_project_logic.getFromKeyFieldsOr404(django_args)
1420 if not project_entity.status in allowed_status:
1421 raise out_of_band.AccessViolation(
1422 message_fmt=DEF_NO_ACTIVE_ENTITY_MSG)
1424 return
1426 @allowDeveloper
1427 def checkIsMyEntity(self, django_args, logic,
1428 field_name='user', user=False):
1429 """Checks whether the entity belongs to the user.
1431 Args:
1432 django_args: a dictionary with django's arguments
1433 logic: the logic that should be used to fetch the entity
1434 field_name: the name of the field the entity uses to store it's owner
1435 user: true iff the entity stores the user's reference, false iff keyname
1438 self.checkIsUser(django_args)
1440 fields = {
1441 'link_id': django_args['link_id'],
1442 field_name: self.user if user else self.user.key().id_or_name()
1445 if 'scope_path' in django_args:
1446 fields['scope_path'] = django_args['scope_path']
1448 entity = logic.getForFields(fields)
1450 if entity:
1451 return
1453 raise out_of_band.AccessViolation(message_fmt=DEF_NOT_YOUR_ENTITY_MSG)
1455 @allowDeveloper
1456 @denySidebar
1457 def checkIsAllowedToManageRole(self, django_args, logic_for_role,
1458 manage_role_logic):
1459 """Returns an alternate HTTP response if the user is not allowed to manage
1460 the role given in args.
1462 Args:
1463 django_args: a dictionary with django's arguments
1464 logic_for_role: determines the logic for the role in args.
1465 manage_role_logic: determines the logic for the role which is allowed
1466 to manage this role.
1468 Raises:
1469 AccessViolationResponse: if the required authorization is not met
1471 Returns:
1472 None if the given role is active and belongs to the current user.
1473 None if the current User has an active role (from manage_role_logic)
1474 that belongs to the same scope as the role that needs to be managed
1477 try:
1478 # check if it is my role the user's own role
1479 self.checkHasActiveRoleForScope(django_args, logic_for_role)
1480 return
1481 except out_of_band.Error:
1482 pass
1484 # apparently it's not the user's role so check
1485 # if managing this role is allowed
1486 fields = {
1487 'link_id': django_args['link_id'],
1488 'scope_path': django_args['scope_path'],
1491 role_entity = role_logic.getFromKeyFieldsOr404(fields)
1492 if role_entity.status != 'active':
1493 raise out_of_band.AccessViolation(message_fmt=DEF_NO_ACTIVE_ROLE_MSG)
1495 fields = {
1496 'link_id': self.user.link_id,
1497 'scope_path': django_args['scope_path'],
1498 'status': 'active'
1501 manage_entity = manage_role_logic.getForFields(fields, unique=True)
1503 if not manage_entity:
1504 raise out_of_band.AccessViolation(message_fmt=DEF_NOT_YOUR_ENTITY_MSG)
1506 return
1508 @allowSidebar
1509 @allowDeveloper
1510 def checkIsDocumentReadable(self, django_args, key_name_field=None):
1511 """Checks whether a document is readable.
1513 Args:
1514 django_args: a dictionary with django's arguments
1515 key_name_field: key name field
1518 if key_name_field:
1519 key_name = django_args[key_name_field]
1520 document = document_logic.getFromKeyNameOr404(key_name)
1521 else:
1522 document = document_logic.getFromKeyFieldsOr404(django_args)
1524 self.checkMembership('read', document.prefix,
1525 document.read_access, django_args)
1527 @denySidebar
1528 @allowDeveloper
1529 def checkIsDocumentWritable(self, django_args, key_name_field=None):
1530 """Checks whether a document is writable.
1532 Args:
1533 django_args: a dictionary with django's arguments
1534 key_name_field: key name field
1537 if key_name_field:
1538 key_name = django_args[key_name_field]
1539 document = document_logic.getFromKeyNameOr404(key_name)
1540 else:
1541 document = document_logic.getFromKeyFieldsOr404(django_args)
1543 self.checkMembership('write', document.prefix,
1544 document.write_access, django_args)
1546 @allowDeveloper
1547 def checkDocumentList(self, django_args):
1548 """Checks whether the user is allowed to list documents.
1550 Args:
1551 django_args: a dictionary with django's arguments
1554 filter = django_args['filter']
1555 prefix = filter['prefix']
1557 checker = rights_logic.Checker(prefix)
1558 roles = checker.getMembership('list')
1560 if not self.hasMembership(roles, filter):
1561 raise out_of_band.AccessViolation(message_fmt=DEF_NO_LIST_ACCESS_MSG)
1563 @allowDeveloper
1564 def checkDocumentPick(self, django_args):
1565 """Checks whether the user has access to the specified pick url.
1567 Will update the 'read_access' field of django_args['GET'].
1569 Args:
1570 django_args: a dictionary with django's arguments
1573 get_args = django_args['GET']
1574 # make mutable in order to inject the proper read_access filter
1575 mutable = get_args._mutable
1576 get_args._mutable = True
1578 if 'prefix' not in get_args:
1579 raise out_of_band.AccessViolation(message_fmt=DEF_PREFIX_NOT_IN_ARGS_MSG)
1581 prefix = get_args['prefix']
1582 django_args['prefix'] = prefix
1583 django_args['scope_path'] = get_args['scope_path']
1585 checker = rights_logic.Checker(prefix)
1586 memberships = checker.getMemberships()
1588 roles = []
1589 for key, value in memberships.iteritems():
1590 if self.hasMembership(value, django_args):
1591 roles.append(key)
1593 if not roles:
1594 roles = ['deny']
1596 get_args.setlist('read_access', roles)
1597 get_args._mutable = mutable
1599 def checkCanEditTimeline(self, django_args):
1600 """Checks whether this program's timeline may be edited.
1602 Args:
1603 django_args: a dictionary with django's arguments
1606 time_line_keyname = timeline_logic.getKeyFieldsFromFields(django_args)
1607 timeline_entity = timeline_logic.getFromKeyName(time_line_keyname)
1609 if not timeline_entity:
1610 # timeline does not exists so deny
1611 self.deny(django_args)
1613 fields = program_logic.getKeyFieldsFromFields(django_args)
1614 self.checkIsHostForProgram(fields)