예제 #1
0
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)
예제 #2
0
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)
예제 #3
0
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)
예제 #4
0
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)