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)
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)
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',)) )
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:
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)
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()
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)
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)
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
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)
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)
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:
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')
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', )))
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. """
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)
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)