Example #1
0
class MigrationTool(PloneBaseTool, UniqueObject, SimpleItem):
    """Handles migrations between Plone releases"""

    implements(IMigrationTool)

    id = 'portal_migration'
    meta_type = 'Plone Migration Tool'
    toolicon = 'skins/plone_images/site_icon.png'

    manage_options = (({
        'label': 'Upgrade',
        'action': '../@@plone-upgrade'
    }, ) + SimpleItem.manage_options)

    _needRecatalog = 0
    _needUpdateRole = 0

    security = ClassSecurityInfo()

    security.declareProtected(ManagePortal, 'getInstanceVersion')

    def getInstanceVersion(self):
        """ The version this instance of plone is on """
        setup = getToolByName(self, 'portal_setup')
        version = setup.getLastVersionForProfile(_DEFAULT_PROFILE)
        if isinstance(version, tuple):
            version = '.'.join(version)

        _version = getattr(self, '_version', None)
        if _version is None:
            self._version = False

        if version == 'unknown':
            if _version:
                # Instance version was not pkg_resources compatible...
                _version = _version.replace('devel (svn/unreleased)', 'dev')
                _version = _version.rstrip('-final')
                _version = _version.rstrip('final')
                _version = _version.replace('alpha', 'a')
                _version = _version.replace('beta', 'b')
                _version = _version.replace('-', '.')
                version = _version
            else:
                version = setup.getVersionForProfile(_DEFAULT_PROFILE)
            self.setInstanceVersion(version)
        return version

    security.declareProtected(ManagePortal, 'setInstanceVersion')

    def setInstanceVersion(self, version):
        """ The version this instance of plone is on """
        setup = getToolByName(self, 'portal_setup')
        setup.setLastVersionForProfile(_DEFAULT_PROFILE, version)
        self._version = False

    security.declareProtected(ManagePortal, 'getFileSystemVersion')

    def getFileSystemVersion(self):
        """ The version this instance of plone is on """
        setup = getToolByName(self, 'portal_setup')
        try:
            return setup.getVersionForProfile(_DEFAULT_PROFILE)
        except KeyError:
            pass
        return None

    security.declareProtected(ManagePortal, 'getSoftwareVersion')

    def getSoftwareVersion(self):
        """ The software version."""
        dist = pkg_resources.get_distribution('Products.CMFPlone')
        return dist.version

    security.declareProtected(ManagePortal, 'needUpgrading')

    def needUpgrading(self):
        """ Need upgrading? """
        return self.getInstanceVersion() != self.getFileSystemVersion()

    security.declareProtected(ManagePortal, 'coreVersions')

    def coreVersions(self):
        """ Useful core information """
        vars = {}
        get_dist = pkg_resources.get_distribution
        vars['Zope'] = get_dist('Zope2').version
        vars['Python'] = sys.version
        vars['Platform'] = sys.platform
        vars['Plone'] = get_dist('Products.CMFPlone').version
        vars['Plone Instance'] = self.getInstanceVersion()
        vars['Plone File System'] = self.getFileSystemVersion()
        vars['CMF'] = get_dist('Products.CMFCore').version
        vars['Debug mode'] = Globals.DevelopmentMode and 'Yes' or 'No'
        try:
            vars['PIL'] = get_dist('PIL').version
        except pkg_resources.DistributionNotFound:
            try:
                vars['PIL'] = get_dist('PILwoTK').version
            except pkg_resources.DistributionNotFound:
                try:
                    vars['PIL'] = "%s (Pillow)" % get_dist('Pillow').version
                except pkg_resources.DistributionNotFound:
                    try:
                        import _imaging
                        vars['PIL'] = 'unknown'
                    except ImportError:
                        pass

        return vars

    security.declareProtected(ManagePortal, 'coreVersionsList')

    def coreVersionsList(self):
        """ Useful core information """
        res = self.coreVersions().items()
        res.sort()
        return res

    security.declareProtected(ManagePortal, 'needUpdateRole')

    def needUpdateRole(self):
        """ Do roles need to be updated? """
        return self._needUpdateRole

    security.declareProtected(ManagePortal, 'needRecatalog')

    def needRecatalog(self):
        """ Does this thing now need recataloging? """
        return self._needRecatalog

    security.declareProtected(ManagePortal, 'upgrade')

    def upgrade(self, REQUEST=None, dry_run=None, swallow_errors=True):
        """ perform the upgrade """
        setup = getToolByName(self, 'portal_setup')

        # This sets the profile version if it wasn't set yet
        version = self.getInstanceVersion()
        upgrades = setup.listUpgrades(_DEFAULT_PROFILE)
        steps = []
        for u in upgrades:
            if isinstance(u, list):
                steps.extend(u)
            else:
                steps.append(u)

        try:
            stream = StringIO()
            handler = logging.StreamHandler(stream)
            handler.setLevel(logging.DEBUG)
            logger.addHandler(handler)
            gslogger = logging.getLogger('GenericSetup')
            gslogger.addHandler(handler)

            if dry_run:
                logger.info("Dry run selected.")

            logger.info("Starting the migration from version: %s" % version)

            for step in steps:
                try:
                    step['step'].doStep(setup)
                    setup.setLastVersionForProfile(_DEFAULT_PROFILE,
                                                   step['dest'])
                    logger.info("Ran upgrade step: %s" % step['title'])
                except (ConflictError, KeyboardInterrupt):
                    raise
                except:
                    logger.error("Upgrade aborted. Error:\n", exc_info=True)

                    if not swallow_errors:
                        raise
                    else:
                        # abort transaction to safe the zodb
                        transaction.abort()
                        break

            logger.info("End of upgrade path, migration has finished")

            if self.needUpgrading():
                logger.error("The upgrade path did NOT reach current version")
                logger.error("Migration has failed")
            else:
                logger.info("Your Plone instance is now up-to-date.")

            # do this once all the changes have been done
            if self.needRecatalog():
                try:
                    catalog = self.portal_catalog
                    # Reduce threshold for the reindex run
                    old_threshold = catalog.threshold
                    pg_threshold = getattr(catalog, 'pgthreshold', 0)
                    catalog.pgthreshold = 300
                    catalog.threshold = 2000
                    catalog.refreshCatalog(clear=1)
                    catalog.threshold = old_threshold
                    catalog.pgthreshold = pg_threshold
                    self._needRecatalog = 0
                except (ConflictError, KeyboardInterrupt):
                    raise
                except:
                    logger.error("Exception was thrown while cataloging:\n",
                                 exc_info=True)
                    if not swallow_errors:
                        raise

            if self.needUpdateRole():
                try:
                    self.portal_workflow.updateRoleMappings()
                    self._needUpdateRole = 0
                except (ConflictError, KeyboardInterrupt):
                    raise
                except:
                    logger.error(
                        "Exception was thrown while updating role "
                        "mappings",
                        exc_info=True)
                    if not swallow_errors:
                        raise

            if dry_run:
                logger.info("Dry run selected, transaction aborted")
                transaction.abort()

            return stream.getvalue()

        finally:
            logger.removeHandler(handler)
            gslogger.removeHandler(handler)

    upgrade = postonly(upgrade)
Example #2
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)

        known = self.listGroupIds()
        for group_id in group_ids:
            g_info = self.getGroupInfo(group_id, raise_keyerror=False)
            if g_info is not None:

                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, raise_keyerror=True):
        """ Return a mappings describing one dynamic group we manage.

        o If 'raise_keyerror' is True, raise KeyError if we don't have an
          existing group definition for 'group_ id'.  Otherwise, return
          None.

        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:
                if raise_keyerror:
                    raise KeyError, group_id
                else:
                    return None

        if not isinstance(original, DynamicGroupDefinition):
            if raise_keyerror:
                raise KeyError, group_id
            else:
                return None

        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)
Example #3
0
def patch_pas():
    # sort alphabetically by patched/added method name
    wrap_method(
        PluggableAuthService,
        '_delOb',
        _delOb
    )
    wrap_method(
        PluggableAuthService,
        '_getAllLocalRoles',
        _getAllLocalRoles,
        add=True,
    )
    wrap_method(
        PluggableAuthService,
        '_doAddGroup',
        _doAddGroup,
        add=True
    )
    wrap_method(
        PluggableAuthService,
        '_doAddUser',
        _doAddUser
    )
    wrap_method(
        PluggableAuthService,
        '_doChangeGroup',
        _doChangeGroup,
        add=True
    )
    wrap_method(
        PluggableAuthService,
        '_doChangeUser',
        _doChangeUser,
        add=True
    )
    wrap_method(
        PluggableAuthService,
        '_doDelGroups',
        _doDelGroups,
        add=True
    )
    wrap_method(
        PluggableAuthService,
        '_doDelUser',
        _doDelUser,
        add=True
    )
    wrap_method(
        PluggableAuthService,
        '_doDelUsers',
        _doDelUsers,
        add=True,
        roles=PermissionRole(ManageUsers, ('Manager',))
    )
    wrap_method(
        PluggableAuthService,
        '_getLocalRolesForDisplay',
        _getLocalRolesForDisplay,
        add=True
    )
    wrap_method(
        PluggableAuthService,
        '_updateGroup',
        _updateGroup,
        add=True
    )
    wrap_method(
        PluggableAuthService,
        'addRole',
        addRole,
        add=True,
        roles=PermissionRole(ManageUsers, ('Manager',))
    )
    wrap_method(
        PluggableAuthService,
        'authenticate',
        authenticate,
        add=True,
        roles=(),
    )
    wrap_method(
        PluggableAuthService,
        'canListAllGroups',
        canListAllGroups,
        add=True,
        roles=PermissionRole(ManageUsers, ('Manager',))
    )
    wrap_method(
        PluggableAuthService,
        'canListAllUsers',
        canListAllUsers,
        add=True,
        roles=PermissionRole(ManageUsers, ('Manager',))
    )
    wrap_method(
        PluggableAuthService,
        'credentialsChanged',
        credentialsChanged,
        add=True,
        roles=PermissionRole(ManageUsers, ('Manager',))
    )
    wrap_method(
        PluggableAuthService,
        'getAllLocalRoles',
        getAllLocalRoles,
        add=True,
    )
    wrap_method(
        PluggableAuthService,
        'getGroup',
        getGroup,
        add=True,
        roles=PermissionRole(ManageUsers, ('Manager',))
    )
    wrap_method(
        PluggableAuthService,
        'getGroupById',
        getGroupById,
        add=True,
        roles=PermissionRole(ManageUsers, ('Manager',))
    )
    wrap_method(
        PluggableAuthService,
        'getGroupByName',
        getGroupByName,
        add=True,
        roles=PermissionRole(ManageUsers, ('Manager',))
    )
    wrap_method(
        PluggableAuthService,
        'getGroupIds',
        getGroupIds,
        add=True,
        roles=PermissionRole(ManageUsers, ('Manager',))
    )
    wrap_method(
        PluggableAuthService,
        'getGroupNames',
        getGroupNames,
        add=True,
        roles=PermissionRole(ManageUsers, ('Manager',))
    )
    wrap_method(
        PluggableAuthService,
        'getGroups',
        getGroups,
        add=True,
        roles=PermissionRole(ManageUsers, ('Manager',))
    )
    wrap_method(
        PluggableAuthService,
        'getLocalRolesForDisplay',
        getLocalRolesForDisplay,
        add=True,
    )
    wrap_method(
        PluggableAuthService,
        'getUserIds',
        getUserIds,
        add=True,
        deprecated="Inefficient GRUF wrapper, use IUserIntrospection instead."
    )
    wrap_method(
        PluggableAuthService,
        'getUserNames',
        getUserNames,
        add=True,
        deprecated="Inefficient GRUF wrapper, use IUserIntrospection instead."
    )
    wrap_method(
        PluggableAuthService,
        'getUsers',
        getUsers,
        add=True,
        roles=PermissionRole(ManageUsers, ('Manager',))
    )
    wrap_method(
        PluggableAuthService,
        'getPureUsers',
        getUsers,
        add=True,
        roles=PermissionRole(ManageUsers, ('Manager',))
    )
    wrap_method(
        PluggableAuthService,
        'userFolderAddUser',
        postonly(userFolderAddUser),
        add=True,
        roles=PermissionRole(ManageUsers, ('Manager',))
    )
    wrap_method(
        PluggableAuthService,
        'userFolderDelUsers',
        postonly(_doDelUsers),
        add=True,
        roles=PermissionRole(ManageUsers, ('Manager',))
    )
    wrap_method(
        PluggableAuthService,
        'userFolderEditGroup',
        postonly(_doChangeGroup),
        add=True,
        roles=PermissionRole(ManageUsers, ('Manager',))
    )
    wrap_method(
        PluggableAuthService,
        'userFolderEditUser',
        postonly(_doChangeUser),
        add=True,
        roles=PermissionRole(ManageUsers, ('Manager',))
    )
    wrap_method(
        PluggableAuthService,
        'userFolderDelGroups',
        postonly(_doDelGroups),
        add=True,
        roles=PermissionRole(ManageUsers, ('Manager',))
    )
    wrap_method(
        PluggableAuthService,
        'userSetGroups',
        _userSetGroups,
        add=True,
        deprecated="Method from GRUF was removed."
    )
    wrap_method(
        PluggableAuthService,
        'userSetPassword',
        userSetPassword,
        add=True,
        roles=PermissionRole(ManageUsers, ('Manager',))
    )
Example #4
0
                sp.rollback()
        except ConflictError:
            raise
        except LinkIntegrityNotificationException:
            raise
        except Exception, e:
            if handle_errors:
                sp.rollback()
                failure[path] = e
            else:
                raise
    transaction_note('Deleted %s' % (', '.join(success)))
    return success, failure


deleteObjectsByPaths = postonly(deleteObjectsByPaths)


def getOrCreateType(portal, atobj, newid, newtypeid):
    """
    Gets the object specified by newid if it already exists under
    atobj or creates it there with the id given in newtypeid
    """
    try:
        newobj = getattr(atobj, newid)  #get it if it already exists
    except AttributeError:  #newobj doesn't already exist
        try:
            _ = atobj.invokeFactory(id=newid, type_name=newtypeid)
        except ValueError:
            _createObjectByType(newtypeid, atobj, newid)
        except Unauthorized:
Example #5
0
                          cascade=InstalledProduct.default_cascade,
                          reinstall=False,
                          REQUEST=None):
        """Removes a list of products
        """
        if products is None:
            products = []
        for pid in products:
            prod = getattr(self, pid)
            prod.uninstall(cascade=cascade, reinstall=reinstall)
            if not reinstall:
                self.manage_delObjects(pid)

        if REQUEST:
            return REQUEST.RESPONSE.redirect(REQUEST['HTTP_REFERER'])
    uninstallProducts = postonly(uninstallProducts)

    security.declareProtected(ManagePortal, 'reinstallProducts')

    def reinstallProducts(self, products, REQUEST=None, omitSnapshots=True):
        """Reinstalls a list of products, the main difference to
        uninstall/install is that it does not remove portal objects
        created during install (e.g. tools, etc.)
        """
        if isinstance(products, basestring):
            products = [products]

        # only delete everything EXCEPT portalobjects (tools etc) for reinstall
        cascade = [c for c in InstalledProduct.default_cascade
                 if c != 'portalobjects']
        self.uninstallProducts(products, cascade, reinstall=True)
Example #6
0
class DefaultWorkflowPolicyDefinition(SimpleItemWithProperties):

    meta_type = 'WorkflowPolicy'
    id = 'default_workflow_policy'
    _isAWorkflowPolicy = 1

    _chains_by_type = None  # PersistentMapping
    _default_chain = None  # Fallback to wf tool

    security = ClassSecurityInfo()

    manage_options = ({'label': 'Workflows', 'action': 'manage_main'}, )
    #
    #   ZMI methods
    #

    security.declareProtected(ManageWorkflowPolicies, '_manage_workflows')
    _manage_workflows = PageTemplateFile(path_join(
        'www', 'define_local_workflow_policy'),
                                         globals(),
                                         __name__='manage_main')

    def __init__(self, id):
        self.id = id
        self.title = ''
        self.description = ''

    security.declareProtected(ManageWorkflowPolicies, 'getId')

    def getId(self):
        """ Return the id
        """
        return self.id

    security.declareProtected(ManageWorkflowPolicies, 'getTitle')

    def getTitle(self):
        """ Return the title
        """
        title = getattr(self, 'title', '')
        return title

    security.declareProtected(ManageWorkflowPolicies, 'getDescription')

    def getDescription(self):
        """ Return the description
        """
        description = getattr(self, 'description', '')
        return description

    security.declareProtected(ManageWorkflowPolicies, 'setTitle')

    def setTitle(self, title):
        """ Set the title
        """
        self.title = title

    security.declareProtected(ManageWorkflowPolicies, 'setDescription')

    def setDescription(self, description):
        """ Set the description
        """
        self.description = description

    security.declareProtected(ManageWorkflowPolicies, 'manage_main')

    def manage_main(self, REQUEST, manage_tabs_message=None):
        """ Show a management screen for changing type to workflow connections

        Display 'None' if there's no chain for a type.
        """
        cbt = self._chains_by_type
        ti = self._listTypeInfo()
        types_info = []
        for t in ti:
            id = t.getId()
            title = t.Title()
            if title == id:
                title = None

            if cbt is not None and id in cbt:
                chain = ', '.join(cbt[id])
            else:
                chain = 'None'

            types_info.append({
                'id': id,
                'title': title,
                'chain': chain,
                # 'cbt': repr(cbt.get(id)),  # for debug purpose
            })
        return self._manage_workflows(REQUEST,
                                      default_chain=', '.join(
                                          self._default_chain or ()),
                                      types_info=types_info,
                                      management_view='Workflows',
                                      manage_tabs_message=manage_tabs_message)

    security.declareProtected(ManageWorkflowPolicies, 'manage_changeWorkflows')

    def manage_changeWorkflows(self,
                               title,
                               description,
                               default_chain,
                               props=None,
                               REQUEST=None):
        """ Changes which workflows apply to objects of which type

        A chain equal to 'None' is empty we remove the entry.
        """
        self.title = title
        self.description = description

        if props is None:
            props = REQUEST
        cbt = self._chains_by_type
        if cbt is None:
            self._chains_by_type = cbt = PersistentMapping()
        ti = self._listTypeInfo()
        # Set up the chains by type.
        for t in ti:
            id = t.getId()
            field_name = 'chain_%s' % id
            chain = props.get(field_name, DEFAULT_CHAIN).strip()

            if chain == 'None':
                if cbt.get(id, _MARKER) is not _MARKER:
                    self.delChain(id)
                continue

            self.setChain(id, chain)

        # Set up the default chain.
        self.setDefaultChain(default_chain)
        if REQUEST is not None:
            return self.manage_main(REQUEST, manage_tabs_message='Changed.')

    manage_changeWorkflows = postonly(manage_changeWorkflows)

    security.declareProtected(ManageWorkflowPolicies, 'setChainForPortalTypes')

    def setChainForPortalTypes(self, pt_names, chain, REQUEST=None):
        """ Set a chain for portal types.
        """
        for portal_type in pt_names:
            self.setChain(portal_type, chain)

    setChainForPortalTypes = postonly(setChainForPortalTypes)

    security.declareProtected(ManageWorkflowPolicies, 'getChainFor')

    def getChainFor(self, ob, managescreen=False):
        """Returns the chain that applies to the object.

        If chain doesn't exist we return None to get a fallback from
        portal_workflow.

        @parm managescreen: If True return the special tuple
                            ('default') instead of the actual default
                            chain (hack).
        """

        cbt = self._chains_by_type
        if isinstance(ob, six.string_types):
            pt = ob
        elif hasattr(aq_base(ob), '_getPortalTypeName'):
            pt = ob._getPortalTypeName()
        else:
            pt = None

        if pt is None:
            return None

        chain = None
        if cbt is not None:
            chain = cbt.get(pt, _MARKER)

        # Backwards compatibility: before chain was a string, not a list
        if chain is not _MARKER and isinstance(chain, six.string_types):
            chain = map(lambda x: x.strip(), chain.split(','))

        Log.debug('Chain founded in policy %s', chain)
        if chain is _MARKER or chain is None:
            return None
        elif len(chain) == 1 and chain[0] == DEFAULT_CHAIN:
            default = self.getDefaultChain(ob)
            if default:
                if managescreen:
                    return chain[0]
                else:
                    return default
            else:
                return None

        return chain

    security.declareProtected(ManageWorkflowPolicies, 'setDefaultChain')

    def setDefaultChain(self, default_chain, REQUEST=None):
        """ Sets the default chain for this tool. """
        wftool = getToolByName(self, 'portal_workflow')

        if isinstance(default_chain, six.string_types):
            default_chain = map(lambda x: x.strip(), default_chain.split(','))
        ids = []
        for wf_id in default_chain:
            if wf_id:
                if not wftool.getWorkflowById(wf_id):
                    raise ValueError("'%s' is not a workflow ID.\nchain: %s" %
                                     (wf_id, repr(default_chain)))
                ids.append(wf_id)

        self._default_chain = tuple(ids)

    setDefaultChain = postonly(setDefaultChain)

    security.declareProtected(ManageWorkflowPolicies, 'getDefaultChain')

    def getDefaultChain(self, ob):
        """ Returns the default chain."""
        if self._default_chain is None:
            wf_tool = getToolByName(self, 'portal_workflow')
            return wf_tool.getDefaultChainFor(ob)
        else:
            return self._default_chain

    security.declareProtected(ManageWorkflowPolicies, 'setChain')

    def setChain(self, portal_type, chain, REQUEST=None):
        """Set the chain for a portal type.

           @type chain: tuple of strings or None
           @param chain: A tuple of workflow ids to be set for the portal type.
                         A few special values exsist:
                           - C{None}: Acquire chain from a policy above,
                                      ultimatly from the portal workflow settings.
                           - C{()} (empty tuple): No workflow for this type.
                           - C{('default',)}: Use the configured default workflow.
        """
        # Verify input data
        if portal_type not in [pt.id for pt in self._listTypeInfo()]:
            raise ValueError("'%s' is not a valid portal type." % portal_type)

        if isinstance(chain, six.string_types):
            chain = map(lambda x: x.strip(), chain.split(','))

        wftool = getToolByName(self, 'portal_workflow')
        cbt = self._chains_by_type
        if cbt is None:
            self._chains_by_type = cbt = PersistentMapping()

        # if chain is None or default, we remove the entry
        if chain is None:
            if portal_type in cbt:
                del cbt[portal_type]
        elif len(chain) == 1 and chain[0] == DEFAULT_CHAIN:
            cbt[portal_type] = chain
        else:
            for wf_id in chain:
                if wf_id != '' and not wftool.getWorkflowById(wf_id):
                    raise ValueError("'%s' is not a workflow ID.\nchain: %s" %
                                     (wf_id, repr(chain)))
            cbt[portal_type] = tuple(chain)

    setChain = postonly(setChain)

    security.declareProtected(ManageWorkflowPolicies, 'delChain')

    def delChain(self, portal_type, REQUEST=None):
        """Delete the chain for a portal type."""
        if portal_type in self._chains_by_type:
            del self._chains_by_type[portal_type]

    delChain = postonly(delChain)

    #
    #   Helper methods
    #
    security.declarePrivate('_listTypeInfo')

    def _listTypeInfo(self):
        """ List the portal types which are available.
        """
        pt = getToolByName(self, 'portal_types', None)
        if pt is None:
            return ()
        else:
            return pt.listTypeInfo()
Example #7
0
class PloneTool(PloneBaseTool, UniqueObject, SimpleItem):
    """Various utility methods."""

    id = 'plone_utils'
    meta_type = 'Plone Utility Tool'
    toolicon = 'skins/plone_images/site_icon.png'
    security = ClassSecurityInfo()
    plone_tool = 1
    # Prefix for forms fields!?
    field_prefix = 'field_'

    @security.protected(ManageUsers)
    def setMemberProperties(self, member, REQUEST=None, **properties):
        pas = getToolByName(self, 'acl_users')
        if safe_hasattr(member, 'getId'):
            member = member.getId()
        user = pas.getUserById(member)
        user.setProperties(**properties)

    @security.public
    @deprecate('`getSiteEncoding` is deprecated. Plone only supports UTF-8 '
               'currently. This method always returns "utf-8"')
    def getSiteEncoding(self):
        """ Get the the site encoding, which is utf-8."""
        return 'utf-8'

    @security.private
    def getMailHost(self):
        """Gets the MailHost."""
        return getattr(aq_parent(self), 'MailHost')

    @security.public
    def validateSingleNormalizedEmailAddress(self, address):
        # Lower-level function to validate a single normalized email address,
        # see validateEmailAddress.
        if not isinstance(address, str):
            return False

        sub = EMAIL_CUTOFF_RE.match(address)
        if sub is not None:
            # Address contains two newlines (possible spammer relay attack)
            return False

        # sub is an empty string if the address is valid
        sub = EMAIL_RE.sub('', address)
        if sub == '':
            return True
        return False

    @security.public
    def validateSingleEmailAddress(self, address):
        # Validate a single email address, see also validateEmailAddresses.
        if not isinstance(address, str):
            return False

        sub = EMAIL_CUTOFF_RE.match(address)
        if sub is not None:
            # Address contains two newlines (spammer attack using
            # "address\n\nSpam message")
            return False

        if len(getaddresses([address])) != 1:
            # none or more than one address
            return False

        # Validate the address
        for name, addr in getaddresses([address]):
            if not self.validateSingleNormalizedEmailAddress(addr):
                return False
        return True

    @security.public
    def validateEmailAddresses(self, addresses):
        # Validate a list of possibly several email addresses, see also
        # validateSingleEmailAddress.
        if not isinstance(addresses, str):
            return False

        sub = EMAIL_CUTOFF_RE.match(addresses)
        if sub is not None:
            # Addresses contains two newlines (spammer attack using
            # "To: list\n\nSpam message")
            return False

        # Validate each address
        for name, addr in getaddresses([addresses]):
            if not self.validateSingleNormalizedEmailAddress(addr):
                return False
        return True

    @security.public
    def editMetadata(self,
                     obj,
                     allowDiscussion=None,
                     title=None,
                     subject=None,
                     description=None,
                     contributors=None,
                     effective_date=None,
                     expiration_date=None,
                     format=None,
                     language=None,
                     rights=None,
                     **kwargs):
        # Responsible for setting metadata on a content object.
        # We assume the obj implements IDublinCoreMetadata.
        mt = getToolByName(self, 'portal_membership')
        if not mt.checkPermission(ModifyPortalContent, obj):
            # FIXME: Some scripts rely on this being string?
            raise Unauthorized

        REQUEST = self.REQUEST
        pfx = self.field_prefix

        def getfield(request, name, default=None, pfx=pfx):
            return request.form.get(pfx + name, default)

        def tuplify(value):
            return tuple(filter(None, value))

        if IDublinCore.providedBy(obj):
            if title is None:
                title = getfield(REQUEST, 'title')
            if description is None:
                description = getfield(REQUEST, 'description')
            if subject is None:
                subject = getfield(REQUEST, 'subject')
            if subject is not None:
                subject = tuplify(subject)
            if contributors is None:
                contributors = getfield(REQUEST, 'contributors')
            if contributors is not None:
                contributors = tuplify(contributors)
            if effective_date is None:
                effective_date = getfield(REQUEST, 'effective_date')
            if effective_date == '':
                effective_date = 'None'
            if expiration_date is None:
                expiration_date = getfield(REQUEST, 'expiration_date')
            if expiration_date == '':
                expiration_date = 'None'

        if IMutableDublinCore.providedBy(obj):
            if title is not None:
                obj.setTitle(title)
            if description is not None:
                obj.setDescription(description)
            if subject is not None:
                obj.setSubject(subject)
            if contributors is not None:
                obj.setContributors(contributors)
            if effective_date is not None:
                obj.setEffectiveDate(effective_date)
            if expiration_date is not None:
                obj.setExpirationDate(expiration_date)
            if format is not None:
                obj.setFormat(format)
            if language is not None:
                obj.setLanguage(language)
            if rights is not None:
                obj.setRights(rights)
            # Make the catalog aware of changes
            obj.reindexObject()

    def _renameObject(self, obj, id):
        if not id:
            REQUEST = self.REQUEST
            id = REQUEST.get('id', '')
            id = REQUEST.get(self.field_prefix + 'id', '')
        if id != obj.getId():
            parent = aq_parent(aq_inner(obj))
            parent.manage_renameObject(obj.getId(), id)

    def _makeTransactionNote(self, obj, msg=''):
        # TODO Why not aq_parent()?
        relative_path = '/'.join(
            getToolByName(self, 'portal_url').getRelativeContentPath(obj)[:-1])
        if not msg:
            msg = relative_path + '/' + obj.title_or_id() \
                + ' has been modified.'
        if not transaction.get().description:
            transaction_note(safe_unicode(msg))

    @security.public
    def contentEdit(self, obj, **kwargs):
        # Encapsulates how the editing of content occurs.
        try:
            self.editMetadata(obj, **kwargs)
        except AttributeError as msg:
            log('Failure editing metadata at: %s.\n%s\n' %
                (obj.absolute_url(), msg))
        if kwargs.get('id', None) is not None:
            self._renameObject(obj, id=kwargs['id'].strip())
        self._makeTransactionNote(obj)

    @security.public
    def availableMIMETypes(self):
        # Returns a map of mimetypes.
        # Requires mimetype registry from Archetypes >= 1.3.
        mtr = getToolByName(self, 'mimetypes_registry')
        return mtr.list_mimetypes()

    @security.protected(View)
    def getWorkflowChainFor(self, object):
        # Proxy the request for the chain to the workflow tool, as
        # this method is private there.
        wftool = getToolByName(self, 'portal_workflow')
        wfs = ()
        try:
            wfs = wftool.getChainFor(object)
        except ConflictError:
            raise
        except:
            pass
        return wfs

    @security.protected(View)
    def getIconFor(self, category, id, default=_marker, context=None):
        # Get an icon for an action, from its icon_expr.
        if context is None:
            context = aq_parent(self)
        action_chain = f'{category}/{id}'
        if category == 'controlpanel':
            tool = getToolByName(context, 'portal_controlpanel')
            actions = [ai for ai in tool.listActionInfos() if ai['id'] == id]
        else:
            tool = getToolByName(context, 'portal_actions')
            actions = tool.listActionInfos(action_chain=action_chain,
                                           object=context)
        if len(actions) > 0:
            icon = actions[0].get('icon', None)
            if icon:
                return icon
        else:
            if default is not _marker:
                icon = default
            else:
                raise KeyError(action_chain)

        return icon

    @security.protected(View)
    def getReviewStateTitleFor(self, obj):
        """Utility method that gets the workflow state title for the
        object's review_state.

        Returns None if no review_state found.
        """
        wf_tool = getToolByName(self, 'portal_workflow')
        wfs = ()
        objstate = None
        try:
            objstate = wf_tool.getInfoFor(obj, 'review_state')
            wfs = wf_tool.getWorkflowsFor(obj)
        except WorkflowException:
            pass
        if wfs:
            for w in wfs:
                if objstate in w.states:
                    return w.states[objstate].title or objstate
        return None

    @security.protected(ManagePortal)
    def changeOwnershipOf(self, object, userid, recursive=0, REQUEST=None):
        """Changes the ownership of an object."""
        membership = getToolByName(self, 'portal_membership')
        acl_users = getattr(self, 'acl_users')
        user = acl_users.getUserById(userid)
        if user is None:
            # The user could be in the top level acl_users folder in
            # the Zope root, in which case this should find him:
            user = membership.getMemberById(userid)
            if user is None:
                raise KeyError(
                    'Only retrievable users in this site can be made owners.')
            # Be careful not to pass MemberData to changeOwnership
            user = user.getUser()
        object.changeOwnership(user, recursive)

        def fixOwnerRole(object, user_id):
            # Get rid of all other owners
            owners = object.users_with_local_role('Owner')
            for o in owners:
                roles = list(object.get_local_roles_for_userid(o))
                roles.remove('Owner')
                if roles:
                    object.manage_setLocalRoles(o, roles)
                else:
                    object.manage_delLocalRoles([o])
            # Fix for 1750
            roles = list(object.get_local_roles_for_userid(user_id))
            roles.append('Owner')
            object.manage_setLocalRoles(user_id, roles)

        fixOwnerRole(object, user.getId())
        if base_hasattr(object, 'reindexObject'):
            object.reindexObject()

        if recursive:
            catalog_tool = getToolByName(self, 'portal_catalog')
            purl = getToolByName(self, 'portal_url')
            _path = purl.getRelativeContentURL(object)
            subobjects = [
                b.getObject() for b in catalog_tool(path={
                    'query': _path,
                    'level': 1
                })
            ]
            for obj in subobjects:
                fixOwnerRole(obj, user.getId())
                if base_hasattr(obj, 'reindexObject'):
                    obj.reindexObject()

    changeOwnershipOf = postonly(changeOwnershipOf)

    @security.public
    def urlparse(self, url):
        """Returns the pieces of url in a six-part tuple.

        Since Python 2.6: urlparse now returns a ParseResult object.
        We just need the tuple form which is tuple(result).
        """
        return tuple(parse.urlparse(url))

    @security.public
    def urlunparse(self, url_tuple):
        """Puts a url back together again, in the manner that
        urlparse breaks it.
        """
        return parse.urlunparse(url_tuple)

    # Enable scripts to get the string value of an exception even if the
    # thrown exception is a string and not a subclass of Exception.
    def exceptionString(self):
        # Don't assign the traceback to s
        # (otherwise will generate a circular reference)
        s = sys.exc_info()[:2]
        if s[0] == None:
            return None
        if isinstance(s[0], str):
            return s[0]
        return str(s[1])

    # Provide a way of dumping an exception to the log even if we
    # catch it and otherwise ignore it
    def logException(self):
        # Dumps most recent exception to the log.
        log_exc()

    @security.public
    def createSitemap(self, context, request=None):
        # Returns a sitemap navtree structure.
        if request is None:
            request = self.REQUEST
        return utils.createSiteMap(context, request)

    def _addToNavTreeResult(self, result, data):
        # Adds a piece of content to the result tree.
        return utils.addToNavTreeResult(result, data)

    @security.protected(AccessContentsInformation)
    def typesToList(self):
        return utils.typesToList(self)

    @security.public
    def createBreadCrumbs(self, context, request=None):
        # Returns a structure for the portal breadcumbs.
        if request is None:
            request = self.REQUEST
        return utils.createBreadCrumbs(context, request)

    @security.public
    def good_id(self, id):
        # Exposes ObjectManager's bad_id test to skin scripts.
        m = bad_id(id)
        if m is not None:
            return 0
        return 1

    @security.public
    def bad_chars(self, id):
        # Returns a list of the Bad characters.
        return BAD_CHARS(id)

    @security.public
    def getInheritedLocalRoles(self, context):
        # Returns a tuple with the acquired local roles.
        portal = getToolByName(context, 'portal_url').getPortalObject()
        result = []
        cont = 1
        if portal != context:
            parent = aq_parent(context)
            while cont:
                if not getattr(parent, 'acl_users', False):
                    break
                userroles = parent.acl_users._getLocalRolesForDisplay(parent)
                for user, roles, role_type, name in userroles:
                    # Find user in result
                    found = 0
                    for user2, roles2, type2, name2 in result:
                        if user2 == user:
                            # Check which roles must be added to roles2
                            for role in roles:
                                if role not in roles2:
                                    roles2.append(role)
                            found = 1
                            break
                    if found == 0:
                        # Add it to result and make sure roles is a list so
                        # we may append and not overwrite the loop variable
                        result.append([user, list(roles), role_type, name])
                if parent == portal:
                    cont = 0
                elif not self.isLocalRoleAcquired(parent):
                    # Role acquired check here
                    cont = 0
                else:
                    parent = aq_parent(parent)

        # Tuplize all inner roles
        for pos in range(len(result) - 1, -1, -1):
            result[pos][1] = tuple(result[pos][1])
            result[pos] = tuple(result[pos])

        return tuple(result)

    #
    # The three methods used in determining what the default-page of a folder
    # is. These are:
    #
    #   - getDefaultPage(folder)
    #       : get id of contentish object that is default-page in the folder
    #   - isDefaultPage(object)
    #       : determine if an object is the default-page in its parent folder
    #   - browserDefault(object)
    #       : lookup rules for old-style content types
    #

    @security.public
    def isDefaultPage(self, obj, request=None):
        # Finds out if the given obj is the default page in its parent folder.
        # Uses the lookup rules of Plone.  Lookup happens over a view, for which
        # in theory a different implementation may exist.
        if request is None:
            request = self.REQUEST
        return check_default_page_via_view(obj, request)

    @security.public
    def getDefaultPage(self, obj, request=None):
        # Given a folderish item, find out if it has a default-page using
        # the lookup rules of Plone (see Products.CMFPlone/defaultpage.py).
        # Lookup happens over a view, for which in theory a different
        # implementation may be used.
        if request is None:
            if hasattr(self, 'REQUEST'):
                request = self.REQUEST
        if request:
            return get_default_page_via_view(obj, request)

    @security.public
    def addPortalMessage(self, message, type='info', request=None):
        """\
        Call this once or more to add messages to be displayed at the
        top of the web page.

        The arguments are:
            message:   a string, with the text message you want to show,
                       or a HTML fragment (see type='structure' below)
            type:      optional, defaults to 'info'. The type determines how
                       the message will be rendered, as it is used to select
                       the CSS class for the message. Predefined types are:
                       'info' - for informational messages
                       'warning' - for warning messages
                       'error' - for messages about restricted access or
                                 errors.

        Portal messages are by default rendered by the global_statusmessage.pt
        page template.

        It is also possible to add messages from page templates, as
        long as they are processed before the portal_message macro is
        called by the main template. Example:

          <tal:block tal:define="temp python:context.plone_utils.addPortalMessage('A random info message')" />  # noqa
        """
        if request is None:
            request = self.REQUEST
        IStatusMessage(request).add(message, type=type)

    @security.public
    def showPortalMessages(self, request=None):
        # Return portal status messages that will be displayed when the
        # response web page is rendered. Portal status messages are by default
        # rendered by the global_statusmessage.pt page template. They will be
        # removed after they have been shown.
        # See addPortalMessages for examples.
        if request is None:
            request = self.REQUEST
        return IStatusMessage(request).show()

    @security.public
    def browserDefault(self, obj):
        """Sets default so we can return whatever we want instead of index_html.

        This method is complex, and interacts with mechanisms such as
        IBrowserDefault (implemented in CMFDynamicViewFTI), LinguaPlone and
        various mechanisms for setting the default page.

        The method returns a tuple (obj, [path]) where path is a path to
        a template or other object to be acquired and displayed on the object.
        The path is determined as follows:

        0. If we're c oming from WebDAV, make sure we don't return a contained
            object "default page" ever
        1. If there is an index_html attribute (either a contained object or
            an explicit attribute) on the object, return that as the
            "default page". Note that this may be used by things like
            File and Image to return the contents of the file, for example,
            not just content-space objects created by the user.
        2. If the object implements IBrowserDefault, query this for the
            default page.
        3. If the object has a property default_page set and this gives a list
            of, or single, object id, and that object is is found in the
            folder or is the name of a skin template, return that id
        4. If the property default_page is set in site_properties and that
            property contains a list of ids of which one id is found in the
            folder, return that id
        5. If the object implements IBrowserDefault, try to get the selected
            layout.
        6. If the type has a 'folderlisting' action and no default page is
            set, use this action. This permits folders to have the default
            'view' action be 'string:${object_url}/' and hence default to
            a default page when clicking the 'view' tab, whilst allowing the
            fallback action to be specified TTW in portal_types (this action
            is typically hidden)
        7. If nothing else is found, fall back on the object's 'view' action.
        8. If this is not found, raise an AttributeError
        """

        # WebDAV in Zope is odd it takes the incoming verb eg: PROPFIND
        # and then requests that object, for example for: /, with verb PROPFIND
        # means acquire PROPFIND from the folder and call it
        # its all very odd and WebDAV'y
        request = getattr(self, 'REQUEST', None)
        if request is not None and 'REQUEST_METHOD' in request:
            if request['REQUEST_METHOD'] not in ['GET', 'POST']:
                return obj, [request['REQUEST_METHOD']]
        # Now back to normal

        #
        # 1. Get an attribute or contained object index_html
        #

        # Note: The base PloneFolder, as well as ATCT's ATCTOrderedFolder
        # defines a method index_html() which returns a ReplaceableWrapper.
        # This is needed for WebDAV to work properly, and to avoid implicit
        # acquisition of index_html's, which are generally on-object only.
        # For the purposes of determining a default page, we don't want to
        # use this index_html(), nor the ComputedAttribute which defines it.

        if not isinstance(getattr(obj, 'index_html', None),
                          ReplaceableWrapper):
            index_obj = getattr(aq_base(obj), 'index_html', None)
            if index_obj is not None \
                    and not isinstance(index_obj, ComputedAttribute):
                return obj, ['index_html']

        #
        # 2. Look for a default_page managed by an IBrowserDefault-implementing
        #    object
        #
        # 3. Look for a default_page property on the object
        #
        # 4. Try the default sitewide default_page setting
        #

        if obj.isPrincipiaFolderish:
            defaultPage = self.getDefaultPage(obj)
            if defaultPage is not None:
                if defaultPage in obj:
                    return obj, [defaultPage]
                # Avoid infinite recursion in the case that the page id == the
                # object id
                elif (defaultPage != obj.getId()
                      and defaultPage != '/'.join(obj.getPhysicalPath())):
                    # For the default_page property, we may get things in the
                    # skin layers or with an explicit path - split this path
                    # to comply with the __browser_default__() spec
                    return obj, defaultPage.split('/')

        # 5. If there is no default page, try IBrowserDefault.getLayout()
        if IBrowserDefault.providedBy(obj):
            browserDefault = obj
        else:
            browserDefault = queryAdapter(obj, IBrowserDefault)
        if browserDefault is not None:
            default_view_fallback = False
            if base_hasattr(obj, 'getTypeInfo'):
                default_view_fallback = obj.getTypeInfo().default_view_fallback
            layout = browserDefault.getLayout(
                check_exists=default_view_fallback)
            if layout is None:
                raise AttributeError(
                    "%s has no assigned layout, perhaps it needs an FTI" % obj)
            else:
                return obj, [layout]

        #
        # 6. If the object has a 'folderlisting' action, use this
        #

        # This allows folders to determine in a flexible manner how they are
        # displayed when there is no default page, whilst still using
        # browserDefault() to show contained objects by default on the 'view'
        # action (this applies to old-style folders only, IBrowserDefault is
        # managed explicitly above)

        if base_hasattr(obj, 'getTypeInfo'):
            try:
                # XXX: This isn't quite right since it assumes the action
                # starts with ${object_url}.  Should we raise an error if
                # it doesn't?
                act = obj.getTypeInfo().getActionInfo(
                    'folder/folderlisting')['url'].split('/')[-1]
                return obj, [act]
            except ValueError:
                pass

            #
            # 7. Fall back on the 'view' action
            #

            try:
                # XXX: This isn't quite right since it assumes the action
                # starts with ${object_url}.  Should we raise an error if
                # it doesn't?
                act = obj.getTypeInfo().getActionInfo(
                    'object/view')['url'].split('/')[-1]
                return obj, [act]
            except ValueError:
                pass

        #
        # 8. If we can't find this either, raise an exception
        #

        raise AttributeError(
            "Failed to get a default page or view_action for %s" %
            (obj.absolute_url(), ))

    @security.public
    def isStructuralFolder(self, obj):
        """Checks if a given object is a "structural folder".

        That is, a folderish item which does not explicitly implement
        INonStructuralFolder to declare that it doesn't wish to be treated
        as a folder by the navtree, the tab generation etc.
        """
        return (obj.isPrincipiaFolderish
                and not INonStructuralFolder.providedBy(obj))

    @security.public
    def acquireLocalRoles(self, obj, status=1, REQUEST=None):
        # If status is 1, allow acquisition of local roles (regular
        # behaviour).
        # If it's 0, prohibit it (it will allow some kind of local role
        # blacklisting).
        mt = getToolByName(self, 'portal_membership')
        if not mt.checkPermission(ModifyPortalContent, obj):
            raise Unauthorized

        # Set local role status...
        # set the variable (or unset it if it's defined)
        if not status:
            obj.__ac_local_roles_block__ = 1
        else:
            if getattr(obj, '__ac_local_roles_block__', None):
                obj.__ac_local_roles_block__ = None

        # Reindex the whole stuff.
        obj.reindexObjectSecurity()

    acquireLocalRoles = postonly(acquireLocalRoles)

    @security.public
    def isLocalRoleAcquired(self, obj):
        # Returns local role acquisition blocking status.
        # True if normal, false if blocked.
        if getattr(obj, '__ac_local_roles_block__', None):
            return False
        return True

    @security.public
    def getOwnerName(self, obj):
        """ Returns the userid of the owner of an object."""
        mt = getToolByName(self, 'portal_membership')
        if not mt.checkPermission(View, obj):
            raise Unauthorized
        return obj.getOwner().getId()

    @security.public
    def normalizeString(self, text):
        """Normalizes a title to an id.

        The relaxed mode was removed in Plone 4.0. You should use either the
        url or file name normalizer from the plone.i18n package instead.

        normalizeString() converts a whole string to a normalized form that
        should be safe to use as in a url, as a css id, etc.
        """
        return utils.normalizeString(text, context=self)

    @security.public
    def listMetaTags(self, context):
        # Lists meta tags helper.
        # Creates a mapping of meta tags -> values for the listMetaTags script.
        result = {}
        mt = getToolByName(self, 'portal_membership')

        registry = getUtility(IRegistry)
        site_settings = registry.forInterface(ISiteSchema,
                                              prefix="plone",
                                              check=False)

        try:
            use_all = site_settings.exposeDCMetaTags
        except AttributeError:
            use_all = False

        security_settings = registry.forInterface(ISecuritySchema,
                                                  prefix='plone')
        view_about = security_settings.allow_anon_views_about \
            or not mt.isAnonymousUser()

        if not use_all:
            metadata_names = {'Description': METADATA_DCNAME['Description']}
        else:
            metadata_names = METADATA_DCNAME
        for accessor, key in metadata_names.items():
            # check non-public properties
            if not view_about and accessor in METADATA_DC_AUTHORFIELDS:
                continue

            # short circuit non-special cases
            if not use_all and accessor not in ('Description', 'Subject'):
                continue

            method = getattr(aq_inner(context).aq_explicit, accessor, None)
            if not callable(method):
                continue

            # Catch AttributeErrors raised by some AT applications
            try:
                value = method()
            except AttributeError:
                value = None

            if not value:
                # No data
                continue
            if accessor == 'Publisher' and value == 'No publisher':
                # No publisher is hardcoded (TODO: still?)
                continue

            # Check for fullnames
            if view_about and accessor in METADATA_DC_AUTHORFIELDS:
                if not isinstance(value, (list, tuple)):
                    value = [value]
                tmp = []
                for userid in value:
                    member = mt.getMemberInfo(userid)
                    name = userid
                    if member:
                        name = member['fullname'] or userid
                    tmp.append(name)
                value = tmp

            if isinstance(value, (list, tuple)):
                # convert a list to a string
                value = ', '.join(value)

            # Special cases
            if accessor == 'Description':
                result['description'] = value
            elif accessor == 'Subject':
                result['keywords'] = value

            if use_all:
                result[key] = value

        if use_all:
            created = context.CreationDate()

            try:
                effective = context.EffectiveDate()
                if effective == 'None':
                    effective = None
                if effective:
                    effective = DateTime(effective)
            except AttributeError:
                effective = None

            try:
                expires = context.ExpirationDate()
                if expires == 'None':
                    expires = None
                if expires:
                    expires = DateTime(expires)
            except AttributeError:
                expires = None

            # Filter out DWIMish artifacts on effective / expiration dates
            if effective is not None and \
               effective > FLOOR_DATE and \
               effective != created:
                eff_str = effective.Date()
            else:
                eff_str = ''

            if expires is not None and expires < CEILING_DATE:
                exp_str = expires.Date()
            else:
                exp_str = ''

            if eff_str or exp_str:
                result['DC.date.valid_range'] = f'{eff_str} - {exp_str}'

        return result

    @security.public
    def getUserFriendlyTypes(self, typesList=None):
        # Get a list of types which are considered "user friendly" for search
        # and selection purposes.
        #
        # This is the list of types available in the portal, minus those
        # defined in the types_not_searched property in site_properties, if it
        # exists.
        #
        # If typesList is given, this is used as the base list; else all types
        # from portal_types are used.
        if typesList is None:
            typesList = []
        registry = getUtility(IRegistry)
        search_settings = registry.forInterface(ISearchSchema, prefix="plone")
        blacklistedTypes = search_settings.types_not_searched

        ttool = getToolByName(self, 'portal_types')
        tool_types = ttool.keys()
        if typesList:
            types = [t for t in typesList if t in tool_types]
        else:
            types = tool_types

        friendlyTypes = set(types) - set(blacklistedTypes)
        return list(friendlyTypes)

    @security.public
    def reindexOnReorder(self, parent):
        # Reindexing of "gopip" isn't needed any longer,
        # but some extensions might need the info anyway. :(
        notify(ReorderedEvent(parent))

    @security.public
    def isIDAutoGenerated(self, id):
        # Determine if an id is autogenerated.
        return utils.isIDAutoGenerated(self, id)

    @security.public
    def getEmptyTitle(self, translated=True):
        """Returns string to be used for objects with no title or id."""
        return utils.getEmptyTitle(self, translated)

    @security.public
    def pretty_title_or_id(self, obj, empty_value=_marker):
        # Return the best possible title or id of an item, regardless
        # of whether obj is a catalog brain or an object, but returning an
        # empty title marker if the id is not set (i.e. it's auto-generated).
        return utils.pretty_title_or_id(self, obj, empty_value=empty_value)

    @security.public
    def getMethodAliases(self, typeInfo):
        # Given an FTI, return the dict of method aliases defined on that
        # FTI. If there are no method aliases (i.e. this FTI doesn't support
        # it), return None.
        getMethodAliases = getattr(typeInfo, 'getMethodAliases', None)
        if getMethodAliases is not None \
                and utils.safe_callable(getMethodAliases):
            return getMethodAliases()
        else:
            return None

    # This is public because we don't know what permissions the user
    # has on the objects to be deleted.  The restrictedTraverse and
    # manage_delObjects calls should handle permission checks for us.
    @security.public
    def deleteObjectsByPaths(self, paths, handle_errors=True, REQUEST=None):
        log_deprecated(
            "deleteObjectsByPaths is deprecated, you should use. "
            "plone.api.content.delete. This method no longer does link integrity checks"
        )  # noqa
        failure = {}
        success = []
        # use the portal for traversal in case we have relative paths
        portal = getToolByName(self, 'portal_url').getPortalObject()
        traverse = portal.restrictedTraverse
        for path in paths:
            # Skip and note any errors
            if handle_errors:
                sp = transaction.savepoint(optimistic=True)
            try:
                obj = traverse(path)
                obj_parent = aq_parent(aq_inner(obj))
                obj_parent.manage_delObjects([obj.getId()])
                success.append(f'{obj.getId()} ({path})')
            except ConflictError:
                raise
            except Exception as e:
                if handle_errors:
                    sp.rollback()
                    failure[path] = e
                    log_exc()
                else:
                    raise
        transaction_note('Deleted %s' % (', '.join(success)))
        return success, failure

    deleteObjectsByPaths = postonly(deleteObjectsByPaths)

    @security.public
    def renameObjectsByPaths(self,
                             paths,
                             new_ids,
                             new_titles,
                             handle_errors=True,
                             REQUEST=None):
        failure = {}
        success = {}
        # use the portal for traversal in case we have relative paths
        portal = getToolByName(self, 'portal_url').getPortalObject()
        traverse = portal.restrictedTraverse
        for i, path in enumerate(paths):
            new_id = new_ids[i]
            new_title = new_titles[i]
            if handle_errors:
                sp = transaction.savepoint(optimistic=True)
            try:
                obj = traverse(path, None)
                obid = obj.getId()
                title = obj.Title()
                change_title = new_title and title != new_title
                changed = False
                if change_title:
                    getSecurityManager().validate(obj, obj, 'setTitle',
                                                  obj.setTitle)
                    obj.setTitle(new_title)
                    notify(ObjectModifiedEvent(obj))
                    changed = True
                if new_id and obid != new_id:
                    parent = aq_parent(aq_inner(obj))
                    parent.manage_renameObjects((obid, ), (new_id, ))
                    changed = True
                elif change_title:
                    # the rename will have already triggered a reindex
                    obj.reindexObject()
                if changed:
                    success[path] = (new_id, new_title)
            except ConflictError:
                raise
            except Exception as e:
                if handle_errors:
                    # skip this object but continue with sub-objects.
                    sp.rollback()
                    failure[path] = e
                else:
                    raise
        transaction_note('Renamed %s' % str(success.keys()))
        return success, failure

    renameObjectsByPaths = postonly(renameObjectsByPaths)
Example #8
0
class GroupAwareRoleManager(ZODBRoleManager):

    meta_type = "Group Aware Role Manager"
    security = ClassSecurityInfo()

    def updateRolesList(self):
        role_holder = aq_parent(aq_inner(self._getPAS()))
        for role in getattr(role_holder, '__ac_roles__', ()):
            if role not in ('Anonymous', 'Authenticated') and \
                    role not in self._roles:
                try:
                    self.addRole(role)
                except KeyError:
                    pass

    # don't blow up if manager already exists; mostly for ZopeVersionControl
    def manage_afterAdd(self, item, container):
        try:
            self.addRole('Manager')
        except KeyError:
            pass

        if item is self:
            self.updateRolesList()

    @security.protected(ManageUsers)
    def assignRoleToPrincipal(self, role_id, principal_id, REQUEST=None):
        try:
            return ZODBRoleManager.assignRoleToPrincipal(
                self,
                role_id,
                principal_id
            )
        except KeyError:
            # Lazily update our roles list and try again
            self.updateRolesList()
            return ZODBRoleManager.assignRoleToPrincipal(
                self,
                role_id,
                principal_id
            )

    @security.protected(ManageUsers)
    def assignRolesToPrincipal(self, roles, principal_id, REQUEST=None):
        """ Assign a specific set of roles, and only those roles, to a
        principal.

        o no return value

        o Raise KeyError if a role_id is unknown.
        """
        for role_id in roles:
            if role_id not in ('Authenticated', 'Anonymous', 'Owner'):
                try:
                    # raise KeyError if unknown!
                    self._roles[role_id]
                except KeyError:
                    # Lazily update our roles list and try again
                    self.updateRolesList()
                    if role_id in self._roles:
                        # check if this role is managed by this plugin, and
                        # set it
                        self._roles[role_id]

        self._principal_roles[principal_id] = tuple(roles)

    assignRolesToPrincipal = postonly(assignRolesToPrincipal)

    @security.private
    def getRolesForPrincipal(self, principal, request=None):
        """ See IRolesPlugin.
        """
        roles = set([])
        principal_ids = set([])
        # Some services need to determine the roles obtained from groups
        # while excluding the directly assigned roles.  In this case
        # '__ignore_direct_roles__' = True should be pushed in the request.
        request = aq_get(self, 'REQUEST', None)
        if request is None \
           or not request.get('__ignore_direct_roles__', False):
            principal_ids.add(principal.getId())

        # Some services may need the real roles of an user but **not**
        # the ones he got through his groups. In this case, the
        # '__ignore_group_roles__'= True should be previously pushed
        # in the request.
        plugins = self._getPAS()['plugins']
        if request is None \
           or not request.get('__ignore_group_roles__', False):
            principal_ids.update(
                getGroupsForPrincipal(principal, plugins, request)
            )
        for pid in principal_ids:
            roles.update(self._principal_roles.get(pid, ()))
        return tuple(roles)

    # implement IAssignRoleCapability

    def allowRoleAssign(self, user_id, role_id):
        """True iff this plugin will allow assigning a certain user a
        certain role.

        Note that at least currently this only checks if the role_id
        exists.  If it exists, this method returns True.  Nothing is
        done with the user_id parameter.  This might be wrong.  See
        http://dev.plone.org/plone/ticket/7762
        """
        present = self.getRoleInfo(role_id)
        if present:
            # if we have a role, we can assign it
            # slightly naive, but should be okay.
            return 1

        return 0

    def listRoleIds(self):
        self.updateRolesList()
        return ZODBRoleManager.listRoleIds(self)

    def listRoleInfo(self):
        self.updateRolesList()
        return ZODBRoleManager.listRoleInfo(self)

    def getRoleInfo(self, role_id):
        if role_id not in self._roles:
            self.updateRolesList()
        return ZODBRoleManager.getRoleInfo(self, role_id)
Example #9
0
class MembershipTool(BaseTool):
    """PAS-based customization of MembershipTool.
    """

    meta_type = "PlonePAS Membership Tool"
    toolicon = 'tool.gif'
    personal_id = '.personal'
    portrait_id = 'MyPortrait'
    default_portrait = 'defaultUser.gif'
    memberarea_type = 'Folder'
    membersfolder_id = 'Members'
    memberareaCreationFlag = False
    security = ClassSecurityInfo()

    user_search_keywords = ('login', 'fullname', 'email', 'exact_match',
                            'sort_by', 'max_results')

    _properties = (getattr(BaseTool, '_properties', ()) + ({
        'id': 'user_search_keywords',
        'type': 'lines',
        'mode': 'rw',
    }, ))

    manage_options = (BaseTool.manage_options +
                      ({
                          'label': 'Portraits',
                          'action': 'manage_portrait_fix'
                      }, ))

    # TODO I'm not quite sure why getPortalRoles is declared 'Managed'
    #    in CMFCore.MembershipTool - but in Plone we are not so anal ;-)
    security.declareProtected(View, 'getPortalRoles')

    security.declareProtected(ManagePortal, 'manage_mapRoles')
    manage_mapRoles = DTMLFile('../zmi/membershipRolemapping', globals())

    security.declareProtected(ManagePortal, 'manage_portrait_fix')
    manage_portrait_fix = DTMLFile('../zmi/portrait_fix', globals())

    @security.protected(ManagePortal)
    def manage_setMemberAreaType(self, type_name, REQUEST=None):
        """ ZMI method to set the home folder type by its type name.
        """
        self.setMemberAreaType(type_name)
        if REQUEST is not None:
            REQUEST['RESPONSE'].redirect(
                self.absolute_url() + '/manage_mapRoles'
                '?manage_tabs_message=Member+area+type+changed.')

    @security.protected(ManagePortal)
    def manage_setMembersFolderById(self, id, REQUEST=None):
        """ ZMI method to set the members folder object by its id.
        """
        self.setMembersFolderById(id)
        if REQUEST is not None:
            REQUEST['RESPONSE'].redirect(
                self.absolute_url() + '/manage_mapRoles'
                '?manage_tabs_message=Members+folder+id+changed.')

    @security.protected(ManagePortal)
    def setMemberAreaType(self, type_name):
        """ Sets the portal type to use for new home folders.
        """
        # No check for folderish since someone somewhere may actually want
        # members to have objects instead of folders as home "directory".
        self.memberarea_type = str(type_name).strip()

    @security.protected(ManagePortal)
    def setMembersFolderById(self, id=''):
        """ Set the members folder object by its id.
        """
        self.membersfolder_id = id.strip()

    @security.public
    def getMembersFolder(self):
        """ Get the members folder object.
        """
        parent = aq_parent(aq_inner(self))
        members = getattr(parent, self.membersfolder_id, None)
        return members

    @security.private
    def addMember(self, id, password, roles, domains, properties=None):
        """Adds a new member to the user folder.

        Security checks will have already been performed.  Called by
        portal_registration.  This one specific to PAS. PAS ignores
        domains. Adding members with login_name also not yet
        supported.
        """
        acl_users = self.acl_users
        acl_users._doAddUser(id, password, roles, domains)

        if properties is not None:
            member = self.getMemberById(id)
            member.setMemberProperties(properties)

    @security.protected(ListPortalMembers)
    def searchForMembers(self, REQUEST=None, **kw):
        """Hacked up version of Plone searchForMembers.

        The following properties can be provided:
        - name
        - email
        - last_login_time
        - before_specified_time
        - roles (any role will cause a match)
        - groupname

        This is an 'AND' request.

        Simple name searches are "fast".
        """
        logger.debug('searchForMembers: started.')

        acl_users = getToolByName(self, "acl_users")

        if REQUEST is not None:
            searchmap = REQUEST
            for key, value in searchmap.items():
                if isinstance(value, str):
                    searchmap[key] = _unicodify_structure(value)
        else:
            searchmap = kw

        # While the parameter is called name it is actually used to search a
        # users name, which is stored in the fullname property. We need to fix
        # that here so the right name is used when calling into PAS plugins.
        if 'name' in searchmap:
            searchmap['fullname'] = searchmap['name']
            del searchmap['name']

        user_search = dict([
            x for x in searchmap.items()
            if x[0] in self.user_search_keywords and x[1]
        ])

        fullname = searchmap.get('fullname', None)
        email = searchmap.get('email', None)
        roles = searchmap.get('roles', None)
        last_login_time = searchmap.get('last_login_time', None)
        before_specified_time = searchmap.get('before_specified_time', None)
        groupname = searchmap.get('groupname', '').strip()

        if fullname:
            fullname = fullname.strip().lower()
        if not fullname:
            fullname = None
        if email:
            email = email.strip().lower()
        if not email:
            email = None

        uf_users = []

        logger.debug('searchForMembers: searching PAS '
                     'with arguments %r.' % user_search)
        for user in acl_users.searchUsers(**user_search):
            uf_users.append(user['userid'])

        if not uf_users:
            return []

        getUserById = acl_users.getUserById

        def dedupe(seq):
            # Thanks http://www.peterbe.com/plog/uniqifiers-benchmark
            seen = set()
            seen_add = seen.add
            # nice trick! set.add() does always return None
            return [x for x in seq if x not in seen and not seen_add(x)]

        uf_users = dedupe(uf_users)
        members = [getUserById(userid) for userid in uf_users]
        members = [member for member in members if member is not None]

        if not (email or fullname or roles or groupname or last_login_time):
            logger.debug('searchForMembers: searching users '
                         'with no extra filter, immediate return.')
            return members

        # Now perform individual checks on each user
        res = []

        for member in members:
            if groupname and groupname not in member.getGroupIds():
                continue

            if roles:
                user_roles = member.getRoles()
                found = 0
                for r in roles:
                    if r in user_roles:
                        found = 1
                        break
                if not found:
                    continue

            if last_login_time:
                last_login = member.getProperty('last_login_time', '')

                if isinstance(last_login, basestring):
                    # value is a string when member hasn't yet logged in
                    last_login = DateTime(last_login or '2000/01/01')

                if before_specified_time:
                    if last_login >= last_login_time:
                        continue
                elif last_login < last_login_time:
                    continue

            res.append(member)

        logger.debug('searchForMembers: finished.')
        return res

    ############
    # sanitize home folders (we may get URL-illegal ids)

    @security.public
    def createMemberarea(self, member_id=None, minimal=None):
        """
        Create a member area for 'member_id' or the authenticated
        user, but don't assume that member_id is url-safe.
        """
        if not self.getMemberareaCreationFlag():
            return None
        membership = getToolByName(self, 'portal_membership')
        members = self.getMembersFolder()

        if not member_id:
            # member_id is optional (see CMFCore.interfaces.portal_membership:
            #     Create a member area for 'member_id' or authenticated user.)
            member = membership.getAuthenticatedMember()
            member_id = member.getId()

        if hasattr(members, 'aq_explicit'):
            members = members.aq_explicit

        if members is None:
            # no members area
            logger.debug('createMemberarea: members area does not exist.')
            return

        safe_member_id = cleanId(member_id)
        if hasattr(members, safe_member_id):
            # has already this member
            logger.debug('createMemberarea: member area '
                         'for %r already exists.' % safe_member_id)
            return

        if not safe_member_id:
            # Could be one of two things:
            # - A Emergency User
            # - cleanId made a empty string out of member_id
            logger.debug('createMemberarea: empty member id '
                         '(%r, %r), skipping member area creation.' %
                         (member_id, safe_member_id))
            return

        # Create member area without security checks
        typesTool = getToolByName(members, 'portal_types')
        fti = typesTool.getTypeInfo(self.memberarea_type)
        member_folder = fti._constructInstance(members, safe_member_id)

        # Get the user object from acl_users
        acl_users = getToolByName(self, "acl_users")
        user = acl_users.getUserById(member_id)
        if user is not None:
            user = user.__of__(acl_users)
        else:
            user = getSecurityManager().getUser()
            # check that we do not do something wrong
            if user.getId() != member_id:
                raise NotImplementedError(
                    'cannot get user for member area creation')

        member_object = self.getMemberById(member_id)

        # Modify member folder
        member_folder = self.getHomeFolder(member_id)
        # Grant Ownership and Owner role to Member
        member_folder.changeOwnership(user)
        member_folder.__ac_local_roles__ = None
        member_folder.manage_setLocalRoles(member_id, ['Owner'])
        # We use ATCT now use the mutators
        fullname = member_object.getProperty('fullname')
        member_folder.setTitle(fullname or member_id)
        member_folder.reindexObject()

        # Hook to allow doing other things after memberarea creation.
        notify_script = getattr(member_folder, 'notifyMemberAreaCreated', None)
        if notify_script is not None:
            notify_script()

    # deal with ridiculous API change in CMF
    security.declarePublic('createMemberArea')
    createMemberArea = createMemberarea

    @security.public
    def getMemberInfo(self, memberId=None):
        # Return 'harmless' Memberinfo of any member, such as Full name,
        # Location, etc
        if not memberId:
            member = self.getAuthenticatedMember()
        else:
            member = self.getMemberById(memberId)

        if member is None:
            return None

        memberinfo = {
            'fullname': member.getProperty('fullname'),
            'description': member.getProperty('description'),
            'location': member.getProperty('location'),
            'language': member.getProperty('language'),
            'home_page': member.getProperty('home_page'),
            'username': member.getUserName(),
            'has_email': bool(member.getProperty('email')),
        }

        return memberinfo

    def _getSafeMemberId(self, id=None):
        """Return a safe version of a member id.

        If no id is given return the id for the currently authenticated user.
        """

        if id is None:
            member = self.getAuthenticatedMember()
            if not hasattr(member, 'getMemberId'):
                return None
            id = member.getMemberId()

        return cleanId(id)

    @security.public
    def getHomeFolder(self, id=None, verifyPermission=0):
        """ Return a member's home folder object, or None.

        Specially instrumented for URL-quoted-member-id folder
        names.
        """
        safe_id = self._getSafeMemberId(id)
        if safe_id is None:
            member = self.getAuthenticatedMember()
            if not hasattr(member, 'getMemberId'):
                return None
            safe_id = member.getMemberId()
        members = self.getMembersFolder()
        if members:
            try:
                folder = members._getOb(safe_id)
                if verifyPermission and not _checkPermission(View, folder):
                    # Don't return the folder if the user can't get to it.
                    return None
                return folder
            # KeyError added to deal with btree member folders
            except (AttributeError, KeyError, TypeError):
                pass
        return None

    def getHomeUrl(self, id=None, verifyPermission=0):
        """ Return the URL to a member's home folder, or None.
        """
        home = self.getHomeFolder(id, verifyPermission)
        if home is not None:
            return home.absolute_url()
        else:
            return None

    @security.public
    def getPersonalFolder(self, member_id=None):
        """
        returns the Personal Item folder for a member
        if no Personal Folder exists will return None
        """
        home = self.getHomeFolder(member_id)
        personal = None
        if home:
            personal = getattr(home, self.personal_id, None)
        return personal

    @security.public
    def getPersonalPortrait(self, id=None, verifyPermission=0):
        """Return a members personal portait.

        Modified from CMFPlone version to URL-quote the member id.
        """
        if not id:
            id = self.getAuthenticatedMember().getId()
        safe_id = self._getSafeMemberId(id)
        membertool = getToolByName(self, 'portal_memberdata')
        portrait = membertool._getPortrait(safe_id)
        if isinstance(portrait, str):
            portrait = None
        if portrait is not None:
            if verifyPermission and not _checkPermission('View', portrait):
                # Don't return the portrait if the user can't get to it
                portrait = None
        if portrait is None:
            portal = getToolByName(self, 'portal_url').getPortalObject()
            portrait = getattr(portal, default_portrait, None)

        return portrait

    @security.protected(SetOwnProperties)
    def deletePersonalPortrait(self, id=None):
        """deletes the Portait of a member.
        """
        authenticated_id = self.getAuthenticatedMember().getId()
        if not id:
            id = authenticated_id
        safe_id = self._getSafeMemberId(id)
        if id != authenticated_id and not _checkPermission(ManageUsers, self):
            raise Unauthorized

        membertool = getToolByName(self, 'portal_memberdata')
        return membertool._deletePortrait(safe_id)

    @security.protected(SetOwnProperties)
    def changeMemberPortrait(self, portrait, id=None):
        """update the portait of a member.

        We URL-quote the member id if needed.

        Note that this method might be called by an anonymous user who
        is getting registered.  This method will then be called from
        plone.app.users and this is fine.  When called from restricted
        python code or with a curl command by a hacker, the
        declareProtected line will kick in and prevent use of this
        method.
        """
        authenticated_id = self.getAuthenticatedMember().getId()
        if not id:
            id = authenticated_id
        safe_id = self._getSafeMemberId(id)
        if authenticated_id and id != authenticated_id:
            # Only Managers can change portraits of others.
            if not _checkPermission(ManageUsers, self):
                raise Unauthorized
        if portrait and portrait.filename:
            scaled, mimetype = scale_image(portrait)
            portrait = Image(id=safe_id, file=scaled, title='')
            membertool = getToolByName(self, 'portal_memberdata')
            membertool._setPortrait(portrait, safe_id)

    @security.protected(ManageUsers)
    def listMembers(self):
        '''Gets the list of all members.
        THIS METHOD MIGHT BE VERY EXPENSIVE ON LARGE USER FOLDERS AND MUST
        BE USED WITH CARE! We plan to restrict its use in the future (ie.
        force large requests to use searchForMembers instead of listMembers,
        so that it will not be possible anymore to have a method returning
        several hundred of users :)
        '''
        return BaseTool.listMembers(self)

    @security.protected(ManageUsers)
    def listMemberIds(self):
        '''Lists the ids of all members.  This may eventually be
        replaced with a set of methods for querying pieces of the
        list rather than the entire list at once.
        '''
        return self.acl_users.getUserIds()

    @security.protected(SetOwnPassword)
    def testCurrentPassword(self, password):
        """ test to see if password is current """
        REQUEST = getattr(self, 'REQUEST', {})
        member = self.getAuthenticatedMember()
        acl_users = self._findUsersAclHome(member.getUserId())
        if not acl_users:
            return 0
        return acl_users.authenticate(member.getUserName(), password, REQUEST)

    def _findUsersAclHome(self, userid):
        portal = getToolByName(self, 'portal_url').getPortalObject()
        acl_users = portal.acl_users
        parent = acl_users
        while parent:
            if acl_users.aq_explicit.getUserById(userid, None) is not None:
                break
            parent = aq_parent(aq_inner(parent)).aq_parent
            acl_users = getattr(parent, 'acl_users')
        if parent:
            return acl_users
        else:
            return None

    @security.protected(SetOwnPassword)
    def setPassword(self, password, domains=None, REQUEST=None):
        '''Allows the authenticated member to set his/her own password.
        '''
        registration = getToolByName(self, 'portal_registration', None)
        if not self.isAnonymousUser():
            member = self.getAuthenticatedMember()
            acl_users = self._findUsersAclHome(member.getUserId())
            if not acl_users:
                # should not possibly ever happen
                raise BadRequest('did not find current user in any '
                                 'user folder')
            if registration:
                failMessage = registration.testPasswordValidity(password)
                if failMessage is not None:
                    raise BadRequest(failMessage)

            if domains is None:
                domains = []
            user = acl_users.getUserById(member.getUserId(), None)
            # we must change the users password trough grufs changepassword
            # to keep her  group settings
            if hasattr(user, 'changePassword'):
                user.changePassword(password)
            else:
                acl_users._doChangeUser(member.getUserId(), password,
                                        member.getRoles(), domains)
            if REQUEST is None:
                REQUEST = aq_get(self, 'REQUEST', None)
            self.credentialsChanged(password, REQUEST=REQUEST)
        else:
            raise BadRequest('Not logged in.')

    setPassword = postonly(setPassword)

    @security.protected(View)
    def getCandidateLocalRoles(self, obj):
        """ What local roles can I assign?
            Override the CMFCore version so that we can see the local roles on
            an object, and so that local managers can assign all roles locally.
        """
        member = self.getAuthenticatedMember()
        # Use getRolesInContext as someone may be a local manager
        if 'Manager' in member.getRolesInContext(obj):
            # Use valid_roles as we may want roles defined only on a subobject
            local_roles = [
                r for r in obj.valid_roles()
                if r not in ('Anonymous', 'Authenticated', 'Shared')
            ]
        else:
            local_roles = [
                role for role in member.getRolesInContext(obj)
                if role not in ('Member', 'Authenticated')
            ]
        local_roles.sort()
        return tuple(local_roles)

    @security.protected(View)
    def loginUser(self, REQUEST=None):
        """ Handle a login for the current user.

        This method takes care of all the standard work that needs to be
        done when a user logs in:
        - clear the copy/cut/paste clipboard
        - PAS credentials update
        - sending a logged-in event
        - storing the login time
        - create the member area if it does not exist
        """
        user = getSecurityManager().getUser()
        if user is None:
            return

        if self.setLoginTimes():
            event.notify(UserInitialLoginInEvent(user))
        else:
            event.notify(UserLoggedInEvent(user))

        if REQUEST is None:
            REQUEST = getattr(self, 'REQUEST', None)
        if REQUEST is None:
            return

        # Expire the clipboard
        if REQUEST.get('__cp', None) is not None:
            REQUEST.RESPONSE.expireCookie('__cp', path='/')

        self.createMemberArea()

        try:
            pas = getToolByName(self, 'acl_users')
            pas.credentials_cookie_auth.login()
        except AttributeError:
            # The cookie plugin may not be present
            pass

    @security.protected(View)
    def logoutUser(self, REQUEST=None):
        """Process a user logout.

        This takes care of all the standard logout work:
        - ask the user folder to logout
        - expire a skin selection cookie
        - invalidate a Zope session if there is one
        """
        # Invalidate existing sessions, but only if they exist.
        sdm = getToolByName(self, 'session_data_manager', None)
        if sdm is not None:
            try:
                # XXX This causes write on read to happen which
                # causes plone.protect to freak out.
                # Please remove this once write on read is fixed
                req = REQUEST or self.REQUEST
                alsoProvides(req, IDisableCSRFProtection)
            except AttributeError:
                pass
            session = sdm.getSessionData(create=0)
            if session is not None:
                session.invalidate()

        if REQUEST is None:
            REQUEST = getattr(self, 'REQUEST', None)
        if REQUEST is not None:
            pas = getToolByName(self, 'acl_users')
            try:
                pas.logout(REQUEST)
            except:
                # XXX Bare except copied from logout.cpy. This should be
                # changed in the next Plone release.
                pass

            # Expire the skin cookie if it is not configured to persist
            st = getToolByName(self, "portal_skins")
            skinvar = st.getRequestVarname()
            if skinvar in REQUEST and not st.getCookiePersistence():
                portal = getToolByName(self, "portal_url").getPortalObject()
                path = '/' + portal.absolute_url(1)
                # XXX check if this path is sane
                REQUEST.RESPONSE.expireCookie(skinvar, path=path)

        user = getSecurityManager().getUser()
        if user is not None:
            event.notify(UserLoggedOutEvent(user))

    @security.protected(View)
    def immediateLogout(self):
        """ Log the current user out immediately.  Used by logout.py so that
            we do not have to do a redirect to show the logged out status. """
        noSecurityManager()

    @security.public
    def setLoginTimes(self):
        """ Called by logged_in to set the login time properties
            even if members lack the "Set own properties" permission.

            The return value indicates if this is the first logged
            login time.
        """
        res = False
        if not self.isAnonymousUser():
            member = self.getAuthenticatedMember()
            default = DateTime('2000/01/01')
            login_time = member.getProperty('login_time', default)
            if login_time == default:
                res = True
                login_time = DateTime()
            member.setProperties(login_time=self.ZopeTime(),
                                 last_login_time=login_time)
        return res

    @security.protected(ManagePortal)
    def getBadMembers(self):
        """Will search for members with bad images in the portal_memberdata
        delete their portraits and return their member ids"""
        memberdata = getToolByName(self, 'portal_memberdata')
        portraits = getattr(memberdata, 'portraits', None)
        if portraits is None:
            return []
        bad_member_ids = []
        TXN_THRESHOLD = 50
        counter = 1
        for member_id in tuple(portraits.keys()):
            portrait = portraits[member_id]
            portrait_data = str(portrait.data)
            if portrait_data == '':
                continue
            if not HAS_PIL:
                raise RuntimeError('No Python Imaging Libraries (PIL) found. '
                                   'Unable to validate profile image. ')
            try:
                import PIL
                PIL.Image.open(StringIO(portrait_data))
            except ConflictError:
                pass
            except:
                # Anything else we have a bad bad image and we destroy it
                # and ask questions later.
                portraits._delObject(member_id)
                bad_member_ids.append(member_id)
            if not counter % TXN_THRESHOLD:
                transaction.savepoint(optimistic=True)
            counter = counter + 1

        return bad_member_ids
Example #10
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:
                    message = ("Multiple groups or users exist with the "
                               "name '%s'. Remove one of the duplicate groups "
                               "or users." % (k))
                    LOG.error(message)
                    raise MultiplePrincipalError(message)

                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)
Example #11
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=None, description=None):
        """ Update properties for 'group_id'

        o Raise KeyError if group_id doesn't already exist.
        """
        if title is not None:
            self._groups[group_id]['title'] = title
        if description is not None:
            self._groups[group_id]['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)
Example #12
0
            obj = traverse(path)
            providesITrashed(obj)
            success.append('%s (%s)' % (obj.title_or_id(), path))
        except ConflictError:
            raise
        except Exception, e:
            if handle_errors:
                sp.rollback()
                failure[path] = e
            else:
                raise

    transaction_note('Moved to trashcan %s' % (', '.join(success)))
    return success, failure

moveObjectsToTrashcanByPaths = postonly(moveObjectsToTrashcanByPaths)


def restoreObjectsFromTrashcanByPaths(self, paths, handle_errors=True,
                                      REQUEST=None):
    failure = {}
    success = []
    # use the portal for traversal in case we have relative paths
    portal = getToolByName(self, 'portal_url').getPortalObject()
    traverse = portal.restrictedTraverse
    for path in paths:
        # Skip and note any errors
        if handle_errors:
            sp = transaction.savepoint(optimistic=True)

        try:
Example #13
0
        raise NotImplementedError("There is no plugin that can "
                                   " delete users.")

    for userdeleter_id, userdeleter in userdeleters:
        try:
            userdeleter.doDeleteUser(id)
        except _SWALLOWABLE_PLUGIN_EXCEPTIONS:
            pass
        else:
            notify(PrincipalDeleted(id))


PluggableAuthService._doDelUser = _doDelUser

PluggableAuthService.userFolderDelUsers = \
    postonly(PluggableAuthService._doDelUsers)
PluggableAuthService.userFolderDelUsers__roles__ = \
    PermissionRole(ManageUsers, ('Manager',))


def _doChangeUser(self, principal_id, password, roles, domains=(), groups=None,
                  REQUEST=None, **kw):
    """
    Given a principal id, change its password, roles, domains, if
    respective plugins for such exist.

    XXX domains are currently ignored.
    """
    # Might be called with 'None' as password from the Plone UI, in
    # prefs_users_overview when resetPassword is not set.
    if password is not None:
class PlacefulWorkflowTool(ImmutableId, Folder, IFAwareObjectManager):
    """
    PlacefulWorkflow Tool
    """

    id = 'portal_placeful_workflow'
    meta_type = 'Placeful Workflow Tool'

    implements(IPlacefulWorkflowTool)

    _actions = []

    security = ClassSecurityInfo()

    manage_options = Folder.manage_options

    def __init__(self):
        # Properties to be edited by site manager
        safeEditProperty(self, 'max_chain_length', 1, data_type='int')

    _manage_addWorkflowPolicyForm = PageTemplateFile(
        path_join('www', 'add_workflow_policy'), globals())

    security.declareProtected(ManageWorkflowPolicies,
                              'manage_addWorkflowPolicyForm')

    def manage_addWorkflowPolicyForm(self, REQUEST):
        """ Form for adding workflow policies.
        """
        wfpt = []
        for key in _workflow_policy_factories.keys():
            wfpt.append(key)
        wfpt.sort()
        return self._manage_addWorkflowPolicyForm(REQUEST,
                                                  workflow_policy_types=wfpt)

    security.declareProtected(ManageWorkflowPolicies,
                              'manage_addWorkflowPolicy')

    def manage_addWorkflowPolicy(
            self,
            id,
            workflow_policy_type='default_workflow_policy (Simple Policy)',
            duplicate_id='empty',
            RESPONSE=None,
            REQUEST=None):
        """ Adds a workflow policies from the registered types.
        """
        if id in ('empty', 'portal_workflow'):
            raise ValueError, "'%s' is reserved. Please choose another id." % id

        factory = _workflow_policy_factories[workflow_policy_type]
        ob = factory(id)
        self._setObject(id, ob)

        if duplicate_id and duplicate_id != 'empty':
            types_tool = getToolByName(self, 'portal_types')
            new_wp = self.getWorkflowPolicyById(id)

            if duplicate_id == 'portal_workflow':
                wf_tool = getToolByName(self, 'portal_workflow')

                new_wp.setDefaultChain(wf_tool._default_chain)

                for ptype in types_tool.objectIds():
                    chain = wf_tool.getChainForPortalType(ptype,
                                                          managescreen=True)
                    if chain:
                        new_wp.setChain(ptype, chain)

            else:
                orig_wp = self.getWorkflowPolicyById(duplicate_id)
                new_wp.setDefaultChain(orig_wp.getDefaultChain('Document'))

                for ptype in types_tool.objectIds():
                    chain = orig_wp.getChainFor(ptype, managescreen=True)
                    if chain:
                        new_wp.setChain(ptype, chain)

        if RESPONSE is not None:
            RESPONSE.redirect(self.absolute_url() +
                              '/manage_main?management_view=Contents')

    manage_addWorkflowPolicy = postonly(manage_addWorkflowPolicy)

    def all_meta_types(self):
        return ({
            'name': 'WorkflowPolicy',
            'action': 'manage_addWorkflowPolicyForm',
            'permission': ManageWorkflowPolicies
        }, )

    security.declareProtected(ManageWorkflowPolicies, 'getWorkflowPolicyById')

    def getWorkflowPolicyById(self, wfp_id):
        """ Retrieve a given workflow policy.
        """
        if wfp_id is None:
            return None
        policy = getattr(self.aq_explicit, wfp_id, _MARKER)
        if policy is not _MARKER:
            if getattr(policy, '_isAWorkflowPolicy', 0):
                return policy
        return None

    security.declarePublic('isValidPolicyName')

    def isValidPolicyName(self, policy_id):
        """ Return True if a policy exist
        """
        return self.getWorkflowPolicyById(policy_id) is not None

    security.declareProtected(ManageWorkflowPolicies, 'getWorkflowPolicies')

    def getWorkflowPolicies(self):
        """ Return the list of workflow policies.
        """
        wfps = []
        for obj_name, obj in self.objectItems():
            if getattr(obj, '_isAWorkflowPolicy', 0):
                wfps.append(obj)
        return tuple(wfps)

    security.declarePublic('getWorkflowPolicyIds')

    def getWorkflowPolicyIds(self):
        """ Return the list of workflow policy ids.
        """
        wfp_ids = []

        for obj_id, obj in self.objectItems():
            if getattr(obj, '_isAWorkflowPolicy', 0):
                wfp_ids.append(obj_id)

        return tuple(wfp_ids)

    security.declarePublic('getWorkflowPolicyInfos')

    def getWorkflowPolicyInfos(self):
        """ Return the list of workflow policy ids.
        """
        wfp_ids = []
        for obj_id, obj in self.objectItems():
            if getattr(obj, '_isAWorkflowPolicy', 0):
                wfp_ids.append({
                    'id': obj_id,
                    'title': obj.title_or_id(),
                    'description': obj.description
                })

        return tuple(wfp_ids)

    security.declareProtected(View, 'getWorkflowPolicyConfig')

    def getWorkflowPolicyConfig(self, ob):
        """ Return a local workflow configuration if it exist
        """
        if self.isSiteRoot(ob):
            # Site root use portal_workflow tool as local policy
            return None
        if not _checkPermission(ManageWorkflowPolicies, ob):
            raise Unauthorized(
                "You need %s permission on %s" %
                (ManageWorkflowPolicies, '/'.join(ob.getPhysicalPath())))

        return getattr(ob.aq_explicit, WorkflowPolicyConfig_id, None)

    security.declareProtected(View, 'isSiteRoot')

    def isSiteRoot(self, ob):
        """ Returns a boolean value indicating if the object is an ISiteRoot
        or the default page of an ISiteRoot.
        """
        siteroot = ISiteRoot.providedBy(ob)
        if siteroot:
            return True
        parent = aq_parent(ob)
        if ISiteRoot.providedBy(parent):
            if (getattr(ob, 'isPrincipiaFolderish', False)
                    and ob.isPrincipiaFolderish):
                # We are looking at a folder in the root
                return False
            # We are at a non-folderish item in the root
            return True
        return False

    def _post_init(self):
        """
        _post_init(self) => called from manage_add method, acquired within ZODB (__init__ is not)
        """
        pass

    #
    #   portal_workflow_policy implementation.
    #

    def getMaxChainLength(self):
        """Return the max workflow chain length"""
        max_chain_length = self.getProperty('max_chain_length')
        return max_chain_length

    def setMaxChainLength(self, max_chain_length):
        """Set the max workflow chain length"""
        safeEditProperty(self,
                         'max_chain_length',
                         max_chain_length,
                         data_type='int')
Example #15
0
def patch_pas():
    # sort alphabetically by patched/added method name
    wrap_method(PluggableAuthService, '_delOb', _delOb)
    wrap_method(
        PluggableAuthService,
        '_getAllLocalRoles',
        _getAllLocalRoles,
        add=True,
    )
    wrap_method(PluggableAuthService, '_doAddGroup', _doAddGroup, add=True)
    wrap_method(PluggableAuthService, '_doAddUser', _doAddUser)
    wrap_method(PluggableAuthService,
                '_doChangeGroup',
                _doChangeGroup,
                add=True)
    wrap_method(PluggableAuthService, '_doChangeUser', _doChangeUser, add=True)
    wrap_method(PluggableAuthService, '_doDelGroups', _doDelGroups, add=True)
    wrap_method(PluggableAuthService, '_doDelUser', _doDelUser, add=True)
    wrap_method(PluggableAuthService,
                '_doDelUsers',
                _doDelUsers,
                add=True,
                roles=PermissionRole(ManageUsers, ('Manager', )))
    wrap_method(PluggableAuthService,
                '_getLocalRolesForDisplay',
                _getLocalRolesForDisplay,
                add=True)
    wrap_method(PluggableAuthService, '_updateGroup', _updateGroup, add=True)
    wrap_method(PluggableAuthService,
                'addRole',
                addRole,
                add=True,
                roles=PermissionRole(ManageUsers, ('Manager', )))
    wrap_method(
        PluggableAuthService,
        'authenticate',
        authenticate,
        add=True,
        roles=(),
    )
    wrap_method(PluggableAuthService,
                'canListAllGroups',
                canListAllGroups,
                add=True,
                roles=PermissionRole(ManageUsers, ('Manager', )))
    wrap_method(PluggableAuthService,
                'canListAllUsers',
                canListAllUsers,
                add=True,
                roles=PermissionRole(ManageUsers, ('Manager', )))
    wrap_method(PluggableAuthService,
                'credentialsChanged',
                credentialsChanged,
                add=True,
                roles=PermissionRole(ManageUsers, ('Manager', )))
    wrap_method(
        PluggableAuthService,
        'getAllLocalRoles',
        getAllLocalRoles,
        add=True,
    )
    wrap_method(PluggableAuthService,
                'getGroup',
                getGroup,
                add=True,
                roles=PermissionRole(ManageUsers, ('Manager', )))
    wrap_method(PluggableAuthService,
                'getGroupById',
                getGroupById,
                add=True,
                roles=PermissionRole(ManageUsers, ('Manager', )))
    wrap_method(PluggableAuthService,
                'getGroupByName',
                getGroupByName,
                add=True,
                roles=PermissionRole(ManageUsers, ('Manager', )))
    wrap_method(PluggableAuthService,
                'getGroupIds',
                getGroupIds,
                add=True,
                roles=PermissionRole(ManageUsers, ('Manager', )))
    wrap_method(PluggableAuthService,
                'getGroupNames',
                getGroupNames,
                add=True,
                roles=PermissionRole(ManageUsers, ('Manager', )))
    wrap_method(PluggableAuthService,
                'getGroups',
                getGroups,
                add=True,
                roles=PermissionRole(ManageUsers, ('Manager', )))
    wrap_method(
        PluggableAuthService,
        'getLocalRolesForDisplay',
        getLocalRolesForDisplay,
        add=True,
    )
    wrap_method(
        PluggableAuthService,
        'getUserIds',
        getUserIds,
        add=True,
        deprecated="Inefficient GRUF wrapper, use IUserIntrospection instead.")
    wrap_method(
        PluggableAuthService,
        'getUserNames',
        getUserNames,
        add=True,
        deprecated="Inefficient GRUF wrapper, use IUserIntrospection instead.")
    wrap_method(PluggableAuthService,
                'getUsers',
                getUsers,
                add=True,
                roles=PermissionRole(ManageUsers, ('Manager', )))
    wrap_method(PluggableAuthService,
                'getPureUsers',
                getUsers,
                add=True,
                roles=PermissionRole(ManageUsers, ('Manager', )))
    wrap_method(PluggableAuthService,
                'userFolderAddUser',
                postonly(userFolderAddUser),
                add=True,
                roles=PermissionRole(ManageUsers, ('Manager', )))
    wrap_method(PluggableAuthService,
                'userFolderDelUsers',
                postonly(_doDelUsers),
                add=True,
                roles=PermissionRole(ManageUsers, ('Manager', )))
    wrap_method(PluggableAuthService,
                'userFolderEditGroup',
                postonly(_doChangeGroup),
                add=True,
                roles=PermissionRole(ManageUsers, ('Manager', )))
    wrap_method(PluggableAuthService,
                'userFolderEditUser',
                postonly(_doChangeUser),
                add=True,
                roles=PermissionRole(ManageUsers, ('Manager', )))
    wrap_method(PluggableAuthService,
                'userFolderDelGroups',
                postonly(_doDelGroups),
                add=True,
                roles=PermissionRole(ManageUsers, ('Manager', )))
    wrap_method(PluggableAuthService,
                'userSetGroups',
                _userSetGroups,
                add=True,
                deprecated="Method from GRUF was removed.")
    wrap_method(PluggableAuthService,
                'userSetPassword',
                userSetPassword,
                add=True,
                roles=PermissionRole(ManageUsers, ('Manager', )))
Example #16
0
        if recursive:
            catalog_tool = getToolByName(self, 'portal_catalog')
            purl = getToolByName(self, 'portal_url')
            _path = purl.getRelativeContentURL(object)
            subobjects = [
                b.getObject() for b in catalog_tool(path={
                    'query': _path,
                    'level': 1
                })
            ]
            for obj in subobjects:
                fixOwnerRole(obj, user.getId())
                if base_hasattr(obj, 'reindexObject'):
                    obj.reindexObject()

    changeOwnershipOf = postonly(changeOwnershipOf)

    @security.public
    def urlparse(self, url):
        """Returns the pieces of url in a six-part tuple.

        Since Python 2.6: urlparse now returns a ParseResult object.
        We just need the tuple form which is tuple(result).
        """
        return tuple(urlparse.urlparse(url))

    @security.public
    def urlunparse(self, url_tuple):
        """Puts a url back together again, in the manner that
        urlparse breaks it.
        """
Example #17
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)
Example #18
0
        if base_hasattr(object, 'reindexObject'):
            object.reindexObject()

        if recursive:
            catalog_tool = getToolByName(self, 'portal_catalog')
            purl = getToolByName(self, 'portal_url')
            _path = purl.getRelativeContentURL(object)
            subobjects = [
                b.getObject()
                for b in catalog_tool(path={'query': _path, 'level': 1})
            ]
            for obj in subobjects:
                fixOwnerRole(obj, user.getId())
                if base_hasattr(obj, 'reindexObject'):
                    obj.reindexObject()
    changeOwnershipOf = postonly(changeOwnershipOf)

    @security.public
    def urlparse(self, url):
        """Returns the pieces of url in a six-part tuple.

        Since Python 2.6: urlparse now returns a ParseResult object.
        We just need the tuple form which is tuple(result).
        """
        return tuple(urlparse.urlparse(url))

    @security.public
    def urlunparse(self, url_tuple):
        """Puts a url back together again, in the manner that
        urlparse breaks it.
        """
                
            if handle_errors:
                sp.rollback()
        except ConflictError:
            raise
        except LinkIntegrityNotificationException:
            raise
        except Exception, e:
            if handle_errors:
                sp.rollback()
                failure[path]= e
            else:
                raise
    transaction_note('Deleted %s' % (', '.join(success)))
    return success, failure
deleteObjectsByPaths = postonly(deleteObjectsByPaths)

def getOrCreateType(portal, atobj, newid, newtypeid):
    """
    Gets the object specified by newid if it already exists under
    atobj or creates it there with the id given in newtypeid
    """    
    try:
        newobj = getattr(atobj,newid) #get it if it already exists
    except AttributeError:  #newobj doesn't already exist
        try:            
            _ = atobj.invokeFactory(id=newid,type_name=newtypeid)            
        except ValueError:
            _createObjectByType(newtypeid, atobj, newid)
        except Unauthorized:
            _createObjectByType(newtypeid, atobj, newid)