Login and logout

Description

Login and logout related programming activities in Plone

Introduction

This chapter contains login and logout related code snippets.

Login entry points

There are two login points in Plone

/login view (appended to any content URL) directs you to the page where you came from after the login.

/login_form view does login without the redirect back to the original page.

Extracting credentials

Extracting credentials try to extract log-in (username, password) from HTTP request.

Below is an example how to extract and authenticate the user manually. It is mostly sui le for unit testing. Note that given login field isn't necessarily the username. For example, betahaus.emaillogin add-on authenticates users by their email addresses.

Credential extraction will go through all plug-ins registered for PlonePAS system.

The first found login/password pair attempt will be used for user authentication.

Unit test example:

def extract_credentials(self, login, password):
    """ Spoof HTTP login attempt.

    Functional test using zope.testbrowser would be
    more appropriate way to test this.
    """

    request  = self.portal.REQUEST

    # Assume publishing process has succeeded and object has been found by traversing
    # (this is usually set by ZPublisher)
    request['PUBLISHED'] = self.portal

    # More ugly ZPublisher stubs
    request['PARENTS'] = [self.portal]
    request.steps = [self.portal]

    # Spoof HTTP request login fields
    request["__ac_name"] = login
    request["__ac_password"] = password

    # Call PluggableAuthService._extractUserIds()
    # which will return a list of user ids  extracted from the request
    plugins = self.portal.acl_users.plugins

    users = self.portal.acl_users._extractUserIds(request, plugins)

    if len(users) == 0:
        return None

    self.assertEqual(len(users), 1)

    # User will be none if the authentication fails
    # or anonymous if there were no credential fields in HTTP request
    return users[0]

Authenticating the user

Using username and password

Authenticating the user will check that username and password are correct.

Pluggable Authentication Service (acl_users under site root) will go through all authentication plug-ins and return the first succesful authenticated users.

Read more in PlonePAS.

Unit test example:

def authenticate_using_credentials(self, login, password):

    request = self.portal.REQUEST

    # Will return valid user object
    user = self.portal.acl_users.authenticate(login, password, request)
    self.assertNotEqual(user, None)

Using username only

Useful for sudo style logins.

def loginUser(self, username):
    """
    Login Plone user (without password)
    """
    self.context.acl_users.session._setupSession(username, self.context.REQUEST.RESPONSE)
    self.request.RESPONSE.redirect(self.portal_state.portal_url())

See also

Post-login actions

Post-login actions are executed after a successful login. Post-login actions which you could want to change are

  • Where to redirect the user after login
  • Setting the status message after login

#ANTTI:./eggs/Plone-3.2.3-py2.4.egg/Products/CMFPlone/skins/plone_login/logged_in.cpy.metadata #ANTTI:./eggs/Plone-3.2.3-py2.4.egg/Products/CMFPlone/skins/plone_login/logged_in.cpy

Post-login code is defined in CMFPlone/skins/plone_scripts/logged_in.cpy.

You need make a copy of both logged_in.cpy and logged_in.cpy.metadata to your add-on product skins structure to override these.

Example logged_in.cpy:

## Controller Python Script "logged_in"
##bind container=container
##bind context=context
##bind namespace=
##bind script=script
##bind state=state
##bind subpath=traverse_subpath
##parameters=
##title=Initial post-login actions
##

from Products.CMFCore.utils import getToolByName
from Products.CMFPlone import PloneMessageFactory as _
REQUEST=context.REQUEST

membership_tool=getToolByName(context, 'portal_membership')
if membership_tool.isAnonymousUser():
    REQUEST.RESPONSE.expireCookie('__ac', path='/')
    context.plone_utils.addPortalMessage(_(u'Login failed. Both login name and password are case sensitive, check that caps lock is not enabled.'), 'error')
    return state.set(status='failure')

member = membership_tool.getAuthenticatedMember()
login_time = member.getProperty('login_time', '2000/01/01')
initial_login = int(str(login_time) == '2000/01/01')
state.set(initial_login=initial_login)

must_change_password = member.getProperty('must_change_password', 0)
state.set(must_change_password=must_change_password)

if initial_login:
    state.set(status='initial_login')
elif must_change_password:
    state.set(status='change_password')

membership_tool.loginUser(REQUEST)

#
# Special login code specific login code
#

# Debug log output about the user we are dealing with
context.plone_log("Got member:" + str(member))

# Check that if the user has a custom method which marks our special members
# needing special actions
if hasattr(member, "getLoginRedirect"):

    # Show a custom login message
    context.plone_utils.addPortalMessage(_(u'You are now logged in. Welcome to supa-dupa-system.'), 'info') # This message is in Plone i18n domain

    # Go to a custom page after login
    REQUEST.RESPONSE.redirect(context.portal_url() + "/some_folder")

return state

Entry points to logged in member handling

See Products.PluggableAuthService.PluggableAuthService._extractUserIds(). It will try to extract credentials from incoming HTTP request, using different "extract" plug-ins of PAS framework.

PluggableAuthService is also known as acl_users persistent object in the site root.

For each set of extracted credentials, try to authenticate a user; accumulate a list of the IDs of such users over all our authentication and extraction plugins.

PluggableAuthService may use ZCacheable pattern to see if the user data exists already in the cache, based on any extractd credentials, instead of actually checking whether the credentials are valid or not. PluggableAuthService must be set to have cache end. By default it is not set, but installing LDAP sets it to RAM cache.

More info

PAS cache settings

Here is a short view snippet to set PAS cache state:

from Products.Five.browser import BrowserView
from zope.app.component.hooks import getSite

from Products.CMFCore.utils import getToolByName

class PASCacheController(BrowserView):
    """
    Set PAS caching parameters from browser address bar.
    """

    def getPAS(self):
        site=getSite()
        return getToolByName(site, "acl_users")

    def setPASCache(self, value):
        """
        Enables or disables pluggable authentication servive caching.

        The setting is stored persistently in PAS

        This caches credentials for authenticated users after the first login.

        This will make authentication and permission operations little bit faster.
        The downside is that the cache must be purged if you want to remove old values from there.
        (user has been deleted, etc.)

        More info

        * http://svn.plone.org/svn/plone/plone.app.ldap/trunk/plone/app/ldap/ploneldap/util.py

        """

        pas = self.getPAS()

        if value:

            # Enable

            if pas.ZCacheable_getManager() is None:
                pas.ZCacheable_setManagerId(manager_id="RAMCache")

            pas.ZCacheable_setEnabled(True)

        else:
            # Disable
            pas.ZCacheable_setManagerId(None)
            pas.ZCacheable_setEnabled(False)


    def __call__(self):
        """ Serve HTTP GET queries.
        """

        cache_value = self.request.form.get("cache", None)

        if cache_value is None:
            # Output help text
            return "Use: http://localhost/@@pas-cache-controller?cache=true"

        value = (cache_value == "true")

        self.setPASCache(value)

        return "Set value to:" + str(value)

... and related ZCML

<browser:page
 for="Products.CMFCore.interfaces.ISiteRoot"
 name="pas-cache-controller"
 class=".pascache.PASCacheController"
 permission="cmf.ManagePortal"
/>

Login as another user ("sudo")

If you need to login to production system another user and you do not know the password, there is an add-on product for it

Another option

Getting logged in users

Todo

Was somewhere, but can't find where.

Locking user account after too many retries

For security reasons, you might want to locking users after too many tries of logins. This protects user accounts against brute force attacks.




Edit this document

The source code of this file is hosted on GitHub. Everyone can update and fix errors in this document with few clicks - no downloads needed.

  1. Go to Login and logout on GitHub.
  2. Press Fork and edit this file button.
  3. Edit file contents using GitHub's text editor in your web browserm
  4. Fill in the Commit message text box at the end of the page telling why you did the changes. Press Propose file change button next to it when done.
  5. On Send a pull request page you don't need to fill in text anymore. Just press Send pull request button.
  6. Your changes are now queued for review under project's Pull requests tab on Github.

For basic information about updating this manual and Sphinx format please see Writing and updating the manual guide.