def test_backend_acl_not_existing_group(self): assert u'NotExistingGroup' not in flaskg.groups acl_rights = ["NotExistingGroup:read,write,admin All:read"] acl = AccessControlList(acl_rights, valid=app.cfg.acl_rights_contents) assert not acl.may(u"Someone", "write")
def testApplyACLByGroup(self): """ security: applying acl by group name""" # This acl string... acl_rights = [ "PGroup,AllGroup:read,write,admin " "AGroup:read " ] acl = AccessControlList(acl_rights, valid=app.cfg.acl_rights_contents) # Should apply these rights: users = ( # user, rights ('Antony', ('read', 'write', 'admin', )), # in PGroup ('Beatrice', ('read', 'write', 'admin', )), # in PGroup ('Charles', ('read', )), # virtually in AGroup ) # Check rights for user, may in users: mayNot = [right for right in app.cfg.acl_rights_contents if right not in may] # User should have these rights... for right in may: assert acl.may(user, right) # But NOT these: for right in mayNot: assert not acl.may(user, right)
def test_backend_acl_allow(self): """ Test if the wiki group backend works with acl code. Check user which has rights. """ acl_rights = ["AdminGroup:admin,read,write"] acl = AccessControlList(acl_rights, valid=app.cfg.acl_rights_contents) for user in self.expanded_groups['AdminGroup']: for permission in ["read", "write", "admin"]: assert acl.may(u"Admin1", permission), '{0} must have {1} permission because he is member of the AdminGroup'.format(user, permission)
def test_backend_acl_with_all(self): acl_rights = ["EditorGroup:read,write,admin All:read"] acl = AccessControlList(acl_rights, valid=app.cfg.acl_rights_contents) for member in self.expanded_groups[u'EditorGroup']: for permission in ["read", "write", "admin"]: assert acl.may(member, permission) assert acl.may(u"Someone", "read") for permission in ["write", "admin"]: assert not acl.may(u"Someone", permission)
def testApplyACLByUser(self): """ security: applying acl by user name""" # This acl string... acl_rights = [ "-MinusGuy:read " "+MinusGuy:read " "+PlusGuy:read " "-PlusGuy:read " "Admin1,Admin2:read,write,admin " "Admin3:read,write,admin " "JoeDoe:read,write " "name with spaces,another one:read,write " "CamelCase,extended name:read,write " "BadGuy: " "All:read " ] acl = AccessControlList(acl_rights, valid=app.cfg.acl_rights_contents) # Should apply these rights: users = ( # user, rights # CamelCase names ('Admin1', ('read', 'write', 'admin')), ('Admin2', ('read', 'write', 'admin')), ('Admin3', ('read', 'write', 'admin')), ('JoeDoe', ('read', 'write')), ('SomeGuy', ('read', )), # Extended names or mix of extended and CamelCase ('name with spaces', ('read', 'write', )), ('another one', ('read', 'write', )), ('CamelCase', ('read', 'write', )), ('extended name', ('read', 'write', )), # Blocking bad guys ('BadGuy', ()), # All other users - every one not mentioned in the acl lines ('All', ('read', )), ('Anonymous', ('read', )), # we check whether ACL processing stops for a user/right match # with ACL modifiers ('MinusGuy', ()), ('PlusGuy', ('read', )), ) # Check rights for user, may in users: mayNot = [right for right in app.cfg.acl_rights_contents if right not in may] # User should have these rights... for right in may: assert acl.may(user, right) # But NOT these: for right in mayNot: assert not acl.may(user, right)
def test_backend_acl_deny(self): """ Test if the wiki group backend works with acl code. Check user which does not have rights. """ acl_rights = ["AdminGroup:read,write"] acl = AccessControlList(acl_rights, valid=app.cfg.acl_rights_contents) assert u"SomeUser" not in flaskg.groups['AdminGroup'] for permission in ["read", "write"]: assert not acl.may(u"SomeUser", permission), 'SomeUser must not have {0} permission because he is not listed in the AdminGroup'.format(permission) assert u'Admin1' in flaskg.groups['AdminGroup'] assert not acl.may(u"Admin1", "admin")
def test_wiki_backend_item_acl_usergroupmember_item(self): """ Test if the wiki group backend works with acl code. First check acl rights of a user that is not a member of group then add user member to an item group and check acl rights """ become_trusted() update_item(u'NewGroup', {USERGROUP: ["ExampleUser"]}, DATA) acl_rights = ["NewGroup:read,write"] acl = AccessControlList(acl_rights, valid=app.cfg.acl_rights_contents) has_rights_before = acl.may(u"AnotherUser", "read") # update item - add AnotherUser to a item group NewGroup update_item(u'NewGroup', {USERGROUP: ["AnotherUser"]}, '') has_rights_after = acl.may(u"AnotherUser", "read") assert not has_rights_before, 'AnotherUser has no read rights because in the beginning he is not a member of a group item NewGroup' assert has_rights_after, 'AnotherUser must have read rights because after appenditem he is member of NewGroup'
def __call__(self, value, start_pos=0, positions=False, mode=u'', **kwargs): """ Calls AccessControlList for tokenization Analyzer behaviour: In index mode: Input: u"JoeDoe,JaneDoe:admin,read,write,destroy +EditorGroup:write All:read" Output: "u'JoeDoe:+read', u'JoeDoe:+write', u'JoeDoe:-create', u'JoeDoe:+admin', u'JoeDoe:+destroy', u'JaneDoe:+read', u'JaneDoe:+write', u'JaneDoe:-create', u'JaneDoe:+admin', u'JaneDoe:+destroy', u'EditorGroup:+write', u'All:+read', u'All:-write', u'All:-create', u'All:-admin', u'All:-destroy' In query mode: Input: u"JoeDoe:+write" Output: u"JoeDoe:+write" :param value: unicode string :param positions: Whether to record token positions in the token. :param start_pos: The position number of the first token. For example, if you set start_pos=2, the tokens will be numbered 2,3,4,... instead of 0,1,2,... """ assert isinstance(value, unicode) pos = start_pos tk = Token() tk.mode = mode if mode == "query": tk.text = value if positions: tk.pos = pos yield tk else: acl = AccessControlList([value], valid=self._acl_rights_contents) for name, permissions in acl.acl: for permission in permissions: sign = "+" if permissions[permission] else "-" tk.text = u"{0}:{1}{2}".format(name, sign, permission) if positions: tk.pos = pos pos += 1 yield tk
def _get_acl(self, itemname): """ Get ACL strings from the last revision's metadata and return ACL object. """ try: item = self.backend.get_item(itemname) # we always use the ACLs set on the latest revision: current_rev = item.get_revision(-1) acls = current_rev[ACL] except (NoSuchItemError, NoSuchRevisionError, KeyError): # do not use default acl here acls = [] if not isinstance(acls, (tuple, list)): acls = (acls, ) default = self.default.default return AccessControlList(self.cfg, acls, default=default, valid=self.valid)
def __init__(self, cfg, backend, hierarchic=False, before=u"", default=u"", after=u"", valid=None): """ @type backend: Some object that implements the storage API. @param backend: The unprotected backend that we want to protect. @type hierarchic: bool @param hierarchic: Indicate whether we want to process ACLs in hierarchic mode. @type before: unicode @param before: ACL to be applied before all the other ACLs. @type default: unicode @param default: If no ACL information is given on the item in question, use this default. @type after: unicode @param after: ACL to be applied after all the other ACLs. @type valid: list of strings or None @param valid: If a list is given, only strings in the list are treated as valid acl privilege descriptors. If None is give, the global wiki default is used. """ self.cfg = cfg self.backend = backend self.hierarchic = hierarchic self.valid = valid self.before = AccessControlList(cfg, [before], default=default, valid=valid) self.default = AccessControlList(cfg, [default], default=default, valid=valid) self.after = AccessControlList(cfg, [after], default=default, valid=valid)
def __init__(self): """ Init Config instance """ self.cache = CacheClass() if self.config_check_enabled: self._config_check() # define directories data_dir = os.path.normpath(self.data_dir) self.data_dir = data_dir # Try to decode certain names which allow unicode self._decode() # After that, pre-compile some regexes self.cache.item_dict_regex = re.compile(self.item_dict_regex, re.UNICODE) self.cache.item_group_regex = re.compile(self.item_group_regex, re.UNICODE) # the ..._regexact versions only match if nothing is left (exact match) self.cache.item_dict_regexact = re.compile(u'^{0}$'.format(self.item_dict_regex, re.UNICODE)) self.cache.item_group_regexact = re.compile(u'^{0}$'.format(self.item_group_regex, re.UNICODE)) # compiled functions ACL self.cache.acl_functions = AccessControlList([self.acl_functions], valid=self.acl_rights_functions) plugins._loadPluginModule(self) if self.user_defaults[TIMEZONE] is None: self.user_defaults[TIMEZONE] = self.timezone_default if self.user_defaults[THEME_NAME] is None: self.user_defaults[THEME_NAME] = self.theme_default # Note: do not assign user_defaults['locale'] = locale_default # to give browser language detection a chance. try: self.language_default = parse_locale(self.locale_default)[0] except ValueError: raise error.ConfigurationError("Invalid locale_default value (give something like 'en_US').") # post process self.auth_can_logout = [] self.auth_login_inputs = [] found_names = [] for auth in self.auth: if not auth.name: raise error.ConfigurationError("Auth methods must have a name.") if auth.name in found_names: raise error.ConfigurationError("Auth method names must be unique.") found_names.append(auth.name) if auth.logout_possible and auth.name: self.auth_can_logout.append(auth.name) for input in auth.login_inputs: if input not in self.auth_login_inputs: self.auth_login_inputs.append(input) self.auth_have_login = len(self.auth_login_inputs) > 0 self.auth_methods = found_names # internal dict for plugin `modules' lists self._site_plugin_lists = {} # check if mail is possible and set flag: self.mail_enabled = (self.mail_smarthost is not None or self.mail_sendmail is not None) and self.mail_from self.mail_enabled = self.mail_enabled and True or False if self.namespace_mapping is None: raise error.ConfigurationError( "No storage configuration specified! You need to define a namespace_mapping. " "For further reference, please see HelpOnStorageConfiguration.") if self.backend_mapping is None: raise error.ConfigurationError( "No storage configuration specified! You need to define a backend_mapping. " + "For further reference, please see HelpOnStorageConfiguration.") if self.acl_mapping is None: raise error.ConfigurationError( "No acl configuration specified! You need to define a acl_mapping. " "For further reference, please see HelpOnStorageConfiguration.") if self.secrets is None: # admin did not setup a real secret raise error.ConfigurationError( "No secret configured! You need to set secrets = 'somelongsecretstring' in your wiki config.") if self.interwikiname is None: # admin did not setup a real interwikiname raise error.ConfigurationError( "No interwikiname configured! " "You need to set interwikiname = u'YourUniqueStableInterwikiName' in your wiki config.") secret_key_names = ['security/ticket', ] if self.textchas: secret_key_names.append('security/textcha') secret_min_length = 10 if isinstance(self.secrets, str): if len(self.secrets) < secret_min_length: raise error.ConfigurationError( "The secrets = '...' wiki config setting is a way too short string " "(minimum length is {0} chars)!".format(secret_min_length)) # for lazy people: set all required secrets to same value secrets = {} for key in secret_key_names: secrets[key] = self.secrets self.secrets = secrets # we check if we have all secrets we need and that they have minimum length for secret_key_name in secret_key_names: try: secret = self.secrets[secret_key_name] if len(secret) < secret_min_length: raise ValueError except (KeyError, ValueError): raise error.ConfigurationError( "You must set a (at least {0} chars long) secret string for secrets['{1}']!".format( secret_min_length, secret_key_name)) from passlib.context import CryptContext try: self.cache.pwd_context = CryptContext(**self.passlib_crypt_context) except ValueError as err: raise error.ConfigurationError("passlib_crypt_context configuration is invalid [{0}].".format(err))
def _parse_acl(self, acl, default=''): return AccessControlList([ acl, ], default=default, valid=self.valid_rights)
def testhasACL(self): acl = AccessControlList(valid=app.cfg.acl_rights_contents) assert not acl.has_acl() acl = AccessControlList(["All:read", ], valid=app.cfg.acl_rights_contents) assert acl.has_acl()
class AclWrapperBackend(object): """ The AMW is bound to a specific request. The actual backend is retrieved from the config upon request initialization. Any method that is in some way relevant to security needs to be wrapped in order to ensure the user has the permissions necessary to perform the desired action. Note: This may *not* inherit from MoinMoin.storage.Backend because that would break our __getattr__ attribute 'redirects' (which are necessary because a backend implementor may decide to use his own helper functions which the items and revisions will still try to call). """ def __init__(self, cfg, backend, hierarchic=False, before=u"", default=u"", after=u"", valid=None): """ @type backend: Some object that implements the storage API. @param backend: The unprotected backend that we want to protect. @type hierarchic: bool @param hierarchic: Indicate whether we want to process ACLs in hierarchic mode. @type before: unicode @param before: ACL to be applied before all the other ACLs. @type default: unicode @param default: If no ACL information is given on the item in question, use this default. @type after: unicode @param after: ACL to be applied after all the other ACLs. @type valid: list of strings or None @param valid: If a list is given, only strings in the list are treated as valid acl privilege descriptors. If None is give, the global wiki default is used. """ self.cfg = cfg self.backend = backend self.hierarchic = hierarchic self.valid = valid self.before = AccessControlList(cfg, [before], default=default, valid=valid) self.default = AccessControlList(cfg, [default], default=default, valid=valid) self.after = AccessControlList(cfg, [after], default=default, valid=valid) def __getattr__(self, attr): # Attributes that this backend does not define itself are just looked # up on the real backend. return getattr(self.backend, attr) def search_items(self, searchterm): """ @see: Backend.search_items.__doc__ """ for item in self.backend.search_items(searchterm): if self._may(item.name, READ): # The item returned needs to be wrapped because otherwise the # item's methods (like create_revision) wouldn't be wrapped. wrapped_item = AclWrapperItem(item, self) yield wrapped_item def get_item(self, itemname): """ @see: Backend.get_item.__doc__ """ if not self._may(itemname, READ): raise AccessDeniedError(flaskg.user.name, READ, itemname) real_item = self.backend.get_item(itemname) # Wrap the item here as well. wrapped_item = AclWrapperItem(real_item, self) return wrapped_item def has_item(self, itemname): """ @see: Backend.has_item.__doc__ """ # We do not hide the sheer existance of items. When trying # to create an item with the same name, the user would notice anyway. return self.backend.has_item(itemname) def create_item(self, itemname): """ @see: Backend.create_item.__doc__ """ if not self._may(itemname, CREATE): raise AccessDeniedError(flaskg.user.name, CREATE, itemname) real_item = self.backend.create_item(itemname) # Wrap item. wrapped_item = AclWrapperItem(real_item, self) return wrapped_item def iteritems(self): """ @see: Backend.iteritems.__doc__ """ for item in self.backend.iteritems(): if self._may(item.name, READ): yield AclWrapperItem(item, self) def history(self, reverse=True): """ @see: Backend.history.__doc__ """ for revision in self.backend.history(reverse): if self._may(revision.item.name, READ): # The revisions returned here should only be StoredRevisions. # We wrap them nevertheless to be sure. Esp. revision.item # would otherwise give access to an unwrapped item. item = revision.item item = AclWrapperItem(item, self) revision = AclWrapperRevision(revision, item) yield revision def _get_acl(self, itemname): """ Get ACL strings from the last revision's metadata and return ACL object. """ try: item = self.backend.get_item(itemname) # we always use the ACLs set on the latest revision: current_rev = item.get_revision(-1) acls = current_rev[ACL] except (NoSuchItemError, NoSuchRevisionError, KeyError): # do not use default acl here acls = [] if not isinstance(acls, (tuple, list)): acls = (acls, ) default = self.default.default return AccessControlList(self.cfg, acls, default=default, valid=self.valid) def _may(self, itemname, right): """ Check if self.username may have <right> access on item <itemname>. For hierarchic=False we just check the item in question. For hierarchic=True, we check each item in the hierarchy. We start with the deepest item and recurse to the top of the tree. If one of those permits, True is returned. This is done *only* if there is *no ACL at all* (not even an empty one) on the items we 'recurse over'. For both configurations, we check `before` before the item/default acl and `after` after the item/default acl, of course. `default` is only used if there is no ACL on the item (and none on any of the item's parents when using hierarchic.) @param itemname: item to get permissions from @param right: the right to check @rtype: bool @return: True if you have permission or False """ username = flaskg.user.name allowed = self.before.may(username, right) if allowed is not None: return allowed if self.hierarchic: items = itemname.split('/') # create item hierarchy list some_acl = False for i in range(len(items), 0, -1): # Create the next pagename in the hierarchy # starting at the leaf, going to the root name = '/'.join(items[:i]) acl = self._get_acl(name) if acl.has_acl(): some_acl = True allowed = acl.may(username, right) if allowed is not None: return allowed # If the item has an acl (even one that doesn't match) we *do not* # check the parents. We only check the parents if there's no acl on # the item at all. break if not some_acl: allowed = self.default.may(username, right) if allowed is not None: return allowed else: acl = self._get_acl(itemname) if acl.has_acl(): allowed = acl.may(username, right) if allowed is not None: return allowed else: allowed = self.default.may(username, right) if allowed is not None: return allowed allowed = self.after.may(username, right) if allowed is not None: return allowed return False