Hook up the ACL system for documents.
authorSverre Rabbelier <srabbelier@gmail.com>
Sat, 31 Jan 2009 22:32:54 +0000
changeset 1135 24d695060863
parent 1134 493e7b5d3ab2
child 1136 aaf75aa8eca5
Hook up the ACL system for documents. Org access checks are left unimplemented as they are not done yet. Patch by: Sverre Rabbelier
app/soc/cache/rights.py
app/soc/views/helper/access.py
app/soc/views/models/document.py
--- a/app/soc/cache/rights.py	Sat Jan 31 22:32:21 2009 +0000
+++ b/app/soc/cache/rights.py	Sat Jan 31 22:32:54 2009 +0000
@@ -32,11 +32,13 @@
     'checkCanProcessRequest',
     'checkHasPickGetArgs',
     'checkHasUserEntity',
+    'checkHasHostEntity',
     'checkIsActive',
     'checkIsApplicationAccepted',
     'checkIsClubAdminForClub',
     'checkIsDeveloper',
-    'checkIsDocumentPublic',
+    'checkIsDocumentReadable',
+    'checkIsDocumentWritable',
     'checkIsGroupActive',
     'checkIsHost',
     'checkIsHostForSponsor',
@@ -48,6 +50,7 @@
     'checkIsProgramActive',
     'checkIsUnusedAccount',
     'checkIsUser',
+    'checkIsUserSelf'
     'checkNotLoggedIn',
     ]
 
--- a/app/soc/views/helper/access.py	Sat Jan 31 22:32:21 2009 +0000
+++ b/app/soc/views/helper/access.py	Sat Jan 31 22:32:54 2009 +0000
@@ -39,7 +39,9 @@
 
 from soc.logic import accounts
 from soc.logic import dicts
+from soc.logic import rights as rights_logic
 from soc.logic.models.club_admin import logic as club_admin_logic
+from soc.logic.models.club_member import logic as club_member_logic
 from soc.logic.models.document import logic as document_logic
 from soc.logic.models.host import logic as host_logic
 from soc.logic.models.notification import logic as notification_logic
@@ -68,6 +70,10 @@
   ' and <a href="%%(sign_in)s">sign in</a>'
   ' again as %(role)s to view this page.')
 
+DEF_NEED_MEMBERSHIP_MSG_FMT = ugettext(
+  'You need to be in the %(status)s group to %(action)s'
+  ' documents in the %(prefix)s prefix.')
+
 DEF_PAGE_DENIED_MSG = ugettext(
   'Access to this page has been restricted')
 
@@ -134,6 +140,18 @@
   rather then modifying rights directly if so desired.
   """
 
+  MEMBERSHIP = {
+    'anyone': 'allow',
+    'club_admin': 'checkIsClubAdminForScope',
+    'club_member': 'checkIsClubMemberForScope',
+    'host': 'checkHasHostEntity',
+    'org_admin': 'deny',
+    'org_mentor': 'deny',
+    'org_student': 'deny',
+    'user': 'checkIsUser',
+    'user_self': 'checkIsUserSelf',
+    }
+
   def __init__(self, params):
     """Adopts base.rights as rights if base is set.
     """
@@ -273,6 +291,40 @@
     for checker_name, args in self[access_type]:
       self.check(use_cache, checker_name, django_args, args)
 
+  def checkMembership(self, action, prefix, status, django_args):
+    """Checks whether the user has access to the specified status.
+
+    Args:
+      action: the action that was performed (e.g., 'read')
+      prefix: the prefix, determines what access set is used
+      status: the access status (e.g., 'public')
+      django_args: the django args to pass on to the checkers
+    """
+
+    checker = rights_logic.Checker(prefix)
+    roles = checker.getMembership(status)
+
+    message_fmt = DEF_NEED_MEMBERSHIP_MSG_FMT % {
+        'action': action,
+        'prefix': prefix,
+        'status': status,
+        }
+
+    # try to see if they belong to any of the roles, if not, raise an
+    # access violation for the specified action, prefix and status.
+    for role in roles:
+      try:
+        checker_name = self.MEMBERSHIP[role]
+        self.doCheck(checker_name, django_args, [])
+
+        # the check passed, we can stop now
+        break
+      except out_of_band.Error:
+        continue
+    else:
+      raise out_of_band.AccessViolation(message_fmt)
+
+
   def allow(self, django_args):
     """Never raises an alternate HTTP response.  (an access no-op, basically).
 
@@ -356,7 +408,23 @@
         'tos_link': redirects.getToSRedirect(site_logic.getSingleton())}
 
     raise out_of_band.LoginRequest(message_fmt=login_msg_fmt)
-  
+
+  @allowDeveloper
+  def checkIsUserSelf(self, django_args):
+    """Checks whether the specified user is the logged in user
+
+    Args:
+      django_args: the keyword args from django, only scope_path is used
+    """
+
+    if not 'scope_path' in django_args:
+      self.deny(django_args)
+
+    if self.user.link_id == django_args['scope_path']:
+      return
+
+    raise out_of_band.AccessViolation()
+
   def checkIsUnusedAccount(self, django_args):
     """Raises an alternate HTTP response if Google Account has a User entity.
 
@@ -593,6 +661,12 @@
 
     raise out_of_band.LoginRequest(message_fmt=login_message_fmt)
 
+  def checkHasHostEntity(self, django_args):
+    """Checks whether the current user has a Host entity.
+    """
+
+    self.checkIsHost({})
+
   @denySidebar
   @allowDeveloper
   def checkIsHostForProgram(self, django_args):
@@ -688,6 +762,65 @@
     raise out_of_band.LoginRequest(message_fmt=login_message_fmt)
 
   @allowDeveloper
+  @allowIfCheckPasses('checkIsClubAdminForClub')
+  def checkIsClubMemberForClub(self, django_args):
+    """Returns an alternate HTTP response if Google Account has no Club Member
+       entity for the specified club.
+
+    Args:
+      django_args: a dictionary with django's arguments
+
+     Raises:
+       AccessViolationResponse: if the required authorization is not met
+
+    Returns:
+      None if Club Member exists for the specified club, or a subclass of
+      django.http.HttpResponse which contains the alternate response
+      should be returned by the calling view.
+    """
+
+    self.checkIsUser(django_args)
+
+    if django_args.get('scope_path'):
+      scope_path = django_args['scope_path']
+    else:
+      scope_path = django_args['link_id']
+
+    fields = {'user': self.user,
+              'scope_path': scope_path,
+              'status': 'active'}
+
+    club_member_entity = club_member_logic.getForFields(fields, unique=True)
+
+    if club_member_entity:
+      return
+
+    login_message_fmt = DEF_DEV_LOGOUT_LOGIN_MSG_FMT % {
+        'role': 'a Club Member for this Club'}
+
+    raise out_of_band.LoginRequest(message_fmt=login_message_fmt)
+
+  def checkIsClubAdminForScope(self, django_args):
+    """Checks whether the current user is a Club Mdmin.
+
+    Args:
+      django_args: the keyword arguments from django, only scope_path is used
+    """
+
+    scope_path = django_args['scope_path']
+    self.checkIsClubAdminForClub({'link_id': scope_path})
+
+  def checkIsClubMemberForScope(self, django_args):
+    """Checks whether the current user is a Club Mdmin.
+
+    Args:
+      django_args: the keyword arguments from django, only scope_path is used
+    """
+
+    scope_path = django_args['scope_path']
+    self.checkIsClubMemberForClub({'link_id': scope_path})
+
+  @allowDeveloper
   def checkIsApplicationAccepted(self, django_args, app_logic):
     """Returns an alternate HTTP response if Google Account has no Club App
        entity for the specified Club.
@@ -883,15 +1016,33 @@
 
   @denySidebar
   @allowDeveloper
-  def checkIsDocumentPublic(self, django_args):
-    """Checks whether a document is public.
+  def checkIsDocumentReadable(self, django_args):
+    """Checks whether a document is readable.
 
     Args:
       django_args: a dictionary with django's arguments
     """
 
     key_fields = document_logic.getKeyFieldsFromFields(django_args)
-    document_logic.getFromKeyFields(key_fields)
+    document = document_logic.getFromKeyFields(key_fields)
+
+    self.checkMembership('read', document.prefix,
+                         document.read_access, django_args)
+
+  @denySidebar
+  @allowDeveloper
+  def checkIsDocumentWritable(self, django_args):
+    """Checks whether a document is writable.
+
+    Args:
+      django_args: a dictionary with django's arguments
+    """
+
+    key_fields = document_logic.getKeyFieldsFromFields(django_args)
+    document = document_logic.getFromKeyFields(key_fields)
+
+    self.checkMembership('write', document.prefix,
+                         document.write_access, django_args)
 
   @allowIfCheckPasses('checkIsHostForProgram')
   def checkIsProgramVisible(self, django_args):
@@ -919,10 +1070,10 @@
     raise out_of_band.AccessViolation(DEF_DEV_LOGOUT_LOGIN_MSG_FMT,
                                       context=context)
 
+  def checkCanEditTimeline(self, django_args):
+    """Checks whether this program's timeline may be edited.
+    """
 
-  def checkCanEditTimeline(self, django_args):
-    """Allows developers and hosts for this program's timeline to edit it.
-    """
     time_line_keyname = django_args['scope_path']
     timeline_entity = timeline_logic.getFromKeyName(time_line_keyname)
 
--- a/app/soc/views/models/document.py	Sat Jan 31 22:32:21 2009 +0000
+++ b/app/soc/views/models/document.py	Sat Jan 31 22:32:54 2009 +0000
@@ -61,7 +61,10 @@
 
     rights = access.Checker(params)
     rights['any_access'] = ['allow']
-    rights['show'] = ['checkIsDocumentPublic']
+    rights['show'] = ['checkIsDocumentReadable']
+    rights['create'] = ['checkIsUser']
+    rights['edit'] = ['checkIsDocumentWritable']
+    rights['delete'] = ['checkIsDocumentWritable']
 
     new_params = {}
     new_params['logic'] = document_logic