Author: rspivak
Date: Tue Nov  1 01:11:05 2005
New Revision: 28867

Added:
   z3lab/cpsblog/trunk/browser/login.pt   (contents, props changed)
   z3lab/cpsblog/trunk/browser/profile.pt   (contents, props changed)
   z3lab/cpsblog/trunk/browser/signup.pt   (contents, props changed)
   z3lab/cpsblog/trunk/browser/signup.py   (contents, props changed)
   z3lab/cpsblog/trunk/etc/
   z3lab/cpsblog/trunk/etc/overrides.zcml   (contents, props changed)
   z3lab/cpsblog/trunk/member.py   (contents, props changed)
Modified:
   z3lab/cpsblog/trunk/blogportal.py
   z3lab/cpsblog/trunk/browser/configure.zcml
   z3lab/cpsblog/trunk/configure.zcml
   z3lab/cpsblog/trunk/interfaces.py
Log:
Additions:
- PAU instantiating on portal creation
- Custom PAU Authenticator and persistent principal implementation
- Sign up form
- Possibility for user change his profile data
- Usage of Session credentials to extract credentials and provide `login` form
- overrides.zcml


Modified: z3lab/cpsblog/trunk/blogportal.py
==============================================================================
--- z3lab/cpsblog/trunk/blogportal.py   (original)
+++ z3lab/cpsblog/trunk/blogportal.py   Tue Nov  1 01:11:05 2005
@@ -1,5 +1,6 @@
 # $Id$
 import zope.interface
+import zope.event
 
 from zope.app.component.site import LocalSiteManager, UtilityRegistration
 from zope.app.folder import Folder
@@ -11,8 +12,15 @@
 from zope.app.intid import IntIds
 from zope.app.intid.interfaces import IIntIds
 
+from zope.app.authentication.authentication import PluggableAuthentication
+from zope.app.authentication.interfaces import IAuthenticatorPlugin
+from zope.app.event.objectevent import ObjectCreatedEvent
+from zope.app.security.interfaces import IAuthentication
+
 from cpsblog.interfaces import IBlogPortal
 from cpsblog.catalog.catalog import Catalog
+from cpsblog.member import MembersDirectory
+from cpsblog.interfaces import IMembersDirectory
 from zc.catalog.extentcatalog import FilterExtent
 
 
@@ -53,3 +61,27 @@
         catalog_reg = UtilityRegistration('catalog', ICatalog, catalog)
         reg_manager.addRegistration(catalog_reg)
         catalog_reg.status = ActiveStatus
+
+        # set up PAU
+        pau = PluggableAuthentication()
+        #pau = BlogPortalAuthentication()
+        zope.event.notify(ObjectCreatedEvent(pau))
+        default['pau'] = pau
+        pau_reg = UtilityRegistration('', IAuthentication, pau)
+        reg_manager.addRegistration(pau_reg)
+        pau_reg.status = ActiveStatus
+
+        pau.authenticatorPlugins = ('cpsblog_principals', )
+        pau.credentialsPlugins = ('Session Credentials', )
+        members_dir = MembersDirectory()
+        zope.event.notify(ObjectCreatedEvent(members_dir))
+        principals_reg = UtilityRegistration('cpsblog_principals',
+                                             IAuthenticatorPlugin,
+                                             members_dir)
+        reg_manager.addRegistration(principals_reg)
+        principals_reg.status = ActiveStatus
+
+        default['pau']['members'] = members_dir
+        registration = UtilityRegistration('', IMembersDirectory, members_dir)
+        reg_manager.addRegistration(registration)
+        registration.status = ActiveStatus

Modified: z3lab/cpsblog/trunk/browser/configure.zcml
==============================================================================
--- z3lab/cpsblog/trunk/browser/configure.zcml  (original)
+++ z3lab/cpsblog/trunk/browser/configure.zcml  Tue Nov  1 01:11:05 2005
@@ -1,5 +1,6 @@
 <configure
-    xmlns="http://namespaces.zope.org/browser";>
+    xmlns="http://namespaces.zope.org/browser";
+    xmlns:zope="http://namespaces.zope.org/zope";>
 
 
   <page
@@ -224,6 +225,44 @@
         />
   </pages>
 
+  <page
+      for="zope.app.site.interfaces.ISite"
+      name="signup.html"
+      template="signup.pt"
+      permission="zope.View"
+      />
+
+  <page
+      for="zope.app.site.interfaces.ISite"
+      name="signup"
+      class=".signup.SignupView"
+      attribute="signup"
+      permission="zope.View"
+      />
+
+  <page
+      for="zope.app.site.interfaces.ISite"
+      name="profile.html"
+      template="profile.pt"
+      class=".signup.ProfileView"
+      permission="zope.View"
+      />
+
+  <page
+      for="zope.app.site.interfaces.ISite"
+      name="profileupdate"
+      class=".signup.ProfileView"
+      attribute="update"
+      permission="zope.View"
+      />
+
+  <page
+      for="*"
+      name="loggedout.html"
+      template="loggedout.pt"
+      permission="zope.Public"
+      />
+
   <include package=".skin" />
   <include package=".widgets" />
 

Added: z3lab/cpsblog/trunk/browser/login.pt
==============================================================================
--- (empty file)
+++ z3lab/cpsblog/trunk/browser/login.pt        Tue Nov  1 01:11:05 2005
@@ -0,0 +1,42 @@
+<html xmlns="http://www.w3.org/1999/xhtml";
+      xmlns:tal="http://xml.zope.org/namespaces/tal";
+      xmlns:metal="http://xml.zope.org/namespaces/metal";
+      xmlns:i18n="http://xml.zope.org/namespaces/i18n";
+      metal:use-macro="context/@@standard_macros/dialog">
+<head>
+  <title metal:fill-slot="title" i18n:translate="">Please log in</title>
+</head>
+<body>
+<div metal:fill-slot="body">
+
+  <h1 i18n:translate="">Please log in</h1>
+
+  <form action="." method="post">
+    <table>
+      <tbody>
+        <tr>
+          <td i18n:translate="">User name:</td>
+          <td><input type="text" name="login" /></td>
+        </tr>
+        <tr>
+          <td i18n:translate="">Password:</td>
+          <td><input type="password" name="password" /></td>
+        </tr>
+        <tr>
+          <td></td>
+          <td><input type="submit" value="Login"
+                     i18n:attributes="login button-login" /></td>
+        </tr>
+      </tbody>
+    </table>
+  </form>
+
+  <p>
+    <span i18n:translate="">Not a member yet?</span>
+    <a href="@@signup.html" i18n:translate="">Sign up for a user
+    account now!</a>
+  </p>
+
+</div>
+</body>
+</html>
\ No newline at end of file

Added: z3lab/cpsblog/trunk/browser/profile.pt
==============================================================================
--- (empty file)
+++ z3lab/cpsblog/trunk/browser/profile.pt      Tue Nov  1 01:11:05 2005
@@ -0,0 +1,76 @@
+<html xmlns="http://www.w3.org/1999/xhtml";
+      xmlns:metal="http://xml.zope.org/namespaces/metal";
+      xmlns:i18n="http://xml.zope.org/namespaces/i18n";
+      metal:use-macro="views/standard_macros/page">
+  <head>
+    <title metal:fill-slot="title" i18n:translate="">Change profile</title>
+  </head>
+  <body>
+    <div metal:fill-slot="body">
+
+      <h1 i18n:translate="">Profile</h1>
+      <div tal:condition="exists: request/psm" tal:content="request/psm" />
+
+      <form action="@@profileupdate" method="post"
+            enctype="multipart/form-data">
+        <table>
+          <tbody>
+            <tr>
+              <td i18n:translate="">Login name:</td>
+              <td tal:content="view/login" />
+            </tr>
+            <tr>
+              <td i18n:translate="">Full name:</td>
+              <td>
+                <input type="text" name="title"
+                       tal:attributes="value view/title" />
+              </td>
+            </tr>
+            <tr>
+              <td i18n:translate="">Password:</td>
+              <td>
+                <input type="password" name="pwd"
+                       tal:attributes="value view/password" />
+              </td>
+            </tr>
+            <tr>
+              <td i18n:translate="">Confirm password:</td>
+              <td>
+                <input type="password" name="confirmation"
+                       tal:attributes="value view/password" />
+              </td>
+            </tr>
+            <tr>
+              <td i18n:translate="">Email:</td>
+              <td>
+                <input type="text" name="email"
+                       tal:attributes="value view/email" />
+              </td>
+            </tr>
+            <tr>
+              <td i18n:translate="" valign="top">Portrait:</td>
+              <td>
+                <input type="file" name="portrait" size="20" />
+                <br />
+                <p>
+                  <img tal:condition="view/portrait"
+                       tal:attributes="src string:${view/portrait}"
+                       width="40" height="60" />
+                  Select an optional image file for your portrait.
+                </p>
+              </td>
+            </tr>
+            <tr>
+              <td></td>
+              <td>
+                <input type="submit" value="Save"
+                       i18n:attributes="value button-save" />
+              </td>
+            </tr>
+          </tbody>
+        </table>
+      </form>
+
+    </div>
+  </body>
+</html>
\ No newline at end of file

Added: z3lab/cpsblog/trunk/browser/signup.pt
==============================================================================
--- (empty file)
+++ z3lab/cpsblog/trunk/browser/signup.pt       Tue Nov  1 01:11:05 2005
@@ -0,0 +1,53 @@
+<html xmlns="http://www.w3.org/1999/xhtml";
+      xmlns:metal="http://xml.zope.org/namespaces/metal";
+      xmlns:i18n="http://xml.zope.org/namespaces/i18n";
+      metal:use-macro="views/standard_macros/page">
+<head>
+  <title metal:fill-slot="title" i18n:translate="">Sign up</title>
+</head>
+<body>
+<div metal:fill-slot="body">
+
+  <h1 i18n:translate="">Sign up</h1>
+
+  <form action="@@signup" method="post" enctype="multipart/form-data">
+    <table>
+      <tbody>
+        <tr>
+          <td i18n:translate="">Login name:</td>
+          <td><input type="text" name="login" /></td>
+        </tr>
+        <tr>
+          <td i18n:translate="">Full name:</td>
+          <td><input type="text" name="title" /></td>
+        </tr>
+        <tr>
+          <td i18n:translate="">Password:</td>
+          <td><input type="password" name="password" /></td>
+        </tr>
+        <tr>
+          <td i18n:translate="">Confirm password:</td>
+          <td><input type="password" name="confirmation" /></td>
+        </tr>
+        <tr>
+          <td i18n:translate="">Email:</td>
+          <td><input type="text" name="email" /></td>
+        </tr>
+        <tr>
+          <td i18n:translate="">Portrait:</td>
+          <td>
+            <input type="file" name="portrait" size="20"/>
+          </td>
+        </tr>
+        <tr>
+          <td></td>
+          <td><input type="submit" value="Sign up"
+                     i18n:attributes="value button-signup" /></td>
+        </tr>
+      </tbody>
+    </table>
+  </form>
+
+</div>
+</body>
+</html>
\ No newline at end of file

Added: z3lab/cpsblog/trunk/browser/signup.py
==============================================================================
--- (empty file)
+++ z3lab/cpsblog/trunk/browser/signup.py       Tue Nov  1 01:11:05 2005
@@ -0,0 +1,86 @@
+# $Id$
+from zope.app import zapi
+from zope.app.exception.interfaces import UserError
+from zope.security.proxy import removeSecurityProxy
+from cpsblog.member import MemberFolder
+from cpsblog.member import Portrait
+from cpsblog.interfaces import IMembersDirectory
+
+from zope.app.securitypolicy.interfaces import IPrincipalRoleManager
+from zope.app.session.interfaces import ISession
+from zope.app.authentication.session import SessionCredentials
+
+class SignupView(object):
+
+    # XXX: this role should be changed
+    roles = ['zope.Manager']
+
+    def signup(self, login='', title='', password='', confirmation='',
+               email='', portrait=None):
+        if password != confirmation:
+            raise UserError('Password and confirmation did not match')
+        mdir = zapi.getUtility(IMembersDirectory, context=self.context)
+        member = mdir[login] = MemberFolder()
+        member.title = title
+        member.password = password
+        member.email = email
+        if portrait:
+            member['portrait'] = Portrait(portrait)
+
+        role_mgr = IPrincipalRoleManager(self.context)
+        role_mgr = removeSecurityProxy(role_mgr)
+
+        principal_id = mdir.prefix + login
+        for role in self.roles:
+            role_mgr.assignRoleToPrincipal(role, principal_id)
+
+        self.request.response.redirect('@@index.html')
+
+class ProfileView(object):
+
+    login = ''
+    title = ''
+    email = ''
+    password = ''
+    portrait = None
+
+    def __init__(self, context, request):
+        self.context, self.request = context, request
+
+        mdir = zapi.getUtility(IMembersDirectory, context=context)
+
+        self.homefolder = mdir.get(request.principal.id[len(mdir.prefix):])
+
+        self.login = self.homefolder.login
+        self.title = self.homefolder.title
+        self.email = self.homefolder.email
+        self.password = self.homefolder.password
+        image = self.homefolder.portrait
+        if image:
+            self.portrait = zapi.absoluteURL(image, request)
+
+    def update(self, login='', title='', pwd='', confirmation='',
+               email='', portrait=None):
+        password = pwd
+        if password != confirmation:
+            raise UserError('Password and confirmation did not match')
+
+        member = self.homefolder
+        member.title = title
+        member.password = password
+        member.email = email
+        if portrait:
+            try:
+                del member['portrait']
+            except KeyError:
+                pass
+            member['portrait'] = Portrait(portrait)
+
+        # We need to change session credentials otherwise redirecting
+        # to the `profile.html` page will result into Unauthorized
+        session_data = ISession(self.request)[
+            'zope.app.authentication.browserplugins']
+        credentials = SessionCredentials(member.login, password)
+        session_data['credentials'] = credentials
+
+        self.request.response.redirect("@@profile.html?psm=Changed")

Modified: z3lab/cpsblog/trunk/configure.zcml
==============================================================================
--- z3lab/cpsblog/trunk/configure.zcml  (original)
+++ z3lab/cpsblog/trunk/configure.zcml  Tue Nov  1 01:11:05 2005
@@ -130,6 +130,31 @@
     <allow attributes="getSiteManager" />
   </content>
 
+  <content class=".member.MembersDirectory">
+    <require
+        permission="zope.View"
+        interface="zope.app.container.interfaces.IReadContainer"
+        />
+    <require
+        permission="zope.ManageContent"
+        set_schema="zope.app.container.interfaces.IWriteContainer"
+        />
+  </content>
+
+  <content class=".member.MemberFolder">
+    <require
+        permission="zope.View"
+        interface=".interfaces.IMemberFolder"
+        />
+  </content>
+
+  <content class=".member.Portrait">
+    <require
+        permission="zope.View"
+        interface=".interfaces.IPortrait"
+        />
+  </content>
+
   <subscriber
       for="zope.app.container.interfaces.IObjectAddedEvent"
       handler=".blogportal.setupSiteManager"

Added: z3lab/cpsblog/trunk/etc/overrides.zcml
==============================================================================
--- (empty file)
+++ z3lab/cpsblog/trunk/etc/overrides.zcml      Tue Nov  1 01:11:05 2005
@@ -0,0 +1,27 @@
+<configure xmlns="http://namespaces.zope.org/zope";
+           xmlns:browser="http://namespaces.zope.org/browser";>
+
+  <!-- Provide local overrides of standard configurations -->
+  <!-- Copy this file to your instance's etc directory -->
+
+  <browser:defaultSkin name="blog" />
+
+  <configure package="cpsblog.browser">
+
+    <browser:page
+        for="zope.exceptions.IUnauthorized"
+        name="index.html"
+        template="login.pt"
+        permission="zope.Public"
+        />
+
+    <browser:page
+        for="*"
+        name="login.html"
+        template="login.pt"
+        permission="zope.Public"
+        />
+
+  </configure>
+
+</configure>
\ No newline at end of file

Modified: z3lab/cpsblog/trunk/interfaces.py
==============================================================================
--- z3lab/cpsblog/trunk/interfaces.py   (original)
+++ z3lab/cpsblog/trunk/interfaces.py   Tue Nov  1 01:11:05 2005
@@ -1,10 +1,14 @@
 # $Id$
-from zope.interface import Interface
+from zope.interface import Interface, Attribute
 from zope.schema import Text, TextLine, Field, Choice, List
 
 from zope.app.container.constraints import ContainerTypesConstraint
 from zope.app.container.constraints import ItemTypePrecondition
+from zope.app.container.constraints import containers, contains
 from zope.app.container.interfaces import IContained, IContainer
+from zope.app.authentication.principalfolder import IInternalPrincipalContainer
+
+from zope.app.file.interfaces import IImage
 
 class IBlogEntry(Interface):
     """Interface for blog entry objects."""
@@ -96,5 +100,33 @@
     def getCategories():
         """Return list of registered categories' names."""
 
+
 class IGlobalCategory(Interface):
     """Category."""
+
+
+class IMemberFolder(IContainer):
+    """A member folder"""
+    containers('.IMembersDirectory')
+
+    email = TextLine(
+        title=u"Email address",
+        required=False)
+
+    full_name = TextLine(
+        title=u"Full name",
+        required=True)
+
+    portrait = Attribute("A portrait of member")
+
+
+class IMembersDirectory(IInternalPrincipalContainer):
+    """Directory to store member folders"""
+    contains(IMemberFolder)
+
+    def getEntry(member_id):
+        """Return member corresponding to member_id"""
+
+
+class IPortrait(IImage):
+    """Holds portrait image of member."""

Added: z3lab/cpsblog/trunk/member.py
==============================================================================
--- (empty file)
+++ z3lab/cpsblog/trunk/member.py       Tue Nov  1 01:11:05 2005
@@ -0,0 +1,84 @@
+# $Id$
+
+from zope import interface
+from zope import component
+
+from zope.app.authentication.principalfolder import PrincipalFolder
+from zope.app.authentication.principalfolder import InternalPrincipal
+from zope.app.authentication import PluggableAuthentication
+from zope.app.authentication import interfaces
+from zope.app.security.interfaces import ILoginPassword
+
+from zope.app.folder import Folder
+from zope.app.file import Image
+from zope.app.dublincore.interfaces import IZopeDublinCore
+
+from zope.app.location.interfaces import ILocation
+from zope.app.session.interfaces import ISession
+
+from cpsblog.interfaces import IMemberFolder
+from cpsblog.interfaces import IMembersDirectory
+from cpsblog.interfaces import IPortrait
+
+class Portrait(Image):
+    interface.implements(IPortrait)
+
+class MemberFolder(Folder, InternalPrincipal):
+    """A member folder"""
+
+    interface.implements(IMemberFolder)
+
+    email = '[EMAIL PROTECTED]'
+    full_name = ''
+    portrait = None
+    _login = ''
+
+    def getLogin(self):
+        """Return login or folder name as user's login."""
+        return self._login or self.__name__
+
+    def setLogin(self, login):
+        """Set login."""
+        self._login = login
+
+    login = property(getLogin, setLogin)
+
+    def getTitle(self):
+        """ """
+        return self.full_name
+
+    def setTitle(self, title):
+        """ """
+        self.full_name = title
+
+    title = property(getTitle, setTitle)
+
+    def getDescription(self):
+        dc = IZopeDublinCore(self)
+        return dc.description
+
+    def setDescription(self, description):
+        dc = IZopeDublinCore(self)
+        dc.description = description
+
+    description = property(getDescription, setDescription)
+
+    def __setitem__(self, key, obj):
+        if IPortrait.providedBy(obj):
+            self.portrait = obj
+        obj.__parent__ = self
+        obj.__name__ = key
+        super(MemberFolder, self).__setitem__(key, obj)
+
+
+class MembersDirectory(PrincipalFolder):
+    """Directory for member folders."""
+
+    interface.implements(IMembersDirectory)
+
+    def __init__(self):
+        super(MembersDirectory, self).__init__(prefix='cpsblog.')
+
+    def __setitem__(self, key, member_folder):
+        member_folder.setLogin(key)
+        super(MembersDirectory, self).__setitem__(key, member_folder)
-- 
http://lists.nuxeo.com/mailman/listinfo/z3lab-checkins

Reply via email to