Add pylint: disable-msg=W0706 to soc.views.helper.access module.
[Melange.git] / app / soc / views / helper / access.py
blobe54c089b008dc5992223f60765540dbac44177a1
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().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)
1118 # check if the application is accepted and the applicant is the current user
1119 if (application.applicant.key() == self.user.key()) and (
1120 application.status == 'accepted'):
1121 return
1123 raise out_of_band.AccessViolation(message_fmt=DEF_NO_APPLICATION_MSG)
1125 def checkIsNotParticipatingInProgramInScope(self, django_args):
1126 """Checks if the current user has no roles for the given
1127 program in django_args.
1129 Args:
1130 django_args: a dictionary with django's arguments
1132 Raises:
1133 AccessViolationResponse: if the current user has a student, mentor or
1134 org admin role for the given program.
1137 if not django_args.get('scope_path'):
1138 raise out_of_band.AccessViolation(message_fmt=DEF_PAGE_DENIED_MSG)
1140 program_entity = program_logic.getFromKeyNameOr404(
1141 django_args['scope_path'])
1142 user_entity = user_logic.getForCurrentAccount()
1144 filter = {'user': user_entity,
1145 'scope': program_entity,
1146 'status': 'active'}
1148 # check if the current user is already a student for this program
1149 student_role = student_logic.getForFields(filter, unique=True)
1151 if student_role:
1152 raise out_of_band.AccessViolation(
1153 message_fmt=DEF_ALREADY_PARTICIPATING_MSG)
1155 # fill the role_list with all the mentor and org admin roles for this user
1156 # role_list = []
1158 filter = {'user': user_entity,
1159 'program': program_entity,
1160 'status': 'active'}
1162 mentor_role = mentor_logic.getForFields(filter, unique=True)
1163 if mentor_role:
1164 # the current user has a role for the given program
1165 raise out_of_band.AccessViolation(
1166 message_fmt=DEF_ALREADY_PARTICIPATING_MSG)
1168 org_admin_role = org_admin_logic.getForFields(filter, unique=True)
1169 if org_admin_role:
1170 # the current user has a role for the given program
1171 raise out_of_band.AccessViolation(
1172 message_fmt=DEF_ALREADY_PARTICIPATING_MSG)
1174 # no roles found, access granted
1175 return
1177 def checkIsNotStudentForProgramInScope(self, django_args):
1178 """Checks if the current user is not a student for the given
1179 program in django_args.
1181 Args:
1182 django_args: a dictionary with django's arguments
1184 Raises:
1185 AccessViolationResponse: if the current user has a student
1186 role for the given program.
1189 if django_args.get('seed'):
1190 key_name = django_args['seed']['scope_path']
1191 else:
1192 key_name = django_args['scope_path']
1194 program_entity = program_logic.getFromKeyNameOr404(key_name)
1195 user_entity = user_logic.getForCurrentAccount()
1197 filter = {'user': user_entity,
1198 'scope': program_entity,
1199 'status': 'active'}
1201 # check if the current user is already a student for this program
1202 student_role = student_logic.getForFields(filter, unique=True)
1204 if student_role:
1205 raise out_of_band.AccessViolation(
1206 message_fmt=DEF_ALREADY_STUDENT_ROLE_MSG)
1208 return
1210 def checkIsNotStudentForProgramOfOrg(self, django_args):
1211 """Checks if the current user has no active Student role for the program
1212 that the organization in the scope_path is participating in.
1214 Args:
1215 django_args: a dictionary with django's arguments
1217 Raises:
1218 AccessViolationResponse: if the current user is a student for the
1219 program the organization is in.
1222 if not django_args.get('scope_path'):
1223 raise out_of_band.AccessViolation(message_fmt=DEF_PAGE_DENIED_MSG)
1225 org_entity = org_logic.getFromKeyNameOr404(django_args['scope_path'])
1226 user_entity = user_logic.getForCurrentAccount()
1228 filter = {'scope': org_entity.scope,
1229 'user': user_entity,
1230 'status': 'active'}
1232 student_role = student_logic.getForFields(filter=filter, unique=True)
1234 if student_role:
1235 raise out_of_band.AccessViolation(
1236 message_fmt=DEF_ALREADY_STUDENT_ROLE_MSG)
1238 return
1240 @allowDeveloper
1241 def checkRoleAndStatusForStudentProposal(self, django_args, allowed_roles,
1242 role_status, proposal_status):
1243 """Checks if the current user has access to the given proposal.
1245 Args:
1246 django_args: a dictionary with django's arguments
1247 allowed_roles: list with names for the roles allowed to pass access check
1248 role_status: list with states allowed for the role
1249 proposal_status: a list with states allowed for the proposal
1251 Raises:
1252 AccessViolationResponse:
1253 - If there is no proposal found
1254 - If the proposal is not in one of the required states.
1255 - If the user does not have any ofe the required roles
1258 self.checkIsUser(django_args)
1260 # bail out with 404 if no proposal is found
1261 proposal_entity = student_proposal_logic.getFromKeyFieldsOr404(django_args)
1263 if not proposal_entity.status in proposal_status:
1264 # this proposal can not be accessed at the moment
1265 raise out_of_band.AccessViolation(
1266 message_fmt=DEF_NO_ACTIVE_ENTITY_MSG)
1268 user_entity = self.user
1270 if 'proposer' in allowed_roles:
1271 # check if this proposal belongs to the current user
1272 student_entity = proposal_entity.scope
1273 if (user_entity.key() == student_entity.user.key()) and (
1274 student_entity.status in role_status):
1275 return
1277 filter = {'user': user_entity,
1278 'status': role_status}
1280 if 'host' in allowed_roles:
1281 # check if the current user is a host for this proposal's program
1282 filter['scope'] = proposal_entity.program
1284 if host_logic.getForFields(filter, unique=True):
1285 return
1287 if 'org_admin' in allowed_roles:
1288 # check if the current user is an admin for this proposal's org
1289 filter['scope'] = proposal_entity.org
1291 if org_admin_logic.getForFields(filter, unique=True):
1292 return
1294 if 'mentor' in allowed_roles:
1295 # check if the current user is a mentor for this proposal's org
1296 filter['scope'] = proposal_entity.org
1298 if mentor_logic.getForFields(filter, unique=True):
1299 return
1301 # no roles found, access denied
1302 raise out_of_band.AccessViolation(
1303 message_fmt=DEF_NEED_ROLE_MSG)
1305 @allowDeveloper
1306 def checkCanStudentPropose(self, django_args, key_location, check_limit):
1307 """Checks if the program for this student accepts proposals.
1309 Args:
1310 django_args: a dictionary with django's arguments
1311 key_location: the key for django_args in which the key_name
1312 from the student is stored
1313 check_limit: iff true checks if the student reached the apps_tasks_limit
1314 for the given program.
1317 self.checkIsUser(django_args)
1319 if django_args.get('seed'):
1320 key_name = django_args['seed'][key_location]
1321 else:
1322 key_name = django_args[key_location]
1324 student_entity = student_logic.getFromKeyName(key_name)
1326 if not student_entity or student_entity.status == 'invalid':
1327 raise out_of_band.AccessViolation(
1328 message_fmt=DEF_SIGN_UP_AS_STUDENT_MSG)
1330 program_entity = student_entity.scope
1332 if not timeline_helper.isActivePeriod(program_entity.timeline,
1333 'student_signup'):
1334 raise out_of_band.AccessViolation(message_fmt=DEF_PAGE_INACTIVE_MSG)
1336 if check_limit:
1337 # count all studentproposals by the student
1338 fields = {'scope': student_entity}
1339 proposal_query = student_proposal_logic.getQueryForFields(fields)
1341 if proposal_query.count() >= program_entity.apps_tasks_limit:
1342 # too many proposals access denied
1343 raise out_of_band.AccessViolation(message_fmt=DEF_MAX_PROPOSALS_REACHED)
1345 return
1347 @allowDeveloper
1348 def checkIsStudent(self, django_args, key_location, status):
1349 """Checks if the current user is the given student.
1351 Args:
1352 django_args: a dictionary with django's arguments
1353 key_location: the key for django_args in which the key_name
1354 from the student is stored
1355 status: the allowed status for the student
1358 self.checkIsUser(django_args)
1360 if 'seed' in django_args:
1361 key_name = django_args['seed'][key_location]
1362 else:
1363 key_name = django_args[key_location]
1365 student_entity = student_logic.getFromKeyName(key_name)
1367 if not student_entity or student_entity.status not in status:
1368 raise out_of_band.AccessViolation(
1369 message_fmt=DEF_SIGN_UP_AS_STUDENT_MSG)
1371 if student_entity.user.key() != self.user.key():
1372 # this is not the page for the current user
1373 self.deny(django_args)
1375 return
1377 @allowDeveloper
1378 def checkIsMyStudentProject(self, django_args):
1379 """Checks whether the project belongs to the current user.
1381 Args:
1382 django_args: a dictionary with django's arguments
1384 Raises:
1385 AccessViolationResponse:
1386 - If there is no project found
1387 - If the project does not belong to the current user
1390 self.checkIsUser()
1392 project_entity = student_project_logic.getFromKeyFieldsOr404(django_args)
1394 if project_entity.student.user.key() != self.user.key():
1395 raise out_of_band.AccessViolation(
1396 message_fmt=DEF_NOT_YOUR_ENTITY_MSG)
1398 return
1400 @allowDeveloper
1401 def checkStudentProjectHasStatus(self, django_args, allowed_status):
1402 """Checks whether the Project has one of the given statusses.
1404 Args:
1405 django_args: a dictionary with django's arguments
1406 allowed_status: list with the allowed statusses for the entity
1408 Raises:
1409 AccessViolationResponse:
1410 - If there is no project found
1411 - If the project is not in the requested status
1414 project_entity = student_project_logic.getFromKeyFieldsOr404(django_args)
1416 if not project_entity.status in allowed_status:
1417 raise out_of_band.AccessViolation(
1418 message_fmt=DEF_NO_ACTIVE_ENTITY_MSG)
1420 return
1422 @allowDeveloper
1423 def checkIsMyEntity(self, django_args, logic,
1424 field_name='user', user=False):
1425 """Checks whether the entity belongs to the user.
1427 Args:
1428 django_args: a dictionary with django's arguments
1429 logic: the logic that should be used to fetch the entity
1430 field_name: the name of the field the entity uses to store it's owner
1431 user: true iff the entity stores the user's reference, false iff keyname
1434 self.checkIsUser(django_args)
1436 fields = {
1437 'link_id': django_args['link_id'],
1438 field_name: self.user if user else self.user.key().name()
1441 if 'scope_path' in django_args:
1442 fields['scope_path'] = django_args['scope_path']
1444 entity = logic.getForFields(fields)
1446 if entity:
1447 return
1449 raise out_of_band.AccessViolation(message_fmt=DEF_NOT_YOUR_ENTITY_MSG)
1451 @allowDeveloper
1452 @denySidebar
1453 def checkIsAllowedToManageRole(self, django_args, logic_for_role,
1454 manage_role_logic):
1455 """Returns an alternate HTTP response if the user is not allowed to manage
1456 the role given in args.
1458 Args:
1459 django_args: a dictionary with django's arguments
1460 logic_for_role: determines the logic for the role in args.
1461 manage_role_logic: determines the logic for the role which is allowed
1462 to manage this role.
1464 Raises:
1465 AccessViolationResponse: if the required authorization is not met
1467 Returns:
1468 None if the given role is active and belongs to the current user.
1469 None if the current User has an active role (from manage_role_logic)
1470 that belongs to the same scope as the role that needs to be managed
1473 try:
1474 # check if it is my role the user's own role
1475 self.checkHasActiveRoleForScope(django_args, logic_for_role)
1476 return
1477 except out_of_band.Error:
1478 pass
1480 # apparently it's not the user's role so check
1481 # if managing this role is allowed
1482 fields = {
1483 'link_id': django_args['link_id'],
1484 'scope_path': django_args['scope_path'],
1487 role_entity = role_logic.getFromKeyFieldsOr404(fields)
1488 if role_entity.status != 'active':
1489 raise out_of_band.AccessViolation(message_fmt=DEF_NO_ACTIVE_ROLE_MSG)
1491 fields = {
1492 'link_id': self.user.link_id,
1493 'scope_path': django_args['scope_path'],
1494 'status': 'active'
1497 manage_entity = manage_role_logic.getForFields(fields, unique=True)
1499 if not manage_entity:
1500 raise out_of_band.AccessViolation(message_fmt=DEF_NOT_YOUR_ENTITY_MSG)
1502 return
1504 @allowSidebar
1505 @allowDeveloper
1506 def checkIsDocumentReadable(self, django_args, key_name_field=None):
1507 """Checks whether a document is readable.
1509 Args:
1510 django_args: a dictionary with django's arguments
1511 key_name_field: key name field
1514 if key_name_field:
1515 key_name = django_args[key_name_field]
1516 document = document_logic.getFromKeyNameOr404(key_name)
1517 else:
1518 document = document_logic.getFromKeyFieldsOr404(django_args)
1520 self.checkMembership('read', document.prefix,
1521 document.read_access, django_args)
1523 @denySidebar
1524 @allowDeveloper
1525 def checkIsDocumentWritable(self, django_args, key_name_field=None):
1526 """Checks whether a document is writable.
1528 Args:
1529 django_args: a dictionary with django's arguments
1530 key_name_field: key name field
1533 if key_name_field:
1534 key_name = django_args[key_name_field]
1535 document = document_logic.getFromKeyNameOr404(key_name)
1536 else:
1537 document = document_logic.getFromKeyFieldsOr404(django_args)
1539 self.checkMembership('write', document.prefix,
1540 document.write_access, django_args)
1542 @allowDeveloper
1543 def checkDocumentList(self, django_args):
1544 """Checks whether the user is allowed to list documents.
1546 Args:
1547 django_args: a dictionary with django's arguments
1550 filter = django_args['filter']
1551 prefix = filter['prefix']
1553 checker = rights_logic.Checker(prefix)
1554 roles = checker.getMembership('list')
1556 if not self.hasMembership(roles, filter):
1557 raise out_of_band.AccessViolation(message_fmt=DEF_NO_LIST_ACCESS_MSG)
1559 @allowDeveloper
1560 def checkDocumentPick(self, django_args):
1561 """Checks whether the user has access to the specified pick url.
1563 Will update the 'read_access' field of django_args['GET'].
1565 Args:
1566 django_args: a dictionary with django's arguments
1569 get_args = django_args['GET']
1570 # make mutable in order to inject the proper read_access filter
1571 mutable = get_args._mutable
1572 get_args._mutable = True
1574 if 'prefix' not in get_args:
1575 raise out_of_band.AccessViolation(message_fmt=DEF_PREFIX_NOT_IN_ARGS_MSG)
1577 prefix = get_args['prefix']
1578 django_args['prefix'] = prefix
1579 django_args['scope_path'] = get_args['scope_path']
1581 checker = rights_logic.Checker(prefix)
1582 memberships = checker.getMemberships()
1584 roles = []
1585 for key, value in memberships.iteritems():
1586 if self.hasMembership(value, django_args):
1587 roles.append(key)
1589 if not roles:
1590 roles = ['deny']
1592 get_args.setlist('read_access', roles)
1593 get_args._mutable = mutable
1595 def checkCanEditTimeline(self, django_args):
1596 """Checks whether this program's timeline may be edited.
1598 Args:
1599 django_args: a dictionary with django's arguments
1602 time_line_keyname = timeline_logic.getKeyFieldsFromFields(django_args)
1603 timeline_entity = timeline_logic.getFromKeyName(time_line_keyname)
1605 if not timeline_entity:
1606 # timeline does not exists so deny
1607 self.deny(django_args)
1609 fields = program_logic.getKeyFieldsFromFields(django_args)
1610 self.checkIsHostForProgram(fields)