Members as content

Introduction

remember (small r) and membrane are framework add-on products for Plone which allows you to manipulate site members as they were normal content objects. The product also allows distributed user management and different user classes.

  • Products.membrane provides framework of integrating acl_users access right to content-like membres and perform tasks like login
  • Products.remember is a basic implementation of this with two different user workflows and normal user schema

Basics

  • Read membrane tutorial.
  • See the example code Products.membrane.example.
  • Read documents at Products.remember/docs/tutorial.
  • See Weblion FacultyStaffDirectory product.
  • It is recommended to enabled debug level logging output for membrane related unit tests, as PlonePAS code swallows several exceptions and do not output them unless debug level is activated

Getting member by username

Example:

from Products.CMFCore.utils  import getToolByName

membrane = getToolByName(context, "membrane_tool")

# getUserAuthProvider returns None if there is no membrane based user match for username
# e.g. this will return None for Zope admin user
sits_user = membrane.getUserAuthProvider(username)
return sits_user

Getting member from MembraneUser or owner record

Below is an example how to resolve member content object from MembraneUser record “owner” who is user “local_user”:

(Pdb) mbtool = self.portal.membrane_tool
(Pdb) owner
<MembraneUser 'local_user'>
(Pdb) mbtool.getUserAuthProvider(owner.getId())
<SitsLocalUser at /plone/country/hospital/local_users/local_user

Creating a member

The following snippet works in unit tests:

mem_password = 'secret'

def_mem_data = {
    'email': 'noreply@xxxxxxxxyyyyyy.com',
    'password': mem_password,
    'confirm_password': mem_password,
     }

mem_data = {
        'portal_member':
        {
          'fullname': 'Portal Member',
          'mail_me': True,
        },
        'admin_member':
        {
          'roles': ['Manager', 'Member']
        },
        'blank_member':
        {},
    }

mdata = getToolByName(self.portal, 'portal_memberdata')

mdata.invokeFactory("MyUserPortalType", name)
member = getattr(mdata, name) #

Populating member fields automatically

Use the following unit test snippet:

def populateUser(self, member):
    """ Auto-ppulate member object required fields based on Archetypes schema.

    @param member: Memberane member content object
    """

    from Products.SitsHospital.content.SitsUser import SitsUser

    schema = SitsUser.schema

    data={}

    for f in schema._fields.values():

        if not f.required:
            continue

        if f.__name__ in [ "password", "id" ]:
            # Do not set password or member id
            continue

        # Autofill member field values
        if f.vocabulary:
            value = f.vocabulary[0][0]
        elif f.__name__ in [ "email" ]:
            value = "test@xyz.com"
        else:
            value = "foo"

        # print "filling in field:" + str(f)

        data[f.__name__] = value

    member.update(**data)

Checking member validy

The following snippet is useful for unit testing:

def assertValidMember(self, member):
    """ Emulate Products.remember.content.member validation behavior with verbose output.

    """
    errors = {}
    # make sure object has required data and metadata
    member.Schema().validate(member, None, errors, 1, 1)
    if errors:
        raise AssertionError("Member contained errors:" + str(errors))

Setting user password

Passwords are stored hashed and can be set using BaseMember._setPassword() method.

_setPassword() takes password as plain-text argument and hashes it before storing:

user_object._setPassword("secret")

Accessing hashed password

Use password attribute directly.

hashed = user_object.password

Password hash should be unicode string.

Note

By default, Products.remember uses HMACHash hasher. As a salt, str(context) string is used. This means that it is not possible to move hashed password from one context item to another. For more information see Products.remember.content.password_hashers module.