/tmp/tmp.21897d717286399d24430d52956fb89b/tmp.21897d717286399d24430d52956fb89b.rst:2: (WARNING/2) Title underline too short. ;-*-Doctest-*- ============
We separate all the logic for the actual log in process. What a user types in when logging in or how the system tackles ambiguities at that point is not our concern here. The result of that process will be tokens that identify the user (perhaps user id) and tokens that authenticate the user (perhaps password). These tokens (perhaps cookies) will then be used to authenticate subsequent requests as a logged in user.
The token (eg, cookie) used to identify the user must be unique and unchanging. This is required so we can implement a system with the minimum need for synchronizing across portals. If the user identifier token is unchanging and unique then each portal need know only what that token is when a user joins that portal and the portal need never be notified when any other identifying data (like login name, email, or fullname) is changed in another portal.
Authentication, however, requires access to changeable data in order to support user changable passwords. As such, the identifying token will be used to retrieve a persistent object containing the necessary authenticating data. This persistent object will be shared between all portals where that user is a member. The object will be shared to remove the need to synchronize between portals on password change and to minimize database load, though primarily the former.
As such the interfaces required are as follows. A user id must be translated by a non-originating portal into the right member object within that portal. The target portal that a user joins from an originating portal, must also determine the userid in the target portal. These two interfaces can accomodate both centralized architectures with only one originating portal and decentralized architectures with more than one originating portal.
Add a membrane user:
>>> from Products.CMFPlone.utils import _createObjectByType >>> from Products.CMFPlone.tests.PloneTestCase import ( ... default_password, ) >>> foo_member = _createObjectByType( ... 'SharedMember', self.portal.portal_memberdata, 'foo_member', ... fullname='Foo Member', password=default_password, ... email='foo@foo.com') >>> foo_userId = foo_member.getId()
At this point our member can only log in at the originating site:
>>> from Products.Five.testbrowser import Browser
>>> foo_browser = Browser()
>>> foo_browser.handleErrors = False
>>> foo_browser.open('http://nohost/plone')
>>> foo_browser.getLink('Log in').click()
>>> foo_browser.getControl('Login Name').value = foo_userId
>>> foo_browser.getControl('Password').value = default_password
>>> foo_browser.getControl('Log in').click()
>>> print foo_browser.contents
<!DOCTYPE...
<dl class="portalMessage info"> <dt>Info</dt>
<dd>Welcome! You are now logged in.</dd> </dl>
...</html>
but isn't logged in at another site:
>>> foo_browser.open('http://nohost/foo_portal')
>>> print foo_browser.contents
<!DOCTYPE...
<li>
<a href="http://nohost/foo_portal/login_form">Log in</a>
</li>
...</html>
and cannot login at that other site:
>>> foo_browser.open('http://nohost/foo_portal')
>>> foo_browser.getLink('Log in').click()
>>> foo_browser.getControl('Login Name').value = foo_userId
>>> foo_browser.getControl('Password').value = default_password
>>> foo_browser.getControl('Log in').click()
>>> print foo_browser.contents
<!DOCTYPE...
<dl class="portalMessage error"> <dt>Error</dt>
<dd>Login failed. Both login name and password are case sensitive,
check that caps lock is not enabled.</dd> </dl>
...</html>
Members from an originating portal can join another portal:
>>> from zope.interface import alsoProvides >>> from zope.component import getMultiAdapter >>> from Products.sharedusers.interfaces import ( ... IJoinableSite, ISiteJoiner) >>> alsoProvides(self.app.foo_portal, IJoinableSite) >>> joiner = getMultiAdapter( ... (self.app.foo_portal, foo_member), ... ISiteJoiner) >>> joiner <Products.sharedusers.join.SiteJoiner object at ...> >>> self.loginAsPortalOwner() >>> joiner.join() <SharedMember at /foo_portal/portal_memberdata/foo_member> >>> self.login()
Now this member should be logged in at the other portal without using the log in form:
>>> foo_browser.open('http://nohost/foo_portal')
>>> print foo_browser.contents
<!DOCTYPE...
<li><a id="user-name"
href="http://nohost/foo_portal/dashboard"><img
src="http://nohost/foo_portal/user.gif" alt="" title="User"
height="16" width="16" />
<span class="visualCaseSensitive">Foo Member</span></a></li>
...</html>
Finally we go to yet another portal where the same user is not a member and verify that we're not logged in there:
>>> foo_browser.open('http://nohost/bar_portal')
>>> print foo_browser.contents
<!DOCTYPE...
<li>
<a href="http://nohost/bar_portal/login_form">Log in</a>
</li>
...</html>
When a user changes their passwor at one site where they're a member, it should carry across to other sites seamlessly.
Change our password:
>>> foo_browser.open('http://nohost/plone/password_form')
>>> new_password = 'foo_secret'
>>> foo_browser.getControl(
... 'Current password').value = default_password
>>> foo_browser.getControl(
... 'New password').value = new_password
>>> foo_browser.getControl(
... 'Confirm password').value = new_password
>>> foo_browser.getControl('Change Password').click()
>>> print foo_browser.contents
<!DOCTYPE...
<dd>Password changed.</dd>
...</html>
We're still logged in at this site:
>>> foo_browser.open('http://nohost/plone')
>>> print foo_browser.contents
<!DOCTYPE...
<li><a id="user-name" href="http://nohost/plone/dashboard"><img
src="http://nohost/plone/user.gif" alt="" title="User" height="16"
width="16" />
<span class="visualCaseSensitive">Foo Member</span></a></li>
...</html>
as well as at the other site:
>>> foo_browser.open('http://nohost/foo_portal')
>>> print foo_browser.contents
<!DOCTYPE...
<li><a id="user-name"
href="http://nohost/foo_portal/dashboard"><img
src="http://nohost/foo_portal/user.gif" alt="" title="User"
height="16" width="16" />
<span class="visualCaseSensitive">Foo Member</span></a></li>
...</html>
but still not at a site this user hasn't joined:
>>> foo_browser.open('http://nohost/bar_portal')
>>> print foo_browser.contents
<!DOCTYPE...
<li>
<a href="http://nohost/bar_portal/login_form">Log in</a>
</li>
...</html>
Now we demonstrate with another user:
>>> bar_member = _createObjectByType( ... 'SharedMember', self.portal.portal_memberdata, 'bar_member', ... fullname='Bar Member', password=default_password, ... email='bar@bar.com') >>> bar_userId = bar_member.getId() >>> alsoProvides(self.app.bar_portal, IJoinableSite) >>> self.loginAsPortalOwner() >>> getMultiAdapter( ... (self.app.bar_portal, bar_member), ... ISiteJoiner).join() <SharedMember at /bar_portal/portal_memberdata/bar_member> >>> self.login()
Now we open another browser and log in as another user:
>>> bar_browser = Browser()
>>> bar_browser.handleErrors = False
>>> bar_browser.open('http://nohost/plone')
>>> bar_browser.getLink('Log in').click()
>>> bar_browser.getControl('Login Name').value = bar_userId
>>> bar_browser.getControl('Password').value = default_password
>>> bar_browser.getControl('Log in').click()
>>> print bar_browser.contents
<!DOCTYPE...
<dl class="portalMessage info"> <dt>Info</dt>
<dd>Welcome! You are now logged in.</dd> </dl>
...</html>
Then we make sure they're logged in only to the portals they are members of:
>>> bar_browser.open('http://nohost/foo_portal')
>>> print bar_browser.contents
<!DOCTYPE...
<li>
<a href="http://nohost/foo_portal/login_form">Log in</a>
</li>
...</html>
>>> bar_browser.open('http://nohost/bar_portal')
>>> print bar_browser.contents
<!DOCTYPE...
<li><a id="user-name"
href="http://nohost/bar_portal/dashboard"><img
src="http://nohost/bar_portal/user.gif" alt="" title="User"
height="16" width="16" />
<span class="visualCaseSensitive">Bar Member</span></a></li>
...</html>