class ZODBGroupManager(BasePlugin): """ PAS plugin for managing groups, and groups of groups in the ZODB """ meta_type = 'ZODB Group Manager' security = ClassSecurityInfo() def __init__(self, id, title=None): self._id = self.id = id self.title = title self._groups = OOBTree() self._principal_groups = OOBTree() # # IGroupEnumerationPlugin implementation # security.declarePrivate('enumerateGroups') def enumerateGroups(self, id=None, title=None, exact_match=False, sort_by=None, max_results=None, **kw): """ See IGroupEnumerationPlugin. """ group_info = [] group_ids = [] plugin_id = self.getId() if isinstance(id, basestring): id = [id] if isinstance(title, basestring): title = [title] if exact_match and (id or title): if id: group_ids.extend(id) elif title: group_ids.extend(title) if group_ids: group_filter = None else: # Searching group_ids = self.listGroupIds() group_filter = _ZODBGroupFilter(id, title, **kw) for group_id in group_ids: if self._groups.get(group_id, None): e_url = '%s/manage_groups' % self.getId() p_qs = 'group_id=%s' % group_id m_qs = 'group_id=%s&assign=1' % group_id info = {} info.update(self._groups[group_id]) info['pluginid'] = plugin_id info['properties_url'] = '%s?%s' % (e_url, p_qs) info['members_url'] = '%s?%s' % (e_url, m_qs) info['id'] = '%s%s' % (self.prefix, info['id']) if not group_filter or group_filter(info): group_info.append(info) return tuple(group_info) # # IGroupsPlugin implementation # security.declarePrivate('getGroupsForPrincipal') def getGroupsForPrincipal(self, principal, request=None): """ See IGroupsPlugin. """ unadorned = self._principal_groups.get(principal.getId(), ()) return tuple(['%s%s' % (self.prefix, x) for x in unadorned]) # # (notional)IZODBGroupManager interface # security.declareProtected(ManageGroups, 'listGroupIds') def listGroupIds(self): """ -> ( group_id_1, ... group_id_n ) """ return self._groups.keys() security.declareProtected(ManageGroups, 'listGroupInfo') def listGroupInfo(self): """ -> ( {}, ...{} ) o Return one mapping per group, with the following keys: - 'id' """ return self._groups.values() security.declareProtected(ManageGroups, 'getGroupInfo') def getGroupInfo(self, group_id): """ group_id -> {} """ return self._groups[group_id] security.declarePrivate('addGroup') def addGroup(self, group_id, title=None, description=None): """ Add 'group_id' to the list of groups managed by this object. o Raise KeyError on duplicate. """ if self._groups.get(group_id) is not None: raise KeyError, 'Duplicate group ID: %s' % group_id self._groups[group_id] = { 'id': group_id, 'title': title, 'description': description } security.declarePrivate('updateGroup') def updateGroup(self, group_id, title, description): """ Update properties for 'group_id' o Raise KeyError if group_id doesn't already exist. """ self._groups[group_id].update({ 'title': title, 'description': description }) self._groups[group_id] = self._groups[group_id] security.declarePrivate('removeGroup') def removeGroup(self, group_id): """ Remove 'role_id' from the list of roles managed by this object, removing assigned members from it before doing so. o Raise KeyError if 'group_id' doesn't already exist. """ for principal_id in self._principal_groups.keys(): self.removePrincipalFromGroup(principal_id, group_id) del self._groups[group_id] # # Group assignment API # security.declareProtected(ManageGroups, 'listAvailablePrincipals') def listAvailablePrincipals(self, group_id, search_id): """ Return a list of principal IDs to that can belong to the group. o If supplied, 'search_id' constrains the principal IDs; if not, return empty list. o Omit principals with existing assignments. """ result = [] if search_id: # don't bother searching if no criteria parent = aq_parent(self) for info in parent.searchPrincipals(max_results=20, sort_by='id', id=search_id, exact_match=False): id = info['id'] title = info.get('title', id) if (group_id not in self._principal_groups.get(id, ()) and group_id != id): result.append((id, title)) return result security.declareProtected(ManageGroups, 'listAssignedPrincipals') def listAssignedPrincipals(self, group_id): """ Return a list of principal IDs belonging to a group. """ result = [] for k, v in self._principal_groups.items(): if group_id in v: parent = aq_parent(self) info = parent.searchPrincipals(id=k, exact_match=True) if len(info) == 0: title = '<%s: not found>' % k else: # always use the title of the first principal found title = info[0].get('title', k) result.append((k, title)) return result security.declareProtected(ManageGroups, 'addPrincipalToGroup') def addPrincipalToGroup(self, principal_id, group_id, REQUEST=None): """ Add a principal to a group. o Return a boolean indicating whether a new assignment was created. o Raise KeyError if 'group_id' is unknown. """ group_info = self._groups[group_id] # raise KeyError if unknown! current = self._principal_groups.get(principal_id, ()) already = group_id in current if not already: new = current + (group_id, ) self._principal_groups[principal_id] = new self._invalidatePrincipalCache(principal_id) return not already addPrincipalToGroup = postonly(addPrincipalToGroup) security.declareProtected(ManageGroups, 'removePrincipalFromGroup') def removePrincipalFromGroup(self, principal_id, group_id, REQUEST=None): """ Remove a prinicpal from from a group. o Return a boolean indicating whether the principal was already a member of the group. o Raise KeyError if 'group_id' is unknown. o Ignore requests to remove a principal if not already a member of the group. """ group_info = self._groups[group_id] # raise KeyError if unknown! current = self._principal_groups.get(principal_id, ()) new = tuple([x for x in current if x != group_id]) already = current != new if already: self._principal_groups[principal_id] = new self._invalidatePrincipalCache(principal_id) return already removePrincipalFromGroup = postonly(removePrincipalFromGroup) # # ZMI # manage_options = (({ 'label': 'Groups', 'action': 'manage_groups', }, ) + BasePlugin.manage_options) security.declarePublic('manage_widgets') manage_widgets = PageTemplateFile('www/zuWidgets', globals(), __name__='manage_widgets') security.declareProtected(ManageGroups, 'manage_groups') manage_groups = PageTemplateFile('www/zgGroups', globals(), __name__='manage_groups') security.declareProtected(ManageGroups, 'manage_twoLists') manage_twoLists = PageTemplateFile('../www/two_lists', globals(), __name__='manage_twoLists') security.declareProtected(ManageGroups, 'manage_addGroup') def manage_addGroup(self, group_id, title=None, description=None, RESPONSE=None): """ Add a group via the ZMI. """ self.addGroup(group_id, title, description) message = 'Group+added' if RESPONSE is not None: RESPONSE.redirect('%s/manage_groups?manage_tabs_message=%s' % (self.absolute_url(), message)) security.declareProtected(ManageGroups, 'manage_updateGroup') def manage_updateGroup(self, group_id, title, description, RESPONSE=None): """ Update a group via the ZMI. """ self.updateGroup(group_id, title, description) message = 'Group+updated' if RESPONSE is not None: RESPONSE.redirect('%s/manage_groups?manage_tabs_message=%s' % (self.absolute_url(), message)) security.declareProtected(ManageGroups, 'manage_removeGroups') def manage_removeGroups(self, group_ids, RESPONSE=None, REQUEST=None): """ Remove one or more groups via the ZMI. """ group_ids = filter(None, group_ids) if not group_ids: message = 'no+groups+selected' else: for group_id in group_ids: self.removeGroup(group_id) message = 'Groups+removed' if RESPONSE is not None: RESPONSE.redirect('%s/manage_groups?manage_tabs_message=%s' % (self.absolute_url(), message)) manage_removeGroups = postonly(manage_removeGroups) security.declareProtected(ManageGroups, 'manage_addPrincipalsToGroup') def manage_addPrincipalsToGroup(self, group_id, principal_ids, RESPONSE=None, REQUEST=None): """ Add one or more principals to a group via the ZMI. """ assigned = [] for principal_id in principal_ids: if self.addPrincipalToGroup(principal_id, group_id): assigned.append(principal_id) if not assigned: message = 'Principals+already+members+of+%s' % group_id else: message = '%s+added+to+%s' % ('+'.join(assigned), group_id) if RESPONSE is not None: RESPONSE.redirect(('%s/manage_groups?group_id=%s&assign=1' + '&manage_tabs_message=%s') % (self.absolute_url(), group_id, message)) manage_addPrincipalsToGroup = postonly(manage_addPrincipalsToGroup) security.declareProtected(ManageGroups, 'manage_removePrincipalsFromGroup') def manage_removePrincipalsFromGroup(self, group_id, principal_ids, RESPONSE=None, REQUEST=None): """ Remove one or more principals from a group via the ZMI. """ removed = [] for principal_id in principal_ids: if self.removePrincipalFromGroup(principal_id, group_id): removed.append(principal_id) if not removed: message = 'Principals+not+in+group+%s' % group_id else: message = 'Principals+%s+removed+from+%s' % ('+'.join(removed), group_id) if RESPONSE is not None: RESPONSE.redirect(('%s/manage_groups?group_id=%s&assign=1' + '&manage_tabs_message=%s') % (self.absolute_url(), group_id, message)) manage_removePrincipalsFromGroup = postonly( manage_removePrincipalsFromGroup)
class ZODBUserManager(BasePlugin, Cacheable): """ PAS plugin for managing users in the ZODB. """ meta_type = 'ZODB User Manager' security = ClassSecurityInfo() def __init__(self, id, title=None): self._id = self.id = id self.title = title self._user_passwords = OOBTree() self._login_to_userid = OOBTree() self._userid_to_login = OOBTree() # # IAuthenticationPlugin implementation # security.declarePrivate('authenticateCredentials') def authenticateCredentials(self, credentials): """ See IAuthenticationPlugin. o We expect the credentials to be those returned by ILoginPasswordExtractionPlugin. """ login = credentials.get('login') password = credentials.get('password') if login is None or password is None: return None userid = self._login_to_userid.get(login, login) reference = self._user_passwords.get(userid) if reference is None: return None if AuthEncoding.is_encrypted(reference): if AuthEncoding.pw_validate(reference, password): return userid, login # Support previous naive behavior digested = sha(password).hexdigest() if reference == digested: return userid, login return None # # IUserEnumerationPlugin implementation # security.declarePrivate('enumerateUsers') def enumerateUsers(self, id=None, login=None, exact_match=False, sort_by=None, max_results=None, **kw): """ See IUserEnumerationPlugin. """ user_info = [] user_ids = [] plugin_id = self.getId() view_name = createViewName('enumerateUsers', id or login) if isinstance(id, basestring): id = [id] if isinstance(login, basestring): login = [login] # Look in the cache first... keywords = copy.deepcopy(kw) keywords.update({ 'id': id, 'login': login, 'exact_match': exact_match, 'sort_by': sort_by, 'max_results': max_results }) cached_info = self.ZCacheable_get(view_name=view_name, keywords=keywords, default=None) if cached_info is not None: return tuple(cached_info) terms = id or login if exact_match: if terms: if id: # if we're doing an exact match based on id, it # absolutely will have been qualified (if we have a # prefix), so we can ignore any that don't begin with # our prefix id = [x for x in id if x.startswith(self.prefix)] user_ids.extend([x[len(self.prefix):] for x in id]) elif login: user_ids.extend( [self._login_to_userid.get(x) for x in login]) # we're claiming an exact match search, if we still don't # have anything, better bail. if not user_ids: return () else: # insane - exact match with neither login nor id return () if user_ids: user_filter = None else: # Searching user_ids = self.listUserIds() user_filter = _ZODBUserFilter(id, login, **kw) for user_id in user_ids: if self._userid_to_login.get(user_id): e_url = '%s/manage_users' % self.getId() qs = 'user_id=%s' % user_id info = { 'id': self.prefix + user_id, 'login': self._userid_to_login[user_id], 'pluginid': plugin_id, 'editurl': '%s?%s' % (e_url, qs) } if not user_filter or user_filter(info): user_info.append(info) # Put the computed value into the cache self.ZCacheable_set(user_info, view_name=view_name, keywords=keywords) return tuple(user_info) # # IUserAdderPlugin implementation # security.declarePrivate('doAddUser') def doAddUser(self, login, password): try: self.addUser(login, login, password) except KeyError: return False return True # # (notional)IZODBUserManager interface # security.declareProtected(ManageUsers, 'listUserIds') def listUserIds(self): """ -> ( user_id_1, ... user_id_n ) """ return self._user_passwords.keys() security.declareProtected(ManageUsers, 'getUserInfo') def getUserInfo(self, user_id): """ user_id -> {} """ return { 'user_id': user_id, 'login_name': self._userid_to_login[user_id], 'pluginid': self.getId() } security.declareProtected(ManageUsers, 'listUserInfo') def listUserInfo(self): """ -> ( {}, ...{} ) o Return one mapping per user, with the following keys: - 'user_id' - 'login_name' """ return [self.getUserInfo(x) for x in self._user_passwords.keys()] security.declareProtected(ManageUsers, 'getUserIdForLogin') def getUserIdForLogin(self, login_name): """ login_name -> user_id o Raise KeyError if no user exists for the login name. """ return self._login_to_userid[login_name] security.declareProtected(ManageUsers, 'getLoginForUserId') def getLoginForUserId(self, user_id): """ user_id -> login_name o Raise KeyError if no user exists for that ID. """ return self._userid_to_login[user_id] security.declarePrivate('addUser') def addUser(self, user_id, login_name, password): if self._user_passwords.get(user_id) is not None: raise KeyError, 'Duplicate user ID: %s' % user_id if self._login_to_userid.get(login_name) is not None: raise KeyError, 'Duplicate login name: %s' % login_name self._user_passwords[user_id] = self._pw_encrypt(password) self._login_to_userid[login_name] = user_id self._userid_to_login[user_id] = login_name # enumerateUsers return value has changed view_name = createViewName('enumerateUsers') self.ZCacheable_invalidate(view_name=view_name) security.declarePrivate('updateUser') def updateUser(self, user_id, login_name): # The following raises a KeyError if the user_id is invalid old_login = self.getLoginForUserId(user_id) del self._login_to_userid[old_login] self._login_to_userid[login_name] = user_id self._userid_to_login[user_id] = login_name security.declarePrivate('removeUser') def removeUser(self, user_id): if self._user_passwords.get(user_id) is None: raise KeyError, 'Invalid user ID: %s' % user_id login_name = self._userid_to_login[user_id] del self._user_passwords[user_id] del self._login_to_userid[login_name] del self._userid_to_login[user_id] # Also, remove from the cache view_name = createViewName('enumerateUsers') self.ZCacheable_invalidate(view_name=view_name) view_name = createViewName('enumerateUsers', user_id) self.ZCacheable_invalidate(view_name=view_name) security.declarePrivate('updateUserPassword') def updateUserPassword(self, user_id, password): if self._user_passwords.get(user_id) is None: raise KeyError, 'Invalid user ID: %s' % user_id if password: self._user_passwords[user_id] = self._pw_encrypt(password) security.declarePrivate('_pw_encrypt') def _pw_encrypt(self, password): """Returns the AuthEncoding encrypted password If 'password' is already encrypted, it is returned as is and not encrypted again. """ if AuthEncoding.is_encrypted(password): return password return AuthEncoding.pw_encrypt(password) # # ZMI # manage_options = (({ 'label': 'Users', 'action': 'manage_users', }, ) + BasePlugin.manage_options + Cacheable.manage_options) security.declarePublic('manage_widgets') manage_widgets = PageTemplateFile('www/zuWidgets', globals(), __name__='manage_widgets') security.declareProtected(ManageUsers, 'manage_users') manage_users = PageTemplateFile('www/zuUsers', globals(), __name__='manage_users') security.declareProtected(ManageUsers, 'manage_addUser') def manage_addUser(self, user_id, login_name, password, confirm, RESPONSE=None): """ Add a user via the ZMI. """ if password != confirm: message = 'password+and+confirm+do+not+match' else: if not login_name: login_name = user_id # XXX: validate 'user_id', 'login_name' against policies? self.addUser(user_id, login_name, password) message = 'User+added' if RESPONSE is not None: RESPONSE.redirect('%s/manage_users?manage_tabs_message=%s' % (self.absolute_url(), message)) security.declareProtected(ManageUsers, 'manage_updateUserPassword') def manage_updateUserPassword(self, user_id, password, confirm, RESPONSE=None, REQUEST=None): """ Update a user's login name / password via the ZMI. """ if password and password != confirm: message = 'password+and+confirm+do+not+match' else: self.updateUserPassword(user_id, password) message = 'password+updated' if RESPONSE is not None: RESPONSE.redirect('%s/manage_users?manage_tabs_message=%s' % (self.absolute_url(), message)) manage_updateUserPassword = postonly(manage_updateUserPassword) security.declareProtected(ManageUsers, 'manage_updateUser') def manage_updateUser(self, user_id, login_name, RESPONSE=None): """ Update a user's login name via the ZMI. """ if not login_name: login_name = user_id # XXX: validate 'user_id', 'login_name' against policies? self.updateUser(user_id, login_name) message = 'Login+name+updated' if RESPONSE is not None: RESPONSE.redirect('%s/manage_users?manage_tabs_message=%s' % (self.absolute_url(), message)) security.declareProtected(ManageUsers, 'manage_removeUsers') def manage_removeUsers(self, user_ids, RESPONSE=None, REQUEST=None): """ Remove one or more users via the ZMI. """ user_ids = filter(None, user_ids) if not user_ids: message = 'no+users+selected' else: for user_id in user_ids: self.removeUser(user_id) message = 'Users+removed' if RESPONSE is not None: RESPONSE.redirect('%s/manage_users?manage_tabs_message=%s' % (self.absolute_url(), message)) manage_removeUsers = postonly(manage_removeUsers) # # Allow users to change their own login name and password. # security.declareProtected(SetOwnPassword, 'getOwnUserInfo') def getOwnUserInfo(self): """ Return current user's info. """ user_id = getSecurityManager().getUser().getId() return self.getUserInfo(user_id) security.declareProtected(SetOwnPassword, 'manage_updatePasswordForm') manage_updatePasswordForm = PageTemplateFile( 'www/zuPasswd', globals(), __name__='manage_updatePasswordForm') security.declareProtected(SetOwnPassword, 'manage_updatePassword') def manage_updatePassword(self, login_name, password, confirm, RESPONSE=None, REQUEST=None): """ Update the current user's password and login name. """ user_id = getSecurityManager().getUser().getId() if password != confirm: message = 'password+and+confirm+do+not+match' else: if not login_name: login_name = user_id # XXX: validate 'user_id', 'login_name' against policies? self.updateUser(user_id, login_name) self.updateUserPassword(user_id, password) message = 'password+updated' if RESPONSE is not None: RESPONSE.redirect('%s/manage_updatePasswordForm' '?manage_tabs_message=%s' % (self.absolute_url(), message)) manage_updatePassword = postonly(manage_updatePassword)
class DynamicGroupsPlugin(Folder, BasePlugin, Cacheable): """ Define groups via business rules. o Membership in a candidate group is established via a predicate, expressed as a TALES expression. Names available to the predicate include: 'group' -- the dynamic group definition object itself 'plugin' -- this plugin object 'principal' -- the principal being tested. 'request' -- the request object. """ meta_type = 'Dynamic Groups Plugin' security = ClassSecurityInfo() def __init__(self, id, title=''): self._setId(id) self.title = title # # Plugin implementations # security.declareProtected(ManageGroups, 'getGroupsForPrincipal') def getGroupsForPrincipal(self, principal, request=None): """ See IGroupsPlugin. """ grps = [] DGD = DynamicGroupDefinition.meta_type for group in self.objectValues(DGD): if group.active and group(principal, request): grps.append('%s%s' % (self.prefix, group.getId())) return grps security.declareProtected(ManageGroups, 'enumerateGroups') def enumerateGroups(self, id=None, exact_match=False, sort_by=None, max_results=None, **kw): """ See IGroupEnumerationPlugin. """ group_info = [] group_ids = [] plugin_id = self.getId() view_name = createViewName('enumerateGroups', id) # Look in the cache first... keywords = copy.deepcopy(kw) keywords.update({ 'id': id, 'exact_match': exact_match, 'sort_by': sort_by, 'max_results': max_results }) cached_info = self.ZCacheable_get(view_name=view_name, keywords=keywords, default=None) if cached_info is not None: return tuple(cached_info) if isinstance(id, str): id = [id] if exact_match and id: group_ids.extend(id) if group_ids: group_filter = None else: # Searching group_ids = self.listGroupIds() group_filter = _DynamicGroupFilter(id, **kw) for group_id in group_ids: url = '/%s/%s/manage_propertiesForm' % (self.absolute_url(1), group_id) info = {} info.update(self.getGroupInfo(group_id)) info['pluginid'] = plugin_id info['properties_url'] = url info['members_url'] = url info['id'] = '%s%s' % (self.prefix, info['id']) if not group_filter or group_filter(info): if info['active']: group_info.append(info) # Put the computed value into the cache self.ZCacheable_set(group_info, view_name=view_name, keywords=keywords) return tuple(group_info) # # Housekeeping # security.declareProtected(ManageGroups, 'listGroupIds') def listGroupIds(self): """ Return a list of IDs for the dynamic groups we manage. """ return self.objectIds(DynamicGroupDefinition.meta_type) security.declareProtected(ManageGroups, 'getGroupInfo') def getGroupInfo(self, group_id): """ Return a mappings describing one dynamic group we manage. o Raise KeyError if we don't have an existing group definition for 'group_ id'. o Keys include: 'id' -- the group's ID 'predicate' -- the TALES expression defining group membership 'active' -- boolean flag: is the group currently active? """ try: original = self._getOb(group_id) except AttributeError: try: original = self._getOb(group_id[len(self.prefix):]) except AttributeError: raise KeyError, group_id if not isinstance(original, DynamicGroupDefinition): raise KeyError, group_id info = {} for k, v in original.propertyItems(): info[k] = v return info security.declareProtected(ManageGroups, 'listGroupInfo') def listGroupInfo(self): """ Return a list of mappings describing the dynamic groups we manage. o Keys include: 'id' -- the group's ID 'predicate' -- the TALES expression defining group membership 'active' -- boolean flag: is the group currently active? """ return [self.getGroupInfo(x) for x in self.listGroupIds()] security.declareProtected(ManageGroups, 'addGroup') def addGroup(self, group_id, predicate, title='', description='', active=True): """ Add a group definition. o Raise KeyError if we have an existing group definition for 'group_id'. """ if group_id in self.listGroupIds(): raise KeyError, 'Duplicate group ID: %s' % group_id info = DynamicGroupDefinition(group_id, predicate, title, description, active) self._setObject(group_id, info) # This method changes the enumerateGroups return value view_name = createViewName('enumerateGroups') self.ZCacheable_invalidate(view_name=view_name) security.declareProtected(ManageGroups, 'updateGroup') def updateGroup(self, group_id, predicate, title=None, description=None, active=None): """ Update a group definition. o Raise KeyError if we don't have an existing group definition for 'group_id'. o Don't update 'title', 'description', or 'active' unless supplied. """ if group_id not in self.listGroupIds(): raise KeyError, 'Invalid group ID: %s' % group_id group = self._getOb(group_id) group._setPredicate(predicate) if title is not None: group.title = title if description is not None: group.description = description if active is not None: group.active = active # This method changes the enumerateGroups return value view_name = createViewName('enumerateGroups') self.ZCacheable_invalidate(view_name=view_name) view_name = createViewName('enumerateGroups', group_id) self.ZCacheable_invalidate(view_name=view_name) security.declareProtected(ManageGroups, 'removeGroup') def removeGroup(self, group_id, REQUEST=None): """ Remove a group definition. o Raise KeyError if we don't have an existing group definition for 'group_id'. """ if group_id not in self.listGroupIds(): raise KeyError, 'Invalid group ID: %s' % group_id self._delObject(group_id) # This method changes the enumerateGroups return value view_name = createViewName('enumerateGroups') self.ZCacheable_invalidate(view_name=view_name) view_name = createViewName('enumerateGroups', group_id) self.ZCacheable_invalidate(view_name=view_name) removeGroup = postonly(removeGroup) # # ZMI # manage_options = (({ 'label': 'Groups', 'action': 'manage_groups' }, ) + Folder.manage_options[:1] + BasePlugin.manage_options[:1] + Folder.manage_options[1:] + Cacheable.manage_options) manage_groups = PageTemplateFile('www/dgpGroups', globals(), __name__='manage_groups') security.declareProtected(ManageGroups, 'manage_addGroup') def manage_addGroup(self, group_id, title, description, predicate, active=True, RESPONSE=None): """ Add a group via the ZMI. """ self.addGroup(group_id, predicate, title, description, active) message = 'Group+%s+added' % group_id if RESPONSE is not None: RESPONSE.redirect('%s/manage_groups?manage_tabs_message=%s' % (self.absolute_url(), message)) security.declareProtected(ManageGroups, 'manage_updateGroup') def manage_updateGroup(self, group_id, predicate, title=None, description=None, active=True, RESPONSE=None): """ Update a group via the ZMI. """ self.updateGroup(group_id, predicate, title, description, active) message = 'Group+%s+updated' % group_id if RESPONSE is not None: RESPONSE.redirect( ('%s/manage_groups?group_id=%s&' + 'manage_tabs_message=%s') % (self.absolute_url(), group_id, message)) security.declareProtected(ManageGroups, 'manage_removeGroups') def manage_removeGroups(self, group_ids, RESPONSE=None, REQUEST=None): """ Remove one or more groups via the ZMI. """ group_ids = filter(None, group_ids) if not group_ids: message = 'no+groups+selected' else: for group_id in group_ids: self.removeGroup(group_id) message = 'Groups+removed' if RESPONSE is not None: RESPONSE.redirect('%s/manage_groups?manage_tabs_message=%s' % (self.absolute_url(), message)) manage_removeGroups = postonly(manage_removeGroups)
class ZODBRoleManager(BasePlugin): """ PAS plugin for managing roles in the ZODB. """ meta_type = 'ZODB Role Manager' security = ClassSecurityInfo() def __init__(self, id, title=None): self._id = self.id = id self.title = title self._roles = OOBTree() self._principal_roles = OOBTree() def manage_afterAdd(self, item, container): if item is self: role_holder = aq_parent(aq_inner(container)) for role in getattr(role_holder, '__ac_roles__', ()): try: if role not in ('Anonymous', 'Authenticated'): self.addRole(role) except KeyError: pass if 'Manager' not in self._roles: self.addRole('Manager') # # IRolesPlugin implementation # security.declarePrivate('getRolesForPrincipal') def getRolesForPrincipal(self, principal, request=None): """ See IRolesPlugin. """ result = list(self._principal_roles.get(principal.getId(), ())) getGroups = getattr(principal, 'getGroups', lambda x: ()) for group_id in getGroups(): result.extend(self._principal_roles.get(group_id, ())) return tuple(result) # # IRoleEnumerationPlugin implementation # def enumerateRoles(self, id=None, exact_match=False, sort_by=None, max_results=None, **kw): """ See IRoleEnumerationPlugin. """ role_info = [] role_ids = [] plugin_id = self.getId() if isinstance(id, str): id = [id] if exact_match and (id): role_ids.extend(id) if role_ids: role_filter = None else: # Searching role_ids = self.listRoleIds() role_filter = _ZODBRoleFilter(id, **kw) for role_id in role_ids: if self._roles.get(role_id): e_url = '%s/manage_roles' % self.getId() p_qs = 'role_id=%s' % role_id m_qs = 'role_id=%s&assign=1' % role_id info = {} info.update(self._roles[role_id]) info['pluginid'] = plugin_id info['properties_url'] = '%s?%s' % (e_url, p_qs) info['members_url'] = '%s?%s' % (e_url, m_qs) if not role_filter or role_filter(info): role_info.append(info) return tuple(role_info) # # IRoleAssignerPlugin implementation # security.declarePrivate('doAssignRoleToPrincipal') def doAssignRoleToPrincipal(self, principal_id, role): return self.assignRoleToPrincipal(role, principal_id) security.declarePrivate('doRemoveRoleFromPrincipal') def doRemoveRoleFromPrincipal(self, principal_id, role): return self.removeRoleFromPrincipal(role, principal_id) # # Role management API # security.declareProtected(ManageUsers, 'listRoleIds') def listRoleIds(self): """ Return a list of the role IDs managed by this object. """ return self._roles.keys() security.declareProtected(ManageUsers, 'listRoleInfo') def listRoleInfo(self): """ Return a list of the role mappings. """ return self._roles.values() security.declareProtected(ManageUsers, 'getRoleInfo') def getRoleInfo(self, role_id): """ Return a role mapping. """ return self._roles[role_id] security.declareProtected(ManageUsers, 'addRole') def addRole(self, role_id, title='', description=''): """ Add 'role_id' to the list of roles managed by this object. o Raise KeyError on duplicate. """ if self._roles.get(role_id) is not None: raise KeyError, 'Duplicate role: %s' % role_id self._roles[role_id] = { 'id': role_id, 'title': title, 'description': description } security.declareProtected(ManageUsers, 'updateRole') def updateRole(self, role_id, title, description): """ Update title and description for the role. o Raise KeyError if not found. """ self._roles[role_id].update({ 'title': title, 'description': description }) security.declareProtected(ManageUsers, 'removeRole') def removeRole(self, role_id, REQUEST=None): """ Remove 'role_id' from the list of roles managed by this object. o Raise KeyError if not found. """ for principal_id in self._principal_roles.keys(): self.removeRoleFromPrincipal(role_id, principal_id) del self._roles[role_id] removeRole = postonly(removeRole) # # Role assignment API # security.declareProtected(ManageUsers, 'listAvailablePrincipals') def listAvailablePrincipals(self, role_id, search_id): """ Return a list of principal IDs to whom a role can be assigned. o If supplied, 'search_id' constrains the principal IDs; if not, return empty list. o Omit principals with existing assignments. """ result = [] if search_id: # don't bother searching if no criteria parent = aq_parent(self) for info in parent.searchPrincipals(max_results=20, sort_by='id', id=search_id, exact_match=False): id = info['id'] title = info.get('title', id) if (role_id not in self._principal_roles.get(id, ()) and role_id != id): result.append((id, title)) return result security.declareProtected(ManageUsers, 'listAssignedPrincipals') def listAssignedPrincipals(self, role_id): """ Return a list of principal IDs to whom a role is assigned. """ result = [] for k, v in self._principal_roles.items(): if role_id in v: # should be at most one and only one mapping to 'k' parent = aq_parent(self) info = parent.searchPrincipals(id=k, exact_match=True) if len(info) > 1: LOG.error( 'searchPrincipals() returned more than one result ' 'for id=%s' % k) assert len(info) in (0, 1) if len(info) == 0: title = '<%s: not found>' % k else: title = info[0].get('title', k) result.append((k, title)) return result security.declareProtected(ManageUsers, 'assignRoleToPrincipal') def assignRoleToPrincipal(self, role_id, principal_id, REQUEST=None): """ Assign a role to a principal (user or group). o Return a boolean indicating whether a new assignment was created. o Raise KeyError if 'role_id' is unknown. """ role_info = self._roles[role_id] # raise KeyError if unknown! current = self._principal_roles.get(principal_id, ()) already = role_id in current if not already: new = current + (role_id, ) self._principal_roles[principal_id] = new self._invalidatePrincipalCache(principal_id) return not already assignRoleToPrincipal = postonly(assignRoleToPrincipal) security.declareProtected(ManageUsers, 'removeRoleFromPrincipal') def removeRoleFromPrincipal(self, role_id, principal_id, REQUEST=None): """ Remove a role from a principal (user or group). o Return a boolean indicating whether the role was already present. o Raise KeyError if 'role_id' is unknown. o Ignore requests to remove a role not already assigned to the principal. """ role_info = self._roles[role_id] # raise KeyError if unknown! current = self._principal_roles.get(principal_id, ()) new = tuple([x for x in current if x != role_id]) already = current != new if already: self._principal_roles[principal_id] = new self._invalidatePrincipalCache(principal_id) return already removeRoleFromPrincipal = postonly(removeRoleFromPrincipal) # # ZMI # manage_options = (({ 'label': 'Roles', 'action': 'manage_roles', }, ) + BasePlugin.manage_options) security.declareProtected(ManageUsers, 'manage_roles') manage_roles = PageTemplateFile('www/zrRoles', globals(), __name__='manage_roles') security.declareProtected(ManageUsers, 'manage_twoLists') manage_twoLists = PageTemplateFile('../www/two_lists', globals(), __name__='manage_twoLists') security.declareProtected(ManageUsers, 'manage_addRole') def manage_addRole(self, role_id, title, description, RESPONSE): """ Add a role via the ZMI. """ self.addRole(role_id, title, description) message = 'Role+added' RESPONSE.redirect('%s/manage_roles?manage_tabs_message=%s' % (self.absolute_url(), message)) security.declareProtected(ManageUsers, 'manage_updateRole') def manage_updateRole(self, role_id, title, description, RESPONSE): """ Update a role via the ZMI. """ self.updateRole(role_id, title, description) message = 'Role+updated' RESPONSE.redirect('%s/manage_roles?role_id=%s&manage_tabs_message=%s' % (self.absolute_url(), role_id, message)) security.declareProtected(ManageUsers, 'manage_removeRoles') def manage_removeRoles(self, role_ids, RESPONSE, REQUEST=None): """ Remove one or more roles via the ZMI. """ role_ids = filter(None, role_ids) if not role_ids: message = 'no+roles+selected' else: for role_id in role_ids: self.removeRole(role_id) message = 'Roles+removed' RESPONSE.redirect('%s/manage_roles?manage_tabs_message=%s' % (self.absolute_url(), message)) manage_removeRoles = postonly(manage_removeRoles) security.declareProtected(ManageUsers, 'manage_assignRoleToPrincipals') def manage_assignRoleToPrincipals(self, role_id, principal_ids, RESPONSE, REQUEST=None): """ Assign a role to one or more principals via the ZMI. """ assigned = [] for principal_id in principal_ids: if self.assignRoleToPrincipal(role_id, principal_id): assigned.append(principal_id) if not assigned: message = 'Role+%s+already+assigned+to+all+principals' % role_id else: message = 'Role+%s+assigned+to+%s' % (role_id, '+'.join(assigned)) RESPONSE.redirect( ('%s/manage_roles?role_id=%s&assign=1' + '&manage_tabs_message=%s') % (self.absolute_url(), role_id, message)) manage_assignRoleToPrincipals = postonly(manage_assignRoleToPrincipals) security.declareProtected(ManageUsers, 'manage_removeRoleFromPrincipals') def manage_removeRoleFromPrincipals(self, role_id, principal_ids, RESPONSE, REQUEST=None): """ Remove a role from one or more principals via the ZMI. """ removed = [] for principal_id in principal_ids: if self.removeRoleFromPrincipal(role_id, principal_id): removed.append(principal_id) if not removed: message = 'Role+%s+alread+removed+from+all+principals' % role_id else: message = 'Role+%s+removed+from+%s' % (role_id, '+'.join(removed)) RESPONSE.redirect( ('%s/manage_roles?role_id=%s&assign=1' + '&manage_tabs_message=%s') % (self.absolute_url(), role_id, message)) manage_removeRoleFromPrincipals = postonly(manage_removeRoleFromPrincipals)