def manage_FTPstat(self, REQUEST): """Psuedo stat, used by FTP for directory listings. """ from AccessControl.User import nobody warn( u'manage_FTPstat is deprecated and will be removed in ' u'Zope 5.', DeprecationWarning, stacklevel=2) mode = 0o0100000 if (hasattr(aq_base(self), 'manage_FTPget')): try: if getSecurityManager().validate(None, self, 'manage_FTPget', self.manage_FTPget): mode = mode | 0o0440 except Unauthorized: pass if nobody.allowed( self.manage_FTPget, getRoles(self, 'manage_FTPget', self.manage_FTPget, ())): mode = mode | 0o0004 # check write permissions if hasattr(aq_base(self), 'PUT'): try: if getSecurityManager().validate(None, self, 'PUT', self.PUT): mode = mode | 0o0220 except Unauthorized: pass if nobody.allowed(self.PUT, getRoles(self, 'PUT', self.PUT, ())): mode = mode | 0o0002 # get size if hasattr(aq_base(self), 'get_size'): size = self.get_size() elif hasattr(aq_base(self), 'manage_FTPget'): size = len(self.manage_FTPget()) else: size = 0 # get modification time if hasattr(aq_base(self), '_p_mtime'): mtime = DateTime(self._p_mtime).timeTime() else: mtime = time.time() # get owner and group owner = group = 'Zope' if hasattr(aq_base(self), 'get_local_roles'): for user, roles in self.get_local_roles(): if 'Owner' in roles: owner = user break return marshal.dumps( (mode, 0, 0, 1, owner, group, size, mtime, mtime, mtime))
def manage_FTPstat(self, REQUEST): """Psuedo stat, used by FTP for directory listings. """ from AccessControl.User import nobody mode = 0o0100000 if (hasattr(aq_base(self), 'manage_FTPget')): try: if getSecurityManager().validate( None, self, 'manage_FTPget', self.manage_FTPget): mode = mode | 0o0440 except Unauthorized: pass if nobody.allowed( self.manage_FTPget, getRoles(self, 'manage_FTPget', self.manage_FTPget, ())): mode = mode | 0o0004 # check write permissions if hasattr(aq_base(self), 'PUT'): try: if getSecurityManager().validate(None, self, 'PUT', self.PUT): mode = mode | 0o0220 except Unauthorized: pass if nobody.allowed( self.PUT, getRoles(self, 'PUT', self.PUT, ())): mode = mode | 0o0002 # get size if hasattr(aq_base(self), 'get_size'): size = self.get_size() elif hasattr(aq_base(self), 'manage_FTPget'): size = len(self.manage_FTPget()) else: size = 0 # get modification time if hasattr(aq_base(self), '_p_mtime'): mtime = DateTime(self._p_mtime).timeTime() else: mtime = time.time() # get owner and group owner = group = 'Zope' if hasattr(aq_base(self), 'get_local_roles'): for user, roles in self.get_local_roles(): if 'Owner' in roles: owner = user break return marshal.dumps( (mode, 0, 0, 1, owner, group, size, mtime, mtime, mtime))
def manage_FTPstat(self, REQUEST): """Psuedo stat, used by FTP for directory listings. """ mode = 0o0040000 from AccessControl.User import nobody # check to see if we are acquiring our objectValues or not if not (len(REQUEST.PARENTS) > 1 and self.objectValues() == REQUEST.PARENTS[1].objectValues()): try: if getSecurityManager().validate(None, self, 'manage_FTPlist', self.manage_FTPlist): mode = mode | 0o0770 except Exception: pass if nobody.allowed( self, getRoles(self, 'manage_FTPlist', self.manage_FTPlist, ())): mode = mode | 0o0007 if hasattr(aq_base(self), '_p_mtime'): mtime = DateTime(self._p_mtime).timeTime() else: mtime = time.time() # get owner and group owner = group = 'Zope' for user, roles in self.get_local_roles(): if 'Owner' in roles: owner = user break return marshal.dumps( (mode, 0, 0, 1, owner, group, 0, mtime, mtime, mtime))
def manage_FTPstat(self,REQUEST): """Psuedo stat, used by FTP for directory listings. """ mode=0040000 from AccessControl.User import nobody # check to see if we are acquiring our objectValues or not if not (len(REQUEST.PARENTS) > 1 and self.objectValues() == REQUEST.PARENTS[1].objectValues()): try: if getSecurityManager().validate( None, self, 'manage_FTPlist', self.manage_FTPlist ): mode=mode | 0770 except: pass if nobody.allowed( self, getRoles(self, 'manage_FTPlist', self.manage_FTPlist, ())): mode=mode | 0007 if hasattr(aq_base(self), '_p_mtime'): mtime = DateTime(self._p_mtime).timeTime() else: mtime = time.time() # get owner and group owner=group='Zope' for user, roles in self.get_local_roles(): if 'Owner' in roles: owner=user break return marshal.dumps((mode,0,0,1,owner,group,0,mtime,mtime,mtime))
def testGetRolesWithMultiThread(self): from AccessControl.ZopeSecurityPolicy import getRoles class C(object): pass class V1(object): class __roles__(object): @staticmethod def rolesForPermissionOn(ob): return ['Member'] class V2(object): class __roles__(object): @staticmethod def rolesForPermissionOn(ob): return ['User'] c = C() c.v1 = V1() c.v2 = V2() self.assertEqual(getRoles(c, None, c.v1, 42), ['Member']) self.assertEqual(getRoles(c, None, c.v2, 42), ['User']) mark = [] def loop(): while 1: getRoles(c, None, c.v2, 42) if len(mark) > 0: return thread.start_new_thread(loop, ()) try: for i in range(1000): self.assertEqual(getRoles(c, None, c.v1, 42), ['Member']) finally: mark.append(None)
def loop(): while 1: getRoles(c, None, c.v2, 42) if len(mark) > 0: return
def validate(self, accessed, container, name, value, context, roles=_noroles, getattr=getattr, _noroles=_noroles, valid_aq_=('aq_parent', 'aq_inner', 'aq_explicit')): ############################################################ # Provide special rules for the acquisition attributes if isinstance(name, str): if name.startswith('aq_') and name not in valid_aq_: if self._verbose: raiseVerbose( 'aq_* names (other than %s) are not allowed' % ', '.join(valid_aq_), accessed, container, name, value, context) raise Unauthorized(name, value) containerbase = aq_base(container) accessedbase = aq_base(accessed) if accessedbase is accessed: # accessed is not a wrapper, so assume that the # value could not have been acquired. accessedbase = container ############################################################ # If roles weren't passed in, we'll try to get them from the object if roles is _noroles: roles = getRoles(container, name, value, _noroles) ############################################################ # We still might not have any roles if roles is _noroles: ############################################################ # We have an object without roles and we didn't get a list # of roles passed in. Presumably, the value is some simple # object like a string or a list. We'll try to get roles # from its container. if container is None: # Either container or a list of roles is required # for ZopeSecurityPolicy to know whether access is # allowable. if self._verbose: raiseVerbose('No container provided', accessed, container, name, value, context) raise Unauthorized(name, value) roles = getattr(container, '__roles__', roles) if roles is _noroles: if containerbase is container: # Container is not wrapped. if containerbase is not accessedbase: if self._verbose: raiseVerbose( 'Unable to find __roles__ in the container ' 'and the container is not wrapped', accessed, container, name, value, context) raise Unauthorized(name, value) else: # Try to acquire roles try: roles = aq_acquire(container, '__roles__') except AttributeError: if containerbase is not accessedbase: if self._verbose: raiseVerbose( 'Unable to find or acquire __roles__ ' 'from the container', accessed, container, name, value, context) raise Unauthorized(name, value) # We need to make sure that we are allowed to # get unprotected attributes from the container. We are # allowed for certain simple containers and if the # container says we can. Simple containers # may also impose name restrictions. p = Containers(type(container), None) if p is None: p = getattr(container, '__allow_access_to_unprotected_subobjects__', None) if p is not None: if not isinstance(p, int): # catches bool too if isinstance(p, dict): if isinstance(name, basestring): p = p.get(name) else: p = 1 else: p = p(name, value) if not p: if self._verbose: raiseVerbose('The container has no security assertions', accessed, container, name, value, context) raise Unauthorized(name, value) if roles is _noroles: return 1 # We are going to need a security-aware object to pass # to allowed(). We'll use the container. value = container # Short-circuit tests if we can: try: if roles is None or 'Anonymous' in roles: return 1 except TypeError: # 'roles' isn't a sequence LOG.error("'%s' passed as roles" " during validation of '%s' is not a sequence." % ( ` roles `, name)) raise # Check executable security stack = context.stack if stack: eo = stack[-1] # If the executable had an owner, can it execute? if self._ownerous: owner = eo.getOwner() if (owner is not None) and not owner.allowed(value, roles): # We don't want someone to acquire if they can't # get an unacquired! if self._verbose: if len(roles) < 1: raiseVerbose("The object is marked as private", accessed, container, name, value, context) elif userHasRolesButNotInContext(owner, value, roles): raiseVerbose( "The owner of the executing script is defined " "outside the context of the object being " "accessed", accessed, container, name, value, context, required_roles=roles, eo_owner=owner, eo=eo) else: raiseVerbose( "The owner of the executing script does not " "have the required permission", accessed, container, name, value, context, required_roles=roles, eo_owner=owner, eo=eo, eo_owner_roles=getUserRolesInContext( owner, value)) raise Unauthorized(name, value) # Proxy roles, which are a lot safer now. proxy_roles = getattr(eo, '_proxy_roles', None) if proxy_roles: # Verify that the owner actually can state the proxy role # in the context of the accessed item; users in subfolders # should not be able to use proxy roles to access items # above their subfolder! owner = eo.getWrappedOwner() if owner is not None: if container is not containerbase: # Unwrapped objects don't need checking if not owner._check_context(container): # container is higher up than the owner, # deny access if self._verbose: raiseVerbose( "The owner of the executing script is " "defined outside the context of the " "object being accessed. The script has " "proxy roles, but they do not apply in " "this context.", accessed, container, name, value, context, required_roles=roles, eo_owner=owner, eo=eo) raise Unauthorized(name, value) for r in proxy_roles: if r in roles: return 1 # Proxy roles actually limit access! if self._verbose: if len(roles) < 1: raiseVerbose("The object is marked as private", accessed, container, name, value, context) else: raiseVerbose( "The proxy roles set on the executing script " "do not allow access", accessed, container, name, value, context, eo=eo, eo_proxy_roles=proxy_roles, required_roles=roles) raise Unauthorized(name, value) try: if self._authenticated and context.user.allowed(value, roles): return 1 except AttributeError: pass if self._verbose: if len(roles) < 1: raiseVerbose("The object is marked as private", accessed, container, name, value, context) elif not self._authenticated: raiseVerbose( "Authenticated access is not allowed by this " "security policy", accessed, container, name, value, context) elif userHasRolesButNotInContext(context.user, value, roles): raiseVerbose( "Your user account is defined outside " "the context of the object being accessed", accessed, container, name, value, context, required_roles=roles, user=context.user) else: raiseVerbose( "Your user account does not " "have the required permission", accessed, container, name, value, context, required_roles=roles, user=context.user, user_roles=getUserRolesInContext(context.user, value)) raise Unauthorized(name, value)
def validate(self, accessed, container, name, value, context, roles=_noroles, getattr=getattr, _noroles=_noroles, valid_aq_=('aq_parent', 'aq_inner', 'aq_explicit')): ############################################################ # Provide special rules for the acquisition attributes if isinstance(name, str): if name.startswith('aq_') and name not in valid_aq_: if self._verbose: raiseVerbose( 'aq_* names (other than %s) are not allowed' % ', '.join(valid_aq_), accessed, container, name, value, context, ) raise Unauthorized(name, value) containerbase = aq_base(container) accessedbase = aq_base(accessed) if accessedbase is accessed: # accessed is not a wrapper, so assume that the # value could not have been acquired. accessedbase = container ############################################################ # If roles weren't passed in, we'll try to get them from the object if roles is _noroles: roles = getRoles(container, name, value, _noroles) ############################################################ # We still might not have any roles if roles is _noroles: ############################################################ # We have an object without roles and we didn't get a list # of roles passed in. Presumably, the value is some simple # object like a string or a list. We'll try to get roles # from its container. if container is None: # Either container or a list of roles is required # for ZopeSecurityPolicy to know whether access is # allowable. if self._verbose: raiseVerbose( 'No container provided', accessed, container, name, value, context) raise Unauthorized(name, value) roles = getattr(container, '__roles__', roles) if roles is _noroles: if containerbase is container: # Container is not wrapped. if containerbase is not accessedbase: if self._verbose: raiseVerbose( 'Unable to find __roles__ in the container ' 'and the container is not wrapped', accessed, container, name, value, context, ) raise Unauthorized(name, value) else: # Try to acquire roles try: roles = aq_acquire(container, '__roles__') except AttributeError: if containerbase is not accessedbase: if self._verbose: raiseVerbose( 'Unable to find or acquire __roles__ ' 'from the container', accessed, container, name, value, context) raise Unauthorized(name, value) # We need to make sure that we are allowed to # get unprotected attributes from the container. We are # allowed for certain simple containers and if the # container says we can. Simple containers # may also impose name restrictions. p = Containers(type(container), None) if p is None: p = getattr(container, '__allow_access_to_unprotected_subobjects__', None) if p is not None: if not isinstance(p, (bool, int)): if isinstance(p, dict): if isinstance(name, string_types): p = p.get(name) else: p = 1 else: p = p(name, value) if not p: if self._verbose: raiseVerbose( 'The container has no security assertions', accessed, container, name, value, context, ) raise Unauthorized(name, value) if roles is _noroles: return 1 # We are going to need a security-aware object to pass # to allowed(). We'll use the container. value = container # Short-circuit tests if we can: try: if roles is None or 'Anonymous' in roles: return 1 except TypeError: # 'roles' isn't a sequence LOG.error( "'%r' passed as roles" " during validation of '%s' is not a sequence." % ( roles, name, ), ) raise # Check executable security stack = context.stack if stack: eo = stack[-1] # If the executable had an owner, can it execute? if self._ownerous: owner = eo.getOwner() if (owner is not None) and not owner.allowed(value, roles): # We don't want someone to acquire if they can't # get an unacquired! if self._verbose: if len(roles) < 1: raiseVerbose( "The object is marked as private", accessed, container, name, value, context, ) elif userHasRolesButNotInContext(owner, value, roles): raiseVerbose( "The owner of the executing script is defined " "outside the context of the object being " "accessed", accessed, container, name, value, context, required_roles=roles, eo_owner=owner, eo=eo, ) else: raiseVerbose( "The owner of the executing script does not " "have the required permission", accessed, container, name, value, context, required_roles=roles, eo_owner=owner, eo=eo, eo_owner_roles=getUserRolesInContext(owner, value), ) raise Unauthorized(name, value) # Proxy roles, which are a lot safer now. proxy_roles = getattr(eo, '_proxy_roles', None) if proxy_roles: # Verify that the owner actually can state the proxy role # in the context of the accessed item; users in subfolders # should not be able to use proxy roles to access items # above their subfolder! owner = eo.getWrappedOwner() if owner is not None: if container is not containerbase: # Unwrapped objects don't need checking if not owner._check_context(container): # container is higher up than the owner, # deny access if self._verbose: raiseVerbose( "The owner of the executing script is " "defined outside the context of the " "object being accessed. The script has " "proxy roles, but they do not apply in " "this context.", accessed, container, name, value, context, required_roles=roles, eo_owner=owner, eo=eo) raise Unauthorized(name, value) for r in proxy_roles: if r in roles: return 1 # Proxy roles actually limit access! if self._verbose: if len(roles) < 1: raiseVerbose( "The object is marked as private", accessed, container, name, value, context) else: raiseVerbose( "The proxy roles set on the executing script " "do not allow access", accessed, container, name, value, context, eo=eo, eo_proxy_roles=proxy_roles, required_roles=roles) raise Unauthorized(name, value) try: if self._authenticated and context.user.allowed(value, roles): return 1 except AttributeError: pass if self._verbose: if len(roles) < 1: raiseVerbose( "The object is marked as private", accessed, container, name, value, context) elif not self._authenticated: raiseVerbose( "Authenticated access is not allowed by this " "security policy", accessed, container, name, value, context) elif userHasRolesButNotInContext(context.user, value, roles): raiseVerbose( "Your user account is defined outside " "the context of the object being accessed", accessed, container, name, value, context, required_roles=roles, user=context.user) else: raiseVerbose( "Your user account does not " "have the required permission", accessed, container, name, value, context, required_roles=roles, user=context.user, user_roles=getUserRolesInContext(context.user, value)) raise Unauthorized(name, value)
def traverse(self, path, response=None, validated_hook=None): """Traverse the object space The REQUEST must already have a PARENTS item with at least one object in it. This is typically the root object. """ request = self request_get = request.get if response is None: response = self.response # remember path for later use browser_path = path # Cleanup the path list if path[:1] == '/': path = path[1:] if path[-1:] == '/': path = path[:-1] clean = [] for item in path.split('/'): # Make sure that certain things that dont make sense # cannot be traversed. if item in ('REQUEST', 'aq_self', 'aq_base'): return response.notFoundError(path) if not item or item == '.': continue elif item == '..': del clean[-1] else: clean.append(item) path = clean # How did this request come in? (HTTP GET, PUT, POST, etc.) method = request_get('REQUEST_METHOD', 'GET').upper() # Probably a browser no_acquire_flag = 0 if (method in ('GET', 'POST', 'PURGE') and not is_xmlrpc_response(response)): # index_html is still the default method, only any object can # override it by implementing its own __browser_default__ method method = 'index_html' elif self.maybe_webdav_client: # Probably a WebDAV client. no_acquire_flag = 1 URL = request['URL'] parents = request['PARENTS'] object = parents[-1] del parents[:] self.roles = getRoles(None, None, object, UNSPECIFIED_ROLES) # if the top object has a __bobo_traverse__ method, then use it # to possibly traverse to an alternate top-level object. if hasattr(object, '__bobo_traverse__'): try: new_object = object.__bobo_traverse__(request) if new_object is not None: object = new_object self.roles = getRoles(None, None, object, UNSPECIFIED_ROLES) except Exception: pass if not path and not method: return response.forbiddenError(self['URL']) # Traverse the URL to find the object: if hasattr(object, '__of__'): # Try to bind the top-level object to the request # This is how you get 'self.REQUEST' object = object.__of__(RequestContainer(REQUEST=request)) parents.append(object) steps = self.steps self._steps = _steps = list(map(quote, steps)) path.reverse() request['TraversalRequestNameStack'] = request.path = path request['ACTUAL_URL'] = request['URL'] + quote(browser_path) # Set the posttraverse for duration of the traversal here self._post_traverse = post_traverse = [] # import time ordering problem if HAS_ZSERVER: from webdav.NullResource import NullResource else: NullResource = None entry_name = '' try: # We build parents in the wrong order, so we # need to make sure we reverse it when we're done. while 1: bpth = getattr(object, '__before_publishing_traverse__', None) if bpth is not None: bpth(object, self) path = request.path = request['TraversalRequestNameStack'] # Check for method: if path: entry_name = path.pop() else: # If we have reached the end of the path, we look to see # if we can find IBrowserPublisher.browserDefault. If so, # we call it to let the object tell us how to publish it. # BrowserDefault returns the object to be published # (usually self) and a sequence of names to traverse to # find the method to be published. # This is webdav support. The last object in the path # should not be acquired. Instead, a NullResource should # be given if it doesn't exist: if (NullResource is not None and no_acquire_flag and hasattr(object, 'aq_base') and not hasattr(object, '__bobo_traverse__')): if (object.__parent__ is not aq_inner(object).__parent__): object = NullResource(parents[-2], object.getId(), self).__of__(parents[-2]) if IBrowserPublisher.providedBy(object): adapter = object else: adapter = queryMultiAdapter((object, self), IBrowserPublisher) if adapter is None: # Zope2 doesn't set up its own adapters in a lot # of cases so we will just use a default adapter. adapter = DefaultPublishTraverse(object, self) object, default_path = adapter.browserDefault(self) if default_path: request._hacked_path = 1 if len(default_path) > 1: path = list(default_path) method = path.pop() request['TraversalRequestNameStack'] = path continue else: entry_name = default_path[0] elif (method and hasattr(object, method) and entry_name != method and getattr(object, method) is not None): request._hacked_path = 1 entry_name = method method = 'index_html' else: if hasattr(object, '__call__'): self.roles = getRoles(object, '__call__', object.__call__, self.roles) if request._hacked_path: i = URL.rfind('/') if i > 0: response.setBase(URL[:i]) break step = quote(entry_name) _steps.append(step) request['URL'] = URL = '%s/%s' % (request['URL'], step) try: subobject = self.traverseName(object, entry_name) if (hasattr(object, '__bobo_traverse__') or hasattr(object, entry_name)): check_name = entry_name else: check_name = None self.roles = getRoles(object, check_name, subobject, self.roles) object = subobject # traverseName() might raise ZTK's NotFound except (KeyError, AttributeError, ztkNotFound): if response.debug_mode: return response.debugError( "Cannot locate object at: %s" % URL) else: return response.notFoundError(URL) except Forbidden as e: if self.response.debug_mode: return response.debugError(e.args) else: return response.forbiddenError(entry_name) parents.append(object) steps.append(entry_name) finally: parents.reverse() # Note - no_acquire_flag is necessary to support # things like DAV. We have to make sure # that the target object is not acquired # if the request_method is other than GET # or POST. Otherwise, you could never use # PUT to add a new object named 'test' if # an object 'test' existed above it in the # heirarchy -- you'd always get the # existing object :( if (no_acquire_flag and hasattr(parents[1], 'aq_base') and not hasattr(parents[1], '__bobo_traverse__')): base = aq_base(parents[1]) if not hasattr(base, entry_name): try: if entry_name not in base: raise AttributeError(entry_name) except TypeError: raise AttributeError(entry_name) # After traversal post traversal hooks aren't available anymore del self._post_traverse request['PUBLISHED'] = parents.pop(0) # Do authorization checks user = groups = None i = 0 if 1: # Always perform authentication. last_parent_index = len(parents) if hasattr(object, '__allow_groups__'): groups = object.__allow_groups__ inext = 0 else: inext = None for i in range(last_parent_index): if hasattr(parents[i], '__allow_groups__'): groups = parents[i].__allow_groups__ inext = i + 1 break if inext is not None: i = inext if hasattr(groups, 'validate'): v = groups.validate else: v = old_validation auth = request._auth if v is old_validation and self.roles is UNSPECIFIED_ROLES: # No roles, so if we have a named group, get roles from # group keys if hasattr(groups, 'keys'): self.roles = list(groups.keys()) else: try: groups = groups() except Exception: pass try: self.roles = list(groups.keys()) except Exception: pass if groups is None: # Public group, hack structures to get it to validate self.roles = None auth = '' if v is old_validation: user = old_validation(groups, request, auth, self.roles) elif self.roles is UNSPECIFIED_ROLES: user = v(request, auth) else: user = v(request, auth, self.roles) while user is None and i < last_parent_index: parent = parents[i] i = i + 1 if hasattr(parent, '__allow_groups__'): groups = parent.__allow_groups__ else: continue if hasattr(groups, 'validate'): v = groups.validate else: v = old_validation if v is old_validation: user = old_validation(groups, request, auth, self.roles) elif self.roles is UNSPECIFIED_ROLES: user = v(request, auth) else: user = v(request, auth, self.roles) if user is None and self.roles != UNSPECIFIED_ROLES: response.unauthorized() if user is not None: if validated_hook is not None: validated_hook(self, user) request['AUTHENTICATED_USER'] = user request['AUTHENTICATION_PATH'] = '/'.join(steps[:-i]) # Remove http request method from the URL. request['URL'] = URL # Run post traversal hooks if post_traverse: result = exec_callables(post_traverse) if result is not None: object = result return object
def traverse(self, path, response=None, validated_hook=None): """Traverse the object space The REQUEST must already have a PARENTS item with at least one object in it. This is typically the root object. """ request=self request_get=request.get if response is None: response=self.response # remember path for later use browser_path = path # Cleanup the path list if path[:1]=='/': path=path[1:] if path[-1:]=='/': path=path[:-1] clean=[] for item in path.split('/'): # Make sure that certain things that dont make sense # cannot be traversed. if item in ('REQUEST', 'aq_self', 'aq_base'): return response.notFoundError(path) if not item or item=='.': continue elif item == '..': del clean[-1] else: clean.append(item) path=clean # How did this request come in? (HTTP GET, PUT, POST, etc.) method = request_get('REQUEST_METHOD', 'GET').upper() if method=='GET' or method=='POST' and not isinstance(response, xmlrpc.Response): # Probably a browser no_acquire_flag=0 # index_html is still the default method, only any object can # override it by implementing its own __browser_default__ method method = 'index_html' elif self.maybe_webdav_client: # Probably a WebDAV client. no_acquire_flag=1 else: no_acquire_flag=0 URL=request['URL'] parents=request['PARENTS'] object=parents[-1] del parents[:] self.roles = getRoles(None, None, object, UNSPECIFIED_ROLES) # if the top object has a __bobo_traverse__ method, then use it # to possibly traverse to an alternate top-level object. if hasattr(object,'__bobo_traverse__'): try: object=object.__bobo_traverse__(request) self.roles = getRoles(None, None, object, UNSPECIFIED_ROLES) except: pass if not path and not method: return response.forbiddenError(self['URL']) # Traverse the URL to find the object: if hasattr(object, '__of__'): # Try to bind the top-level object to the request # This is how you get 'self.REQUEST' object=object.__of__(RequestContainer(REQUEST=request)) parents.append(object) steps=self.steps self._steps = _steps = map(quote, steps) path.reverse() request['TraversalRequestNameStack'] = request.path = path request['ACTUAL_URL'] = request['URL'] + quote(browser_path) # Set the posttraverse for duration of the traversal here self._post_traverse = post_traverse = [] entry_name = '' try: # We build parents in the wrong order, so we # need to make sure we reverse it when we're done. while 1: bpth = getattr(object, '__before_publishing_traverse__', None) if bpth is not None: bpth(object, self) path = request.path = request['TraversalRequestNameStack'] # Check for method: if path: entry_name = path.pop() else: # If we have reached the end of the path, we look to see # if we can find IBrowserPublisher.browserDefault. If so, # we call it to let the object tell us how to publish it. # BrowserDefault returns the object to be published # (usually self) and a sequence of names to traverse to # find the method to be published. # This is webdav support. The last object in the path # should not be acquired. Instead, a NullResource should # be given if it doesn't exist: if (no_acquire_flag and hasattr(object, 'aq_base') and not hasattr(object,'__bobo_traverse__')): if object.aq_parent is not object.aq_inner.aq_parent: from webdav.NullResource import NullResource object = NullResource(parents[-2], object.getId(), self).__of__(parents[-2]) if IBrowserPublisher.providedBy(object): adapter = object else: adapter = queryMultiAdapter((object, self), IBrowserPublisher) if adapter is None: # Zope2 doesn't set up its own adapters in a lot # of cases so we will just use a default adapter. adapter = DefaultPublishTraverse(object, self) object, default_path = adapter.browserDefault(self) if default_path: request._hacked_path=1 if len(default_path) > 1: path = list(default_path) method = path.pop() request['TraversalRequestNameStack'] = path continue else: entry_name = default_path[0] elif (method and hasattr(object,method) and entry_name != method and getattr(object, method) is not None): request._hacked_path=1 entry_name = method method = 'index_html' else: if hasattr(object, '__call__'): self.roles = getRoles(object, '__call__', object.__call__, self.roles) if request._hacked_path: i=URL.rfind('/') if i > 0: response.setBase(URL[:i]) break step = quote(entry_name) _steps.append(step) request['URL'] = URL = '%s/%s' % (request['URL'], step) try: subobject = self.traverseName(object, entry_name) if (hasattr(object,'__bobo_traverse__') or hasattr(object, entry_name)): check_name = entry_name else: check_name = None self.roles = getRoles( object, check_name, subobject, self.roles) object = subobject # traverseName() might raise ZTK's NotFound except (KeyError, AttributeError, ztkNotFound): if response.debug_mode: return response.debugError( "Cannot locate object at: %s" % URL) else: return response.notFoundError(URL) except Forbidden, e: if self.response.debug_mode: return response.debugError(e.args) else: return response.forbiddenError(entry_name) parents.append(object) steps.append(entry_name) finally: parents.reverse() # Note - no_acquire_flag is necessary to support # things like DAV. We have to make sure # that the target object is not acquired # if the request_method is other than GET # or POST. Otherwise, you could never use # PUT to add a new object named 'test' if # an object 'test' existed above it in the # heirarchy -- you'd always get the # existing object :( if (no_acquire_flag and hasattr(parents[1], 'aq_base') and not hasattr(parents[1],'__bobo_traverse__')): if not (hasattr(parents[1].aq_base, entry_name) or parents[1].aq_base.has_key(entry_name)): raise AttributeError, entry_name # After traversal post traversal hooks aren't available anymore del self._post_traverse request['PUBLISHED'] = parents.pop(0) # Do authorization checks user=groups=None i=0 if 1: # Always perform authentication. last_parent_index=len(parents) if hasattr(object, '__allow_groups__'): groups=object.__allow_groups__ inext=0 else: inext=None for i in range(last_parent_index): if hasattr(parents[i],'__allow_groups__'): groups=parents[i].__allow_groups__ inext=i+1 break if inext is not None: i=inext if hasattr(groups, 'validate'): v=groups.validate else: v=old_validation auth=request._auth if v is old_validation and self.roles is UNSPECIFIED_ROLES: # No roles, so if we have a named group, get roles from # group keys if hasattr(groups,'keys'): self.roles=groups.keys() else: try: groups=groups() except: pass try: self.roles=groups.keys() except: pass if groups is None: # Public group, hack structures to get it to validate self.roles=None auth='' if v is old_validation: user=old_validation(groups, request, auth, self.roles) elif self.roles is UNSPECIFIED_ROLES: user=v(request, auth) else: user=v(request, auth, self.roles) while user is None and i < last_parent_index: parent=parents[i] i=i+1 if hasattr(parent, '__allow_groups__'): groups=parent.__allow_groups__ else: continue if hasattr(groups,'validate'): v=groups.validate else: v=old_validation if v is old_validation: user=old_validation(groups, request, auth, self.roles) elif self.roles is UNSPECIFIED_ROLES: user=v(request, auth) else: user=v(request, auth, self.roles) if user is None and self.roles != UNSPECIFIED_ROLES: response.unauthorized() if user is not None: if validated_hook is not None: validated_hook(self, user) request['AUTHENTICATED_USER']=user request['AUTHENTICATION_PATH']='/'.join(steps[:-i]) # Remove http request method from the URL. request['URL']=URL # Run post traversal hooks if post_traverse: result = exec_callables(post_traverse) if result is not None: object = result return object
def test_security_equivalence(self): """ This test demonstrates that the traditional declarative security of Zope 2 can be replaced by ZCML statements without any loss of information. """ # We start out with two classes, ``Dummy1`` and ``Dummy2``. They # are identical in every way, except that ``Dummy2`` has security # declarations and ``Dummy1`` does not. Before we do anything, none # of them have security access controls: from AccessControl.tests.testZCML import Dummy1, Dummy2 self.assertFalse(hasattr(Dummy1, '__ac_permissions__')) self.assertFalse(hasattr(Dummy2, '__ac_permissions__')) # Before we can make security declarations through ZCML, we need to # register the directive and the permission: import AccessControl from zope.configuration.xmlconfig import XMLConfig XMLConfig('meta.zcml', AccessControl)() XMLConfig('permissions.zcml', AccessControl)() # Now we initialize the security for ``Dummy2`` and provide some # ZCML declarations for ``Dummy1``: from io import StringIO configure_zcml = StringIO(u''' <configure xmlns="http://namespaces.zope.org/zope"> <class class="AccessControl.tests.testZCML.Dummy1"> <allow attributes="foo" /> <!--deny attributes="baz" /--> <!-- XXX not yet supported --> </class> <class class="AccessControl.tests.testZCML.Dummy1"> <require attributes="bar keg" permission="zope2.ViewManagementScreens" /> </class> </configure> ''') from zope.configuration.xmlconfig import xmlconfig xmlconfig(configure_zcml) from AccessControl.class_init import InitializeClass InitializeClass(Dummy2) # Now we compare their access controls: ac1 = getattr(Dummy1, '__ac_permissions__') ac2 = getattr(Dummy2, '__ac_permissions__') self.assertEqual( [(perm, set(attr)) for perm, attr in dict(ac1).items()], [(perm, set(attr)) for perm, attr in dict(ac2).items()]) # Now we look at the individual permissions: from AccessControl.ZopeSecurityPolicy import getRoles from AccessControl import ACCESS_PUBLIC from AccessControl import ACCESS_PRIVATE dummy1 = Dummy1() self.assertEqual( getRoles(dummy1, 'bar', dummy1.bar, ('Def', )), ('Manager', )) self.assertEqual( getRoles(dummy1, 'keg', dummy1.keg, ('Def', )), ('Manager', )) self.assertEqual( getRoles(dummy1, 'foo', dummy1.foo, ('Def', )), ACCESS_PUBLIC) # XXX Not yet supported. # self.assertEqual( # getRoles(dummy1, 'baz', dummy1.baz, ('Def', )), # ACCESS_PRIVATE) dummy2 = Dummy2() self.assertEqual( getRoles(dummy2, 'bar', dummy2.bar, ('Def', )), ('Manager', )) self.assertEqual( getRoles(dummy2, 'keg', dummy2.keg, ('Def', )), ('Manager', )) self.assertEqual( getRoles(dummy2, 'foo', dummy2.foo, ('Def', )), ACCESS_PUBLIC) self.assertEqual( getRoles(dummy2, 'baz', dummy2.baz, ('Def', )), ACCESS_PRIVATE)
def traverse(self, path, response=None, validated_hook=None): """Traverse the object space The REQUEST must already have a PARENTS item with at least one object in it. This is typically the root object. """ request=self request_get=request.get if response is None: response=self.response debug_mode=response.debug_mode # remember path for later use browser_path = path # Cleanup the path list if path[:1]=='/': path=path[1:] if path[-1:]=='/': path=path[:-1] clean=[] for item in path.split('/'): # Make sure that certain things that dont make sense # cannot be traversed. if item in ('REQUEST', 'aq_self', 'aq_base'): return response.notFoundError(path) if not item or item=='.': continue elif item == '..': del clean[-1] else: clean.append(item) path=clean # How did this request come in? (HTTP GET, PUT, POST, etc.) method=req_method=request_get('REQUEST_METHOD', 'GET').upper() if method=='GET' or method=='POST' and not isinstance(response, xmlrpc.Response): # Probably a browser no_acquire_flag=0 # index_html is still the default method, only any object can # override it by implementing its own __browser_default__ method method = 'index_html' elif self.maybe_webdav_client: # Probably a WebDAV client. no_acquire_flag=1 else: no_acquire_flag=0 URL=request['URL'] parents=request['PARENTS'] object=parents[-1] del parents[:] roles = getRoles(None, None, object, UNSPECIFIED_ROLES) # if the top object has a __bobo_traverse__ method, then use it # to possibly traverse to an alternate top-level object. if hasattr(object,'__bobo_traverse__'): try: object=object.__bobo_traverse__(request) roles = getRoles(None, None, object, UNSPECIFIED_ROLES) except: pass if not path and not method: return response.forbiddenError(self['URL']) # Traverse the URL to find the object: if hasattr(object, '__of__'): # Try to bind the top-level object to the request # This is how you get 'self.REQUEST' object=object.__of__(RequestContainer(REQUEST=request)) parents.append(object) steps=self.steps self._steps = _steps = map(quote, steps) path.reverse() request['TraversalRequestNameStack'] = request.path = path request['ACTUAL_URL'] = request['URL'] + quote(browser_path) # Set the posttraverse for duration of the traversal here self._post_traverse = post_traverse = [] entry_name = '' try: # We build parents in the wrong order, so we # need to make sure we reverse it when we're doe. while 1: bpth = getattr(object, '__before_publishing_traverse__', None) if bpth is not None: bpth(object, self) path = request.path = request['TraversalRequestNameStack'] # Check for method: if path: entry_name = path.pop() elif hasattr(object, '__browser_default__'): # If we have reached the end of the path. We look to see # if the object implements __browser_default__. If so, we # call it to let the object tell us how to publish it # __browser_default__ returns the object to be published # (usually self) and a sequence of names to traverse to # find the method to be published. (Casey) request._hacked_path=1 object, default_path = object.__browser_default__(request) if len(default_path) > 1: path = list(default_path) method = path.pop() request['TraversalRequestNameStack'] = path continue else: entry_name = default_path[0] elif (method and hasattr(object,method) and entry_name != method and getattr(object, method) is not None): request._hacked_path=1 entry_name = method method = 'index_html' else: if (hasattr(object, '__call__')): roles = getRoles(object, '__call__', object.__call__, roles) if request._hacked_path: i=URL.rfind('/') if i > 0: response.setBase(URL[:i]) break step = quote(entry_name) _steps.append(step) request['URL'] = URL = '%s/%s' % (request['URL'], step) got = 0 if entry_name[:1]=='_': if debug_mode: return response.debugError( "Object name begins with an underscore at: %s" % URL) else: return response.forbiddenError(entry_name) if hasattr(object,'__bobo_traverse__'): try: subobject=object.__bobo_traverse__(request,entry_name) if type(subobject) is type(()) and len(subobject) > 1: # Add additional parents into the path parents[-1:] = list(subobject[:-1]) object, subobject = subobject[-2:] except (AttributeError, KeyError): if debug_mode: return response.debugError( "Cannot locate object at: %s" % URL) else: return response.notFoundError(URL) else: try: # Note - no_acquire_flag is necessary to support # things like DAV. We have to make sure # that the target object is not acquired # if the request_method is other than GET # or POST. Otherwise, you could never use # PUT to add a new object named 'test' if # an object 'test' existed above it in the # heirarchy -- you'd always get the # existing object :( if (no_acquire_flag and len(path) == 0 and hasattr(object, 'aq_base')): if hasattr(object.aq_base, entry_name): subobject=getattr(object, entry_name) else: raise AttributeError, entry_name else: subobject=getattr(object, entry_name) except AttributeError: got=1 try: subobject=object[entry_name] except (KeyError, IndexError, TypeError, AttributeError): if debug_mode: return response.debugError( "Cannot locate object at: %s" % URL) else: return response.notFoundError(URL) # Ensure that the object has a docstring, or that the parent # object has a pseudo-docstring for the object. Objects that # have an empty or missing docstring are not published. doc = getattr(subobject, '__doc__', None) if doc is None: doc = getattr(object, '%s__doc__' % entry_name, None) if not doc: return response.debugError( "The object at %s has an empty or missing " \ "docstring. Objects must have a docstring to be " \ "published." % URL ) # Hack for security: in Python 2.2.2, most built-in types # gained docstrings that they didn't have before. That caused # certain mutable types (dicts, lists) to become publishable # when they shouldn't be. The following check makes sure that # the right thing happens in both 2.2.2+ and earlier versions. if not typeCheck(subobject): return response.debugError( "The object at %s is not publishable." % URL ) roles = getRoles( object, (not got) and entry_name or None, subobject, roles) # Promote subobject to object object=subobject parents.append(object) steps.append(entry_name) finally: parents.reverse() # After traversal post traversal hooks aren't available anymore del self._post_traverse request['PUBLISHED'] = parents.pop(0) # Do authorization checks user=groups=None i=0 if 1: # Always perform authentication. last_parent_index=len(parents) if hasattr(object, '__allow_groups__'): groups=object.__allow_groups__ inext=0 else: inext=None for i in range(last_parent_index): if hasattr(parents[i],'__allow_groups__'): groups=parents[i].__allow_groups__ inext=i+1 break if inext is not None: i=inext if hasattr(groups, 'validate'): v=groups.validate else: v=old_validation auth=request._auth if v is old_validation and roles is UNSPECIFIED_ROLES: # No roles, so if we have a named group, get roles from # group keys if hasattr(groups,'keys'): roles=groups.keys() else: try: groups=groups() except: pass try: roles=groups.keys() except: pass if groups is None: # Public group, hack structures to get it to validate roles=None auth='' if v is old_validation: user=old_validation(groups, request, auth, roles) elif roles is UNSPECIFIED_ROLES: user=v(request, auth) else: user=v(request, auth, roles) while user is None and i < last_parent_index: parent=parents[i] i=i+1 if hasattr(parent, '__allow_groups__'): groups=parent.__allow_groups__ else: continue if hasattr(groups,'validate'): v=groups.validate else: v=old_validation if v is old_validation: user=old_validation(groups, request, auth, roles) elif roles is UNSPECIFIED_ROLES: user=v(request, auth) else: user=v(request, auth, roles) if user is None and roles != UNSPECIFIED_ROLES: response.unauthorized() if user is not None: if validated_hook is not None: validated_hook(self, user) request['AUTHENTICATED_USER']=user request['AUTHENTICATION_PATH']='/'.join(steps[:-i]) # Remove http request method from the URL. request['URL']=URL # Run post traversal hooks if post_traverse: result = exec_callables(post_traverse) if result is not None: object = result return object