def accountFromPrincipal(principal): """Adapt ILaunchpadPrincipal to IAccount.""" if ILaunchpadPrincipal.providedBy(principal): return principal.account else: # This is not actually necessary when this is used as an adapter # from ILaunchpadPrincipal, as we know we always have an # ILaunchpadPrincipal. # # When Zope3 interfaces allow returning None for "cannot adapt" # we can return None here. ##return None raise ComponentLookupError
def person_from_principal(principal): """Adapt `ILaunchpadPrincipal` to `IPerson`.""" if ILaunchpadPrincipal.providedBy(principal): if principal.person is None: raise ComponentLookupError return principal.person else: # This is not actually necessary when this is used as an adapter # from ILaunchpadPrincipal, as we know we always have an # ILaunchpadPrincipal. # # When Zope3 interfaces allow returning None for "cannot adapt" # we can return None here. ##return None raise ComponentLookupError
def iter_authorization(objecttoauthorize, permission, principal, cache, breadth_first=True): """Work through `IAuthorization` adapters for `objecttoauthorize`. Adapters are permitted to delegate checks to other adapters, and this manages that delegation such that the minimum number of checks are made, subject to a breadth-first check of delegations. This also updates `cache` as it goes along, though `cache` can be `None` if no caching is desired. Only leaf values are cached; the results of a delegated authorization are not cached. """ # Check if this calculation has already been done. if cache is not None and objecttoauthorize in cache: if permission in cache[objecttoauthorize]: # Result cached => yield and return. yield cache[objecttoauthorize][permission] return # Create a check_auth function to call checkAuthenticated or # checkUnauthenticated as appropriate. if ILaunchpadPrincipal.providedBy(principal): check_auth = lambda authorization: ( authorization.checkAuthenticated(IPersonRoles(principal.person))) else: check_auth = lambda authorization: ( authorization.checkUnauthenticated()) # Each entry in queue should be an iterable of (object, permission) # tuples, upon which permission checks will be performed. queue = deque() enqueue = (queue.append if breadth_first else queue.appendleft) # Enqueue the starting object and permission. enqueue(((objecttoauthorize, permission),)) while len(queue) != 0: for obj, permission in queue.popleft(): # Unwrap object; see checkPermission for why. obj = removeAllProxies(obj) # First, check the cache. if cache is not None: if obj in cache and permission in cache[obj]: # Result cached => yield and skip to the next. yield cache[obj][permission] continue # Get an IAuthorization for (obj, permission). authorization = queryAdapter(obj, IAuthorization, permission) if authorization is None: # No authorization adapter => denied. yield False continue # We have an authorization adapter, so check it. This is one of # the possibly-expensive bits that a cache can help with. result = check_auth(authorization) # Is the authorization adapter delegating to other objects? if isinstance(result, Iterable): enqueue(result) continue # We have a non-delegated result. if result is not True and result is not False: warnings.warn( '%r returned %r (%r)' % ( authorization, result, type(result))) result = bool(result) # Update the cache if one has been provided. if cache is not None: if obj in cache: cache[obj][permission] = result else: cache[obj] = {permission: result} # Let the world know. yield result
def checkPermission(self, permission, object): """Check the permission, object, user against the launchpad authorization policy. If the object is a view, then consider the object to be the view's context. If we are running in read-only mode, all permission checks are failed except for launchpad.View requests, which are checked as normal. All other permissions are used to protect write operations. Workflow: - If the principal is not None and its access level is not what is required by the permission, deny. - If the object to authorize is private and the principal has no access to private objects, deny. - If we have zope.Public, allow. (But we shouldn't ever get this.) - If we have launchpad.AnyPerson and the principal is an ILaunchpadPrincipal then allow. - If the object has an IAuthorization named adapter, named after the permission, use that to check the permission. - Otherwise, deny. """ # If we have a view, get its context and use that to get an # authorization adapter. if IView.providedBy(object): objecttoauthorize = object.context else: objecttoauthorize = object if objecttoauthorize is None: # We will not be able to lookup an adapter for this, so we can # return False already. return False # Remove all proxies from object to authorize. The security proxy is # removed for obvious reasons but we also need to remove the location # proxy (which is used on apidoc.lp.dev) because otherwise we can't # create a weak reference to our object in our security policy cache. objecttoauthorize = removeAllProxies(objecttoauthorize) participations = [ participation for participation in self.participations if participation.principal is not system_user] if len(participations) > 1: raise RuntimeError("More than one principal participating.") # The participation's cache of (object -> permission -> result), or # None if the participation does not support caching. participation_cache = None # A cache of (permission -> result) for objecttoauthorize, or None if # the participation does not support caching. This resides as a value # of participation_cache. object_cache = None if len(participations) == 0: principal = None else: participation = participations[0] if IApplicationRequest.providedBy(participation): participation_cache = participation.annotations.setdefault( LAUNCHPAD_SECURITY_POLICY_CACHE_KEY, weakref.WeakKeyDictionary()) object_cache = participation_cache.setdefault( objecttoauthorize, {}) if permission in object_cache: return object_cache[permission] principal = removeAllProxies(participation.principal) if (principal is not None and not isinstance(principal, UnauthenticatedPrincipal)): access_level = self._getPrincipalsAccessLevel( principal, objecttoauthorize) if not self._checkRequiredAccessLevel( access_level, permission, objecttoauthorize): return False if not self._checkPrivacy(access_level, objecttoauthorize): return False # The following two checks shouldn't be needed, strictly speaking, # because zope.Public is CheckerPublic, and the Zope security # machinery shortcuts this to always allow it. However, it is here as # a "belt and braces". It is also a bit of a lie: if the permission is # zope.Public, privacy and access levels (checked above) will be # irrelevant! if permission == 'zope.Public': return True if permission is CheckerPublic: return True if (permission == 'launchpad.AnyPerson' and ILaunchpadPrincipal.providedBy(principal)): return True # If there are delegated authorizations they must *all* be allowed # before permission to access objecttoauthorize is granted. result = all( iter_authorization( objecttoauthorize, permission, principal, participation_cache, breadth_first=True)) # Cache the top-level result. Be warned that this result /may/ be # based on 10s or 100s of delegated authorization checks, and so even # small changes in the model data could invalidate this result. if object_cache is not None: object_cache[permission] = result return result
def iter_authorization(objecttoauthorize, permission, principal, cache, breadth_first=True): """Work through `IAuthorization` adapters for `objecttoauthorize`. Adapters are permitted to delegate checks to other adapters, and this manages that delegation such that the minimum number of checks are made, subject to a breadth-first check of delegations. This also updates `cache` as it goes along, though `cache` can be `None` if no caching is desired. Only leaf values are cached; the results of a delegated authorization are not cached. """ # Check if this calculation has already been done. if cache is not None and objecttoauthorize in cache: if permission in cache[objecttoauthorize]: # Result cached => yield and return. yield cache[objecttoauthorize][permission] return # Create a check_auth function to call checkAuthenticated or # checkUnauthenticated as appropriate. if ILaunchpadPrincipal.providedBy(principal): check_auth = lambda authorization: (authorization.checkAuthenticated(IPersonRoles(principal.person))) else: check_auth = lambda authorization: (authorization.checkUnauthenticated()) # Each entry in queue should be an iterable of (object, permission) # tuples, upon which permission checks will be performed. queue = deque() enqueue = queue.append if breadth_first else queue.appendleft # Enqueue the starting object and permission. enqueue(((objecttoauthorize, permission),)) while len(queue) != 0: for obj, permission in queue.popleft(): # Unwrap object; see checkPermission for why. obj = removeAllProxies(obj) # First, check the cache. if cache is not None: if obj in cache and permission in cache[obj]: # Result cached => yield and skip to the next. yield cache[obj][permission] continue # Get an IAuthorization for (obj, permission). authorization = queryAdapter(obj, IAuthorization, permission) if authorization is None: # No authorization adapter => denied. yield False continue # We have an authorization adapter, so check it. This is one of # the possibly-expensive bits that a cache can help with. result = check_auth(authorization) # Is the authorization adapter delegating to other objects? if isinstance(result, Iterable): enqueue(result) continue # We have a non-delegated result. if result is not True and result is not False: warnings.warn("%r returned %r (%r)" % (authorization, result, type(result))) result = bool(result) # Update the cache if one has been provided. if cache is not None: if obj in cache: cache[obj][permission] = result else: cache[obj] = {permission: result} # Let the world know. yield result
def checkPermission(self, permission, object): """Check the permission, object, user against the launchpad authorization policy. If the object is a view, then consider the object to be the view's context. If we are running in read-only mode, all permission checks are failed except for launchpad.View requests, which are checked as normal. All other permissions are used to protect write operations. Workflow: - If the principal is not None and its access level is not what is required by the permission, deny. - If the object to authorize is private and the principal has no access to private objects, deny. - If we have zope.Public, allow. (But we shouldn't ever get this.) - If we have launchpad.AnyPerson and the principal is an ILaunchpadPrincipal then allow. - If the object has an IAuthorization named adapter, named after the permission, use that to check the permission. - Otherwise, deny. """ # If we have a view, get its context and use that to get an # authorization adapter. if IView.providedBy(object): objecttoauthorize = object.context else: objecttoauthorize = object if objecttoauthorize is None: # We will not be able to lookup an adapter for this, so we can # return False already. return False # Remove all proxies from object to authorize. The security proxy is # removed for obvious reasons but we also need to remove the location # proxy (which is used on apidoc.lp.dev) because otherwise we can't # create a weak reference to our object in our security policy cache. objecttoauthorize = removeAllProxies(objecttoauthorize) participations = [ participation for participation in self.participations if participation.principal is not system_user ] if len(participations) > 1: raise RuntimeError("More than one principal participating.") # The participation's cache of (object -> permission -> result), or # None if the participation does not support caching. participation_cache = None # A cache of (permission -> result) for objecttoauthorize, or None if # the participation does not support caching. This resides as a value # of participation_cache. object_cache = None if len(participations) == 0: principal = None else: participation = participations[0] if IApplicationRequest.providedBy(participation): participation_cache = participation.annotations.setdefault( LAUNCHPAD_SECURITY_POLICY_CACHE_KEY, weakref.WeakKeyDictionary() ) object_cache = participation_cache.setdefault(objecttoauthorize, {}) if permission in object_cache: return object_cache[permission] principal = removeAllProxies(participation.principal) if principal is not None and not isinstance(principal, UnauthenticatedPrincipal): access_level = self._getPrincipalsAccessLevel(principal, objecttoauthorize) if not self._checkRequiredAccessLevel(access_level, permission, objecttoauthorize): return False if not self._checkPrivacy(access_level, objecttoauthorize): return False # The following two checks shouldn't be needed, strictly speaking, # because zope.Public is CheckerPublic, and the Zope security # machinery shortcuts this to always allow it. However, it is here as # a "belt and braces". It is also a bit of a lie: if the permission is # zope.Public, privacy and access levels (checked above) will be # irrelevant! if permission == "zope.Public": return True if permission is CheckerPublic: return True if permission == "launchpad.AnyPerson" and ILaunchpadPrincipal.providedBy(principal): return True # If there are delegated authorizations they must *all* be allowed # before permission to access objecttoauthorize is granted. result = all( iter_authorization(objecttoauthorize, permission, principal, participation_cache, breadth_first=True) ) # Cache the top-level result. Be warned that this result /may/ be # based on 10s or 100s of delegated authorization checks, and so even # small changes in the model data could invalidate this result. if object_cache is not None: object_cache[permission] = result return result