Logo Search packages:      
Sourcecode: zope-cmfmember version File versions  Download package

member.py

from Products.CMFCore.interfaces.portal_memberdata import MemberData \
         as IMemberData

import types
import copy
import urllib
import random
import re

from DateTime import DateTime

from Globals import InitializeClass
from AccessControl import ClassSecurityInfo, ModuleSecurityInfo, User, \
     Unauthorized
from AccessControl.PermissionRole import rolesForPermissionOn
from Acquisition import aq_inner, aq_parent, aq_base, aq_chain, aq_get
from ZODB.POSException import ConflictError

from Products import CMFCore
from Products.CMFCore import CMFCorePermissions
from Products.CMFCore.utils import getToolByName, _limitGrantedRoles, \
     _verifyActionPermissions
from Products.CMFCore.Expression import createExprContext
from Products.Archetypes import public as atapi
from Products.Archetypes.utils import shasattr
from Products.Archetypes.debug import log
from Products.Archetypes.interfaces.base import IBaseContent
from Products.Archetypes.Schema import Schemata

from Products.CMFMember.permission import VIEW_PUBLIC_PERMISSION, \
     EDIT_ID_PERMISSION, VIEW_OTHER_PERMISSION, EDIT_PROPERTIES_PERMISSION, \
     VIEW_SECURITY_PERMISSION, EDIT_PASSWORD_PERMISSION, \
     EDIT_SECURITY_PERMISSION, MAIL_PASSWORD_PERMISSION, ADD_MEMBER_PERMISSION, \
     VIEW_PERMISSION, REGISTER_PERMISSION

from Products.CMFMember.tools.registration import RegistrationTool
from Products.CMFMember.config import *
from Products.CMFMember.Extensions.Workflow import triggerAutomaticTransitions

from Products.CMFMember.utils import logException, changeOwnership, unique
from Products.CMFMember.interfaces.member import IMember, IMemberPlone

if USE_SCHEMA_EDITOR:
    from Products.ATSchemaEditorNG.ParentManagedSchema import ParentManagedSchema \
         as MemberBase
    from Products.ATSchemaEditorNG.interfaces import IParentManagedSchema
else:
    class MemberBase:
        pass
    IParentManagedSchema = None

from member_schema import content_schema
metadata_schema = atapi.ExtensibleMetadata.schema.copy()
# some metadata field may already be defined in the member schema
for dup_field in ('language', 'description'):
    if content_schema.has_key(dup_field):
        metadata_schema.delField(dup_field)

# generate the addMember method ourselves so we can do some extra
# initialization
security = ModuleSecurityInfo('Products.CMFMember.Member')
security.declareProtected(ADD_MEMBER_PERMISSION, 'addMember')
def addMember(self, id, **kwargs):
    o = Member(id)
    self._setObject(id, o)
    o = getattr(self, id)
    o.initializeArchetype(**kwargs)



_marker = []
ALLOWED_MEMBER_ID_PATTERN = re.compile( "^[A-Za-z][A-Za-z0-9_]*$" )

def_sec_profile = {'password': '',
                   'roles': (),
                   'domains': (),
                   'groups': ()}

auto_roles = ('Authenticated', 'Anonymous')

class BaseMember(MemberBase):

    security = ClassSecurityInfo()

    __implements__ = (IMemberData, IMember, IMemberPlone)
    if USE_SCHEMA_EDITOR:
        __implements__ += (IParentManagedSchema,)

    archetype_name = portal_type = meta_type = 'Member'
    base_archetype = None

    # Give a nice icon
    content_icon = "user.gif"
    
    # Note that we override BaseContent.schema
    schema = content_schema + metadata_schema
    
    global_allow = 0

    # for Plone compatibility -- managed by workflow state
    listed = 0
    # version used in migrating
    version = VERSION

    # used simply to register the permission with zope
    security.declareProtected(REGISTER_PERMISSION, 'bogus')
    bogus = 'bogus'

    externalStorage = None
    externalStorages = []

    default_roles = ('Member',)
    
    def __init__(self, userid):
        self.id = str(userid)
        self._userInfo = None

        # for plone compatibility
        self.listed = 0

    def initializeArchetype(self, **kwargs):
        """ Perform some extra initialization. """
        self.base_archetype.initializeArchetype(self, **kwargs)
        self.getUser()
        self._setPassword(self._generatePassword())

    def view(self, **kwargs):
        """View action"""
        #XXX CMF1.4 compatibility
        try:
            actions = self.getTypeInfo().getActions()
            for action in actions:
                if action.get('id', None) == 'view':
                    if _verifyActionPermissions(self, action):
                        action = self.restrictedTraverse(action['action'])
                        if action is not None:
                            return action(**kwargs)
        except AttributeError:
            actions = self.getTypeInfo().listActions()
            for action in actions:
                if action.id == 'view':
                    if _verifyActionPermissions(self, action):
                        portal = getToolByName(self,'portal_url').getPortalObject()
                        url=action.action(createExprContext(self.aq_parent,
                                                            portal,
                                                            self))
                        path=urllib.splithost(urllib.splittype(url)[1])[1]
                        # strip the leading slash or traverse will try to
                        # find portal_memberdata in the zmi's root (and fail!)
                        action = self.restrictedTraverse(path[1:])
                        if action is not None:
                            return action(**kwargs)

        raise 'Unauthorized', ('No accessible views available for %s' %
                               '/'.join(self.getPhysicalPath()))

    def __str__(self):
        return self.id

    def __call__(self, *args, **kwargs):
        return self.id

    # ########################################################################
    # User interface
    security.declareProtected(VIEW_PUBLIC_PERMISSION, 'fileAs')
    def fileAs(self):
        """ returns a user friendly identifier of the member, fullname by
        default.  can be overridden in subclasses to support different
        filing policies.  used by the Title field.
        """
        return self.getFullname()

    def index_html(self):
        """ Acquire if not present. """
        return self.view()

    security.declarePublic('lookup_provider')
    def lookup_provider(self):
        """ override schemaeditor to get 'schema provider' explicitly  """
        return getToolByName(self, 'portal_memberdata')
        
    security.declarePublic('getUserName')
    def getUserName(self):
        """Return the username of a user"""
        return self.getUser().getUserName()

    security.declareProtected(VIEW_PUBLIC_PERMISSION, 'getFullname')
    def getFullname(self):
        return self.getField('fullname').get(self)

    security.declareProtected(VIEW_PUBLIC_PERMISSION, 'getEmail')
    def getEmail(self):
        return self.getField('email').get(self)

    security.declareProtected(VIEW_OTHER_PERMISSION, 'getLast_login_time')
    def getLast_login_time(self):
        return self.getField('last_login_time').get(self)

    security.declareProtected(VIEW_OTHER_PERMISSION, 'getLogin_time')
    def getLogin_time(self):
        return self.getField('login_time').get(self)

    security.declarePrivate('getPassword')
    def getPassword(self):
        """Return the password of the user."""
        try:
            return self.getUser()._getPassword()
        except NotImplementedError:
            return ''            

    security.declarePrivate('getDefaultRoles')
    def getDefaultRoles(self):
        return self.default_roles

    security.declarePublic('getRoles')
    def getRoles(self, filter_groups=False):
        """ Return the list of roles assigned to a user.  If 'filter_groups'
        is True then only return the roles that are explicitly assigned
        to the user, not any that are the result of group membership.
        """
        roles=()
        try:
            user=self.getUser()
            if user is None:
                # Temporary fix: when reimporting a plone portal with
                # CMFMember it breaks on catalog indexing because the
                # portal_memberdata gets imported before acl_users
                # also fixes case of orphaned member
                # XXX: find a way to force acl_users to be imported before
                # CMFMember stuff
                return ()

            if filter_groups and shasattr(user, 'getUserRoles'):
                # only works w/ GRUF users
                roles = user.getUserRoles()
            else:
                roles=user.getRoles()
        except TypeError:
            #XXX The user is not in this acl_users so we get None
            if self.getUser().roles is None:
                self.getUser().roles=self.getDefaultRoles()
            if filter_groups and shasattr(user, 'getUserRoles'):
                # only works w/ GRUF users
                roles = user.getUserRoles()
            else:
                roles=self.getUser().getRoles()

        # Remove duplicate roles
        rolefilter = {}
        for r in roles:
            rolefilter[r] = 1
        return tuple(rolefilter.keys())

    security.declarePublic('getFilteredRoles')
    def getFilteredRoles(self, filter_groups=False):
        """Return the list of roles EXCEPT for the ones that are
           automatic, i.e. Authenticated and Anonymous.  Used by
           the MemberData UI to prevent folks from removing those
           roles from any members."""
        roles = list(self.getRoles(filter_groups))
        # remove automatically added roles
        self._removeAutoRoles(roles)
        return tuple(roles)

    security.declarePublic('getDomains')
    def getDomains(self):
        """Return the list of domain restrictions for a user"""
        domains=()
        try:
            user=self.getUser()
            if user is None:
                # temp fix:
                # when reimporting a  plone portal with
                # CMFMember it breaks on catalog indexing
                # because the posrtal_memberdata gets
                # imported before acl_users
                # also fixes case of orphaned member
                # XXX: find a solution to force acl_users to
                # be imported before CMFMember stuff
                return ()

            domains=user.getDomains()
        except TypeError:
            if self.getUser().domains is None:
                self.getUser().domains=()
            domains=self.getUser().getDomains()
        return domains

    security.declarePublic('getRolesInContext')
    def getRolesInContext(self, object):
        """Return the list of roles assigned to the user,
           including local roles assigned in context of
           the passed in object."""
        return tuple(self.getUser().getRolesInContext(object))

    security.declarePublic('has_role')
    def has_role(self, roles, object=None):
        """Check to see if a user has a given role or roles."""
        return self.getUser().has_role(roles, object)

    security.declarePublic('showPasswordOnRegistration')
    def showPasswordOnRegistration(self):
        """Indicates if the password fields should be visible on
           the join_form.
        """
        if self.hasUser():
            return 0
        site_props = self.portal_properties.site_properties
        return not site_props.validate_email

    # dummy method
    def _setConfirmPassword(self, value):
        pass

    # dummy method
    def _getConfirmPassword(self):
        return ''

    def _setPassword(self, password):
        if password:
            self.setSecurityProfile(password=password)

    security.declareProtected(EDIT_PROPERTIES_PERMISSION, 'setFullname')
    def setFullname(self, fullname):
        self.getField('fullname').set(self, fullname)

    security.declareProtected(EDIT_PROPERTIES_PERMISSION, 'setEmail')
    def setEmail(self, email):
        self.getField('email').set(self, email)

    security.declareProtected(EDIT_PROPERTIES_PERMISSION, 'setLast_login_time')
    def setLast_login_time(self, last_login_time):
        self.getField('last_login_time').set(self, last_login_time)

    security.declareProtected(EDIT_PROPERTIES_PERMISSION, 'setLogin_time')
    def setLogin_time(self, login_time):
        self.getField('login_time').set(self, login_time)

    security.declarePrivate('setRoles')
    def setRoles(self, roles):
        roles = self._stringToList(roles)
        self.setSecurityProfile(roles=roles)

    security.declarePrivate('setDomains')
    def setDomains(self, domains):
        # get rid of empty string domains!
        domains = self._stringToList(domains)
        self.setSecurityProfile(domains=domains)

    security.declarePrivate('setSecurityProfile')
    def setSecurityProfile(self, password=None, roles=None, domains=None,
                           groups=None):
        """
        Set the user's basic security profile
        """
        if roles is None:
            roles = self.getFilteredRoles(filter_groups=True)
        if domains is None:
            domains = self.getDomains()
        if groups is None:
            groups = self.getGroups()

        if self.hasUser():
            # if our user lives in a user folder, do this the right way
            user = self._getUserFromInfo(self._userInfo)
            # Use GRUF's _updateUser if available so groups don't get blanked out
            uf = aq_parent(aq_inner(user))
            if getattr(uf.aq_base, '_updateUser', None) is not None:
                uf._updateUser(self.id, password, roles, domains, groups)
            else:
                uf._doChangeUser(self.id, password, roles, domains)

            if shasattr(self, '_v_user'):
                delattr(self, '_v_user')  # remove the cached user
        else:
            # we have a temporary user in hand -- set its attributes by hand
            self._sec_profile = {'password': password,
                                 'roles': roles,
                                 'domains': domains,
                                 'groups': groups}
            user = self.getUser()
            if password and shasattr(user, 'changePassword'):
                # for GRUF
                user.changePassword(password)
            elif password:
                # for ordinary acl_users
                user.__ = password
            if groups is not None:
                acl_users = getToolByName(self, 'acl_users')
                if shasattr(acl_users, 'getGroupPrefix'):
                    pref = acl_users.getGroupPrefix()
                    roles += tuple([pref+g for g in groups])
            user.roles = roles
            user.domains = domains

        self.reindexObject(idxs=['getFilteredRoles', 'getGroups'])

    security.declarePublic('getPortalSkin')
    def getPortalSkin(self):
        portalskin = self.Schema()['portal_skin'].get(self)
        try:
            skins_tool = getToolByName(self, 'portal_skins')
            if portalskin in skins_tool.getSkinSelections():
                return portalskin
            else:
                return skins_tool.getDefaultSkin()
        except AttributeError:
            return portalskin

    #for compatibility to CMFCore member handling
    security.declarePrivate('setMemberProperties')
    def setMemberProperties(self,props):
        return self.edit(**props)

    ######################################
    # group management methods

    security.declarePrivate('setGroups')
    def setGroups(self,groups):
        '''assigns the groups to the user using GroupUserFolder'''
        acl_users=getToolByName(self,'acl_users')
        groups=self._stringToList(groups) #clean out the empty ones

        if not shasattr(acl_users,'getGroupPrefix'):
            return # do nothing if GRUF is not installed

        #pref=acl_users.getGroupPrefix()

        #roles = tuple([r for r in self.getFilteredRoles(filter_groups=True) \
        #               if not r.startswith(pref) ])
        groups = tuple(groups)
        self.setSecurityProfile(groups=groups)

    def getGroups(self):
        ''' fetches the groups from GRUF '''
        acl_users=getToolByName(self,'acl_users')

        user=self.getUser()

        # not a registered member yet, groups are stored in roles
        if not self.hasUser():
            pref = acl_users.getGroupPrefix()
            groups = [g[len(pref):] for g in self.getRoles() \
                      if g.startswith(pref)]
            return groups

        if not shasattr(user,'getGroups'): #return empty list if user comes another acl_user
            return []

        groups=user.getGroups()
        try:
            res = [g.getUserNameWithoutGroupPrefix() \
                   for g in groups if g.getUserNameWithoutGroupPrefix()]
        except AttributeError:
            # then the groups come as array of strings
            pref=acl_users.getGroupPrefix()
            res = [g[len(pref):] for g in groups if g != pref]

        return res

    def getRawGroups(self):
        '''fetches the Group names from GRUF with prefix '''
        user=self.getUser()
        acl_users=getToolByName(self,'acl_users')
        pref=acl_users.getGroupPrefix()

        # not a registered member yet, groups are stored in roles
        if not self.hasUser():
            groups = [g for g in self.getRoles() \
                      if g.startswith(pref)]
            return groups

        if not shasattr(user,'getGroups'): #return empty list if user comes another acl_user
            return []

        groups=user.getGroups()
        try:
            res = [g.getId() for g in groups if g.getId() != pref]
        except AttributeError:
            res = [g for g in groups if g != pref]
        return res

    def valid_groups(self):
        acl_users=getToolByName(self,'acl_users')
        if not shasattr(acl_users,'getGroups'):
            return []
        groups=acl_users.getGroups()
        res = [g.id for g in groups if g.id]
        return res

    def _getUserAndGroupRoles(self):
        """Return effective roles for this user, determines directly and
        by assigned groups"""
        acl_users=getToolByName(self,'acl_users')
        pref=acl_users.getGroupPrefix()
        valid_groups = acl_users.getGroupNames()
        roles = list(self.getFilteredRoles())
        for group in self.getRawGroups():
            if group not in valid_groups:
                continue
            roles.extend(acl_users.getGroup(group).getUserRoles())
            try:
                roles.remove(group)
            except ValueError:
                pass
        return unique(roles)

    # ########################################################################
    # Validators and vocabulary methods

    security.declarePrivate('validate_id')
    def validate_id(self, id):
        # we can't always trust the id argument, b/c the autogen'd
        # id will be passed in if the reg form id field is blank
        form = self.REQUEST.form
        if form.has_key('id') and not form['id']:
            return self.translate('Input is required but no input given.',
                                  default='You did not enter a login name.'),
        elif self.id and id != self.id:
            # don't validate if we're not changing the user id
            memberdata_tool = getToolByName(self, 'portal_memberdata')
            if memberdata_tool.has_key(id) or \
                   not ALLOWED_MEMBER_ID_PATTERN.match( id ) or \
                   id == 'Anonymous User':
                msg = "The login name you selected is already " + \
                      "in use or is not valid. Please choose another."
                return self.translate(msg, default=msg)


    security.declarePrivate('validate_password')
    def validate_password(self, password):
        # no change -- ignore
        if not password:
            return None

        registration_tool = getToolByName(self, 'portal_registration')
        return registration_tool.testPasswordValidity(password)


    security.declarePrivate('validate_roles')
    def validate_roles(self, roles):
        roles = self._stringToList(roles)
        valid = self.valid_roles() + ('Authenticated',) + \
                tuple(self.getRawGroups())

        for r in roles:
            if r not in valid:
                return '%s is not a valid role.' % (r)
        return None


    security.declarePrivate('post_validate')
    def post_validate(self, REQUEST, errors):
        form = REQUEST.form
        if form.has_key('password'):
            password = form.get('password', None)
            confirm = form.get('confirm_password', None)

            if not password:
                errors['password'] = \
                    self.translate('Input is required but no input given.',
                                   default='You did not enter a password.')

            if not errors.get('password', None):
                if password and \
                       (password == REQUEST.get('id', None) or \
                        password == self.id):
                    errors['password'] = \
                        self.translate('id_pass_same',
                                       default="Your username and password are the " +
                                       "same.  This is really not a good idea.",
                                       domain='cmfmember-plone'),
                    
            if not (errors.get('password', None)) and \
                   not (errors.get('confirm_password', None)):
                if password != confirm:
                    errors['password'] = \
                        errors['confirm_password'] = \
                        self.translate('Passwords do not match.',
                                       default='Passwords do not match.'),


    security.declarePublic('isValid')
    def isValid(self):
        """ Check to make sure a Member object's fields satisfy schema constraints"""
        errors = {}
        # make sure object has required data and metadata
        self.Schema().validate(self, None, errors, 1, 1)
        if errors:
            return 0
        return 1


    # Vocabulary methods
    def editors(self):
        return self.portal_properties.site_properties.available_editors

    def valid_roles(self):
        return self.getUser().valid_roles()

    def filtered_valid_roles(self):
        """ return valid roles minus any automatic roles """
        roles = list(self.valid_roles())
        self._removeAutoRoles(roles)
        return tuple(roles)
    
    def available_skins(self):
        # give managers the ability to choose any skin
        managePortal = getToolByName(self, 'portal_membership').checkPermission(CMFCorePermissions.ManagePortal, self)
        skins_tool = getToolByName(self, 'portal_skins')
        if skins_tool.getAllowAny() or managePortal:
            return getToolByName(self, 'portal_skins').getSkinSelections()
        else:
            return [self.getPortalSkin()]

    def getDefaultSkin(self):
        return getToolByName(self, 'portal_skins').getDefaultSkin()

    def getSiteLanguages(self):
        return atapi.DisplayList(self.availableLanguages())

    # ########################################################################
    # Contract with portal_membership

    security.declarePublic('getMemberId')
    def getMemberId(self):
        """Get the member id """
        return self.getUserName()


    security.declareProtected(EDIT_PROPERTIES_PERMISSION, 'setProperties')
    def setProperties(self, mapping=None, **kwargs):
        """assign all the props to member attributes, we expect
        to be able to find a mutator for each of these props
        """
        # if mapping is not a dict, assume it is REQUEST
        if mapping:
            if not type(mapping) == type({}):
                data = {}
                for k,v in mapping.form.items():
                    data[k] = v
                mapping = data
        else:
            mapping = {}

        if kwargs:
            # mapping could be a request object thats not really a dict,
            # this is what we get
            mapping.update(kwargs)

        for fieldname in mapping.keys():
            # have to check permissions by hand... ugh!
            field = self.getField(fieldname)
            if field is not None and not field.checkPermission("edit", self):
                raise Unauthorized
                        
        self.update(**mapping)


    security.declarePrivate('setMemberProperties')
    def setMemberProperties(self, mapping):
        self.setProperties(mapping)


    def _getProperty(self, id):
        """Try to get a member property.  If the property is not found,
        raise an AttributeError"""
        try:
            field = self.Schema().get(id, None)
        except AttributeError:
            # probably USE_SCHEMA_EDITOR was turned on but no schema editor
            # initializations have happened
            if USE_SCHEMA_EDITOR and not shasattr(self.aq_explicit, '_schemas'):
                self._clear(safe=True)
            field = self.Schema().get(id, None)
        if field is not None:
            if not field.checkPermission('view', self):
                raise Unauthorized
            accessor = getattr(self, field.accessor, None)
            value = accessor()
        else:
            base = aq_base(self)
            value = getattr(base, id)
        return value


    security.declarePublic('getProperty')
    def getProperty(self, id, default=_marker):
        try:
            return self._getProperty(id)
        except AttributeError:
            # member does not have a value for given property
            # try memberdata_tool for default value
            memberdata_tool = getToolByName(self, 'portal_memberdata')
            tool_value = memberdata_tool.getProperty(id, _marker)
            user_value = getattr(self.getUser(), id, _marker)

            # If the tool doesn't have the property, use user_value or default
            if tool_value is _marker:
                if user_value is not _marker:
                    return user_value
                elif default is not _marker:
                    return default
                else:
                    raise ValueError, 'The property %s does not exist' % id

            # If the tool has an empty property and we have a user_value, use it
            if not tool_value and user_value is not _marker:
                return user_value

            # Otherwise return the tool value
            return tool_value

    def isListed(self):
        # XXX this is rather inflexible...
        roles = rolesForPermissionOn('View', self)
        if 'Member' in roles:
            return 'Yes'
        else:
            return 'No'


# ##############################################################################
# Methods for managing the user object associated with this member
#
# Member objects are associated with a user.  However, not all Member objects
# have a real user in acl_users -- the real user in an acl_users folder is only
# created upon registration.
#
# * For Members who do have a real associated User object in an acl_users folder
# somewhere, we cache the associated User in a volatile variable in the Member
# object.  The path to the associated user is stored in self._userInfo as a
# 2-tuple consisting of a path to the acl_users folder and the id of the user.
# The method _getUserFromInfo converts this user info into a real user that is
# cached in self._v_user when getUser() is first called after the object is
# awakened from the ZODB.
#
# * For Members who do not yet have associated acl_users user objects, we
# create a temporary placeholder user object that is stored in self._v_user
# (this seemed like a good idea at the time -- I'm not sure this is the right
# way to do this).
#
# self._v_user The representation is a 1-tuple containing the cached user (the
#   tuple is a hack to preserve the acquisition context)
#
# self._userInfo stores the path to the acl_users containing the user object
#   and the user id IF a real user exists in an acl_users folder.  If no real
#   user object exists, self._userInfo is None

    security.declarePublic('getUser')
    def getUser(self):
        """Return the user object associated with the member"""
        if self._userInfo is None:
            self._createTempUser()
        elif not shasattr(self, '_v_user') or self._v_user is None:
            self._v_user = (self._getUserFromInfo(self._userInfo),)
            if self._v_user[0] is None:
                # orphan! -- our associated user object has been deleted
                self._createTempUser()
        return self._v_user[0]


    security.declarePrivate('setUser')
    def setUser(self, user):
        """Set the Member's user object"""
        # make sure the user is wrapped with an acquisition context
        base = getattr(user, 'aq_base', None)
        if base is None:
            # look up the user by id
            u = self._getUserById(user.getUserName())
            if u:
                # wrapped user found
                user = u
            else:
                # user not found -- wrap it in portal.acl_users
                uf = getToolByName(self, 'acl_users')
                user = user.__of__(uf)
        user = aq_inner(user) # get portal_membership, etc out of the aquisition chain
        acl_users = aq_parent(user)

        self._userInfo = self._getInfoFromUser(user)
        assert self._userInfo != []
        assert(user.getUserName() == self.id)
        self._v_user = (user,)


    security.declarePublic('hasUser')
    def hasUser(self):
        """Returns 1 if there is a real acl_users user associated with this
        Member, 0 if a temporary internal one."""
        return self._userInfo is not None


    def isOrphan(self):
        if self._userInfo:
            if self._getUserFromInfo(self._userInfo) is None:
                return 1
        return 0


    def _createUser(self):
        """Create a real user for this member in the portal's acl_users folder"""
        acl_users = getToolByName(self, 'acl_users')
        sec = getattr(self, '_sec_profile', None)
        if sec is None:
            sec = getattr(self, '_sec_profile', def_sec_profile)
        if shasattr(self, '_sec_profile'):
            del self._sec_profile
        acl_users.userFolderAddUser(self.id,
                                    sec['password'],
                                    sec['roles'],
                                    sec['domains'])
        user = acl_users.getUser(self.id).__of__(acl_users)
        self._userInfo = self._getInfoFromUser(user)
        self._v_user = (user,)


    def _createTempUser(self):
        """Create a temporary placeholder user for this member to use for
        delegation until a real acl_users user is created"""
        acl_users = getToolByName(self, 'acl_users')
        sec = getattr(self, '_sec_profile', def_sec_profile)
        self._v_user = (User.SimpleUser(self.id,
                                        sec['password'],
                                        sec['roles'],
                                        sec['domains']).__of__(acl_users),)


    def _getInfoFromUser(self, user):
        """Convert a user to a tuple consisting of a path string + a user id.
        If the user's acl_users folder is outside the portal, generate an
        absolute path starting with a '/' starting from the zope root.  If the
        acl_users folder is in the portal, generate a path not starting with a
        '/' that is relative to the portal root."""
        if user is None:
            return None
        acl_users = aq_parent(aq_inner(user))
        path = '/'.join(acl_users.getPhysicalPath())
        portal_path = '/'.join(getToolByName(self, 'portal_url').getPortalObject().getPhysicalPath())+'/'
        if path.startswith(portal_path):
           path = path[len(portal_path):]
        return (path, user.getUserName())


    def _getUserFromInfo(self, info):
        """Convert a user info tuple into a wrapped user."""
        if info is None:
            return None
        path, id = info
        portal = getToolByName(self, 'portal_url').getPortalObject()
        acl_users = portal.unrestrictedTraverse(path)
        user = acl_users.getUser(id)
        if user is None:
                # temp fix:
                # when reimporting a  plone portal with
                # CMFMember it breaks on catalog indexing
                # because the posrtal_memberdata gets
                # imported before acl_users
                # XXX: find a solution to force acl_users to
                # be imported before CMFMember stuff
            return user
        return user.__of__(acl_users)
        # XXX should context be self.acl_users or self._user's original context?
        # return aq_base(self._user).__of__(self.acl_users)


    def _isPortalUser(self):
        """Test whether a user object comes from the portal's acl_users folder"""
        if not self._userInfo:
            return 0
        return self._userInfo[0] == 'acl_users'


    def _updateCredentials(self):
        if self.REQUEST:
            # don't log out the current user
            if not self.hasUser():
                # Make an extra check to see if the user is there
                acl_users = getToolByName(self, 'acl_users')
                user = acl_users.getUserById(self.getId(), None)
                if user is not None:
                    self.setUser(user)
                else:
                    raise AssertionError
            user = self.getUser()
            acl_users = aq_parent(user)
            if shasattr(acl_users, 'credentialsChanged'):
                # Use an interface provided by LoginManager.
                acl_users.credentialsChanged(user, self.getId(),
                                             self.getPassword())
            else:
                p = getattr(self.REQUEST, '_credentials_changed_path', None)
                if p is not None:
                    # Use an interface provided by CookieCrumbler.
                    change = self.restrictedTraverse(p)
                    # XXX this needs to get the password from the request
                    #     or elsewhere
                    change(user, self.getId(), self.getPassword())

    def _updateExternalStorages(self):
        """ Go through all external storages that wait until they
            are told to flush out the values. These storages are 
            mostly UserFolder storages since they need to wait
            for a user object."""
        
        if self.externalStorage:
            for xStorage in self._getExternalStorages():
                xStorage.updateUserObject(self)
        self.externalStorages = []
        
    def _getExternalStorages(self):
        return self.externalStorages    
        
    def _getUserFolderForUser(self, id=None):
        f = getToolByName(self, 'portal_url').getPortalObject()
        if id is None:
            return f.acl_users
        while 1:
            if not shasattr(f, 'objectIds'):
                return
            if 'acl_users' in f.objectIds():
                if shasattr(f.acl_users, 'getUser'):
                    user = f.acl_users.getUser(id)
                    if user is not None:
                        return f.acl_users
            if shasattr(f, 'getParentNode'):
                f = f.getParentNode()
            else:
                return None


    def _getUserById(self, id):
        """
        A utility method for finding a user by searching through
        portal.acl_users as well as the acl_users folders for all
        zope folders containing portal.

        Returns the user in the acquisition context of its containing
        folder.
        """
        acl_users = self._getUserFolderForUser(id)
        if acl_users is None:
            return None
        return acl_users.getUser(id).__of__(acl_users)


    def _changeUserInfo(self, move, old_user, new_user=None):
        """
        A utility method for transferring/deleting local roles and ownership
        when a member is copied, renamed, or deleted.

        If move = 0, local roles are copied from old_user to new_user and
        ownership remains the same.

        If move = 1, local roles are moved from old_user to new_user.
        If new_user is None, old_user's local roles are simply removed.
        Ownership is transferred from old_user to new_user.  If new_user is
        None, objects owned by old_user are deleted.
        """

        old_user_id = old_user.getUserName()

        if new_user:
            new_user_id = new_user.getUserName()
        else:
            new_user_id = None

        catalog = getToolByName(self, 'portal_catalog')

        localRoleObjects = catalog.search({'indexedUsersWithLocalRoles':old_user.getUserName()})

        reindex = []

        for o in localRoleObjects:
            object = o.getObject()
            if object is not None and object != self:
                if new_user_id:
                    # copy local roles for old user to local roles for new user
                    roles = object.get_local_roles_for_userid(old_user_id)
                    if roles:
                        object.manage_addLocalRoles(new_user_id, roles)
                        reindex.append(object)
                if move:
                    # remove local roles for old user
                    object.manage_delLocalRoles([old_user_id])

        if move:
            old_user_path = '/'.join(aq_parent(aq_inner(old_user)).getPhysicalPath())+'/'+old_user_id
            ownedObjects = catalog.search({'indexedOwner':old_user_path})

            for o in ownedObjects:
                object = o.getObject()
                if object is not None and object != self:
                    if self.handleOrphanedContent(object, new_user):
                        reindex.append(object)

        for o in reindex:
            o.reindexObject()

    # ########################################################################
    # Overrides of base class mutators that trigger workflow transitions

    def update(self, **kwargs):
        membership_tool = getToolByName(self, 'portal_membership')
        updateSelf = membership_tool.getAuthenticatedMember().getUserName() == self.getUserName()
        ret = self.base_archetype.update(self, **kwargs)
        # XXX need better test for password update
        if updateSelf and kwargs.has_key('password'):
            self._updateCredentials()
        # invoke any automated workflow transitions after update
        triggerAutomaticTransitions(self)

        self._updateExternalStorages()
        return ret


    def processForm(self, data=1, metadata=0, REQUEST=None, values=None):
        membership_tool = getToolByName(self, 'portal_membership')
        updateSelf = membership_tool.getAuthenticatedMember().getUserName() == self.getUserName()
        ret = self.base_archetype.processForm(self, data, metadata, REQUEST, values)
        if updateSelf:
            update_credentials = False
            # XXX need better test for password update
            if REQUEST and REQUEST.has_key('password'):
                update_credentials = True
            elif self.REQUEST and self.REQUEST.has_key('password'):
                update_credentials = True
            if update_credentials:
                self._updateCredentials()
        # invoke any automated workflow transitions after update
        triggerAutomaticTransitions(self)

        self._updateExternalStorages()
        
        return ret


    # ########################################################################
    # Methods triggered by workflow transitions

    security.declarePrivate('cm_resetPassword')
    def cm_resetPassword(self):
          self._setPassword(self._generatePassword())       
      
    security.declarePrivate('cm_register')
    def cm_register(self, auto=0):
        try:
            registration_tool = getToolByName(self, 'portal_registration')
            email_notify = 0
            # create a real user
            if not self.hasUser():
                # Limit the granted roles.
                # Anyone is always allowed to grant the 'Member' role.
                roles = self.getFilteredRoles()
                effective_roles = self._getUserAndGroupRoles()
                _limitGrantedRoles(effective_roles, self, self.getDefaultRoles())
                self._createUser()
                self.setRoles(roles)
                # only send mail if we had to create a new user -- this avoids
                # sending mail to users who are already registered at the Zope
                # root level
                email_notify = 1

                self._updateExternalStorages()

            # make the user the owner of the current member object
            self.changeOwnership(self.getUser(), 1)
            self.manage_setLocalRoles(self.getUserName(), ['Owner'])
            # XXX - should we invoke this for members with users in the
            #       Zope root acl_user?
            try:
                registration_tool.afterAdd(self, self.getUserName(),
                                           self.getPassword(), None)
            except ConflictError:
                raise
            except:
                logException()
            self.cm_updateListed()
          
            # Fix up fields that need different settings after registration
            # This is somewhat of an ugly hack. Find a better way! 
            # (Probably needs AT support to really fix this.)
            fldchg = (('id', {'required':0}),
                    ('password', {'required':0}),
                    ('confirm_password', {'required':0}),
                    )
            schema = self.getSchema()
            for fieldname, params in fldchg:
              field = schema.get(fieldname, None)
              if field:
                  for key, value in params.items():
                      setattr(field, key, value)

            if email_notify and auto:
                # check to see if 'mail_me' was set for auto-registration
                site_props = self.portal_properties.site_properties
                if not self.Schema()['mail_me'].get(self) and \
                   not site_props.validate_email:
                    email_notify = 0

            mdc = getToolByName(self, 'portal_memberdata')
            if getattr(mdc, 'unit_test_mode', False):
                email_notify = 0

            if email_notify:
                registration_tool.registeredNotify(self.getUserName())
        except:
            # write tracebacks because otherwise workflow will swallow exceptions
            logException()
            raise

    security.declarePrivate('cm_updateListed')
    def cm_updateListed(self):
        """Extract the correct value of the Member's 'listed' attribute from
           current security settings."""
        membership_tool = getToolByName(self, 'portal_membership')
        self.listed = membership_tool.checkPermission(VIEW_PUBLIC_PERMISSION, self)

    # ########################################################################
    # Methods for dealing with copy / rename / delete

    security.declareProtected(EDIT_ID_PERMISSION, 'setId')
    def setId(self, id):
        if not id:
            id = self.getId()
        memberdata=getToolByName(self, 'portal_memberdata')
        memberdata.manage_renameObjects( (self.getId(),), (id,) )


    def _notifyOfCopyTo(self, container, op=0):
        try:
            self.base_archetype._notifyOfCopyTo(self, container, op)
            if op:
                self._v_old_user = [self.getUser()] # get user with aq context, store in a list to keep from stomping wrapper
        except:
            logException()
            raise


    security.declarePrivate('manage_afterAdd')
    def manage_afterAdd(self, object, container):
        try:
            self.base_archetype.manage_afterAdd(self, object, container)

            if shasattr(object, '_migrating'):
                return
            old_user = getattr(object, '_v_old_user', None)
            if old_user is not None:
                delattr(object, '_v_old_user')
                old_user = old_user[0]

                if old_user and object.id == old_user.getUserName():
                    return

                # object has been renamed
                if object.hasUser():
                    # The old member had a real user, so we need to
                    # create a real user for the new member and transfer
                    # ownership and local roles to the new member's user
                    sec_profile = {'password': old_user._getPassword(),
                                   'roles': old_user.getRoles(),
                                   'domains': old_user.getDomains(),
                                   }
                    self._sec_profile = sec_profile
                    object._createUser()
                    portal = getToolByName(self, 'portal_url').getPortalObject()
                    # change local roles and content ownership info
                    new_user = object.getUser()
                    object._changeUserInfo(1, old_user, new_user)

                    # make the user the owner of the current member object
                    object.manage_delLocalRoles([old_user.getUserName()])
                    object.changeOwnership(new_user, 1)
                    object.manage_setLocalRoles(object.getUserName(), ['Owner'])

                    # Delete the old user object if it was from portal.acl_users but not
                    # if it was from zope_root.acl_users.
                    if object._isPortalUser():
                        # delete the old user
                        if portal.acl_users.getUser(old_user.getUserName()):
                            portal.acl_users.__class__.userFolderDelUsers(portal.acl_users, [old_user.getUserName()])
        except:
            logException()
            raise


    security.declarePrivate('manage_afterClone')
    def manage_afterClone(self, object):
        try:
            if self.hasUser():
                old_user = self.getUser()
                # the copied member had a real user -- create a real user for the copy
                sec_profile = {'password': old_user._getPassword(),
                               'roles': old_user.getRoles(),
                               'domains': old_user.getDomains(),
                               }
                self._sec_profile = sec_profile
                self._createUser()
                new_user = self.getUser()

                portal = getToolByName(self, 'portal_url').getPortalObject()
                self._changeUserInfo(0, old_user, new_user)

                # make the user the owner of the current member object
                roles = list(self.get_local_roles_for_userid(old_user.getUserName()))
                if 'Owner' in roles:
                    roles.remove('Owner')
                    if roles:
                        self.manage_setLocalRoles(old_user.getUserName(), roles)
                    else:
                        self.manage_delLocalRoles([old_user.getUserName()])
                self.changeOwnership(self.getUser(), 1)
                self.manage_setLocalRoles(self.getUserName(), ['Owner'])
        except:
            logException()
            raise


    security.declarePrivate('manage_beforeDelete')
    def manage_beforeDelete(self, item, container):
        try:
            if shasattr(self, '_v_old_user'):
                # if we are in the midst of a move, do nothing
                self.base_archetype.manage_beforeDelete(self, item,
                                                        container)
                return

            if shasattr(self, '_migrating'):
                # if we are in the midst of a migration, do nothing
                return

            # XXX FIXME this is a hack
            # Here's what this addresses (I think): when a site is deleted,
            # acl_users may be deleted before portal_memberdata.  The member
            # object in portal_memberdata holds a reference to the user object
            # in acl_users.  When acl_users gets deleted, the user object
            # remains because its refcount is > 0, but it is now unwrapped.
            # At this point we should just bail.
            if self.getUser() == None:
                self._v_user = None # remove references to user
                return

            portal = getToolByName(self, 'portal_url').getPortalObject()

            # make sure user comes from the portal.acl_users folder and not
            # zope.acl_users or somewhere above the portal
            if self._isPortalUser():
                # recurse through the portal and delete all of the user's content
                # XXX we should create some other options here
                self._changeUserInfo(1, self.getUser(), None)
                # Delete the User object if it's in the current portal's acl_users folder.
                if portal.acl_users.getUser(self.id):
                    portal.acl_users.__class__.userFolderDelUsers(portal.acl_users, [self.id])
            self._v_user = None # remove references to user
            self.base_archetype.manage_beforeDelete(self, item, container)
        except:
            logException()
            raise


    # ########################################################################
    def _generatePassword(self):
        try:
            registration_tool = getToolByName(self, 'portal_registration')
            return registration_tool.generatePassword()
        except AttributeError:

            try:
                password = ''
                n = len(RegistrationTool.password_chars)
                for i in range(0,6):
                    password += RegistrationTool.password_chars[random.randint(0,n-1)]
                return password
            except:
                logException()
                raise

    # replacement for portal_registration's mailPassword function
    security.declareProtected(MAIL_PASSWORD_PERMISSION, 'mailPassword')
    def mailPassword(self):
        """
        Email a forgotten password to a member.
        """
        # assert that the email address is valid
        email = self.getEmail()
        utils = getToolByName(self, 'plone_utils')
        if (not email) or (not utils.validateSingleEmailAddress(email)):
            raise 'ValueError', 'Invalid email address.'

        # Rather than have the template try to use the mailhost, we will
        # render the message ourselves and send it from here (where we
        # don't need to worry about 'UseMailHost' permissions).
        self.REQUEST.set('password', self.getPassword())
        mail_text = self.mail_password_template( self
                                               , self.REQUEST
                                               , member=self
                                               , password=self.getPassword()
                                               , member_id=self.getId()
                                               , member_email=email
                                               )
        host = self.MailHost
        host.send( mail_text )

    # ########################################################################
    # utility methods

    def _stringToList(self, s):
        if s is None:
            return []
        if isinstance(s, types.StringType):
            # split on , or \n and ignore \r
            s = s.replace('\r',',')
            s = s.replace('\n',',')
            s = s.split(',')
        s= [v.strip() for v in s if v.strip()]
        s = filter(None, s)
        return [o for o in s if o]

    def _removeAutoRoles(self, roles_list):
        """ removes automatic roles from passed in list """
        for auto_role in auto_roles:
            while auto_role in roles_list:
                roles_list.remove(auto_role)

    # XXX REFACTOR ME
    # This is used for a hack in MemberDataContainer
    def _getTypeName(self):
        return 'CMFMember Content'


    # ########################################################################
    # Migration methods.  Used to migrate CMFCore member data to Member
    # objects or to migrate from from one type of member objects to another.

    def _migrate(self, old_member, excluded_fields, out):
        """Set Member attributes using values from old_member"""

        if type(old_member) == type({}):
            properties = old_member['properties']
            fields = self.Schema().fields() + ['listed', 'login_time', 'last_login_time']
            for new_field in fields:
                if type(new_field) != type(''):
                    name = new_field.getName()
                if name not in ['id', 'password', 'roles', 'domains'] and \
                   name not in excluded_fields: # fields managed by user object
                    try:
                        value = properties.get(name)
                        self._migrateSetNewValue(name, value, out)
                        out.append('%s.%s = %s' % (old_member['user'].getUserName(), name, str(value)))
                    except:
                        out.append('Unable to get field %s from member %s' % (name, old_member['user'].getUserName()))
                self.setId(old_member['user'].getUserName())

            self.portrait = old_member['portrait']
            self.setUser(old_member['user'])

        else:
            fields = self.Schema().fields() + ['listed', 'login_time', 'last_login_time']
            for new_field in fields:
                if type(new_field) != type(''):
                    name = new_field.getName()
                if name not in ['password', 'roles', 'domains'] and \
                   name not in excluded_fields: # fields managed by user object
                    try:
                        value = self._migrateGetOldValue(old_member, name, out)
                        self._migrateSetNewValue(name, value, out)
                        out.append('%s.%s = %s' % (str(old_member.getMemberId()), name, str(value)))
                    except:
                        out.append('Unable to get field %s from member %s' % (name, old_member.getUserName()))
                        pass

            # move the user over
            if old_member.hasUser():
                self.setUser(old_member.getUser())

    def _migrateGetOldValue(self, old, id, out):
        """Try to get a value from an old member object using a variety of
        methods."""
        old_schema = getattr(old, 'Schema', None)
        if old_schema is not None:
            old_schema = old_schema()

        new_schema = self.Schema()

        if old_schema:
            old_field = old_schema.get(id, None)
            if old_field:
                accessor = getattr(old, old_field.accessor, None)
                if accessor is not None:
                    return accessor()
        new_field = new_schema.get(id)
        accessor = getattr(old, new_field.accessor, None)
        if callable(accessor):
            try:
                return accessor()
            except:
                pass

        if shasattr(old, id):
            return getattr(old, id)

        out.append('Unable to get property %s from member %s\n' % (new_field.name, old.getMemberId()))
        raise ValueError


    def _migrateSetNewValue(self, id, value, out):
        """Utility method for setting a Member attribute using a variety of methods"""
        new_schema = self.Schema()
        new_field = new_schema.get(id, None)
        if new_field:
            if new_field.mutator is not None:
                mutator = getattr(self, new_field.mutator)
                mutator(value)
                return
            if shasattr(self, id):
                setattr(self, id, value)
                return
            out.append('Unable to set property %s from member %s\n' % (new_field.name, self.getMemberId()))
            raise ValueError
        else:
            if shasattr(self, id):
                setattr(self, id, value)
                return
            out.append('Unable to set property %s from member %s\n' % (id, self.getMemberId()))
            raise ValueError

    def getSchema(self):
        """ return the class's schema, possibly provided by
            schema editor """
        if USE_SCHEMA_EDITOR:
            md_tool = getToolByName(self, 'portal_memberdata')
            return md_tool.atse_getSchemaById(self.portal_type)
        else:
            return self.schema
                
    def registerStorageForUpdate(self, storage):
        self.externalStorages.append(storage)
              
InitializeClass(BaseMember)

01441 class Member(BaseMember, atapi.BaseContent):
    """A description of a member"""

    __implements__ = (BaseMember.__implements__ + 
                      atapi.BaseContent.__implements__)

    security = ClassSecurityInfo()
    base_archetype = atapi.BaseContent

    def __init__(self, userid):
        atapi.BaseContent.__init__(self, userid)
        BaseMember.__init__(self, userid)

atapi.registerType(Member)
InitializeClass(Member)

01457 class FolderishMember(BaseMember, atapi.BaseFolder):
    """A description of a member"""

    __implements__ = (BaseMember.__implements__ +
                      atapi.BaseFolder.__implements__)
    
    security = ClassSecurityInfo()
    base_archetype = atapi.BaseFolder

    def __init__(self, userid):
        atapi.BaseFolder.__init__(self, userid)
        BaseMember.__init__(self, userid)

InitializeClass(FolderishMember)

Generated by  Doxygen 1.6.0   Back to index