[Scodoc-devel] [SVN] Scolar : [1218] Mise sous SVN des deux anciens produits Zope adaptes pour ScoDoc.

eviennet at lipn.univ-paris13.fr eviennet at lipn.univ-paris13.fr
Jeu 21 Mar 22:49:46 CET 2013


Une pièce jointe HTML a été nettoyée...
URL: <https://www-rt.iutv.univ-paris13.fr/pipermail/scodoc-devel/attachments/20130321/36caafd2/attachment-0001.html>
-------------- section suivante --------------

Property changes on: trunk
___________________________________________________________________
Modified: svn:ignore
   - *~
*.pyc


   + *.pyc


Modified: trunk/ZScoDoc.py
===================================================================
--- trunk/ZScoDoc.py	2013-03-21 09:18:39 UTC (rev 1217)
+++ trunk/ZScoDoc.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -58,6 +58,7 @@
 from Globals import INSTANCE_HOME
 from Acquisition import Implicit
 
+# 
 try:
     import Products.ZPsycopgDA.DA as ZopeDA
 except:
@@ -192,13 +193,15 @@
         Note: la connexion est fix\xE9e (SCOUSERS) (base cr\xE9e par l'installeur) !
         Les utilisateurs avanc\xE9s pourront la changer ensuite.
         """
+        # ce connecteur zope - db est encore pour l'instant utilis\xE9 par exUserFolder.pgAuthSource
+        # (en lecture seule en principe)
         oid = 'UsersDB'
         log('create_users_cnx: in %s' % self.id)
         da = ZopeDA.Connection(
             oid, 'Cnx bd utilisateurs',
             SCO_DEFAULT_SQL_USERS_CNX,
             False,
-            check=1, tilevel=2, encoding='iso8859-15')
+            check=1, tilevel=2, encoding='LATIN1')
         self._setObject(oid, da)
         
     security.declareProtected('View', 'change_admin_user')

Modified: trunk/ZScoUsers.py
===================================================================
--- trunk/ZScoUsers.py	2013-03-21 09:18:39 UTC (rev 1217)
+++ trunk/ZScoUsers.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -102,12 +102,8 @@
         ( {'label': 'Contents', 'action': 'manage_main'}, )
         + PropertyManager.manage_options # add the 'Properties' tab
         + (
-# this line is kept as an example with the files :
-#     dtml/manage_editZScolarForm.dtml
-#     html/ZScolar-edit.stx
-#	{'label': 'Properties', 'action': 'manage_editForm',},
-	{'label': 'View',       'action': 'index_html'},
-        )
+            {'label': 'View',       'action': 'index_html'},
+            )
         + Item.manage_options            # add the 'Undo' & 'Owner' tab 
         + RoleManager.manage_options     # add the 'Security' tab
         )
@@ -230,13 +226,15 @@
         u = self._user_list( args={'user_name':user_name} )[0]
         if u['status'] == 'old' and u['roles'] and u['roles'][0] != '-':
             roles = [ '-' + r for r in u['roles'].split(',') ]
-            self.acl_users.manage_editUser( user_name, {'roles' : roles} )
+            cursor = cnx.cursor(cursor_factory=ScoDocCursor)
+            self.acl_users.scodoc_editUser(cursor, user_name, roles=roles )
             self.get_userlist_cache().inval_cache()
         elif not u['status'] and u['roles'] and u['roles'][0] == '-':
             roles = [ r[1:] for r in u['roles'].split(',') if (r and r[0] == '-')]
-            self.acl_users.manage_editUser( user_name, {'roles' : roles} )
+            cursor = cnx.cursor(cursor_factory=ScoDocCursor)
+            self.acl_users.scodoc_editUser(cursor, user_name, roles=roles )
             self.get_userlist_cache().inval_cache()
-        
+    
     def _user_delete(self, user_name):
         # delete user
         cnx = self.GetUsersDBConnexion()
@@ -352,14 +350,10 @@
         cursor = cnx.cursor(cursor_factory=ScoDocCursor)
         cursor.execute('update sco_users set date_modif_passwd=now(), passwd_temp=0 where user_name=%(user_name)s',
                        { 'user_name' : user_name } )
-        req = { 'password' : password,
-                'password_confirm' : password,
-                'roles' : [user[0]['roles']] }
-
-        # Laisse le exUserFolder modifier les donnees
-        self.acl_users.manage_editUser( user_name, req )
-        # Termine cette transaction:
-        self.UsersDB().db.commit()
+        
+        # Laisse le exUserFolder modifier les donnees:
+        self.acl_users.scodoc_editUser(cursor, user_name, password=password, roles=[user[0]['roles']])
+        
         log("change_password: change ok for %s" % user_name)
         self.get_userlist_cache().inval_cache() #>
 
@@ -806,7 +800,8 @@
     security.declareProtected(ScoUsersAdmin, 'create_user')
     def create_user(self, args, REQUEST=None):
         "creation utilisateur zope"
-        cnx = self.GetUsersDBConnexion()        
+        cnx = self.GetUsersDBConnexion()
+        cursor = cnx.cursor(cursor_factory=ScoDocCursor)
         passwd = args['passwd']
         args['passwd'] = 'undefined'
         if 'passwd2' in args:
@@ -814,12 +809,11 @@
         log('create_user: args=%s' % args) # log apres supr. du mot de passe !
         r = self._userEditor.create(cnx, args)
         self.get_userlist_cache().inval_cache() #>
+        
         # call exUserFolder to set passwd
-        args['password'] = passwd
-        args['password_confirm'] = passwd
-        args['roles'] = args['roles'].split(',')
-        junk = self.acl_users.manage_editUser( args['user_name'], args )
-        #log('create_user: junk=%s\n' % junk )
+        roles = args['roles'].split(',')
+        self.acl_users.scodoc_editUser(cursor, args['user_name'], password=passwd, roles=roles )
+        
         if REQUEST:
             return REQUEST.RESPONSE.redirect( REQUEST.URL1 )
 
@@ -1041,10 +1035,7 @@
    self._setObject(id, ZScoUsers(id, title))
    if REQUEST is not None:
         return self.manage_main(self, REQUEST)
-        #return self.manage_editForm(self, REQUEST)
 
-# The form used to get the instance id from the user.
-#manage_addZAbsencesForm = DTMLFile('dtml/manage_addZAbsencesForm', globals())
 
 
     

Modified: trunk/ZScolar.py
===================================================================
--- trunk/ZScolar.py	2013-03-21 09:18:39 UTC (rev 1217)
+++ trunk/ZScolar.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -1531,25 +1531,25 @@
         
         return header + '\n'.join(H) + self.sco_footer(REQUEST)
 
-    security.declareProtected(ScoView, 'doChangeGroup') # XXX unused
-    def _xxx_doChangeGroup(self, etudid, partition_id, group_id, REQUEST=None,
-                       redirect=True):
-        """Change le groupe de l'etudiant dans cette partition. 
-        Si la valeur de group_id est '' (vide) ou 'None', le met \xE0 NULL (aucun groupe).
-        """
-        # inutilise, non test\xE9 (serait utilisable par formChangeGroupe si on l'implemente)
-        partition = sco_groups.get_partition(self, partition_id)
-        formsemestre_id=partition['formsemestre_id']
-        if not self.Notes.can_change_groups(REQUEST, formsemestre_id):
-            raise ScoValueError("Vous n'avez pas le droit d'effectuer cette op\xE9ration !")
-        sem = self.Notes.get_formsemestre(partition['formsemestre_id'])        
-        if sem['etat'] != '1':
-            raise ScoValueError('Modification impossible: semestre verrouill\xE9')
+    # security.declareProtected(ScoView, 'doChangeGroup') # XXX unused
+    # def _xxx_doChangeGroup(self, etudid, partition_id, group_id, REQUEST=None,
+    #                    redirect=True):
+    #     """Change le groupe de l'etudiant dans cette partition. 
+    #     Si la valeur de group_id est '' (vide) ou 'None', le met \xE0 NULL (aucun groupe).
+    #     """
+    #     # inutilise, non test\xE9 (serait utilisable par formChangeGroupe si on l'implemente)
+    #     partition = sco_groups.get_partition(self, partition_id)
+    #     formsemestre_id=partition['formsemestre_id']
+    #     if not self.Notes.can_change_groups(REQUEST, formsemestre_id):
+    #         raise ScoValueError("Vous n'avez pas le droit d'effectuer cette op\xE9ration !")
+    #     sem = self.Notes.get_formsemestre(partition['formsemestre_id'])        
+    #     if sem['etat'] != '1':
+    #         raise ScoValueError('Modification impossible: semestre verrouill\xE9')
         
-        log('doChangeGroup(etudid=%s,partition_id=%s,group_id=%s)'%(etudid,formsemestre_id, group_id))
-        sco_groups.change_etud_group_in_partition(self, etudid, group_id, partition, REQUEST=REQUEST)        
-        if redirect:
-            REQUEST.RESPONSE.redirect('ficheEtud?etudid='+etudid)
+    #     log('doChangeGroup(etudid=%s,partition_id=%s,group_id=%s)'%(etudid,formsemestre_id, group_id))
+    #     sco_groups.change_etud_group_in_partition(self, etudid, group_id, partition, REQUEST=REQUEST)        
+    #     if redirect:
+    #         REQUEST.RESPONSE.redirect('ficheEtud?etudid='+etudid)
     
     # --- Gestion des groupes:
     security.declareProtected(ScoView, 'affectGroups')

Added: trunk/ZopeProducts/README
===================================================================
--- trunk/ZopeProducts/README	                        (rev 0)
+++ trunk/ZopeProducts/README	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,5 @@
+
+ Produits Zope2 anciens et adaptes pour ScoDoc
+
+E. Viennet 2013
+

Added: trunk/ZopeProducts/ZPsycopgDA/DA.py
===================================================================
--- trunk/ZopeProducts/ZPsycopgDA/DA.py	                        (rev 0)
+++ trunk/ZopeProducts/ZPsycopgDA/DA.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,360 @@
+# ZPsycopgDA/DA.py - ZPsycopgDA Zope product: Database Connection
+#
+# Copyright (C) 2004-2010 Federico Di Gregorio  <fog at debian.org>
+#
+# psycopg2 is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published
+# by the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# psycopg2 is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
+# License for more details.
+
+# Import modules needed by _psycopg to allow tools like py2exe to do
+# their work without bothering about the module dependencies.
+
+
+ALLOWED_PSYCOPG_VERSIONS = ('2.4', '2.4.1', '2.4.4', '2.4.5', '2.4.6')
+
+import sys
+import time
+import db
+import re
+
+import Acquisition
+import Shared.DC.ZRDB.Connection
+
+from db import DB
+from Globals import HTMLFile
+from ExtensionClass import Base
+from App.Dialogs import MessageDialog
+from DateTime import DateTime
+
+# ImageFile is deprecated in Zope >= 2.9
+try:
+    from App.ImageFile import ImageFile
+except ImportError:
+    # Zope < 2.9.  If PIL's installed with a .pth file, we're probably
+    # hosed.
+    from ImageFile import ImageFile
+
+# import psycopg and functions/singletons needed for date/time conversions
+
+import psycopg2
+from psycopg2 import NUMBER, STRING, ROWID, DATETIME
+from psycopg2.extensions import INTEGER, LONGINTEGER, FLOAT, BOOLEAN, DATE
+from psycopg2.extensions import TIME, INTERVAL
+from psycopg2.extensions import new_type, register_type
+
+
+# add a new connection to a folder
+
+manage_addZPsycopgConnectionForm = HTMLFile('dtml/add',globals())
+
+def manage_addZPsycopgConnection(self, id, title, connection_string,
+                                 zdatetime=None, tilevel=2,
+                                 encoding='', check=None, REQUEST=None):
+    """Add a DB connection to a folder."""
+    self._setObject(id, Connection(id, title, connection_string,
+                                   zdatetime, check, tilevel, encoding))
+    if REQUEST is not None: return self.manage_main(self, REQUEST)
+
+
+# the connection object
+
+class Connection(Shared.DC.ZRDB.Connection.Connection):
+    """ZPsycopg Connection."""
+    _isAnSQLConnection = 1
+    
+    id                = 'Psycopg2_database_connection' 
+    database_type     = 'Psycopg2'
+    meta_type = title = 'Z Psycopg 2 Database Connection'
+    icon              = 'misc_/conn'
+
+    def __init__(self, id, title, connection_string,
+                 zdatetime, check=None, tilevel=2, encoding='UTF-8'):
+        self.zdatetime = zdatetime
+        self.id = str(id)
+        self.edit(title, connection_string, zdatetime,
+                  check=check, tilevel=tilevel, encoding=encoding)
+        
+    def factory(self):
+        return DB
+
+    ## connection parameters editing ##
+    
+    def edit(self, title, connection_string,
+             zdatetime, check=None, tilevel=2, encoding='UTF-8'):
+        self.title = title
+        self.connection_string = connection_string
+        self.zdatetime = zdatetime
+        self.tilevel = tilevel
+        self.encoding = encoding
+        
+        if check: self.connect(self.connection_string)
+
+    manage_properties = HTMLFile('dtml/edit', globals())
+
+    def manage_edit(self, title, connection_string,
+                    zdatetime=None, check=None, tilevel=2, encoding='UTF-8',
+                    REQUEST=None):
+        """Edit the DB connection."""
+        self.edit(title, connection_string, zdatetime,
+                  check=check, tilevel=tilevel, encoding=encoding)
+        if REQUEST is not None:
+            msg = "Connection edited."
+            return self.manage_main(self,REQUEST,manage_tabs_message=msg)
+
+    def connect(self, s):
+        try:
+            self._v_database_connection.close()
+        except:
+            pass
+
+        # check psycopg version and raise exception if does not match
+        if psycopg2.__version__.split(' ')[0] not in ALLOWED_PSYCOPG_VERSIONS:
+            raise ImportError("psycopg version mismatch (imported %s)" %
+                              psycopg2.__version__)
+
+        self._v_connected = ''
+        dbf = self.factory()
+        
+        # TODO: let the psycopg exception propagate, or not?
+        self._v_database_connection = dbf(
+            self.connection_string, self.tilevel, self.get_type_casts(), self.encoding)
+        self._v_database_connection.open()
+        self._v_connected = DateTime()
+
+        return self
+
+    def get_type_casts(self):
+        # note that in both cases order *is* important
+        if self.zdatetime:
+            return ZDATETIME, ZDATE, ZTIME
+        else:
+            return DATETIME, DATE, TIME
+
+    ## browsing and table/column management ##
+
+    manage_options = Shared.DC.ZRDB.Connection.Connection.manage_options
+    # + (
+    #    {'label': 'Browse', 'action':'manage_browse'},)
+
+    #manage_tables = HTMLFile('dtml/tables', globals())
+    #manage_browse = HTMLFile('dtml/browse', globals())
+
+    info = None
+    
+    def table_info(self):
+        return self._v_database_connection.table_info()
+
+
+    def __getitem__(self, name):
+        if name == 'tableNamed':
+            if not hasattr(self, '_v_tables'): self.tpValues()
+            return self._v_tables.__of__(self)
+        raise KeyError, name
+
+    def tpValues(self):
+        res = []
+        conn = self._v_database_connection
+        for d in conn.tables(rdb=0):
+            try:
+                name = d['TABLE_NAME']
+                b = TableBrowser()
+                b.__name__ = name
+                b._d = d
+                b._c = c
+                try:
+                    b.icon = table_icons[d['TABLE_TYPE']]
+                except:
+                    pass
+                r.append(b)
+            except:
+                pass
+        return res
+
+
+## database connection registration data ##
+
+classes = (Connection,)
+
+meta_types = ({'name':'Z Psycopg 2 Database Connection',
+               'action':'manage_addZPsycopgConnectionForm'},)
+
+folder_methods = {
+    'manage_addZPsycopgConnection': manage_addZPsycopgConnection,
+    'manage_addZPsycopgConnectionForm': manage_addZPsycopgConnectionForm}
+
+__ac_permissions__ = (
+    ('Add Z Psycopg Database Connections',
+     ('manage_addZPsycopgConnectionForm', 'manage_addZPsycopgConnection')),)
+
+# add icons
+
+misc_={'conn': ImageFile('icons/DBAdapterFolder_icon.gif', globals())}
+
+for icon in ('table', 'view', 'stable', 'what', 'field', 'text', 'bin',
+             'int', 'float', 'date', 'time', 'datetime'):
+    misc_[icon] = ImageFile('icons/%s.gif' % icon, globals())
+
+
+## zope-specific psycopg typecasters ##
+
+# convert an ISO timestamp string from postgres to a Zope DateTime object
+def _cast_DateTime(iso, curs):
+    if iso:
+        if iso in ['-infinity', 'infinity']:
+            return iso
+        else:
+            return DateTime(iso)
+
+# convert an ISO date string from postgres to a Zope DateTime object
+def _cast_Date(iso, curs):
+    if iso:
+        if iso in ['-infinity', 'infinity']:
+            return iso
+        else:
+            return DateTime(iso)
+
+# Convert a time string from postgres to a Zope DateTime object.
+# NOTE: we set the day as today before feeding to DateTime so
+# that it has the same DST settings.
+def _cast_Time(iso, curs):
+    if iso:
+        if iso in ['-infinity', 'infinity']:
+            return iso
+        else:
+            return DateTime(time.strftime('%Y-%m-%d %H:%M:%S',
+                                      time.localtime(time.time())[:3]+
+                                      time.strptime(iso[:8], "%H:%M:%S")[3:]))
+
+# NOTE: we don't cast intervals anymore because they are passed
+# untouched to Zope.
+def _cast_Interval(iso, curs):
+    return iso
+
+ZDATETIME = new_type((1184, 1114), "ZDATETIME", _cast_DateTime)
+ZINTERVAL = new_type((1186,), "ZINTERVAL", _cast_Interval)
+ZDATE = new_type((1082,), "ZDATE", _cast_Date)
+ZTIME = new_type((1083,), "ZTIME", _cast_Time)
+
+
+## table browsing helpers ##
+
+class TableBrowserCollection(Acquisition.Implicit):
+    pass
+
+class Browser(Base):
+    def __getattr__(self, name):
+        try:
+            return self._d[name]
+        except KeyError:
+            raise AttributeError, name
+
+class values:
+    def len(self):
+        return 1
+
+    def __getitem__(self, i):
+        try:
+            return self._d[i]
+        except AttributeError:
+            pass
+        self._d = self._f()
+        return self._d[i]
+
+class TableBrowser(Browser, Acquisition.Implicit):
+    icon = 'what'
+    Description = check = ''
+    info = HTMLFile('table_info', globals())
+    menu = HTMLFile('table_menu', globals())
+
+    def tpValues(self):
+        v = values()
+        v._f = self.tpValues_
+        return v
+
+    def tpValues_(self):
+        r=[]
+        tname=self.__name__
+        for d in self._c.columns(tname):
+            b=ColumnBrowser()
+            b._d=d
+            try: b.icon=field_icons[d['Type']]
+            except: pass
+            b.TABLE_NAME=tname
+            r.append(b)
+        return r
+            
+    def tpId(self): return self._d['TABLE_NAME']
+    def tpURL(self): return "Table/%s" % self._d['TABLE_NAME']
+    def Name(self): return self._d['TABLE_NAME']
+    def Type(self): return self._d['TABLE_TYPE']
+
+    manage_designInput=HTMLFile('designInput',globals())
+    def manage_buildInput(self, id, source, default, REQUEST=None):
+        "Create a database method for an input form"
+        args=[]
+        values=[]
+        names=[]
+        columns=self._columns
+        for i in range(len(source)):
+            s=source[i]
+            if s=='Null': continue
+            c=columns[i]
+            d=default[i]
+            t=c['Type']
+            n=c['Name']
+            names.append(n)
+            if s=='Argument':
+                values.append("<dtml-sqlvar %s type=%s>'" %
+                              (n, vartype(t)))
+                a='%s%s' % (n, boboType(t))
+                if d: a="%s=%s" % (a,d)
+                args.append(a)
+            elif s=='Property':
+                values.append("<dtml-sqlvar %s type=%s>'" %
+                              (n, vartype(t)))
+            else:
+                if isStringType(t):
+                    if find(d,"\'") >= 0: d=join(split(d,"\'"),"''")
+                    values.append("'%s'" % d)
+                elif d:
+                    values.append(str(d))
+                else:
+                    raise ValueError, (
+                        'no default was given for <em>%s</em>' % n)
+
+class ColumnBrowser(Browser):
+    icon='field'
+
+    def check(self):
+        return ('\t<input type=checkbox name="%s.%s">' %
+                (self.TABLE_NAME, self._d['Name']))
+    def tpId(self): return self._d['Name']
+    def tpURL(self): return "Column/%s" % self._d['Name']
+    def Description(self):
+        d=self._d
+        if d['Scale']:
+            return " %(Type)s(%(Precision)s,%(Scale)s) %(Nullable)s" % d
+        else:
+            return " %(Type)s(%(Precision)s) %(Nullable)s" % d
+
+table_icons={
+    'TABLE': 'table',
+    'VIEW':'view',
+    'SYSTEM_TABLE': 'stable',
+    }
+
+field_icons={
+    NUMBER.name: 'i',
+    STRING.name: 'text',
+    DATETIME.name: 'date',
+    INTEGER.name: 'int',
+    FLOAT.name: 'float',
+    BOOLEAN.name: 'bin',
+    ROWID.name: 'int'
+    }

Added: trunk/ZopeProducts/ZPsycopgDA/__init__.py
===================================================================
--- trunk/ZopeProducts/ZPsycopgDA/__init__.py	                        (rev 0)
+++ trunk/ZopeProducts/ZPsycopgDA/__init__.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,29 @@
+# ZPsycopgDA/__init__.py - ZPsycopgDA Zope product
+#
+# Copyright (C) 2004-2010 Federico Di Gregorio  <fog at debian.org>
+#
+# psycopg2 is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published
+# by the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# psycopg2 is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
+# License for more details.
+
+# Import modules needed by _psycopg to allow tools like py2exe to do
+# their work without bothering about the module dependencies.
+
+__doc__ = "ZPsycopg Database Adapter Registration." 
+__version__ = '2.0'
+
+import DA
+
+def initialize(context):
+    context.registerClass(
+        DA.Connection,
+        permission = 'Add Z Psycopg 2 Database Connections',
+        constructors = (DA.manage_addZPsycopgConnectionForm,
+                        DA.manage_addZPsycopgConnection),
+        icon = 'icons/DBAdapterFolder_icon.gif')

Added: trunk/ZopeProducts/ZPsycopgDA/db.py
===================================================================
--- trunk/ZopeProducts/ZPsycopgDA/db.py	                        (rev 0)
+++ trunk/ZopeProducts/ZPsycopgDA/db.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,214 @@
+# ZPsycopgDA/db.py - query execution
+#
+# Copyright (C) 2004-2010 Federico Di Gregorio  <fog at debian.org>
+#
+# psycopg2 is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published
+# by the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# psycopg2 is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
+# License for more details.
+
+# Import modules needed by _psycopg to allow tools like py2exe to do
+# their work without bothering about the module dependencies.
+
+from Shared.DC.ZRDB.TM import TM
+from Shared.DC.ZRDB import dbi_db
+
+from ZODB.POSException import ConflictError
+
+import site
+import pool
+
+import psycopg2
+from psycopg2.extensions import INTEGER, LONGINTEGER, FLOAT, BOOLEAN, DATE, TIME
+from psycopg2.extensions import TransactionRollbackError, register_type
+from psycopg2 import NUMBER, STRING, ROWID, DATETIME 
+
+
+# the DB object, managing all the real query work
+
+class DB(TM, dbi_db.DB):
+
+    _p_oid = _p_changed = _registered = None
+
+    def __init__(self, dsn, tilevel, typecasts, enc='utf-8'):
+        self.dsn = dsn
+        self.tilevel = tilevel
+        self.typecasts = typecasts
+        if enc is None or enc == "":
+            self.encoding = "utf-8"
+        else:
+            self.encoding = enc
+        self.failures = 0
+        self.calls = 0
+        self.make_mappings()
+
+    def getconn(self, init=True):
+        # if init is False we are trying to get hold on an already existing
+        # connection, so we avoid to (re)initialize it risking errors.
+        conn = pool.getconn(self.dsn)
+        if init:
+            # use set_session where available as in these versions
+            # set_isolation_level generates an extra query.
+            if psycopg2.__version__ >= '2.4.2':
+                conn.set_session(isolation_level=int(self.tilevel))
+            else:
+                conn.set_isolation_level(int(self.tilevel))
+            encoding = self.encoding
+            # Compatibility LATIN1/iso8859-15 fro ScoDoc:
+            if encoding == 'iso8859-15':
+                encoding = 'LATIN1'
+            conn.set_client_encoding(encoding)
+
+            for tc in self.typecasts:
+                register_type(tc, conn)
+        return conn
+
+    def putconn(self, close=False):
+        try:
+            conn = pool.getconn(self.dsn, False)
+        except AttributeError:
+            pass
+        pool.putconn(self.dsn, conn, close)
+
+    def getcursor(self):
+        conn = self.getconn(False)
+        return conn.cursor()
+
+    def _finish(self, *ignored):
+        try:
+            conn = self.getconn(False)
+            conn.commit()
+            self.putconn()
+        except AttributeError:
+            pass
+
+    def _abort(self, *ignored):
+        try:
+            conn = self.getconn(False)
+            conn.rollback()
+            self.putconn()
+        except AttributeError:
+            pass
+
+    def open(self):
+        # this will create a new pool for our DSN if not already existing,
+        # then get and immediately release a connection
+        self.getconn()
+        self.putconn()
+
+    def close(self):
+        # FIXME: if this connection is closed we flush all the pool associated
+        # with the current DSN; does this makes sense?
+        pool.flushpool(self.dsn)
+
+    def sortKey(self):
+        return 1
+
+    def make_mappings(self):
+        """Generate the mappings used later by self.convert_description()."""
+        self.type_mappings = {}
+	for t, s in [(INTEGER,'i'), (LONGINTEGER, 'i'), (NUMBER, 'n'),  
+	             (BOOLEAN,'n'), (ROWID, 'i'),
+	             (DATETIME, 'd'), (DATE, 'd'), (TIME, 'd')]:
+            for v in t.values:
+	        self.type_mappings[v] = (t, s)
+
+    def convert_description(self, desc, use_psycopg_types=False):
+        """Convert DBAPI-2.0 description field to Zope format."""
+        items = []
+        for name, typ, width, ds, p, scale, null_ok in desc:
+	    m = self.type_mappings.get(typ, (STRING, 's'))
+            items.append({
+                'name': name,
+                'type': use_psycopg_types and m[0] or m[1],
+                'width': width,
+                'precision': p,
+                'scale': scale,
+                'null': null_ok,
+                })
+        return items
+
+    ## tables and rows ##
+
+    def tables(self, rdb=0, _care=('TABLE', 'VIEW')):
+        self._register()
+        c = self.getcursor()
+        c.execute(
+            "SELECT t.tablename AS NAME, 'TABLE' AS TYPE "
+            "  FROM pg_tables t WHERE tableowner <> 'postgres' "
+            "UNION SELECT v.viewname AS NAME, 'VIEW' AS TYPE "
+            "  FROM pg_views v WHERE viewowner <> 'postgres' "
+            "UNION SELECT t.tablename AS NAME, 'SYSTEM_TABLE\' AS TYPE "
+            "  FROM pg_tables t WHERE tableowner = 'postgres' "
+            "UNION SELECT v.viewname AS NAME, 'SYSTEM_TABLE' AS TYPE "
+            "FROM pg_views v WHERE viewowner = 'postgres'")
+        res = []
+        for name, typ in c.fetchall():
+            if typ in _care:
+                res.append({'TABLE_NAME': name, 'TABLE_TYPE': typ})
+        self.putconn()
+        return res
+
+    def columns(self, table_name):
+        self._register()
+        c = self.getcursor()
+        try:
+            r = c.execute('SELECT * FROM "%s" WHERE 1=0' % table_name)
+        except:
+            return ()
+        self.putconn()
+        return self.convert_description(c.description, True)
+    
+    ## query execution ##
+
+    def query(self, query_string, max_rows=None, query_data=None):
+        self._register()
+        self.calls = self.calls+1
+
+        desc = ()
+        res = []
+        nselects = 0
+
+        c = self.getcursor()
+
+        try:
+            for qs in [x for x in query_string.split('\0') if x]:
+                try:
+                    if query_data:
+                        c.execute(qs, query_data)
+                    else:
+                        c.execute(qs)
+                except TransactionRollbackError:
+                    # Ha, here we have to look like we are the ZODB raising conflict errrors, raising ZPublisher.Publish.Retry just doesn't work
+                    #logging.debug("Serialization Error, retrying transaction", exc_info=True)
+                    raise ConflictError("TransactionRollbackError from psycopg2")
+                except psycopg2.OperationalError:
+                    #logging.exception("Operational error on connection, closing it.")
+                    try:
+                        # Only close our connection
+                        self.putconn(True)
+                    except:
+                        #logging.debug("Something went wrong when we tried to close the pool", exc_info=True)
+                        pass
+                if c.description is not None:
+                    nselects += 1
+                    if c.description != desc and nselects > 1:
+                        raise psycopg2.ProgrammingError(
+                            'multiple selects in single query not allowed')
+                    if max_rows:
+                        res = c.fetchmany(max_rows)
+                    else:
+                        res = c.fetchall()
+                    desc = c.description
+            self.failures = 0
+
+        except StandardError, err:
+            self._abort()
+            raise err
+        
+        return self.convert_description(desc), res

Added: trunk/ZopeProducts/ZPsycopgDA/dtml/add.dtml
===================================================================
--- trunk/ZopeProducts/ZPsycopgDA/dtml/add.dtml	                        (rev 0)
+++ trunk/ZopeProducts/ZPsycopgDA/dtml/add.dtml	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,108 @@
+<dtml-var manage_page_header>
+
+<dtml-var "manage_form_title(this(), _,
+           form_title='Add Z Psycopg 2 Database Connection',
+           help_product='ZPsycopgDA',
+           help_topic='ZPsycopgDA-Method-Add.stx'
+           )">
+
+<p class="form-help">
+A Zope Psycopg 2 Database Connection is used to connect and execute
+queries on a PostgreSQL database.
+</p>
+
+<p class="form-help"> 
+In the form below <em>Connection String</em> (also called the Data Source Name
+or DSN for short) is a string... (TODO: finish docs)
+</p>
+
+<form action="manage_addZPsycopgConnection" method="POST">
+<table cellspacing="0" cellpadding="2" border="0">
+  <tr>
+    <td align="left" valign="top">
+    <div class="form-label">
+    Id
+    </div>
+    </td>
+    <td align="left" valign="top">
+    <input type="text" name="id" size="40"
+           value="Psycopg2_database_connection" />
+    </td>
+  </tr>
+  <tr>
+    <td align="left" valign="top">
+    <div class="form-optional">
+    Title
+    </div>
+    </td>
+    <td align="left" valign="top">
+    <input type="text" name="title" size="40"
+        value="Z Psycopg 2 Database Connection"/>
+    </td>
+  </tr>
+  <tr>
+    <td align="left" valign="top">
+    <div class="form-label">
+    Connection string
+    </div>
+    </td>
+    <td align="left" valign="top">
+    <input type="text" name="connection_string" size="40" value="" />
+    </td>
+  </tr>
+  <tr>
+    <td align="left" valign="top">
+    <div class="form-label">
+    Connect immediately
+    </div>
+    </td>
+    <td align="left" valign="top">
+    <input type="checkbox" name="check" value="YES" checked="YES" />
+    </td>
+  </tr>
+  <tr>
+    <td align="left" valign="top">
+    <div class="form-label">
+    Use Zope's internal DateTime
+    </div>
+    </td>
+    <td align="left" valign="top">
+    <input type="checkbox" name="zdatetime" value="YES" checked="YES" />
+    </td>
+  </tr>
+  <tr>
+    <td align="left" valign="top">
+    <div class="form-label">
+    Transaction isolation level
+    </div>
+    </td>
+    <td align="left" valign="top">
+      <select name="tilevel:int">
+        <option value="4">Read uncommitted</option>
+        <option value="1">Read committed</option>
+        <option value="2" selected="YES">Repeatable read</option>
+        <option value="3">Serializable</option>
+      </select>
+    </td>
+  </tr>
+  <tr>
+    <td align="left" valign="top">
+    <div class="form-label">
+    Encoding
+    </div>
+    </td>
+    <td align="left" valign="top">
+    <input type="text" name="encoding" size="40" value="" />
+    </td>
+  </tr>
+  <tr>
+    <td align="left" valign="top" colspan="2">
+    <div class="form-element">
+    <input class="form-element" type="submit" name="submit" value=" Add " />
+    </div>
+    </td>
+  </tr>
+</table>
+</form>
+
+<dtml-var manage_page_footer>

Added: trunk/ZopeProducts/ZPsycopgDA/dtml/browse.dtml
===================================================================
--- trunk/ZopeProducts/ZPsycopgDA/dtml/browse.dtml	                        (rev 0)
+++ trunk/ZopeProducts/ZPsycopgDA/dtml/browse.dtml	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,11 @@
+<html>
+  <head><title><dtml-var title_or_id >tables</title></head>
+  <body bgcolor="#FFFFFF" link="#000099" vlink="#555555" alink="#77003B">
+    <dtml-var manage_tabs>
+    <dtml-tree header="info">
+      <IMG SRC="<dtml-var SCRIPT_NAME >/misc_/ZPsycopgDA/<dtml-var icon>"
+       ALT="<dtml-var Type>" BORDER="0">
+      <dtml-var Name><dtml-var Description>
+    </dtml-tree>
+  </body>
+</html>

Added: trunk/ZopeProducts/ZPsycopgDA/dtml/edit.dtml
===================================================================
--- trunk/ZopeProducts/ZPsycopgDA/dtml/edit.dtml	                        (rev 0)
+++ trunk/ZopeProducts/ZPsycopgDA/dtml/edit.dtml	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,84 @@
+<dtml-var manage_page_header>
+<dtml-var manage_tabs>
+
+<form action="manage_edit" method="POST">
+<table cellspacing="0" cellpadding="2" border="0">
+  <tr>
+    <td align="left" valign="top">
+    <div class="form-optional">
+    Title
+    </div>
+    </td>
+    <td align="left" valign="top">
+    <input type="text" name="title" size="40"
+        value="&dtml-title;"/>
+    </td>
+  </tr>
+  <tr>
+    <td align="left" valign="top">
+    <div class="form-label">
+    Connection string
+    </div>
+    </td>
+    <td align="left" valign="top">
+    <input type="text" name="connection_string" size="40"
+           value="&dtml-connection_string;" />
+    </td>
+  </tr>
+  <tr>
+    <td align="left" valign="top">
+    <div class="form-label">
+    Use Zope's internal DateTime
+    </div>
+    </td>
+    <td align="left" valign="top">
+    <input type="checkbox" name="zdatetime" value="YES"
+      <dtml-if expr="zdatetime">checked="YES"</dtml-if> />
+    </td>
+  </tr>
+  <tr>
+    <td align="left" valign="top">
+    <div class="form-label">
+    Transaction isolation level
+    </div>
+    </td>
+    <td align="left" valign="top">
+      <select name="tilevel:int">
+        <option value="4"
+                <dtml-if expr="tilevel==4">selected="YES"</dtml-if>>
+        Read uncommitted</option>
+        <option value="1"
+                <dtml-if expr="tilevel==1">selected="YES"</dtml-if>>
+        Read committed</option>
+        <option value="2"
+                <dtml-if expr="tilevel==2">selected="YES"</dtml-if>>
+        Repeatable read</option>
+        <option value="3"
+                <dtml-if expr="tilevel==3">selected="YES"</dtml-if>>
+        Serializable</option>
+      </select>
+    </td>
+  </tr>
+  <tr>
+    <td align="left" valign="top">
+    <div class="form-label">
+    Encoding
+    </div>
+    </td>
+    <td align="left" valign="top">
+    <input type="text" name="encoding" size="40"
+           value="&dtml-encoding;" />
+    </td>
+  </tr>
+  <tr>
+    <td align="left" valign="top" colspan="2">
+    <div class="form-element">
+    <input class="form-element" type="submit" name="submit"
+     value=" Save Changes " />
+    </div>
+    </td>
+  </tr>
+</table>
+</form>
+
+<dtml-var manage_page_footer>

Added: trunk/ZopeProducts/ZPsycopgDA/dtml/table_info.dtml
===================================================================
--- trunk/ZopeProducts/ZPsycopgDA/dtml/table_info.dtml	                        (rev 0)
+++ trunk/ZopeProducts/ZPsycopgDA/dtml/table_info.dtml	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,7 @@
+<dtml-var standard_html_header>
+
+<dtml-var TABLE_TYPE><dtml-if TABLE_OWNER>
+ owned by <dtml-var TABLE_OWNER></dtml-if>
+<dtml-if REMARKS><br><dtml-var REMARKS></dtml-if>
+
+<dtml-var standard_html_footer>

Added: trunk/ZopeProducts/ZPsycopgDA/icons/DBAdapterFolder_icon.gif
===================================================================
(Binary files differ)


Property changes on: trunk/ZopeProducts/ZPsycopgDA/icons/DBAdapterFolder_icon.gif
___________________________________________________________________
Added: svn:executable
   + *
Added: svn:mime-type
   + application/octet-stream

Added: trunk/ZopeProducts/ZPsycopgDA/icons/bin.gif
===================================================================
(Binary files differ)


Property changes on: trunk/ZopeProducts/ZPsycopgDA/icons/bin.gif
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/ZopeProducts/ZPsycopgDA/icons/date.gif
===================================================================
(Binary files differ)


Property changes on: trunk/ZopeProducts/ZPsycopgDA/icons/date.gif
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/ZopeProducts/ZPsycopgDA/icons/datetime.gif
===================================================================
(Binary files differ)


Property changes on: trunk/ZopeProducts/ZPsycopgDA/icons/datetime.gif
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/ZopeProducts/ZPsycopgDA/icons/field.gif
===================================================================
(Binary files differ)


Property changes on: trunk/ZopeProducts/ZPsycopgDA/icons/field.gif
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/ZopeProducts/ZPsycopgDA/icons/float.gif
===================================================================
(Binary files differ)


Property changes on: trunk/ZopeProducts/ZPsycopgDA/icons/float.gif
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/ZopeProducts/ZPsycopgDA/icons/int.gif
===================================================================
(Binary files differ)


Property changes on: trunk/ZopeProducts/ZPsycopgDA/icons/int.gif
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/ZopeProducts/ZPsycopgDA/icons/stable.gif
===================================================================
(Binary files differ)


Property changes on: trunk/ZopeProducts/ZPsycopgDA/icons/stable.gif
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/ZopeProducts/ZPsycopgDA/icons/table.gif
===================================================================
(Binary files differ)


Property changes on: trunk/ZopeProducts/ZPsycopgDA/icons/table.gif
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/ZopeProducts/ZPsycopgDA/icons/text.gif
===================================================================
(Binary files differ)


Property changes on: trunk/ZopeProducts/ZPsycopgDA/icons/text.gif
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/ZopeProducts/ZPsycopgDA/icons/time.gif
===================================================================
(Binary files differ)


Property changes on: trunk/ZopeProducts/ZPsycopgDA/icons/time.gif
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/ZopeProducts/ZPsycopgDA/icons/view.gif
===================================================================
(Binary files differ)


Property changes on: trunk/ZopeProducts/ZPsycopgDA/icons/view.gif
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/ZopeProducts/ZPsycopgDA/icons/what.gif
===================================================================
(Binary files differ)


Property changes on: trunk/ZopeProducts/ZPsycopgDA/icons/what.gif
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/ZopeProducts/ZPsycopgDA/pool.py
===================================================================
--- trunk/ZopeProducts/ZPsycopgDA/pool.py	                        (rev 0)
+++ trunk/ZopeProducts/ZPsycopgDA/pool.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,193 @@
+# ZPsycopgDA/pool.py - ZPsycopgDA Zope product: connection pooling
+#
+# Copyright (C) 2004-2010 Federico Di Gregorio  <fog at debian.org>
+#
+# psycopg2 is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published
+# by the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# psycopg2 is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
+# License for more details.
+
+# Import modules needed by _psycopg to allow tools like py2exe to do
+# their work without bothering about the module dependencies.
+
+# All the connections are held in a pool of pools, directly accessible by the
+# ZPsycopgDA code in db.py.
+
+import threading
+import psycopg2
+from psycopg2.pool import PoolError
+
+
+class AbstractConnectionPool(object):
+    """Generic key-based pooling code."""
+
+    def __init__(self, minconn, maxconn, *args, **kwargs):
+        """Initialize the connection pool.
+
+        New 'minconn' connections are created immediately calling 'connfunc'
+        with given parameters. The connection pool will support a maximum of
+        about 'maxconn' connections.
+        """
+        self.minconn = minconn
+        self.maxconn = maxconn
+        self.closed = False
+
+        self._args = args
+        self._kwargs = kwargs
+
+        self._pool = []
+        self._used = {}
+        self._rused = {} # id(conn) -> key map
+        self._keys = 0
+
+        for i in range(self.minconn):
+            self._connect()
+
+    def _connect(self, key=None):
+        """Create a new connection and assign it to 'key' if not None."""
+        conn = psycopg2.connect(*self._args, **self._kwargs)
+        if key is not None:
+            self._used[key] = conn
+            self._rused[id(conn)] = key
+        else:
+            self._pool.append(conn)
+        return conn
+
+    def _getkey(self):
+        """Return a new unique key."""
+        self._keys += 1
+        return self._keys
+
+    def _getconn(self, key=None):
+        """Get a free connection and assign it to 'key' if not None."""
+        if self.closed: raise PoolError("connection pool is closed")
+        if key is None: key = self._getkey()
+
+        if key in self._used:
+            return self._used[key]
+
+        if self._pool:
+            self._used[key] = conn = self._pool.pop()
+            self._rused[id(conn)] = key
+            return conn
+        else:
+            if len(self._used) == self.maxconn:
+                raise PoolError("connection pool exausted")
+            return self._connect(key)
+
+    def _putconn(self, conn, key=None, close=False):
+        """Put away a connection."""
+        if self.closed: raise PoolError("connection pool is closed")
+        if key is None: key = self._rused[id(conn)]
+
+        if not key:
+            raise PoolError("trying to put unkeyed connection")
+
+        if len(self._pool) < self.minconn and not close:
+            self._pool.append(conn)
+        else:
+            conn.close()
+
+        # here we check for the presence of key because it can happen that a
+        # thread tries to put back a connection after a call to close
+        if not self.closed or key in self._used:
+            del self._used[key]
+            del self._rused[id(conn)]
+
+    def _closeall(self):
+        """Close all connections.
+
+        Note that this can lead to some code fail badly when trying to use
+        an already closed connection. If you call .closeall() make sure
+        your code can deal with it.
+        """
+        if self.closed: raise PoolError("connection pool is closed")
+        for conn in self._pool + list(self._used.values()):
+            try:
+                conn.close()
+            except:
+                pass
+        self.closed = True
+
+
+class PersistentConnectionPool(AbstractConnectionPool):
+    """A pool that assigns persistent connections to different threads.
+
+    Note that this connection pool generates by itself the required keys
+    using the current thread id.  This means that until a thread puts away
+    a connection it will always get the same connection object by successive
+    `!getconn()` calls. This also means that a thread can't use more than one
+    single connection from the pool.
+    """
+
+    def __init__(self, minconn, maxconn, *args, **kwargs):
+        """Initialize the threading lock."""
+        import threading
+        AbstractConnectionPool.__init__(
+            self, minconn, maxconn, *args, **kwargs)
+        self._lock = threading.Lock()
+
+        # we we'll need the thread module, to determine thread ids, so we
+        # import it here and copy it in an instance variable
+        import thread
+        self.__thread = thread
+
+    def getconn(self):
+        """Generate thread id and return a connection."""
+        key = self.__thread.get_ident()
+        self._lock.acquire()
+        try:
+            return self._getconn(key)
+        finally:
+            self._lock.release()
+
+    def putconn(self, conn=None, close=False):
+        """Put away an unused connection."""
+        key = self.__thread.get_ident()
+        self._lock.acquire()
+        try:
+            if not conn: conn = self._used[key]
+            self._putconn(conn, key, close)
+        finally:
+            self._lock.release()
+
+    def closeall(self):
+        """Close all connections (even the one currently in use.)"""
+        self._lock.acquire()
+        try:
+            self._closeall()
+        finally:
+            self._lock.release()
+
+
+_connections_pool = {}
+_connections_lock = threading.Lock()
+
+def getpool(dsn, create=True):
+    _connections_lock.acquire()
+    try:
+        if not _connections_pool.has_key(dsn) and create:
+            _connections_pool[dsn] = \
+                PersistentConnectionPool(4, 200, dsn)
+    finally:
+        _connections_lock.release()
+    return _connections_pool[dsn]
+
+def flushpool(dsn):
+    _connections_lock.acquire()
+    try:
+        _connections_pool[dsn].closeall()
+        del _connections_pool[dsn]
+    finally:
+        _connections_lock.release()
+
+def getconn(dsn, create=True):
+    return getpool(dsn, create=create).getconn()
+
+def putconn(dsn, conn, close=False):
+    getpool(dsn).putconn(conn, close=close)

Added: trunk/ZopeProducts/exUserFolder/AuthSources/LDAPAuthSource/LDAPAuthSource.py
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/LDAPAuthSource/LDAPAuthSource.py	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/LDAPAuthSource/LDAPAuthSource.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,686 @@
+import string, Acquisition, sys
+
+from Globals import HTMLFile, MessageDialog, INSTANCE_HOME
+
+from OFS.Folder import Folder
+
+from Products.exUserFolder.exUserFolder import exUserFolder
+from Products.exUserFolder.Plugins import PluginRegister
+
+import ldap
+import ldapurl
+import sys
+import time
+import threading
+import zLOG
+
+bind_none = 1
+bind_system = 2
+bind_user = 3
+
+LOG_STRING="XUF.LDAPAuthSource"
+
+def manage_addLDAPAuthSource(self, REQUEST):
+	""" Add a LDAP Auth Source """
+
+	# Required
+	url = REQUEST['LDAPUrl']
+	compareDNOnServer = REQUEST['LDAPCompareDNOnServer']
+	drefAliases = REQUEST['LDAPDereferenceAliases']
+	startTLS = REQUEST['LDAPStartTLS']
+
+	# Optional
+	binddn = REQUEST.get('LDAPBindDN', '')
+	bindPassword = REQUEST.get('LDAPBindPassword', '')
+
+	certDBPath = REQUEST.get('LDAPCertDBPath', '')
+
+	groupAttribute = REQUEST.get('LDAPGroupAttribute','')
+	groupAttributeIsDn = REQUEST.get('LDAPGroupAttributeIsDN', 0)
+
+	requireGroup = REQUEST.get('LDAPRequireGroup', [])
+	requireUser = REQUEST.get('LDAPRequireUser', [])
+	requireDN = REQUEST.get('LDAPRequireDN', '')
+	
+	defaultRole = REQUEST.get('LDAPDefaultRole', '')
+	defaultManager = REQUEST.get('LDAPDefaultManager', '')
+	searchCacheSize = REQUEST.get('LDAPSearchCacheSize', 0)
+	compareCacheSize = REQUEST.get('LDAPCompareCacheSize', 0)
+	searchCacheTTL = REQUEST.get('LDAPSearchCacheTTL', 0)
+	compareCacheTTL = REQUEST.get('LDAPCompareCacheTTL', 0)	
+	
+	ob=LDAPAuthSource(url, compareDNOnServer, drefAliases, startTLS,
+					  binddn, bindPassword, certDBPath, groupAttribute,
+					  groupAttributeIsDn, defaultRole, searchCacheSize,
+					  compareCacheSize, searchCacheTTL, compareCacheTTL,
+					  defaultManager, requireUser, requireGroup, requireDN)
+
+	self._setObject('LDAPAuthSource', ob, None, None, 0)
+	self.currentAuthSource=ob       
+
+
+manage_addLDAPAuthSourceForm=HTMLFile('manage_addLDAPAuthSourceForm', globals())
+manage_editLDAPAuthSourceForm=HTMLFile('manage_editLDAPAuthSourceForm', globals())
+
+from LDAPCache import *
+from LDAPConnection import *
+
+LDAP_CACHE = LDAPCreateCache(50, nodeCompare=urlNodeCompare)
+CONN_CACHE = []
+
+class LDAPAuthSource(Folder):
+	meta_type='Authentication Source'
+	id       ='LDAPAuthSource'
+	title    ='LDAP Authentication'
+	icon     ='misc_/exUserFolder/exUserFolderPlugin.gif'
+	
+
+	manage_editForm=manage_editLDAPAuthSourceForm
+	manage_tabs = Acquisition.Acquired
+
+	def __init__(self, url, compareDnOnServer, drefAliases, startTLS,
+				 binddn='', bindPassword='', certDBPath='',
+				 groupAttribute='', groupAttributeIsDN = 0,
+				 defaultRole = '', searchCacheSize=0, compareCacheSize=0,
+				 searchCacheTTL=0, compareCacheTTL=0, defaultManager='',
+				 requireUser=[], requireGroup=[], requireDN=''):
+
+		self.setParams(url, compareDnOnServer, drefAliases, startTLS,
+					  binddn, bindPassword, certDBPath, groupAttribute,
+					  groupAttributeIsDN, defaultRole, searchCacheSize,
+					  compareCacheSize, searchCacheTTL, compareCacheTTL,
+					  defaultManager, requireUser, requireGroup, requireDN
+					   )
+		
+
+	def setParams(self,url, compareDnOnServer, drefAliases, startTLS,
+				 binddn='', bindPassword='', certDBPath='',
+				 groupAttribute='', groupAttributeIsDN = 0,
+				 defaultRole = '', searchCacheSize=0, compareCacheSize=0,
+				 searchCacheTTL=0, compareCacheTTL=0, defaultManager='',
+				 requireUser=[], requireGroup=[], requireDN=''):
+		self.url = url
+		self.compareDNOnServer = compareDnOnServer
+		self.dereferenceAliases = drefAliases
+		self.startTLS = startTLS
+		self.bindDN = binddn
+		self.bindPassword = bindPassword
+		self.certDBPath = certDBPath
+		self.groupAttribute = groupAttribute
+		self.groupAttributeIsDN = groupAttributeIsDN
+		self.defaultRole = defaultRole
+		self.searchCacheSize=searchCacheSize
+		self.compareCacheSize=compareCacheSize
+		self.searchCacheTTL=searchCacheTTL
+		self.compareCacheTTL=compareCacheTTL
+		self.defaultManager=defaultManager
+		self.requireGroup=requireGroup
+		self.requireUser=requireUser
+		self.requireDN=requireDN
+
+		result = ldapurl.LDAPUrl(self.url)
+		self._basedn=result.dn
+		self._filter=result.filterstr
+		if not self._filter:
+			self._filter='(objectClass=*)'
+			
+		self._scope=result.scope
+		if not self._scope:
+			self._scope = ldap.SCOPE_SUB
+		self._attribute=result.attrs
+		self._hostport=result.hostport
+		self._urlscheme=result.urlscheme
+		hostport=string.split(self._hostport, ':')
+		self._host=hostport[0]
+		if len(hostport) == 2:
+			self._port = int(hostport[1])
+		else:
+			if self._urlscheme=='ldap':
+				self._port=389
+			elif self._urlscheme=='ldaps':
+				self._port=636
+			else:
+				# Ack..
+				self._port=0
+		
+				  
+	def manage_editAuthSource(self, REQUEST):
+		""" Handle Editing LDAP Auth Params """
+		# Required
+		url = REQUEST['LDAPUrl']
+		compareDNOnServer = REQUEST['LDAPCompareDNOnServer']
+		drefAliases = REQUEST['LDAPDereferenceAliases']
+		startTLS = REQUEST['LDAPStartTLS']
+
+		# Optional
+		binddn = REQUEST.get('LDAPBindDN', '')
+		bindPassword = REQUEST.get('LDAPBindPassword', '')
+
+		certDBPath = REQUEST.get('LDAPCertDBPath', '')
+
+		groupAttribute = REQUEST.get('LDAPGroupAttribute','')
+		groupAttributeIsDN = REQUEST.get('LDAPGroupAttributeIsDN', 0)
+
+		requireGroup = REQUEST.get('LDAPRequireGroup', [])
+		requireUser = REQUEST.get('LDAPRequireUser', [])
+		requireDN = REQUEST.get('LDAPRequireDN', '')
+		
+		defaultRole = REQUEST.get('LDAPDefaultRole', '')
+		defaultManager = REQUEST.get('LDAPDefaultManager', '')
+		searchCacheSize = REQUEST.get('LDAPSearchCacheSize', 0)
+		compareCacheSize = REQUEST.get('LDAPCompareCacheSize', 0)
+		searchCacheTTL = REQUEST.get('LDAPSearchCacheTTL', 0)
+		compareCacheTTL = REQUEST.get('LDAPCompareCacheTTL', 0)	
+
+		self.setParams(url, compareDNOnServer, drefAliases, startTLS,
+					  binddn, bindPassword, certDBPath, groupAttribute,
+					  groupAttributeIsDN, defaultRole, searchCacheSize,
+					  compareCacheSize, searchCacheTTL, compareCacheTTL,
+					  defaultManager, requireUser, requireGroup, requireDN)
+		
+		if REQUEST is not None:
+			return self.MessageDialog(
+				title = 'Edited',
+				message = "Properties for %s changed." % self.id,
+				action = 'manage_editLDAPAuthSourceForm')
+		return ''
+
+	def deleteUsers(self, userids):
+		pass
+
+	def createUser(self, username, password, roles):
+		pass
+
+	def updateUser(self, username, password, roles):
+		self.currentPropSource.setUserProperty(username=username,
+											   key='roles', value=roles)
+
+	def listUserNames(self):
+		return []
+
+	def listUsers(self):
+		return []
+
+	def getUsers(self):
+		return []
+
+	def _ldap_createCaches(self):
+		sCache = LDAPCreateCache(self.searchCacheSize, nodeCompare=searchNodeCompare)
+		cCache = LDAPCreateCache(self.compareCacheSize, nodeCompare=compareNodeCompare)
+		dnCache = LDAPCreateCache(self.compareCacheSize, nodeCompare=dnCompareNodeCompare)
+		curl = URLNode(self.url, sCache, cCache, dnCache)
+
+		LDAPCacheInsert(LDAP_CACHE, curl)
+		return curl
+		
+
+	def _ldap_buildFilter(self, username):
+		filter = "(&%s(%s=%s))"%(self._filter, self._attribute[0], username)
+		return filter
+
+	def listOneUser(self, username):
+
+		l = self._ldap_Open()
+		if not l:
+			return []
+		results = self._ldap_userExists(l, username)
+
+		if not results:
+			return []
+
+		roles=[]
+		if self.currentPropSource:
+			roles = self.currentPropSource.getUserProperty(username=username, key='_roles', default=[])
+
+		if not roles:
+			roles=[]
+		if self.defaultRole and self.defaultRole not in roles:
+			roles.append(self.defaultRole)
+
+		if self.defaultManager and self.defaultManager==username:
+			roles.append('Manager')
+
+		dn = results[0]
+		data = results[1]
+		self.currentPropSource.setUserProperty(username=username, key='dn', value=dn)
+		for k,v in data.items():
+			self.currentPropSource.setUserProperty(username=username, key=k, value=v)
+
+		return [{'username':username, 'password':'', 'roles':roles},]
+
+	def getConnections(self):
+		return CONN_CACHE
+
+	def addConnection(self, l):
+		CONN_CACHE.append(l)
+
+	def deleteConnection(self, l):
+		index = 0
+		for ll in self.getConnections:
+			if l == ll:
+				del CONN_CACHE[index]
+				break
+			index = index + 1
+
+	def getLDAPCache(self, key):
+		return LDAPCacheFetch(LDAP_CACHE, key)
+
+	def _ldap_FindConnection(self):
+		zLOG.LOG(LOG_STRING,
+				 100,
+				 "Entering _ldapFindConnection")
+		connections = self.getConnections()
+		l = None
+		for l in connections:
+			if ( l.port == self._port and
+				 l.host == self._host ):
+				zLOG.LOG(LOG_STRING,
+						 100,
+						 "Found a Cached One")
+				break
+		else:
+			l = None
+
+		if l:
+			if ( (self.bindDN and not l.bounddn) or
+				 (not self.bindDN and l.bounddn) or
+				 ((self.bindDN and l.bounddn) and self.bindDN != l.bounddn)):
+				l.boundas = bind_none
+			else:
+				l.boundas = bind_system
+		else:
+			lock=threading.Lock()
+			l = LDAPConnection(None, lock, None, self._host, self._port, bind_none)
+			self.addConnection(l)
+		return l
+
+	def _ldap_Open(self):
+		l = self._ldap_FindConnection()
+		connected = self._ldap_connectToServer(l)
+		if not connected:
+			return None
+		return l
+			
+	def _ldap_userExists(self, l, username):
+		
+		result = []
+		filter = self._ldap_buildFilter(username)
+
+		zLOG.LOG(LOG_STRING, 100, "Filter: %s"%(filter))
+		
+		try:
+			result = l.ldapConn.search_s(self._basedn, self._scope, filter)
+		except:
+			import traceback as tb
+			zLOG.LOG(LOG_STRING, 100, string.join(
+				tb.format_exception_only(sys.exc_type, sys.exc_value)))
+			return None
+
+		resultData = result
+
+		if len(resultData) != 1:
+			zLOG.LOG(LOG_STRING, 100, "Multiple Results: %d"%(len(resultData)))
+			return None
+
+		thisResult=resultData[0]
+
+		curl = self.getLDAPCache(self.url)
+		if not curl:
+			curl = self._ldap_createCaches()
+
+		dn = thisResult[0]
+		if self.requireDN:
+			if not dn:
+				zLOG.LOG(LOG_STRING, 100,
+						 "The User's DN has not been defined: failing auth")
+				return None
+			result = self._ldap_comparedn(l, dn, self.requireDN, curl)
+			if result:
+				return thisResult
+			
+		if self.requireUser:
+			if not dn:
+				zLOG.LOG(LOG_STRING, 100,
+						 "The User's DN has not been defined: failing auth")
+				return None
+			for t in self.requireUser:
+				if not t: # lines gives us one empty one..
+					continue
+				result = self._ldap_compare(l, dn, self._attribute, t, curl.compare_cache)
+				if result:
+					return thisResult
+
+				for tt in string.split(t):
+					result = self._ldap_compare(l, dn, self._attribute, tt, curl.compare_cache)
+					if result:
+						return thisResult
+		if self.requireGroup:
+			if self.groupAttributeIsDN:
+				if not dn:
+					zLOG.LOG(LOG_STRING, 100,
+							 "The User's DN has not been defined: failing auth")
+					return 0
+			w = 'group'
+			for t in self.requireGroup:
+				if not t:
+					continue
+				zLOG.LOG(LOG_STRING, 100,
+						 "testing for group membership in '%s'"%(t))
+				for ent in self.groupAttribute:
+					if not ent:
+						continue
+					zLOG.LOG(LOG_STRING, 100,
+							 "testing for group membership in %s=%s"%(ent,
+																	  [username,dn][self.groupAttributeIsDN]))
+					result = self._ldap_compare(l, t, ent, 
+												[username,dn][self.groupAttributeIsDN],
+												curl.dn_compare_cache)
+					if result:
+						return thisResult
+		
+		return thisResult
+
+	def _ldap_comparedn(self, ldc, dn, reqdn, curl):
+		if not self.compareDNOnServer:
+			return cmp(dn, reqdn) == 0
+
+		newnode = DNCompareNode(reqdn)
+		node = curl.dnCompareCache.fetch(newnode)
+		if node:
+			return 1
+		
+		try:
+			ldc.lock.acquire()
+			connected = self._ldap_connectToServer(ldc)
+			if not connected:
+				return 0
+			try:
+				result = ldc.ldapConn.search_ext_s(reqdn, ldap.SCOPE_BASE,
+												   '(objectclass=*)',
+												   None, 1)
+			except:
+				return 0
+			if result != ldap.SUCCESS:
+				return 0
+
+			entry = result[0]
+			searchdn=entry[0]
+			if dn != searchdn:
+				return 0
+			newnode.dn = dn
+			curl.dnCompareCache.insert(newnode)
+			return 1
+		finally:
+			ldc.lock.release()
+			
+
+	def _ldap_compare(self, ldc, dn, attrib, value, cache):
+		curtime = time.time()
+		theCompareNode=CompareNode(dn, attrib, value, 0.0)
+		compare_node = cache.fetch(theCompareNode)
+		if compare_node:
+			zLOG.LOG(LOG_STRING, 100,
+					 "Found It...")
+			if (curtime - compare_node.lastcompare) > self.compareCacheTTL:
+				zLOG.LOG(LOG_STRING, 100,
+						 "...but it's too old.")
+				cache.remove(compare_node)
+			else:
+				zLOG.LOG(LOG_STRING, 100,
+						 "...and it's good.")
+				return 1
+		try:
+			ldc.lock.acquire()
+			connected = self._ldap_connectToServer(ldc)
+			if not connected:
+				return 0
+
+			zLOG.LOG(LOG_STRING, 100,
+					 "Doing LDAP compare of %s=%s in entry %s"%(attrib, value, dn))
+			zLOG.LOG(LOG_STRING, 100,
+					 "LDAP OP: compare")
+
+			try:
+				result = ldc.ldapConn.compare_s(dn, attrib, value)
+			except ldap.SERVER_DOWN:
+				self.freeConnection(ldc, 1)
+				return 0
+
+			if result == ldap.COMPARE_TRUE:
+				zLOG.LOG(LOG_STRING, 100,
+						 "Compare succeeded; caching result")
+				compare_node.lastcompare=curtime
+				cache.insert(compare_node)
+				return 1
+			zLOG.LOG(LOG_STRING, 100,
+					 "Compare failed")
+			return 0
+		finally:
+			ldc.lock.release()
+			
+	def _ldap_connectToServer(self, l):
+
+		zLOG.LOG(LOG_STRING, 100,
+				 "Entering _ldap_connectToServer")
+		
+		if not l.ldapConn:
+			l.boundas=bind_none
+			if l.bounddn:
+				l.bounddn = None
+
+			zLOG.LOG(LOG_STRING, 100,
+					 "Opening Connection to ldap server(s) '%s'"%(self._host))
+			zLOG.LOG(LOG_STRING, 100,
+					 "LDAP OP: init")
+
+			l.init()			
+			if not l.ldapConn:
+				zLOG.LOG(LOG_STRING, 100,
+						 "Could not connect to LDAP server")
+				return 0
+
+			try:
+				result = l.ldapConn.set_option(ldap.OPT_REFERRALS, 0)
+			except:
+				zLOG.LOG(LOG_STRING, 100,
+						 "Setting LDAP REFERRAL Option")
+				return 0
+
+			try:
+				l.ldapConn.set_option(ldap.OPT_DEREF, self.dereferenceAliases)
+			except:
+				zLOG.LOG(LOG_STRING, 100,
+						 "Setting LDAP dereference option failed")
+				
+			if self.startTLS and 0:
+				version = ldap.VERSION3
+				try:
+					result = l.ldapConn.set_option(ldap.OPT_PROTOCOL_VERSION, version)
+				except:
+					zLOG.LOG(LOG_STRING, 100,
+							 "Setting LDAP version option failed")
+					return 0
+
+				zLOG.LOG(LOG_STRING, 100,
+						 "Starting TLS for this connection")
+				l.withtls = 1
+				try:
+					l.ldapConn.start_tls_s()
+				except:
+					zLOG.LOG(LOG_STRING, 100,
+							 "Start TLS Failed.")
+					return 0
+			else:
+				l.withtls=0
+
+		if l.boundas == bind_system:
+			return 1
+
+		zLOG.LOG(LOG_STRING, 100,
+				 "Binding to server '%s' as %s/%s"%(self._host,
+													self.bindDN,
+													self.bindPassword))
+
+		zLOG.LOG(LOG_STRING, 100,
+				 "LDAP OP: simple bind")
+		
+		result = l.ldapConn.simple_bind(self.bindDN, self.bindPassword)
+
+		zLOG.LOG(LOG_STRING, 100, "Simple Bind returned: %d"%(result))
+
+		if result == ldap.SERVER_DOWN:
+			zLOG.LOG(LOG_STRING, 100,
+					 "Server Down: Could not bind to LDAP server '%s' as %s: %s"%(
+				self._host,
+				self.bindDN,
+				self.bindPassword))
+			self.freeConnection(l, 1)
+			return 0
+
+		if result != 1:
+			self.freeConnection(l)
+			zLOG.LOG(LOG_STRING, 100,
+					 "Could not bind to LDAP server '%s' as %s: %s"%(self._host,
+																	 self.bindDN,
+																	 self.bindPassword))
+			return 0
+
+		l.bounddn = self.bindDN
+		l.boundas = bind_system
+		return 1
+
+	def freeConnection(self, ldc, log=0):
+
+		if log:
+			zLOG.LOG(LOG_STRING, 100,
+					 "Server is down")
+		if ldc.ldapConn:
+			zLOG.LOG(LOG_STRING, 100,
+					 "Freeing connection to ldap server(s) '%s'"%(self._host))
+			ldc.ldapConn.unbind_s()
+			ldc.ldapConn=None
+			ldc.boundas = bind_none
+			if ldc.bounddn:
+				ldc.bounddn=None
+
+		self.deleteConnection(ldc)
+
+	def remoteAuthMethod(self, username, password):
+
+		zLOG.LOG(LOG_STRING, 100,
+				 "Entering remoteAuthMethod")
+
+		l = self._ldap_FindConnection()
+		curl = self.getLDAPCache(self.url)
+		if not curl:
+			curl = self._ldap_createCaches()
+		
+		if not l:
+			zLOG.LOG(LOG_STRING,
+					 100, "Could not find/create LDAPConnection")
+			return 0
+
+		zLOG.LOG(LOG_STRING, 100,
+				 "Using URL: %s"%(self.url))
+
+		searchNode = curl.search_cache.fetch(username)
+		if searchNode and searchNode.bindpw:
+			zLOG.LOG(LOG_STRING, 100,
+					 "Found entry in cache for '%s'..."%(username))
+			curtime=time.time()
+			if curtime == searchNode.lastBind > self.searchCacheTTL:
+				zLOG.LOG(LOG_STRING, 100,
+						 "... but entry is too old (%d seconds)"%(curtime-searchnode.lastbind))
+				curl.search_cache.remove(username)
+			elif searchNode.bindpw != password:
+				zLOG.LOG(LOG_STRING, 100,
+						 "... but entry password doesn't match")
+				curl.search_cache.remove(username)
+			else:
+				zLOG.LOG(LOG_STRING, 100,
+						 "... and entry is valid")
+				return 1
+
+		zLOG.LOG(LOG_STRING, 100,
+				 "Entry for %s is not in the cache"%(username))
+
+		filtbuf = self._ldap_buildFilter(username)
+		try:
+			l.lock.acquire()
+
+			if not self._ldap_connectToServer(l):
+				return 0
+
+			zLOG.LOG(LOG_STRING, 100,
+					 "Performing a search (scope = %d) with filter %s"%(
+				self._scope, filtbuf))
+
+			zLOG.LOG(LOG_STRING, 100,
+					 "LDAP OP: search")
+
+			result = None
+			try:
+				result = l.ldapConn.search_s(self._basedn, self._scope,
+											 filtbuf)
+			except ldap.SERVER_DOWN:
+				self.freeConnection(l, 1)
+				return 0
+			except:
+				zLOG.LOG(LOG_STRING, 100,
+						 "LDAP search for %s failed: LDAP error: %s; URI %s"%(
+					filtbuf, result, self.url))
+				return 0
+
+			resultData=result
+			count = len(resultData)
+			if count != 1:
+				zLOG.LOG(LOG_STRING, 100,
+						 "Search must return exactly 1 entry; found %s entries for search %s: URI %s"%(count, filtbuf, self.url))
+				return 0
+
+			
+			entry=resultData[0]
+			dn=entry[0]
+			zLOG.LOG(LOG_STRING, 100,
+					 "DN returned from search is %s"%(dn))
+
+			if len(password) <= 0:
+				zLOG.LOG(LOG_STRING, 100,
+						 "Empty Password")
+
+				return 0
+
+			zLOG.LOG(LOG_STRING, 100,
+					 "Validating user '%s' via bind"%(username))
+
+			zLOG.LOG(LOG_STRING, 100,
+					 "LDAP OP: simple bind")
+
+			l.boundas = bind_user
+
+			try:
+				result = l.ldapConn.simple_bind_s(dn, password)
+			except ldap.SERVER_DOWN:
+				self.freeConnection(l, 1)
+				return 0
+			except e:
+				zLOG.LOG(LOG_STRING, 100,
+						 "User bind as %s failed: LDAP error: %s; URI %s"%(
+					dn, e, self.url))
+				return 0
+
+			zLOG.LOG(LOG_STRING, 100,
+					 "authenticate: accepting")
+
+		finally:
+			l.lock.release()
+
+		zLOG.LOG(LOG_STRING, 100,
+				 "Adding user '%s' to the cache"%(dn))
+
+		sNode = SearchNode(username, dn, password, time.time())
+		curl.search_cache.insert(sNode)
+		return 1
+
+LDAPAuthReg=PluginRegister('LDAPAuthSource', 'LDAP Authentication Source', LDAPAuthSource, manage_addLDAPAuthSourceForm, manage_addLDAPAuthSource, manage_editLDAPAuthSourceForm)
+exUserFolder.authSources['LDAPAuthSource']=LDAPAuthReg

Added: trunk/ZopeProducts/exUserFolder/AuthSources/LDAPAuthSource/LDAPCache.py
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/LDAPAuthSource/LDAPCache.py	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/LDAPAuthSource/LDAPCache.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,160 @@
+import time
+
+def urlNodeCompare(a, b):
+	return cmp(a.url, b.url)
+
+def searchNodeCompare(a, b):
+	return cmp(a.username, b.username)
+
+def compareNodeCompare(a, b):
+	return ( cmp(a.dn, b.dn) == 0 and
+			 cmp(a.attrib, b.attrib) == 0 and
+			 cmp(a.value, b.value) == 0 )
+
+def dnCompareNodeCompare(a, b):
+	return cmp(a.reqdn, b.reqdn)
+
+def connectionNodeCompare(a, b):
+	return cmp(a.ldapConn, b.ldapConn)
+
+class URLNode:
+	def __init__(self, url, search, compare, dn_compare):
+		self.url = url
+		self.search_cache = search
+		self.compare_cache = compare
+		self.dn_compare_cache=dn_compare
+
+class SearchNode:
+	def __init__(self, username, dn, bindpw, lastbind):
+		self.username = username
+		self.dn = dn
+		self.bindpw = bindpw
+		self.lastbind = lastbind
+
+class CompareNode:
+	def __init__(self, dn, attrib, value, lastcompare):
+		self.dn = dn
+		self.attrib = attrib
+		self.value = value
+		self.lastcompare = lastcompare
+
+class DNCompareNode:
+	def __init__(self, reqdn, dn=''):
+		self.dn=dn
+		self.reqdn=reqdn
+
+class CacheNode:
+	def __init__(self, payload):
+		self.payload = payload
+		self.add_time = time.time()
+
+	def __del__(self):
+		del self.payload
+	
+
+class LDAPCache:
+	def __init__(self, maxentries, cmpare=cmp, hashfunc=hash):
+
+		self.maxentries = int(maxentries)
+
+		self.size = self.maxentries / 3
+
+		if self.size < 64:
+			self.size = 64
+			
+		self.fullmark = self.maxentries / 4 * 3
+		
+		self.numentries = 0
+
+		self.marktime=0.0
+		self.cache_nodes={}
+		self.numpurges = 0
+		self.avg_purgetime=0.0
+		self.last_purge = 0.0
+		self.npurged = 0
+		self.fetches = 0
+		self.hits = 0
+		self.inserts = 0
+		self.removes = 0
+		self.cmpare=cmpare
+		self.hashfunc=hash
+
+	def __del__(self):
+		for p in self.cache_nodes.values():
+			del p
+
+	def fetch(self, payload):
+		self.fetches = self.fetches + 1
+		pHash = self.hashfunc(payload)
+
+		entries = self.cache_nodes.get(pHash, [])
+		for p in entries:
+			if self.cmpare(payload, p.payload) == 0:
+				return p.payload
+
+	def insert(self, payload):
+		pHash=self.hashfunc(payload)
+		entries = self.cache_nodes.get(pHash, [])
+		entries.append(CacheNode(payload))
+		self.cache_nodes[pHash]=entries
+		self.numentries = self.numentries + 1
+		if self.numentries == self.fullmark:
+			self.marktime = time.time()
+		if self.numentries >= self.maxentries:
+			self.purge()
+
+	def purge(self):
+		self.last_purge = time.time()
+		self.npurged = 0
+		self.numpurges = self.numpurges + 1
+
+		for k,n in self.cache_nodes.items():
+			index = 0
+			indices=[]
+			for p in n:
+				if p.add_time < self.marktime:
+					indices.append(index)
+
+			indices.reverse()
+			for i in indices:
+				del n[i]
+				self.numentries = self.numentries - 1
+				self.npurged = self.npurged + 1
+			self.cache_nodes[k]=n
+
+		t = time.time()
+		self.avg_purgetime = (
+			(t - self.last_purge) +
+			(self.avg_purgetime * (self.numpurges-1))) / self.numpurges
+
+	def remove(self, payload):
+		pHash = self.hashfunc(payload)
+		entries = self.cache_nodes.get(pHash, [])
+		index = 0
+		for p in entries:
+			if self.cmpare(payload, p.payload)==0:
+				del entries[index]
+				self.cache_nodes[pHash]=entries
+				self.numentries = self.numentries - 1
+				break
+			index = index + 1
+		
+	
+def LDAPCreateCache(maxentries, nodeCompare=cmp, nodeHash=hash):
+	if maxentries <= 0:
+		return None
+
+	cache = LDAPCache(maxentries, nodeCompare, nodeHash)
+	return cache
+
+def LDAPDestoryCache(cache):
+	del cache
+
+def LDAPCacheFetch(cache, payload):
+	return cache.fetch(payload)
+
+def LDAPCacheInsert(cache, payload):
+	return cache.insert(payload)
+
+def LDAPCacheRemove(cache, payload):
+	return cache.remove(payload)

Added: trunk/ZopeProducts/exUserFolder/AuthSources/LDAPAuthSource/LDAPConnection.py
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/LDAPAuthSource/LDAPConnection.py	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/LDAPAuthSource/LDAPConnection.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,25 @@
+import ldap
+import threading
+
+class LDAPConnection:
+	def __init__(self, ldapConn, lock, bounddn, host, port, boundas):
+		self.ldapConn = ldapConn
+		self.lock = lock
+		self.bounddn = bounddn
+		self.host = host
+		self.port = port
+		self.boundas = boundas
+
+
+	def init(self):
+		try:
+			self.ldapConn = ldap.open(self.host, self.port)
+			
+		except:
+			import traceback as tb
+			zLOG.LOG(LOG_STRING, 100, string.join(
+				tb.format_exception_only(sys.exc_type, sys.exc_value)))
+
+			self.ldapConn = None
+
+

Added: trunk/ZopeProducts/exUserFolder/AuthSources/LDAPAuthSource/README
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/LDAPAuthSource/README	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/LDAPAuthSource/README	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,20 @@
+This is a reimplementation of the auth_ldap auth source for apache, written by
+Dave Carrigan and others.
+
+You can find the original auth_ldap Apache code at
+http://www.rudedog.org/auth_ldap/
+
+This auth source is covered by the Apache license;
+
+Copyright (C) 1998, 1999 Enbridge Pipelines Inc. 
+Copyright (C) 1999-2001 Dave Carrigan
+Copyright (C) 2003 The Internet (Aust) Pty Ltd.
+All rights reserved.
+
+This module is free software; you can redistribute it and/or modify
+it under the same terms as Apache itself. This module is
+distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. The copyright holder of this
+module can not be held liable for any general, special, incidental
+or consequential damages arising out of the use of the module.

Added: trunk/ZopeProducts/exUserFolder/AuthSources/LDAPAuthSource/__init__.py
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/LDAPAuthSource/__init__.py	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/LDAPAuthSource/__init__.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1 @@
+import LDAPAuthSource

Added: trunk/ZopeProducts/exUserFolder/AuthSources/LDAPAuthSource/manage_addLDAPAuthSourceForm.dtml
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/LDAPAuthSource/manage_addLDAPAuthSourceForm.dtml	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/LDAPAuthSource/manage_addLDAPAuthSourceForm.dtml	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,86 @@
+<dtml-var "DialogHeader(_.None,_,DialogTitle='Add LDAP Authentication Source')">
+<FORM ACTION="&dtml-URL;" METHOD="POST">
+<dtml-in "REQUEST.form.keys()">
+<input type="HIDDEN" name="<dtml-var sequence-item>" value="<dtml-var "REQUEST[_
+.getitem('sequence-item',0)]">">
+</dtml-in>
+<input type="HIDDEN" name="doProp" value="1">
+<table cellspacing="2">
+
+<tr><th><dtml-babel src="'en'">URL</dtml-babel></th>
+	<td><input name="LDAPUrl" type="text" value="ldap://" size="80"></td></tr>
+<tr><td colspan="2">(ldap://host:port/basedn?username_attribute?scope?filter) (use ldaps:// for secure)</td>
+
+<tr><th><dtml-babel src="'en'">BindDN</dtml-babel></th>
+	<td><input name="LDAPBindDN" type="text"></td></tr>
+
+<tr><th><dtml-babel src="'en'">Bind Password</dtml-babel></th>
+	<td><input name="LDAPBindPassword" type="text"></td></tr>
+
+<tr><th><dtml-babel src="'en'">Certificate DB Path</dtml-babel></th>
+	<td><input name="LDAPCertDBPath" type="text"></td></tr>
+
+<tr><th><dtml-babel src="'en'">Require DN</dtml-babel></th>
+	<td><input name="LDAPRequireDN" type="text"></td></tr>
+
+<tr><th><dtml-babel src="'en'">Compare DN On Server</dtml-babel></th>
+	<td><select name="LDAPCompareDNOnServer:int">
+		<option value="0">No</option>
+		<option value="1">Yes</option>
+		</select>
+		</td></tr>
+
+<tr><th><dtml-babel src="'en'">Dereference Aliases</dtml-babel></th>
+	<td><select name="LDAPDereferenceAliases:int">
+		<option value="0">Never</option>
+		<option value="1">Searching</option>
+		<option value="2">Finding</option>
+		<option value="3" selected>Always</option>
+		</select>
+		</td></tr>
+
+<tr><th><dtml-babel src="'en'">Group Attribute</dtml-babel></th>
+	<td><textarea name="LDAPGroupAttribute"></textarea></td></tr>
+
+<tr><th><dtml-babel src="'en'">Group Attribute is DN</dtml-babel></th>
+	<td><select name="LDAPGroupAttributeIsDN:int">
+		<option value="0">No</option>
+		<option value="1 selected">Yes</option>
+		</select>
+		</td></tr>
+
+<tr><th><dtml-babel src="'en'">Start TLS</dtml-babel></th>
+	<td><select name="LDAPStartTLS:int">
+		<option value="0">No</option>
+		<option value="1">Yes</option>
+		</select>
+		</td></tr>
+
+<tr><th><dtml-babel src="'en'">Require Group</dtml-babel></th>
+	<td><textarea name="LDAPRequireGroup:lines"></textarea></td></tr>
+
+<tr><th><dtml-babel src="'en'">Require User</dtml-babel></th>
+	<td><textarea name="LDAPRequireUser:lines"></textarea></td></tr>
+
+<tr><th><dtml-babel src="'en'">Manager User Name (must auth)</dtml-babel></th>
+	<td><input name="LDAPDefaultManager" type="text"></td></tr>
+
+<tr><th><dtml-babel src="'en'">Default Role</dtml-babel></th>
+	<td><input name="LDAPDefaultRole" type="text"></td></tr>
+
+
+
+<tr><th><dtml-babel src="'en'">Search Cache Size</dtml-babel></th>
+	<td><input name="LDAPSearchCacheSize:int" type="text" value="1024"></td></tr>
+<tr><th><dtml-babel src="'en'">Search Cache TTL</dtml-babel></th>
+	<td><input name="LDAPSearchCacheTTL:int" type="text" value="1800"></td></tr>
+
+<tr><th><dtml-babel src="'en'">Compare Cache Size</dtml-babel></th>
+	<td><input name="LDAPCompareCacheSize:int" type="text" value="1024"></td></tr>
+<tr><th><dtml-babel src="'en'">Compare Cache TTL</dtml-babel></th>
+	<td><input name="LDAPCompareCacheTTL:int" type="text" value="1800"></td></tr>
+
+<tr><td><br><input type="SUBMIT" value="<dtml-babel src="'en'">Add</dtml-babel>"></td></tr>
+</table>
+</form>
+<dtml-var DialogFooter>

Added: trunk/ZopeProducts/exUserFolder/AuthSources/LDAPAuthSource/manage_editLDAPAuthSourceForm.dtml
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/LDAPAuthSource/manage_editLDAPAuthSourceForm.dtml	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/LDAPAuthSource/manage_editLDAPAuthSourceForm.dtml	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,83 @@
+<dtml-var "DialogHeader(_.None,_,DialogTitle='Edit LDAP Authentication Source')">
+<dtml-with currentAuthSource>
+<FORM ACTION="manage_editAuthSource" METHOD="POST">
+<table cellspacing="2">
+
+<tr><th><dtml-babel src="'en'">URL</dtml-babel></th>
+	<td><input name="LDAPUrl" type="text" value="&dtml-url;" size="80"></td></tr>
+<tr><td colspan="2">(ldap://host:port/basedn?username_attribute?scope?filter) (use ldaps:// for secure)</td>
+
+<tr><th><dtml-babel src="'en'">BindDN</dtml-babel></th>
+	<td><input name="LDAPBindDN" type="text" value="&dtml-bindDN;"></td></tr>
+
+<tr><th><dtml-babel src="'en'">Bind Password</dtml-babel></th>
+	<td><input name="LDAPBindPassword" type="text" value="&dtml-bindPassword;"></td></tr>
+
+<tr><th><dtml-babel src="'en'">Certificate DB Path</dtml-babel></th>
+	<td><input name="LDAPCertDBPath" type="text" value="&dtml-certDBPath;"></td></tr>
+
+<tr><th><dtml-babel src="'en'">Require DN</dtml-babel></th>
+	<td><input name="LDAPRequireDN" type="text" value="&dtml-requireDN;"></td></tr>
+
+<tr><th><dtml-babel src="'en'">Compare DN On Server</dtml-babel></th>
+	<td><select name="LDAPCompareDNOnServer:int">
+		<option value="0">No</option>
+		<option value="1">Yes</option>
+		</select>
+		</td></tr>
+
+<tr><th><dtml-babel src="'en'">Dereference Aliases</dtml-babel></th>
+	<td><select name="LDAPDereferenceAliases:int">
+		<option value="0" <dtml-if "dereferenceAliases==0">selected</dtml-if>>Never</option>
+		<option value="1" <dtml-if "dereferenceAliases==1">selected</dtml-if>>Searching</option>
+		<option value="2" <dtml-if "dereferenceAliases==2">selected</dtml-if>>Finding</option>
+		<option value="3" <dtml-if "dereferenceAliases==3">selected</dtml-if>>Always</option>
+		</select>
+		</td></tr>
+
+<tr><th><dtml-babel src="'en'">Group Attribute</dtml-babel></th>
+	<td><input name="LDAPGroupAttribute" type="text"></td></tr>
+
+<tr><th><dtml-babel src="'en'">Group Attribute is DN</dtml-babel></th>
+	<td><select name="LDAPGroupAttributeIsDN:int">
+		<option value="0">No</option>
+		<option value="1 selected">Yes</option>
+		</select>
+		</td></tr>
+
+<tr><th><dtml-babel src="'en'">Start TLS</dtml-babel></th>
+	<td><select name="LDAPStartTLS:int">
+		<option value="0">No</option>
+		<option value="1">Yes</option>
+		</select>
+		</td></tr>
+
+<tr><th><dtml-babel src="'en'">Require Group</dtml-babel></th>
+	<td><textarea name="LDAPRequireGroup:lines">&dtml-groupAttribute;</textarea></td></tr>
+
+<tr><th><dtml-babel src="'en'">Require User</dtml-babel></th>
+	<td><textarea name="LDAPRequireUser:lines"></textarea></td></tr>
+
+<tr><th><dtml-babel src="'en'">Manager User Name (must auth)</dtml-babel></th>
+	<td><input name="LDAPDefaultManager" type="text" value="&dtml-defaultManager;"></td></tr>
+
+<tr><th><dtml-babel src="'en'">Default Role</dtml-babel></th>
+	<td><input name="LDAPDefaultRole" type="text" value="&dtml-defaultRole;"></td></tr>
+
+
+
+<tr><th><dtml-babel src="'en'">Search Cache Size</dtml-babel></th>
+	<td><input name="LDAPSearchCacheSize:int" type="text" value="&dtml-searchCacheSize;"></td></tr>
+<tr><th><dtml-babel src="'en'">Search Cache TTL</dtml-babel></th>
+	<td><input name="LDAPSearchCacheTTL:int" type="text" value="&dtml-searchCacheTTL;"></td></tr>
+
+<tr><th><dtml-babel src="'en'">Compare Cache Size</dtml-babel></th>
+	<td><input name="LDAPCompareCacheSize:int" type="text" value="&dtml-compareCacheSize;"></td></tr>
+<tr><th><dtml-babel src="'en'">Compare Cache TTL</dtml-babel></th>
+	<td><input name="LDAPCompareCacheTTL:int" type="text" value="&dtml-compareCacheTTL;"></td></tr>
+
+<tr><td><br><input type="SUBMIT" value="<dtml-babel src="'en'">Change</dtml-babel>"></td></tr>
+</table>
+</form>
+</dtml-with>
+<dtml-var DialogFooter>

Added: trunk/ZopeProducts/exUserFolder/AuthSources/__init__.py
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/__init__.py	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/__init__.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,47 @@
+#
+# Extensible User Folder
+#
+# (C) Copyright 2000-2004 The Internet (Aust) Pty Ltd
+# ACN: 082 081 472  ABN: 83 082 081 472
+# All Rights Reserved
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# Author: Andrew Milton <akm at theinternet.com.au>
+# $Id: __init__.py,v 1.1 2004/11/10 14:15:34 akm Exp $
+
+import etcAuthSource
+import httpsAuthSource
+import mysqlAuthSource
+import pgAuthSource
+import pgAuthSourceAlt
+import radiusAuthSource
+import smbAuthSource
+import usAuthSource
+import zodbAuthSource
+import zodbBTreeAuthSource
+
+#
+# These have special requirements for external libraries
+# that my not be present.
+#
+
+try:
+	import nisAuthSource
+except:
+	pass
+
+try:
+	import LDAPAuthSource
+except:
+	pass

Added: trunk/ZopeProducts/exUserFolder/AuthSources/etcAuthSource/.cvsignore
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/etcAuthSource/.cvsignore	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/etcAuthSource/.cvsignore	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,4 @@
+*.pyc
+*.pyo
+*~
+.*.swp

Added: trunk/ZopeProducts/exUserFolder/AuthSources/etcAuthSource/CHANGES.txt
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/etcAuthSource/CHANGES.txt	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/etcAuthSource/CHANGES.txt	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,5 @@
+etcUserFolder Changes
+
+  etcUserFolder 1.2.0
+
+    Initial release for Zope

Added: trunk/ZopeProducts/exUserFolder/AuthSources/etcAuthSource/README.txt
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/etcAuthSource/README.txt	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/etcAuthSource/README.txt	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,17 @@
+etcUserFolder
+
+  The etcUserFolder product authenticates off of a standard UNIX
+  password file.  The password file used can be specified in the
+  product.  It is a bit misnamed, because etcUserFolder can
+  authenticate off of any file whose first two colon delimited fields
+  are uid and crypted password.
+
+  This Product requires the crypt module.  There may, or may not be
+  such a module for Win32.  This product may only be compatable with
+  UNIX.  Zope does not come with the crypt module (because of the
+  non-cross platform nature of it) so you will need to either get the
+  cryptmodule.c (from Python) and compile it, or copy your existing
+  cryptmodule.so file into Zope's path, or use the one that comes with 
+  etcUserFolder which was compiled for RedHat Linux 5.2 (which may not 
+  work on your platform).
+

Added: trunk/ZopeProducts/exUserFolder/AuthSources/etcAuthSource/__init__.py
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/etcAuthSource/__init__.py	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/etcAuthSource/__init__.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,89 @@
+##############################################################################
+#
+# Zope Public License (ZPL) Version 0.9.4
+# ---------------------------------------
+# 
+# Copyright (c) Digital Creations.  All rights reserved.
+# 
+# Redistribution and use in source and binary forms, with or
+# without modification, are permitted provided that the following
+# conditions are met:
+# 
+# 1. Redistributions in source code must retain the above
+#    copyright notice, this list of conditions, and the following
+#    disclaimer.
+# 
+# 2. Redistributions in binary form must reproduce the above
+#    copyright notice, this list of conditions, and the following
+#    disclaimer in the documentation and/or other materials
+#    provided with the distribution.
+# 
+# 3. Any use, including use of the Zope software to operate a
+#    website, must either comply with the terms described below
+#    under "Attribution" or alternatively secure a separate
+#    license from Digital Creations.
+# 
+# 4. All advertising materials, documentation, or technical papers
+#    mentioning features derived from or use of this software must
+#    display the following acknowledgement:
+# 
+#      "This product includes software developed by Digital
+#      Creations for use in the Z Object Publishing Environment
+#      (http://www.zope.org/)."
+# 
+# 5. Names associated with Zope or Digital Creations must not be
+#    used to endorse or promote products derived from this
+#    software without prior written permission from Digital
+#    Creations.
+# 
+# 6. Redistributions of any form whatsoever must retain the
+#    following acknowledgment:
+# 
+#      "This product includes software developed by Digital
+#      Creations for use in the Z Object Publishing Environment
+#      (http://www.zope.org/)."
+# 
+# 7. Modifications are encouraged but must be packaged separately
+#    as patches to official Zope releases.  Distributions that do
+#    not clearly separate the patches from the original work must
+#    be clearly labeled as unofficial distributions.
+# 
+# Disclaimer
+# 
+#   THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS ``AS IS'' AND
+#   ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+#   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+#   FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT
+#   SHALL DIGITAL CREATIONS OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+#   DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+#   CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+#   PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+#   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+#   ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+#   LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+#   IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+#   THE POSSIBILITY OF SUCH DAMAGE.
+# 
+# Attribution
+# 
+#   Individuals or organizations using this software as a web site
+#   must provide attribution by placing the accompanying "button"
+#   and a link to the accompanying "credits page" on the website's
+#   main entry point.  In cases where this placement of
+#   attribution is not feasible, a separate arrangment must be
+#   concluded with Digital Creations.  Those using the software
+#   for purposes other than web sites must provide a corresponding
+#   attribution in locations that include a copyright using a
+#   manner best suited to the application environment.
+# 
+# This software consists of contributions made by Digital
+# Creations and many individuals on behalf of Digital Creations.
+# Specific attributions are listed in the accompanying credits
+# file.
+# 
+##############################################################################
+# $Id: __init__.py,v 1.1 2004/11/10 14:15:34 akm Exp $
+__doc__="""etc User Folder Product
+"""
+
+import etcAuthSource

Added: trunk/ZopeProducts/exUserFolder/AuthSources/etcAuthSource/etcAuthSource.py
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/etcAuthSource/etcAuthSource.py	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/etcAuthSource/etcAuthSource.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,310 @@
+##############################################################################
+#
+# Zope Public License (ZPL) Version 0.9.4
+# ---------------------------------------
+# 
+# Copyright (c) Digital Creations.  All rights reserved.
+# 
+# Redistribution and use in source and binary forms, with or
+# without modification, are permitted provided that the following
+# conditions are met:
+# 
+# 1. Redistributions in source code must retain the above
+#    copyright notice, this list of conditions, and the following
+#    disclaimer.
+# 
+# 2. Redistributions in binary form must reproduce the above
+#    copyright notice, this list of conditions, and the following
+#    disclaimer in the documentation and/or other materials
+#    provided with the distribution.
+# 
+# 3. Any use, including use of the Zope software to operate a
+#    website, must either comply with the terms described below
+#    under "Attribution" or alternatively secure a separate
+#    license from Digital Creations.
+# 
+# 4. All advertising materials, documentation, or technical papers
+#    mentioning features derived from or use of this software must
+#    display the following acknowledgement:
+# 
+#      "This product includes software developed by Digital
+#      Creations for use in the Z Object Publishing Environment
+#      (http://www.zope.org/)."
+# 
+# 5. Names associated with Zope or Digital Creations must not be
+#    used to endorse or promote products derived from this
+#    software without prior written permission from Digital
+#    Creations.
+# 
+# 6. Redistributions of any form whatsoever must retain the
+#    following acknowledgment:
+# 
+#      "This product includes software developed by Digital
+#      Creations for use in the Z Object Publishing Environment
+#      (http://www.zope.org/)."
+# 
+# 7. Modifications are encouraged but must be packaged separately
+#    as patches to official Zope releases.  Distributions that do
+#    not clearly separate the patches from the original work must
+#    be clearly labeled as unofficial distributions.
+# 
+# Disclaimer
+# 
+#   THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS ``AS IS'' AND
+#   ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+#   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+#   FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT
+#   SHALL DIGITAL CREATIONS OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+#   DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+#   CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+#   PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+#   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+#   ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+#   LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+#   IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+#   THE POSSIBILITY OF SUCH DAMAGE.
+# 
+# Attribution
+# 
+#   Individuals or organizations using this software as a web site
+#   must provide attribution by placing the accompanying "button"
+#   and a link to the accompanying "credits page" on the website's
+#   main entry point.  In cases where this placement of
+#   attribution is not feasible, a separate arrangment must be
+#   concluded with Digital Creations.  Those using the software
+#   for purposes other than web sites must provide a corresponding
+#   attribution in locations that include a copyright using a
+#   manner best suited to the application environment.
+# 
+# This software consists of contributions made by Digital
+# Creations and many individuals on behalf of Digital Creations.
+# Specific attributions are listed in the accompanying credits
+# file.
+# 
+##############################################################################
+# (C) Copyright 2000,2001 The Internet (Aust) Pty Ltd
+# ACN: 082 081 472  ABN: 83 082 081 472
+# All Rights Reserved
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# Author: Andrew Milton <akm at theinternet.com.au>
+# $Id: etcAuthSource.py,v 1.2 2004/12/14 05:34:40 akm Exp $
+"""User Db product
+
+Authenticates off of an /etc/passwd like file.  This file must be in
+the etcUsers directory in the top level Zope directory.  etcUserFolder 
+only uses the first two columns of a password file, so files generated 
+by htpasswd should work also.  The format must be:
+
+uid:crypted_password
+
+"""
+
+import os, string, Acquisition
+
+from Globals import HTMLFile, MessageDialog, INSTANCE_HOME
+from string import join,strip,split,lower,upper,find
+
+from OFS.Folder import Folder
+
+try:
+	from crypt import crypt
+except:
+	from Products.exUserFolder.fcrypt.fcrypt import crypt
+
+from Products.exUserFolder.exUserFolder import exUserFolder
+from Products.exUserFolder.Plugins import PluginRegister
+
+path_split=os.path.split
+path_join=os.path.join
+exists=os.path.exists
+
+def manage_addetcAuthSource(self, REQUEST):
+
+	""" """
+	pwfile=REQUEST['etcauth_pwfile']
+	default_role=REQUEST['etcauth_default_role']
+	ob=etcAuthSource(pwfile=pwfile, default_role=default_role)
+	self._setObject('etcAuthSource', ob, None, None, 0)
+	self.currentAuthSource=ob	
+
+manage_addetcAuthSourceForm=HTMLFile('manage_addetcAuthSourceForm', globals())
+manage_editetcAuthSourceForm=HTMLFile('manage_editetcAuthSourceForm', globals())
+
+class etcAuthSource(Folder):
+	""" """
+
+	meta_type='Authentication Source'
+	id		 ='etcAuthSource'
+	title	 ='File System Authentication'
+	icon ='misc_/exUserFolder/exUserFolderPlugin.gif'
+
+	manage_properties=HTMLFile('properties', globals())
+
+	manage_editForm=manage_editetcAuthSourceForm
+	manage_tabs=Acquisition.Acquired
+
+	#
+	# You can define this to go off and do the authentication instead of
+	# using the basic one inside the User Object
+	#
+	remoteAuthMethod=None
+
+	def __init__(self, default_role, pwfile='etcUsers'):
+		self.pwfile=pwfile
+		self.default_role=default_role
+
+	def manage_editAuthSource(self, REQUEST):
+
+		""" """
+		self.pwfile=REQUEST['etcauth_pwfile']
+		self.default_role=REQUEST['etcauth_default_role']
+
+	def cryptPassword_old(self, username, password):
+		u=self.listOneUser(username)
+		if not u:
+			salt = username[:2]
+		else:
+			salt = u[0]['password'][:2]
+		secret = crypt(password, salt)
+		return secret
+
+	#
+	# We don't let you delete, create, or edit users
+	#
+	def deleteUsers(self, userids):
+		pass
+
+	def createUser(self, username, password, roles):
+		pass
+
+	def updateUser(self, username, password, roles):
+		if roles.count(self.default_role):
+			roles.remove(self.default_role)
+		self.currentPropSource.setUserProperty(username=username, key='_roles',
+											   value=roles)
+	def listUserNames(self):
+		users = []
+		pf = open(getPath('exUsers', self.pwfile), 'r')
+		while 1:
+			n = pf.readline()
+			username=string.split(n,':')[0]
+			if not n:
+				break
+			users.append(username)
+		return users
+		
+	def listUsers(self):
+		users = []
+		un=self.listUserNames()
+		for username in un:
+			for user in self.listOneUser(username):
+				users.append(user)
+		return users
+
+	def listOneUser(self, username):
+		users = []
+		pf = open(getPath('exUsers', self.pwfile), 'r')
+		while 1:
+			n = pf.readline()
+			if not n:
+				break
+			n=string.strip(n) # Kill the cr/lf
+			fields=string.split(n,':')
+			lUsername=fields[0]
+			if username!=lUsername:
+				continue
+			password=fields[1]
+
+			roles=[]
+			if self.currentPropSource:
+				roles=self.currentPropSource.getUserProperty(username=username, key='_roles')
+
+			if roles and self.default_role:
+				if not roles.count(self.default_role):
+					roles=roles.append(self.default_role)
+			elif self.default_role:
+				roles=[self.default_role,]
+			else:
+				roles=[]
+			
+			users.append({'username':username,
+						  'password':password,
+						  'roles':roles})
+		return users
+		
+etcAuthReg=PluginRegister('etcAuthSource', 'File Based Authentication Source',
+						  etcAuthSource, manage_addetcAuthSourceForm,
+						  manage_addetcAuthSource,
+						  manage_editetcAuthSourceForm)
+exUserFolder.authSources['etcAuthSource']=etcAuthReg
+
+
+
+def _getPath(home, prefix, name, suffixes):
+
+	d=path_join(home, prefix)
+
+	if d==prefix: raise ValueError, (
+		'The prefix, %s, should be a relative path' % prefix)
+	d=path_join(d,name)
+	if d==name: raise ValueError, ( # Paranoia
+		'The file name, %s, should be a simple file name' % name)
+	for s in suffixes:
+		if s: s="%s.%s" % (d, s)
+		else: s=d
+		if exists(s): return s
+
+def getPath(prefix, name, checkProduct=1, suffixes=('',)):
+	"""Find a file in one of several relative locations
+
+	Arguments:
+
+	  prefix -- The location, relative to some home, to look for the
+				file
+
+	  name -- The name of the file.	 This must not be a path.
+
+	  checkProduct -- a flag indicating whether product directories
+		should be used as additional hope ares to be searched. This
+		defaults to a true value.
+
+		If this is true and the name contains a dot, then the
+		text before the dot is treated as a product name and
+		the product package directory is used as anothe rhome.
+
+	  suffixes -- a sequences of file suffixes to check.
+		By default, the name is used without a suffix.
+
+	The search takes on multiple homes which are INSTANCE_HOME,
+	the directory containing the directory containing SOFTWARE_HOME, and
+	possibly product areas.		
+	"""
+	d,n = path_split(name)
+	if d: raise ValueError, (
+		'The file name, %s, should be a simple file name' % name)
+
+	sw=path_split(path_split(SOFTWARE_HOME)[0])[0]
+	for home in (INSTANCE_HOME, sw):
+		if checkProduct:
+			l=find(name, '.')
+			if l > 0:
+				p=name[:l]
+				n=name[l+1:]
+				r=_getPath(home, "Products/%s/%s/" % (p,prefix),
+						   n, suffixes)
+
+				if r is not None: return r
+		r=_getPath(home, prefix, name, suffixes)
+		if r is not None: return r

Added: trunk/ZopeProducts/exUserFolder/AuthSources/etcAuthSource/manage_addetcAuthSourceForm.dtml
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/etcAuthSource/manage_addetcAuthSourceForm.dtml	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/etcAuthSource/manage_addetcAuthSourceForm.dtml	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,28 @@
+<dtml-var "DialogHeader(DialogTitle='File Authentication Source')">
+<FORM ACTION="&dtml-URL;" METHOD="POST">
+<dtml-in "REQUEST.form.keys()">
+<input type="HIDDEN" name="<dtml-var sequence-item>" value="<dtml-var "REQUEST[_.getitem('sequence-item',0)]">">
+</dtml-in>
+<input type="HIDDEN" name="doProp" value="1">
+<TABLE CELLSPACING="2">
+<TR>
+
+  <th align="LEFT" valign="TOP"><em><dtml-babel src="'en'">Password File</dtml-babel></em></th>
+  <TD ALIGN="LEFT" VALIGN="TOP">
+	    <input name=etcauth_pwfile value="etcUsers">
+  </TD>
+</tr>
+<tr>
+  <th align="LEFT" valign="TOP"><em><dtml-babel src="'en'">Default Role</dtml-babel></em></th>
+  <TD ALIGN="LEFT" VALIGN="TOP">
+	    <input name=etcauth_default_role value="etcUser">
+  </TD>
+
+</TR>
+<TR>
+<TD></TD>
+<TD><BR><INPUT TYPE="SUBMIT" VALUE=" <dtml-babel src="'en'">NEXT</dtml-babel> "></TD>
+</TR>
+</TABLE>
+</FORM>
+<dtml-var DialogFooter>

Added: trunk/ZopeProducts/exUserFolder/AuthSources/etcAuthSource/manage_editetcAuthSourceForm.dtml
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/etcAuthSource/manage_editetcAuthSourceForm.dtml	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/etcAuthSource/manage_editetcAuthSourceForm.dtml	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,25 @@
+<dtml-var "DialogHeader(DialogTitle='File Authentication Source',dialog_width='100%')">
+<dtml-var manage_tabs>
+<FORM ACTION="manage_editAuthSource" METHOD="POST">
+<TABLE CELLSPACING="2">
+<TR>
+
+  <th align="LEFT" valign="TOP"><em><dtml-babel src="'en'">Password File</dtml-babel></em></th>
+  <TD ALIGN="LEFT" VALIGN="TOP">
+	    <input name=etcauth_pwfile value="<dtml-var "currentAuthSource.pwfile">">
+  </TD>
+</tr>
+<tr>
+  <th align="LEFT" valign="TOP"><em><dtml-babel src="'en'">Default Role</dtml-babel></em></th>
+  <TD ALIGN="LEFT" VALIGN="TOP">
+	    <input name=etcauth_default_role value="<dtml-var "currentAuthSource.default_role">">
+  </TD>
+
+</TR>
+<TR>
+<TD></TD>
+<TD><BR><INPUT TYPE="SUBMIT" VALUE=" <dtml-babel src="'en'">Edit</dtml-babel> "></TD>
+</TR>
+</TABLE>
+</FORM>
+<dtml-var DialogFooter>

Added: trunk/ZopeProducts/exUserFolder/AuthSources/httpsAuthSource/__init__.py
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/httpsAuthSource/__init__.py	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/httpsAuthSource/__init__.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1 @@
+import httpsAuthSource

Added: trunk/ZopeProducts/exUserFolder/AuthSources/httpsAuthSource/httpsAuthSource.py
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/httpsAuthSource/httpsAuthSource.py	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/httpsAuthSource/httpsAuthSource.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,276 @@
+"""
+ HTTPS Authentication Source for exUserFolder
+
+ This class only authenticates users against an https service
+ It stores roles in the prop source
+
+ This plugin requires that ssl support be compiled into the python interpreter
+ 
+ $Header: /cvsroot/exuserfolder/exUserFolder/AuthSources/httpsAuthSource/httpsAuthSource.py,v 1.1 2004/11/10 14:15:34 akm Exp $
+"""
+
+import string, re
+import httplib, urllib, urlparse
+
+import Acquisition
+
+from Globals import HTMLFile, MessageDialog, InitializeClass
+
+from OFS.Folder import Folder
+
+from Products.exUserFolder.exUserFolder import exUserFolder
+from Products.exUserFolder.Plugins import PluginRegister
+from AccessControl import ClassSecurityInfo
+
+from time import time
+
+from zLOG import LOG, ERROR, DEBUG
+
+def manage_addhttpsAuthSource(self, REQUEST):
+    """ Add an https Auth Source """
+
+    obj = httpsAuthSource(REQUEST)
+    self._setObject('httpsAuthSource', obj, None, None, 0)
+    obj = getattr(self, 'httpsAuthSource')
+    
+    self.currentAuthSource = obj
+    return ''
+manage_addhttpsAuthSourceForm=HTMLFile('manage_addhttpsAuthSourceForm', globals())
+manage_edithttpsAuthSourceForm=HTMLFile('manage_edithttpsAuthSourceForm', globals())
+
+class httpsAuthSource(Folder):
+    """ Authenticate Users against an HTTPS service """
+
+    meta_type= 'Authentication Source'
+    id   = 'httpsAuthSource'
+    title= 'HTTPS Authentication'
+    icon = 'misc_/exUserFolder/exUserFolderPlugin.gif'
+
+    ROLES_KEY = '_roles'
+
+    manage_editForm = manage_edithttpsAuthSourceForm
+    manage_properties=HTMLFile('properties', globals())
+    manage_tabs=Acquisition.Acquired
+
+
+    # Cache successful auths so we don't make N network requests per page
+    _v_authenticCache = {}
+
+    # Cache unauthorized users since this acl folder needs to fail before checking higher ones
+    _v_unAuthenticCache = {}
+
+    CACHE_TIMEOUT = 60  # how long we store auth in the cache.  (in seconds)
+    UNAUTH_CACHE_MAXSIZE = 50 # limit the size that the unauthenticated cache can grow
+
+    def __init__(self, REQUEST):
+	self._setProps(REQUEST)
+
+    def manage_editAuthSource(self, REQUEST):
+	""" Handle output of manage_main"""
+	self._setProps(REQUEST)
+
+        self.clearAuthCache()
+        
+	if REQUEST is not None:
+	    return self.MessageDialog(self,
+				      REQUEST=REQUEST,
+				      title = 'Edited',
+				      message = "Properties for %s changed." % self.id,
+				      action = 'manage_editAuthSourceForm')
+
+
+    def _setProps(self,REQUEST):
+	self.defaultRole = REQUEST.get('defaultRole', 'Member')
+	self.serviceUrl    = REQUEST['serviceUrl']		
+
+	self.userNameParam = REQUEST['userNameParam']
+	self.passwdParam   = REQUEST['passwdParam']
+        self.authResponse  = REQUEST['authResponse']
+        self.authResponsePattern = re.compile(self.authResponse)
+	
+    #
+    # Don't allow for creation - this happens automatically upon first login
+    # 
+    def createUser(self, username, password, roles):
+	pass
+
+    # we don't store the password - just pass it in the remoteAuthentication
+    def cryptPassword_old(self, username, password):
+	pass
+	
+    def deleteUsers(self, userids):
+	"""delete user from the prop source"""
+	self.currentPropSource.deleteUsers(userids)
+
+    def updateUser(self, username, password, roles):
+	"""Update a user's roles
+	   Passwords are managed on the other end, not here"""
+
+	self.currentPropSource.setUserProperty(username=username,
+					       key=self.ROLES_KEY,
+					       value=roles)
+
+    def listUserNames(self):
+	"""Return a list of usernames"""
+	return self.currentPropSource.listUsers()
+
+    # Return a list of user dictionaries the same as listOnUser
+    def listUsers(self):
+	pass
+
+    # Return one user matching the username
+    # Should be a dictionary;
+    # {'username':username, 'password':cryptedPassword, 'roles':list_of_roles}
+    def listOneUser(self, username):
+	roles=[]
+
+	# Attempt to aq the roles from the parent, this code
+	# works inside a cmfsite but is not general case
+	try:
+	    portal   = self.portal_url.getPortalObject()
+	    acl_users = portal.aq_parent.acl_users
+	    roles += acl_users.getUser(username).getRolesInContext(portal)
+	except:
+	    pass
+
+	# We store site specific roles in the prop source - only tested with zodbBTreeProps source
+	xufRoles = self.currentPropSource.getUserProperty(username=username,
+							  key=self.ROLES_KEY,
+							  default=[])
+	# getUserProperty returns None if user has no props
+	if xufRoles:
+	    roles += xufRoles
+
+	# this happens when listOneUser is called before authenticate (?)
+	if not roles:
+	    roles = ['Anonymous']
+
+	# LOG(self.id, DEBUG, username)
+	# LOG(self.id, DEBUG, roles)
+	return [{'username':username,
+		 'password':'*****',
+		 'roles':roles}]
+
+    def authenticate(self, username, passwd):
+	"""Authenticate a username/password combination
+	against the HTTPS service"""
+
+
+        ## Emergency override - uncomment this if you get locked out of your site
+        ## return 1 
+    
+        # first check the authorization cache to minimize network traffic
+        if self.isAuthenticCached(username, passwd):
+            return 1
+        elif self.isUnAuthenticCached(username, passwd):
+            return 0
+        
+        
+	auth = 0		
+	# LOG(self.id, ERROR, "%s %s" % (username, passwd))
+
+	params = urllib.urlencode({self.userNameParam: username,
+                                   self.passwdParam: passwd})
+
+        # by passing in params, this POSTs
+        response = urllib.urlopen(self.serviceUrl, params)
+        
+	auth = self._parseResponse(response.read())
+	
+	# Everyone gets 'Authenticated' and the defaultRole
+	# This also insures that user ends up in prop list upon first login
+	if auth and not self.currentPropSource.getUserProperty(key=self.ROLES_KEY,
+							       username=username,
+							       default=None):
+	    roles = ['Authenticated', self.defaultRole]
+	    self.currentPropSource.setUserProperty(username=username,
+						   key=self.ROLES_KEY,
+						   value=roles)
+        # store auth results in cache
+        if auth:
+            self.cacheAuth(username, passwd)
+        else:
+            self.cacheUnAuth(username, passwd)
+
+	return auth
+
+    # tell xuf to use our authenticate method
+    remoteAuthMethod = authenticate
+
+    def _parseResponse(self, response):
+        """ Try to match the expected authorization regex against the response
+            If its found, they're in"""
+
+	# LOG(self.id, DEBUG, response)
+
+	retVal = 0
+	try:
+            if self.authResponsePattern.match(response):
+		retVal = 1
+	    else:
+		retVal = 0
+	except Exception, e:
+	    ERROR("%s: Could not parse the response. (response=%s, auth pattern=%s)" %
+                  (e, response, self.authResponse))
+	    retVal = 0
+
+	return retVal
+
+
+    #
+    # cacheing methods
+    #
+    def isAuthenticCached(self, username, passwd):
+        key = (username, passwd)
+        now = time()
+        if (self._v_authenticCache.has_key(key) and
+            (now - self._v_authenticCache[key]) < self.CACHE_TIMEOUT):
+            # LOG(self.id, ERROR, "Cached authentic user")
+            return 1
+        else:
+            return 0
+
+    def isUnAuthenticCached(self, username, passwd):
+        key = (username, passwd)
+        now = time()
+        if (self._v_unAuthenticCache.has_key(key) and
+            (now - self._v_unAuthenticCache[key]) < self.CACHE_TIMEOUT):
+            # LOG(self.id, ERROR, "Cached UN-Authentic user")
+            return 1
+        else:
+            return 0
+        
+    def cacheAuth(self, username, passwd):
+        """Store successful auth attempts"""
+        key = (username, passwd)
+        timestamp = time()
+        self._v_authenticCache[key] = timestamp
+
+
+    def cacheUnAuth(self, username, passwd):
+        """Store failed auth attempts"""
+        # don't let the unauth cache grow unbounded
+        if len(self._v_unAuthenticCache) > self.UNAUTH_CACHE_MAXSIZE:
+            self._v_unAuthenticCache = {}
+
+        key = (username, passwd)
+        timestamp = time()
+        self._v_unAuthenticCache[key] = timestamp
+
+    def clearAuthCache(self):
+        """clear the user cache"""
+        self._v_authenticCache.clear()
+        self._v_unAuthenticCache.clear()        
+
+
+#
+# Register the plugin and it's manage forms
+#
+httpsAuthReg=PluginRegister('httpsAuthSource',
+			    'HTTPS Authentication Source',
+			    httpsAuthSource,
+			    manage_addhttpsAuthSourceForm,
+			    manage_addhttpsAuthSource,
+			    manage_edithttpsAuthSourceForm)
+exUserFolder.authSources['httpsAuthSource']=httpsAuthReg
+

Added: trunk/ZopeProducts/exUserFolder/AuthSources/httpsAuthSource/manage_addhttpsAuthSourceForm.dtml
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/httpsAuthSource/manage_addhttpsAuthSourceForm.dtml	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/httpsAuthSource/manage_addhttpsAuthSourceForm.dtml	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,48 @@
+<dtml-var "DialogHeader(DialogTitle='HTTPS Authentication Source')">
+<FORM ACTION="&dtml-URL;" METHOD="POST">
+<dtml-in "REQUEST.form.keys()">
+<input type="HIDDEN" name="<dtml-var sequence-item>" value="<dtml-var "REQUEST[_.getitem('sequence-item',0)]">">
+</dtml-in>
+<input type="HIDDEN" name="doProp" value="1">
+<TABLE CELLSPACING="2">
+<TR>
+
+  <th align="LEFT" valign="TOP"><em><dtml-babel src="'en'">Service URL</dtml-babel></em></th>
+  <TD ALIGN="LEFT" VALIGN="TOP">
+	    <input name=serviceUrl value="http://www.yourdomain.net/services/authService">
+  </TD>
+</tr>
+<TR>
+
+  <th align="LEFT" valign="TOP"><em><dtml-babel src="'en'">User Name Parameter</dtml-babel></em></th>
+  <TD ALIGN="LEFT" VALIGN="TOP">
+	    <input name=userNameParam value="username">
+  </TD>
+</tr>
+<TR>
+
+  <th align="LEFT" valign="TOP"><em><dtml-babel src="'en'">Passwd Parameter</dtml-babel></em></th>
+  <TD ALIGN="LEFT" VALIGN="TOP">
+	    <input name=passwdParam value="password">
+  </TD>
+</tr>
+<TR>
+  <th align="LEFT" valign="TOP"><em><dtml-babel src="'en'">Authorization Response Pattern</dtml-babel></em></th>
+  <TD ALIGN="LEFT" VALIGN="TOP">
+	    <input name=authResponse value="^LoginResponseAuth=1$">
+  </TD>
+</tr>
+<tr>
+  <th align="LEFT" valign="TOP"><em><dtml-babel src="'en'">Default Role</dtml-babel></em></th>
+  <TD ALIGN="LEFT" VALIGN="TOP">
+	    <input name=defaultRole value="Member">
+  </TD>
+
+</TR>
+<TR>
+<TD></TD>
+<TD><BR><INPUT TYPE="SUBMIT" VALUE=" <dtml-babel src="'en'">NEXT</dtml-babel> "></TD>
+</TR>
+</TABLE>
+</FORM>
+<dtml-var DialogFooter>

Added: trunk/ZopeProducts/exUserFolder/AuthSources/httpsAuthSource/manage_edithttpsAuthSourceForm.dtml
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/httpsAuthSource/manage_edithttpsAuthSourceForm.dtml	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/httpsAuthSource/manage_edithttpsAuthSourceForm.dtml	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,45 @@
+<dtml-var "DialogHeader(DialogTitle='HTTPS Authentication Source',dialog_width='100%')">
+<dtml-var manage_tabs>
+<FORM ACTION="manage_editAuthSource" METHOD="POST">
+<TABLE CELLSPACING="2">
+<TR>
+
+  <th align="LEFT" valign="TOP"><em><dtml-babel src="'en'">Service URL</dtml-babel></em></th>
+  <TD ALIGN="LEFT" VALIGN="TOP">
+	    <input name=serviceUrl value="<dtml-var "currentAuthSource.serviceUrl">">
+  </TD>
+</tr>
+<TR>
+
+  <th align="LEFT" valign="TOP"><em><dtml-babel src="'en'">User Name Parameter</dtml-babel></em></th>
+  <TD ALIGN="LEFT" VALIGN="TOP">
+	    <input name=userNameParam value="<dtml-var "currentAuthSource.userNameParam">">
+  </TD>
+</tr>
+<TR>
+
+  <th align="LEFT" valign="TOP"><em><dtml-babel src="'en'">Passwd Parameter</dtml-babel></em></th>
+  <TD ALIGN="LEFT" VALIGN="TOP">
+	    <input name=passwdParam value="<dtml-var "currentAuthSource.passwdParam">">
+  </TD>
+</tr>
+<TR>
+  <th align="LEFT" valign="TOP"><em><dtml-babel src="'en'">Authorization Response Pattern</dtml-babel></em></th>
+  <TD ALIGN="LEFT" VALIGN="TOP">
+	    <input name=authResponse value="<dtml-var "currentAuthSource.authResponse">">
+  </TD>
+</tr>
+<tr>
+  <th align="LEFT" valign="TOP"><em><dtml-babel src="'en'">Default Role</dtml-babel></em></th>
+  <TD ALIGN="LEFT" VALIGN="TOP">
+	    <input name=defaultRole value="<dtml-var "currentAuthSource.defaultRole">">
+  </TD>
+
+</TR>
+<TR>
+<TD></TD>
+<TD><BR><INPUT TYPE="SUBMIT" VALUE=" <dtml-babel src="'en'">Edit</dtml-babel> "></TD>
+</TR>
+</TABLE>
+</FORM>
+<dtml-var DialogFooter>

Added: trunk/ZopeProducts/exUserFolder/AuthSources/httpsAuthSource/tests/testSSL.py
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/httpsAuthSource/tests/testSSL.py	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/httpsAuthSource/tests/testSSL.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,15 @@
+
+#
+# Not a real unit test.
+# Just a test to make sure that ssl works inside your python
+# Make sure to test using your Zope's python
+#
+from httplib import HTTPSConnection
+
+HOSTNAME = 'mail.yahoo.com'  # or any other secure server..
+
+conn = HTTPSConnection(HOSTNAME)
+conn.putrequest('GET', '/')
+conn.endheaders()
+response = conn.getresponse()
+print response.read()


Property changes on: trunk/ZopeProducts/exUserFolder/AuthSources/httpsAuthSource/tests/testSSL.py
___________________________________________________________________
Added: svn:executable
   + *

Added: trunk/ZopeProducts/exUserFolder/AuthSources/mysqlAuthSource/.cvsignore
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/mysqlAuthSource/.cvsignore	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/mysqlAuthSource/.cvsignore	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,4 @@
+*.pyc
+*.pyo
+*~
+.*.swp

Added: trunk/ZopeProducts/exUserFolder/AuthSources/mysqlAuthSource/__init__.py
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/mysqlAuthSource/__init__.py	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/mysqlAuthSource/__init__.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,2 @@
+# $Id: __init__.py,v 1.1 2004/11/10 14:15:35 akm Exp $
+import mysqlAuthSource

Added: trunk/ZopeProducts/exUserFolder/AuthSources/mysqlAuthSource/manage_addmysqlAuthSourceForm.dtml
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/mysqlAuthSource/manage_addmysqlAuthSourceForm.dtml	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/mysqlAuthSource/manage_addmysqlAuthSourceForm.dtml	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,40 @@
+<dtml-var "DialogHeader(_.None,_,DialogTitle='Add MySQL Authentication Source')">
+<FORM ACTION="&dtml-URL;" METHOD="POST">
+<dtml-in "REQUEST.form.keys()">
+<input type="HIDDEN" name="<dtml-var sequence-item>" value="<dtml-var "REQUEST[_.getitem('sequence-item',0)]">">
+</dtml-in>
+<input type="HIDDEN" name="doProp" value="1">
+<TABLE CELLSPACING="2">
+<tr><th><dtml-babel src="'en'">Database Connection</dtml-babel>:</th>
+    <td>
+        <select name="mysqlauth_connection">
+        <dtml-in "SQLConnectionIDs()">
+            <option value="<dtml-var sequence-item>">
+            <dtml-var sequence-key></option>
+        </dtml-in>
+        </select>
+     </td>
+</tr>
+<tr>
+	<th><dtml-babel src="'en'">Table Name</dtml-babel>:</th>
+	<td><input type="text" name="mysqlauth_table" value="passwd"></td>
+</tr>
+<tr>
+	<th><dtml-babel src="'en'">Username Column</dtml-babel>:</th>
+	<td><input type="text" name="mysqlauth_usernameColumn" value="username"></td>
+</tr>
+<tr>
+	<th><dtml-babel src="'en'">Password Column</dtml-babel>:</th>
+	<td><input type="text" name="mysqlauth_passwordColumn" value="password"></td>
+</tr>
+<tr>
+	<th><dtml-babel src="'en'">Roles Column</dtml-babel>:</th>
+	<td><input type="text" name="mysqlauth_rolesColumn" value="roles"></td>
+</tr>
+<TR>
+<TD></TD>
+<TD><BR><INPUT TYPE="SUBMIT" VALUE="<dtml-babel src="'en'">Add</dtml-babel>"></TD>
+</TR>
+</TABLE>
+</FORM>
+<dtml-var DialogFooter>

Added: trunk/ZopeProducts/exUserFolder/AuthSources/mysqlAuthSource/manage_editmysqlAuthSourceForm.dtml
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/mysqlAuthSource/manage_editmysqlAuthSourceForm.dtml	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/mysqlAuthSource/manage_editmysqlAuthSourceForm.dtml	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,37 @@
+<dtml-var "DialogHeader(_.None,_,DialogTitle='MySQL Authentication Source',dialog_width='100%')">
+<dtml-var manage_tabs>
+<FORM ACTION="manage_editAuthSource" METHOD="POST">
+<TABLE CELLSPACING="2">
+<tr><th><dtml-babel src="'en'">Database Connection</dtml-babel>:</th>
+    <td>
+        <select name="mysqlauth_connection">
+        <dtml-in "SQLConnectionIDs()">
+			<option value="<dtml-var sequence-item>"<dtml-if "currentAuthSource.connection==_['sequence-item']"> SELECTED</dtml-if>>
+            <dtml-var sequence-key></option>
+        </dtml-in>
+        </select>
+     </td>
+</tr>
+<tr>
+	<th><dtml-babel src="'en'">Table Name</dtml-babel>:</th>
+	<td><input type="text" name="mysqlauth_table" value="<dtml-var "currentAuthSource.table">"></td>
+</tr>
+<tr>
+	<th><dtml-babel src="'en'">Username Column</dtml-babel>:</th>
+	<td><input type="text" name="mysqlauth_usernameColumn" value="<dtml-var "currentAuthSource.usernameColumn">"></td>
+</tr>
+<tr>
+	<th><dtml-babel src="'en'">Password Column</dtml-babel>:</th>
+	<td><input type="text" name="mysqlauth_passwordColumn" value="<dtml-var "currentAuthSource.passwordColumn">"></td>
+</tr>
+<tr>
+	<th><dtml-babel src="'en'">Roles Column</dtml-babel>:</th>
+	<td><input type="text" name="mysqlauth_rolesColumn" value="<dtml-var "currentAuthSource.rolesColumn">"></td>
+</tr>
+<TR>
+<TD></TD>
+<TD><BR><INPUT TYPE="SUBMIT" VALUE=" <dtml-babel src="'en'">Edit</dtml-babel> "></TD>
+</TR>
+</TABLE>
+</FORM>
+<dtml-var DialogFooter>

Added: trunk/ZopeProducts/exUserFolder/AuthSources/mysqlAuthSource/mysqlAuthSource.py
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/mysqlAuthSource/mysqlAuthSource.py	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/mysqlAuthSource/mysqlAuthSource.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,316 @@
+#
+# Extensible User Folder
+# 
+# MySQL Authentication Source for exUserFolder
+#
+# (C) Copyright 2000,2001 The Internet (Aust) Pty Ltd
+# ACN: 082 081 472  ABN: 83 082 081 472
+# All Rights Reserved
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# Author: Clint Brubakken <cabrubak at acm.org> adapted from pgPropSource by Andrew Milton <akm at theinternet.com.au>
+# $Id: mysqlAuthSource.py,v 1.1 2004/11/10 14:15:35 akm Exp $
+
+#
+# This class only authenticates users, it stores no properties.
+#
+
+import string,Acquisition
+
+from Globals import HTMLFile, MessageDialog, INSTANCE_HOME
+
+from OFS.Folder import Folder
+
+from Products.ZSQLMethods.SQL import SQL
+
+from Products.exUserFolder.exUserFolder import exUserFolder
+from Products.exUserFolder.Plugins import PluginRegister
+
+try:
+	from crypt import crypt
+except:
+	from Products.exUserFolder.fcrypt.fcrypt import crypt
+
+
+def manage_addmysqlAuthSource(self, REQUEST):
+	""" Add a MySQL Auth Source """
+
+	connection=REQUEST['mysqlauth_connection']
+	table=REQUEST['mysqlauth_table']
+	usernameColumn=REQUEST['mysqlauth_usernameColumn']
+	passwordColumn=REQUEST['mysqlauth_passwordColumn']
+	rolesColumn=REQUEST['mysqlauth_rolesColumn']
+	o = mysqlAuthSource(connection, table, usernameColumn, passwordColumn,
+					 rolesColumn)
+	self._setObject('mysqlAuthSource', o, None, None, 0)
+	o=getattr(self,'mysqlAuthSource')
+	if hasattr(o, 'postInitialisation'):
+		o.postInitialisation(REQUEST)
+	
+	self.currentAuthSource=o
+	return ''
+
+manage_addmysqlAuthSourceForm=HTMLFile('manage_addmysqlAuthSourceForm', globals())
+manage_editmysqlAuthSourceForm=HTMLFile('manage_editmysqlAuthSourceForm', globals())
+
+class mysqlAuthSource(Folder):
+	""" Authenticate Users against a MySQL Database """
+
+	meta_type='Authentication Source'
+	title='MySQL Authentication'
+
+	icon ='misc_/exUserFolder/exUserFolderPlugin.gif'
+
+	manage_tabs=Acquisition.Acquired
+
+	manage_editForm=manage_editmysqlAuthSourceForm
+
+	#
+	# You can define this to go off and do the authentication instead of
+	# using the basic one inside the User Object
+	#
+	remoteAuthMethod=None
+	
+	def __init__(self, connection, table, usernameColumn, passwordColumn,
+				 rolesColumn):
+		self.id='mysqlAuthSource'
+		self.connection=connection
+		self.table=table
+		self.usernameColumn=usernameColumn
+		self.passwordColumn=passwordColumn
+		self.rolesColumn=rolesColumn
+		self.addSQLQueries()
+
+	def manage_editAuthSource(self, REQUEST):
+		""" Edit a MySQL Auth Source """
+
+		self.connection=REQUEST['mysqlauth_connection']
+		self.table=REQUEST['mysqlauth_table']
+		self.usernameColumn=REQUEST['mysqlauth_usernameColumn']
+		self.passwordColumn=REQUEST['mysqlauth_passwordColumn']
+		self.rolesColumn=REQUEST['mysqlauth_rolesColumn']
+		self.delSQLQueries()
+		self.addSQLQueries() # Re-add queries with new parameters
+
+	def createUser(self, username, password, roles):
+		""" Add A Username """
+
+		if type(roles) != type([]):
+			if roles:
+				roles=list(roles)
+			else:
+				roles=[]
+
+		rolestring=''
+		for role in roles:
+			rolestring=rolestring+role+','
+
+		rolestring=rolestring[:-1]
+		secret=self.cryptPassword(username, password)
+		self.sqlInsertUser(username=username,
+						   password=secret,
+						   roles=rolestring)
+
+	def updateUser(self, username, password, roles):
+		if type(roles) != type([]):
+			if roles:
+				roles=list(roles)
+			else:
+				roles=[]
+		
+		rolestring=''
+		for role in roles:
+			print role
+			rolestring=rolestring+role+','
+
+		rolestring=rolestring[:-1]
+
+		# Don't change passwords if it's null
+		if password:
+			secret=self.cryptPassword(username, password)
+			self.sqlUpdateUserPassword(username=username,
+									   password=secret)
+			
+		self.sqlUpdateUser(username=username,
+						   roles=rolestring)
+
+	def delSQLQueries(self):
+		sqllist=self.objectIds('Z SQL Method')
+		self.manage_delObjects(ids=sqllist)
+
+	def addSQLQueries(self):
+		sqlListUsers=SQL(
+			'sqlListUsers',
+			'List All Users',
+			self.connection,
+			'table=%s'%(self.table),
+			_sqlListUsers)
+
+		self._setObject('sqlListUsers', sqlListUsers)
+
+		sqlListOneUser=SQL(
+			'sqlListOneUser',
+			'List ONE User',
+			self.connection,
+			'table=%s usernameColumn=%s username:string'%(
+			self.table, self.usernameColumn),
+			_sqlListOneUser)
+
+		self._setObject('sqlListOneUser', sqlListOneUser)
+
+		sqlDeleteOneUser=SQL(
+			'sqlDeleteOneUser',
+			'Delete One User',
+			self.connection,
+			'table=%s usernameColumn=%s username:string'%(
+			self.table,self.usernameColumn),
+			_sqlDeleteOneUser)
+
+		self._setObject('sqlDeleteOneUser', sqlDeleteOneUser)
+
+		sqlInsertUser=SQL(
+			'sqlInsertUser',
+			'Insert One User',
+			self.connection,
+			'table=%s usernameColumn=%s passwordColumn=%s rolesColumn=%s username:string password:string roles:string'%(
+			self.table, self.usernameColumn, self.passwordColumn, self.rolesColumn),
+			_sqlInsertUser)
+
+		self._setObject('sqlInsertUser', sqlInsertUser)
+
+		sqlUpdateUser=SQL(
+			'sqlUpdateUser',
+			'Update User',
+			self.connection,
+			'table=%s rolesColumn=%s username:string roles:string'%(self.table, self.rolesColumn),
+			_sqlUpdateUser)
+
+		self._setObject('sqlUpdateUser', sqlUpdateUser)
+
+		sqlUpdateUserPassword=SQL(
+			'sqlUpdateUserPassword',
+			'Update just the password',
+			self.connection,
+			'table=%s usernameColumn=%s passwordColumn=%s username:string password:string'%(self.table, self.usernameColumn, self.passwordColumn),
+			_sqlUpdateUserPassword)
+
+		self._setObject('sqlUpdateUserPassword', sqlUpdateUserPassword)
+
+	def cryptPassword_old(self, username, password):
+			salt =username[:2]
+			secret = crypt(password, salt)
+			return secret
+
+	def deleteUsers(self, userids):
+		for uid in userids:
+			self.sqlDeleteOneUser(username=uid)
+
+	def listUserNames(self):
+		"""Returns a real list of user names """
+		users = []
+		result=self.sqlListUsers()
+		for n in result:
+			username=sqlattr(n,self.usernameColumn)
+			users.append(username)
+		return users
+		
+	def listUsers(self):
+		"""Returns a list of user names or [] if no users exist"""		
+		users = []
+		result=self.sqlListUsers()
+		for n in result:
+			roles=[]
+			username=sqlattr(n,self.usernameColumn)
+			if sqlattr(n, self.rolesColumn):
+				roles=string.split(sqlattr(n,self.rolesColumn),',')
+			password=sqlattr(n, self.passwordColumn)
+			N={'username':username, 'password':password, 'roles':roles}
+			users.append(N)
+		return users
+
+	def listOneUser(self,username):
+		users = []
+		result=self.sqlListOneUser(username=username)
+		for n in result:
+			roles=[]
+			username=sqlattr(n,self.usernameColumn)
+			password=sqlattr(n,self.passwordColumn)
+			if sqlattr(n, self.rolesColumn):
+				roles=string.split(sqlattr(n,self.rolesColumn),',')  #Andreas
+			N={'username':username, 'password':password, 'roles':roles}
+			users.append(N)
+		return users
+	
+	def postInitialisation(self, REQUEST):
+		pass
+
+mysqlAuthReg=PluginRegister('mysqlAuthSource', 'MySQL Authentication Source',
+						 mysqlAuthSource, manage_addmysqlAuthSourceForm,
+						 manage_addmysqlAuthSource,
+						 manage_editmysqlAuthSourceForm)
+exUserFolder.authSources['mysqlAuthSource']=mysqlAuthReg
+
+from string import upper, lower
+import Missing
+mt=type(Missing.Value)
+
+def typeconv(val):
+    if type(val)==mt:
+        return ''
+    return val
+
+def sqlattr(ob, attr):
+    name=attr
+    if hasattr(ob, attr):
+        return typeconv(getattr(ob, attr))
+    attr=upper(attr)
+    if hasattr(ob, attr):
+        return typeconv(getattr(ob, attr))
+    attr=lower(attr)
+    if hasattr(ob, attr):
+        return typeconv(getattr(ob, attr))
+    raise NameError, name
+
+
+
+_sqlListUsers="""
+SELECT * FROM <dtml-var table>
+"""
+
+_sqlListOneUser="""
+SELECT * FROM <dtml-var table>
+where <dtml-var usernameColumn>=<dtml-sqlvar username type=string>
+"""
+
+_sqlDeleteOneUser="""
+DELETE FROM <dtml-var table>
+where <dtml-var usernameColumn>=<dtml-sqlvar username type=string>
+"""
+
+_sqlInsertUser="""
+INSERT INTO <dtml-var table> (<dtml-var usernameColumn>, <dtml-var passwordColumn>, <dtml-var rolesColumn>)
+VALUES (<dtml-sqlvar username type=string>,
+        <dtml-sqlvar password type=string>,
+		<dtml-sqlvar roles type=string>)
+"""
+
+_sqlUpdateUserPassword="""
+UPDATE <dtml-var table> set <dtml-var passwordColumn>=<dtml-sqlvar password type=string>
+WHERE <dtml-var usernameColumn>=<dtml-sqlvar username type=string>
+"""
+
+_sqlUpdateUser="""
+UPDATE <dtml-var table> set <dtml-var rolesColumn>=<dtml-sqlvar roles type=string>
+WHERE <dtml-var usernameColumn>=<dtml-sqlvar username type=string>
+"""

Added: trunk/ZopeProducts/exUserFolder/AuthSources/nisAuthSource/__init__.py
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/nisAuthSource/__init__.py	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/nisAuthSource/__init__.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1 @@
+import nisAuthSource

Added: trunk/ZopeProducts/exUserFolder/AuthSources/nisAuthSource/manage_addnisAuthSourceForm.dtml
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/nisAuthSource/manage_addnisAuthSourceForm.dtml	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/nisAuthSource/manage_addnisAuthSourceForm.dtml	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,12 @@
+<dtml-var "DialogHeader(_.None,_,DialogTitle='Add NIS Authentication Source')">
+<FORM ACTION="&dtml-URL;" METHOD="POST">
+<dtml-in "REQUEST.form.keys()">
+<input type="HIDDEN" name="<dtml-var sequence-item>" value="<dtml-var "REQUEST[_.getitem('sequence-item',0)]">">
+</dtml-in>
+<input type="HIDDEN" name="doProp" value="1">
+<Table><tr><th><dtml-babel src="'en'">Default Role</dtml-babel></th>   
+        <td><input name="nisauth_default_role:required"></td></tr>
+       <tr><th>Do not use local roles</th><td><input name="nisauth_NoLocalRoles" type="Checkbox"></td></tr></Table>
+<INPUT TYPE="SUBMIT" VALUE=" <dtml-babel src="'en'">NEXT</dtml-babel> ">
+</FORM>
+<dtml-var DialogFooter>

Added: trunk/ZopeProducts/exUserFolder/AuthSources/nisAuthSource/manage_editnisAuthSourceForm.dtml
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/nisAuthSource/manage_editnisAuthSourceForm.dtml	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/nisAuthSource/manage_editnisAuthSourceForm.dtml	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,10 @@
+<dtml-var "DialogHeader(_.None,_,DialogTitle='NIS Authentication  Source',dialog_width='100%')">
+<dtml-var manage_tabs>
+<FORM ACTION="manage_editAuthSource" METHOD="POST">
+<Table><tr><th><dtml-babel src="'en'">Default Role</dtml-babel></th>   
+        <td><input name="nisauth_default_role:required" value="<dtml-var "currentAuthSource.default_role">"></td></tr>
+       <tr><th>Do not use local roles</th>
+       <td><input name="nisauth_NoLocalRoles" type="Checkbox" <dtml-if "currentAuthSource.NoLocalRoles">CHECKED</dtml-if>></td></tr></Table>
+<INPUT TYPE="SUBMIT" VALUE=" Next ">
+</FORM>
+<dtml-var DialogFooter>

Added: trunk/ZopeProducts/exUserFolder/AuthSources/nisAuthSource/nisAuthSource.py
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/nisAuthSource/nisAuthSource.py	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/nisAuthSource/nisAuthSource.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,237 @@
+#
+# Extensible User Folder
+# 
+# NIS Authentication Source for exUserFolder
+#
+
+# Author: Jason Gibson <jason.gibson at sbcglobal.net>
+
+#
+# This class only authenticates users, it stores no properties.
+#
+
+import string
+import nis
+
+from Globals import HTMLFile, MessageDialog, INSTANCE_HOME, DTMLFile
+
+from OFS.Folder import Folder
+
+from ZODB.PersistentMapping import PersistentMapping
+
+from Products.exUserFolder.exUserFolder import exUserFolder
+from Products.exUserFolder.Plugins import PluginRegister
+
+try:
+    from crypt import crypt
+except:
+    from Products.exUserFolder.fcrypt.fcrypt import crypt
+
+def manage_addnisAuthSource(self, REQUEST):
+    """ Add a nis Auth Source """
+    try:
+        if nis.cat('passwd.byname'):
+            default_role=REQUEST['nisauth_default_role']
+            NoLocalRoles=REQUEST.has_key('nisauth_NoLocalRoles')
+            o = nisAuthSource(default_role,NoLocalRoles)
+            self._setObject('nisAuthSource', o, None, None, 0)
+            o=getattr(self,'nisAuthSource')
+            if hasattr(o, 'postInitialisation'):
+                o.postInitialisation(REQUEST)
+            self.currentAuthSource=o
+            return ''
+        else:
+            return self.MessageDialog(self,REQUEST=REQUEST,
+                                    title  ='NIS Error', 
+                                    message='No users in passwd.byname',
+                                    action ='manage_main')
+    except nis.error:        
+         return self.MessageDialog(self,REQUEST=REQUEST,
+                                    title  ='NIS Error', 
+                                    message='NIS server unreachable',
+                                    action ='manage_main')
+
+
+manage_addnisAuthSourceForm=HTMLFile('manage_addnisAuthSourceForm', globals())
+manage_editnisAuthSourceForm=HTMLFile('manage_editnisAuthSourceForm', globals())
+
+class nisAuthSource(Folder):
+    """ Authenticate Users against NIS 
+    
+    This folder has 2 modes:
+    1. Authenticates only those users for which you add a local role
+    2. Authenticates without local roles.  
+    
+    Since #1 uses local roles, it should play nice with Prop sources and memberships, 
+    where #2 will not. """
+
+    meta_type='Authentication Source'
+    title='NIS Authentication'
+    icon ='misc_/exUserFolder/exUserFolderPlugin.gif'
+    manage_editForm=manage_editnisAuthSourceForm
+        
+    def __init__(self,default_role,NoLocalRoles):
+        self.id='nisAuthSource'
+        self.default_role=default_role
+        self.NoLocalRoles=NoLocalRoles
+        self.data=PersistentMapping()
+        self.manage_addUserForm=DTMLFile('manage_addNISUserForm',globals())
+        self.manage_editUserForm=DTMLFile('manage_editNISUserForm',globals()) #not used.  No way to plug it into exUserFolder.py
+    
+    def manage_editAuthSource(self,REQUEST):
+        """ """
+        self.default_role=REQUEST['nisauth_default_role']
+        self.NoLocalRoles=REQUEST.has_key('nisauth_NoLocalRoles')
+
+    # Create a User to store local roles
+    def createUser(self, username, password, roles):
+        import pdb
+        pdb.set_trace()
+        """ Add A Username """
+        if self.NoLocalRoles:
+            return self.MessageDialog(self,REQUEST=REQUEST,
+                        title  ='Create Error', 
+                        message='Cannot create user.  No local roles allowed',
+                        action ='manage_main')
+        else:
+            if self._listOneNISUser(username) and (not self.data.has_key(username)):
+                if type(roles) != type([]):
+                    if roles:
+                        roles=list(roles)
+                    else:
+                        roles=[self.default_role]
+                self.data[username]=PersistentMapping()
+                self.data[username].update({'username': username,
+                                            'roles': roles})
+            else:
+                return self.MessageDialog(self,REQUEST=REQUEST,
+                            title  ='Create Error', 
+                            message='Cannot create user.  Username not in NIS',
+                            action ='manage_main')
+
+    # Update a user's roles
+    # Passwords are managed via the users NIS unix accounts, not here
+    def updateUser(self, username, password, roles):
+        if self.NoLocalRoles:
+            return self.MessageDialog(self,REQUEST=REQUEST,
+                        title  ='Create Error', 
+                        message='Cannot create user.  No local roles allowed',
+                        action ='manage_main')
+        else:
+            self.data[username].update({'roles':roles})
+
+    # Encrypt a password
+    def cryptPassword_old(self, username, password):
+        NISuser=self._listOneNISUser(username)
+        if self.NoLocalRoles:
+            user=NISuser
+        else:
+            user=self.listOneUser(username)
+        salt = NISuser['password'][:2]
+        secret = crypt(password, salt)
+        return secret
+
+    # Delete a set of local users
+    def deleteUsers(self, userids):
+        if self.NoLocalRoles:
+            return self.MessageDialog(self,REQUEST=REQUEST,
+                        title  ='Create Error', 
+                        message='Cannot create user.  No local roles allowed',
+                        action ='manage_main')
+        for name in userids:
+            del self.data[name]
+
+    # Return a list of usernames
+    def listUserNames(self,listNIS=None):
+        if self.NoLocalRoles or listNIS:
+            usernames=self._listNISUserNames()
+        else:
+            usernames=self.data.keys()
+            usernames.sort()
+        return usernames
+        
+    # Return one user matching the username
+    # Should be a dictionary;
+    # {'username':username, 'password':cryptedPassword, 'roles':list_of_roles}
+    def listOneUser(self, username):
+        users = []
+        udata={}
+        NISuser=self._listOneNISUser(username)
+        if NISuser and len(NISuser)>0:
+            if self.NoLocalRoles:
+                udata=NISuser
+            else:
+                udata['username'] = username
+                udata['password']=NISuser['password']
+                udata['roles']=self.data[username]['roles']
+            if udata is not None:
+                users.append(udata)
+        return users
+
+    # Return a list of user dictionaries the same as listOneUser
+    def listUsers(self):
+        if self.NoLocalRoles:
+            users=self._listNISUsers()
+        else:
+            NISusers=self._listNISUsers()
+            NISusers_dict={}
+            for user in NISusers:
+                NISusers_dict[ user['username'] ]=user
+            users=self.data.values()
+            for num in range(0,len(users)):
+                username=users[num]['username']
+                users[num]['password']=NISusers_dict[username]['password']
+        return users
+
+    def _listNISUserNames(self):
+        nis_users=nis.cat('passwd.byname')
+        usernames=nis_users.keys()
+        usernames.sort()
+        return usernames
+
+    def _listOneNISUser(self,username):
+        roles=[self.default_role]
+        try:
+            nis_user=nis.match(username,'passwd.byname')
+            username,passwd,other=string.split(nis_user,':',2)
+            data={'username':username,
+                  'password':passwd,
+                  'roles':roles}
+        except nis.error:
+            data=None
+        return data
+        
+    def _listNISUsers(self):
+        users=[]
+        roles=[self.default_role]
+        try:
+            nis_users=nis.cat('passwd.byname')
+            userlist=nis_users.keys()
+            userlist.sort()
+            for user in userlist:
+                username,passwd,other=string.split(nis_users[user],':',2)
+                data={'username':username,
+                      'password':passwd,
+                      'roles':roles}
+                users.append(data)
+        except nis.error:
+            data=None
+        
+        return users
+    
+    
+    #
+    # You can define this to go off and do the authentication instead of
+    # using the basic one inside the User Object
+    #
+    remoteAuthMethod=None
+
+    def postInitialisation(self, REQUEST):
+        pass
+        
+
+nisAuthReg=PluginRegister('nisAuthSource', 'NIS Authentication Source',
+                         nisAuthSource, manage_addnisAuthSourceForm,
+                         manage_addnisAuthSource,
+                         manage_editnisAuthSourceForm)
+exUserFolder.authSources['nisAuthSource']=nisAuthReg

Added: trunk/ZopeProducts/exUserFolder/AuthSources/pgAuthSource/.cvsignore
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/pgAuthSource/.cvsignore	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/pgAuthSource/.cvsignore	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,4 @@
+*.pyc
+*.pyo
+*~
+.*.swp

Added: trunk/ZopeProducts/exUserFolder/AuthSources/pgAuthSource/__init__.py
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/pgAuthSource/__init__.py	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/pgAuthSource/__init__.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,2 @@
+# $Id: __init__.py,v 1.1 2004/11/10 14:15:36 akm Exp $
+import pgAuthSource

Added: trunk/ZopeProducts/exUserFolder/AuthSources/pgAuthSource/manage_addpgAuthSourceForm.dtml
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/pgAuthSource/manage_addpgAuthSourceForm.dtml	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/pgAuthSource/manage_addpgAuthSourceForm.dtml	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,40 @@
+<dtml-var "DialogHeader(_.None,_,DialogTitle='Add Postgresql Authentication Source')">
+<FORM ACTION="&dtml-URL;" METHOD="POST">
+<dtml-in "REQUEST.form.keys()">
+<input type="HIDDEN" name="<dtml-var sequence-item>" value="<dtml-var "REQUEST[_.getitem('sequence-item',0)]">">
+</dtml-in>
+<input type="HIDDEN" name="doProp" value="1">
+<TABLE CELLSPACING="2">
+<tr><th><dtml-babel src="'en'">Database Connection</dtml-babel>:</th>
+    <td>
+        <select name="pgauth_connection">
+        <dtml-in "SQLConnectionIDs()">
+            <option value="<dtml-var sequence-item>">
+            <dtml-var sequence-key></option>
+        </dtml-in>
+        </select>
+     </td>
+</tr>
+<tr>
+	<th><dtml-babel src="'en'">Table Name</dtml-babel>:</th>
+	<td><input type="text" name="pgauth_table" value="passwd"></td>
+</tr>
+<tr>
+	<th><dtml-babel src="'en'">Username Column</dtml-babel>:</th>
+	<td><input type="text" name="pgauth_usernameColumn" value="username"></td>
+</tr>
+<tr>
+	<th><dtml-babel src="'en'">Password Column</dtml-babel>:</th>
+	<td><input type="text" name="pgauth_passwordColumn" value="password"></td>
+</tr>
+<tr>
+	<th><dtml-babel src="'en'">Roles Column</dtml-babel>:</th>
+	<td><input type="text" name="pgauth_rolesColumn" value="roles"></td>
+</tr>
+<TR>
+<TD></TD>
+<TD><BR><INPUT TYPE="SUBMIT" VALUE="<dtml-babel src="'en'">Add</dtml-babel>"></TD>
+</TR>
+</TABLE>
+</FORM>
+<dtml-var DialogFooter>

Added: trunk/ZopeProducts/exUserFolder/AuthSources/pgAuthSource/manage_editpgAuthSourceForm.dtml
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/pgAuthSource/manage_editpgAuthSourceForm.dtml	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/pgAuthSource/manage_editpgAuthSourceForm.dtml	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,37 @@
+<dtml-var "DialogHeader(_.None,_,DialogTitle='Postgresql Authentication Source',dialog_width='100%')">
+<dtml-var manage_tabs>
+<FORM ACTION="manage_editAuthSource" METHOD="POST">
+<TABLE CELLSPACING="2">
+<tr><th><dtml-babel src="'en'">Database Connection</dtml-babel>:</th>
+    <td>
+        <select name="pgauth_connection">
+        <dtml-in "SQLConnectionIDs()">
+			<option value="<dtml-var sequence-item>"<dtml-if "currentAuthSource.connection==_['sequence-item']"> SELECTED</dtml-if>>
+            <dtml-var sequence-key></option>
+        </dtml-in>
+        </select>
+     </td>
+</tr>
+<tr>
+	<th><dtml-babel src="'en'">Table Name</dtml-babel>:</th>
+	<td><input type="text" name="pgauth_table" value="<dtml-var "currentAuthSource.table">"></td>
+</tr>
+<tr>
+	<th><dtml-babel src="'en'">Username Column</dtml-babel>:</th>
+	<td><input type="text" name="pgauth_usernameColumn" value="<dtml-var "currentAuthSource.usernameColumn">"></td>
+</tr>
+<tr>
+	<th><dtml-babel src="'en'">Password Column</dtml-babel>:</th>
+	<td><input type="text" name="pgauth_passwordColumn" value="<dtml-var "currentAuthSource.passwordColumn">"></td>
+</tr>
+<tr>
+	<th><dtml-babel src="'en'">Roles Column</dtml-babel>:</th>
+	<td><input type="text" name="pgauth_rolesColumn" value="<dtml-var "currentAuthSource.rolesColumn">"></td>
+</tr>
+<TR>
+<TD></TD>
+<TD><BR><INPUT TYPE="SUBMIT" VALUE=" <dtml-babel src="'en'">Edit</dtml-babel> "></TD>
+</TR>
+</TABLE>
+</FORM>
+<dtml-var DialogFooter>

Added: trunk/ZopeProducts/exUserFolder/AuthSources/pgAuthSource/pgAuthSource.py
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/pgAuthSource/pgAuthSource.py	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/pgAuthSource/pgAuthSource.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,325 @@
+#
+# Extensible User Folder
+# 
+# Postgres Authentication Source for exUserFolder
+#
+# (C) Copyright 2000,2001 The Internet (Aust) Pty Ltd
+# ACN: 082 081 472  ABN: 83 082 081 472
+# All Rights Reserved
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# Author: Andrew Milton <akm at theinternet.com.au>
+# $Id: pgAuthSource.py,v 1.1 2004/11/10 14:15:36 akm Exp $
+
+#
+# This class only authenticates users, it stores no properties.
+#
+
+import string,Acquisition
+
+from Globals import HTMLFile, MessageDialog, INSTANCE_HOME
+
+from OFS.Folder import Folder
+
+from Products.ZSQLMethods.SQL import SQL
+
+from Products.exUserFolder.exUserFolder import exUserFolder
+from Products.exUserFolder.Plugins import PluginRegister
+
+try:
+	from crypt import crypt
+except:
+	from Products.exUserFolder.fcrypt.fcrypt import crypt
+
+
+def manage_addpgAuthSource(self, REQUEST):
+	""" Add a Postgres Auth Source """
+
+	connection=REQUEST['pgauth_connection']
+	table=REQUEST['pgauth_table']
+	usernameColumn=REQUEST['pgauth_usernameColumn']
+	passwordColumn=REQUEST['pgauth_passwordColumn']
+	rolesColumn=REQUEST['pgauth_rolesColumn']
+	o = pgAuthSource(connection, table, usernameColumn, passwordColumn,
+					 rolesColumn)
+	self._setObject('pgAuthSource', o, None, None, 0)
+	o=getattr(self,'pgAuthSource')
+	if hasattr(o, 'postInitialisation'):
+		o.postInitialisation(REQUEST)
+	
+	self.currentAuthSource=o
+	return ''
+
+manage_addpgAuthSourceForm=HTMLFile('manage_addpgAuthSourceForm', globals())
+manage_editpgAuthSourceForm=HTMLFile('manage_editpgAuthSourceForm', globals())
+
+class pgAuthSource(Folder):
+	""" Authenticate Users against a Postgres Database """
+
+	meta_type='Authentication Source'
+	title='Postgresql Authentication'
+
+	icon ='misc_/exUserFolder/exUserFolderPlugin.gif'
+
+	manage_tabs=Acquisition.Acquired
+
+	manage_editForm=manage_editpgAuthSourceForm
+
+	#
+	# You can define this to go off and do the authentication instead of
+	# using the basic one inside the User Object
+	#
+	remoteAuthMethod=None
+	
+	def __init__(self, connection, table, usernameColumn, passwordColumn,
+				 rolesColumn):
+		self.id='pgAuthSource'
+		self.connection=connection
+		self.table=table
+		self.usernameColumn=usernameColumn
+		self.passwordColumn=passwordColumn
+		self.rolesColumn=rolesColumn
+		self.addSQLQueries()
+
+	def manage_editAuthSource(self, REQUEST):
+		""" Edit a Postgres Auth Source """
+
+		self.connection=REQUEST['pgauth_connection']
+		self.table=REQUEST['pgauth_table']
+		self.usernameColumn=REQUEST['pgauth_usernameColumn']
+		self.passwordColumn=REQUEST['pgauth_passwordColumn']
+		self.rolesColumn=REQUEST['pgauth_rolesColumn']
+		self.delSQLQueries()
+		self.addSQLQueries() # Re-add queries with new parameters
+
+	def createUser(self, username, password, roles):
+		""" Add A Username """
+
+		if type(roles) != type([]):
+			if roles:
+				roles=list(roles)
+			else:
+				roles=[]
+
+		rolestring=''
+		for role in roles:
+			rolestring=rolestring+role+','
+
+		rolestring=rolestring[:-1]
+		secret=self.cryptPassword(username, password)
+		self.sqlInsertUser(username=username,
+						   password=secret,
+						   roles=rolestring)
+		self._v_lastUser={}
+
+	def updateUser(self, username, password, roles):
+		if type(roles) != type([]):
+			if roles:
+				roles=list(roles)
+			else:
+				roles=[]
+		
+		rolestring=''
+		for role in roles:
+			print role
+			rolestring=rolestring+role+','
+
+		rolestring=rolestring[:-1]
+
+		# Don't change passwords if it's null
+		if password:
+			secret=self.cryptPassword(username, password)
+			self.sqlUpdateUserPassword(username=username,
+									   password=secret)
+			
+		self.sqlUpdateUser(username=username,
+						   roles=rolestring)
+		self._v_lastUser={}
+		
+	def delSQLQueries(self):
+		sqllist=self.objectIds('Z SQL Method')
+		self.manage_delObjects(ids=sqllist)
+
+	def addSQLQueries(self):
+		sqlListUsers=SQL(
+			'sqlListUsers',
+			'List All Users',
+			self.connection,
+			'table=%s'%(self.table),
+			_sqlListUsers)
+
+		self._setObject('sqlListUsers', sqlListUsers)
+
+		sqlListOneUser=SQL(
+			'sqlListOneUser',
+			'List ONE User',
+			self.connection,
+			'table=%s usernameColumn=%s username:string'%(
+			self.table, self.usernameColumn),
+			_sqlListOneUser)
+
+		self._setObject('sqlListOneUser', sqlListOneUser)
+
+		sqlDeleteOneUser=SQL(
+			'sqlDeleteOneUser',
+			'Delete One User',
+			self.connection,
+			'table=%s usernameColumn=%s username:string'%(
+			self.table,self.usernameColumn),
+			_sqlDeleteOneUser)
+
+		self._setObject('sqlDeleteOneUser', sqlDeleteOneUser)
+
+		sqlInsertUser=SQL(
+			'sqlInsertUser',
+			'Insert One User',
+			self.connection,
+			'table=%s usernameColumn=%s passwordColumn=%s rolesColumn=%s username:string password:string roles:string'%(
+			self.table, self.usernameColumn, self.passwordColumn, self.rolesColumn),
+			_sqlInsertUser)
+
+		self._setObject('sqlInsertUser', sqlInsertUser)
+
+		sqlUpdateUser=SQL(
+			'sqlUpdateUser',
+			'Update User',
+			self.connection,
+			'table=%s rolesColumn=%s username:string roles:string'%(self.table, self.rolesColumn),
+			_sqlUpdateUser)
+
+		self._setObject('sqlUpdateUser', sqlUpdateUser)
+
+		sqlUpdateUserPassword=SQL(
+			'sqlUpdateUserPassword',
+			'Update just the password',
+			self.connection,
+			'table=%s usernameColumn=%s passwordColumn=%s username:string password:string'%(self.table, self.usernameColumn, self.passwordColumn),
+			_sqlUpdateUserPassword)
+
+		self._setObject('sqlUpdateUserPassword', sqlUpdateUserPassword)
+
+	def cryptPassword_old(self, username, password):
+			salt =username[:2]
+			secret = crypt(password, salt)
+			return secret
+
+	def deleteUsers(self, userids):
+		for uid in userids:
+			self.sqlDeleteOneUser(username=uid)
+		self._v_lastUser={}			
+
+	def listUserNames(self):
+		"""Returns a real list of user names """
+		users = []
+		result=self.sqlListUsers()
+		for n in result:
+			username=sqlattr(n,self.usernameColumn)
+			users.append(username)
+		return users
+		
+	def listUsers(self):
+		"""Returns a list of user names or [] if no users exist"""		
+		users = []
+		result=self.sqlListUsers()
+		for n in result:
+			roles=[]
+			username=sqlattr(n,self.usernameColumn)
+			if sqlattr(n, self.rolesColumn):
+				roles=string.split(sqlattr(n,self.rolesColumn),',')
+			password=sqlattr(n, self.passwordColumn)
+			N={'username':username, 'password':password, 'roles':roles}
+			users.append(N)
+		return users
+
+	def listOneUser(self,username):
+		if getattr(self, '_v_lastUser', {}):
+			if self._v_lastUser['username']==username:
+				return self._v_lastUser['users']		
+		users = []
+		result=self.sqlListOneUser(username=username)
+		for n in result:
+			roles=[]
+			username=sqlattr(n,self.usernameColumn)
+			password=sqlattr(n,self.passwordColumn)
+			if sqlattr(n, self.rolesColumn):
+				roles=string.split(sqlattr(n,self.rolesColumn),',')  #Andreas
+			N={'username':username, 'password':password, 'roles':roles}
+			users.append(N)
+		self._v_lastUser={}
+		self._v_lastUser['username']=username
+		self._v_lastUser['users']=users			
+		return users
+	
+	def postInitialisation(self, REQUEST):
+		self._v_lastUser={}
+
+pgAuthReg=PluginRegister('pgAuthSource', 'Postgresql Authentication Source',
+						 pgAuthSource, manage_addpgAuthSourceForm,
+						 manage_addpgAuthSource,
+						 manage_editpgAuthSourceForm)
+exUserFolder.authSources['pgAuthSource']=pgAuthReg
+
+from string import upper, lower
+import Missing
+mt=type(Missing.Value)
+
+def typeconv(val):
+    if type(val)==mt:
+        return ''
+    return val
+
+def sqlattr(ob, attr):
+    name=attr
+    if hasattr(ob, attr):
+        return typeconv(getattr(ob, attr))
+    attr=upper(attr)
+    if hasattr(ob, attr):
+        return typeconv(getattr(ob, attr))
+    attr=lower(attr)
+    if hasattr(ob, attr):
+        return typeconv(getattr(ob, attr))
+    raise NameError, name
+
+
+
+_sqlListUsers="""
+SELECT * FROM <dtml-var table>
+"""
+
+_sqlListOneUser="""
+SELECT * FROM <dtml-var table>
+where <dtml-var usernameColumn>=<dtml-sqlvar username type=string>
+"""
+
+_sqlDeleteOneUser="""
+DELETE FROM <dtml-var table>
+where <dtml-var usernameColumn>=<dtml-sqlvar username type=string>
+"""
+
+_sqlInsertUser="""
+INSERT INTO <dtml-var table> (<dtml-var usernameColumn>, <dtml-var passwordColumn>, <dtml-var rolesColumn>)
+VALUES (<dtml-sqlvar username type=string>,
+        <dtml-sqlvar password type=string>,
+		<dtml-sqlvar roles type=string>)
+"""
+
+_sqlUpdateUserPassword="""
+UPDATE <dtml-var table> set <dtml-var passwordColumn>=<dtml-sqlvar password type=string>
+WHERE <dtml-var usernameColumn>=<dtml-sqlvar username type=string>
+"""
+
+_sqlUpdateUser="""
+UPDATE <dtml-var table> set <dtml-var rolesColumn>=<dtml-sqlvar roles type=string>
+WHERE <dtml-var usernameColumn>=<dtml-sqlvar username type=string>
+"""

Added: trunk/ZopeProducts/exUserFolder/AuthSources/pgAuthSourceAlt/.cvsignore
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/pgAuthSourceAlt/.cvsignore	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/pgAuthSourceAlt/.cvsignore	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,4 @@
+*.pyc
+*.pyo
+*~
+.*.swp

Added: trunk/ZopeProducts/exUserFolder/AuthSources/pgAuthSourceAlt/README.pgAuthSource-Alternate
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/pgAuthSourceAlt/README.pgAuthSource-Alternate	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/pgAuthSourceAlt/README.pgAuthSource-Alternate	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,20 @@
+This alternate pgAuthSource was developed to allow Zope and jakarta-tomcat to 
+share common PostGreSQL auth tables. It's really just a mod of the original 
+pgAuthSource, with changes to the original kept to a minimum. This should help
+when it comes to cross porting improvements / maintenence changes between the 
+two versions.
+
+The only thing that's new is the table schema. This auth source uses:
+A user table 
+	Username, password
+
+A role table:
+	rolename
+
+and a associative userrole table for relating the two:
+	username, rolename
+
+ps. Use the Source, Luke!
+If you dig a little you will find a couple of different ways of crypting 
+passwords commented out (plain and MD5).
+

Added: trunk/ZopeProducts/exUserFolder/AuthSources/pgAuthSourceAlt/__init__.py
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/pgAuthSourceAlt/__init__.py	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/pgAuthSourceAlt/__init__.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,2 @@
+# $Id: __init__.py,v 1.1 2004/11/10 14:15:36 akm Exp $
+import pgAuthSource

Added: trunk/ZopeProducts/exUserFolder/AuthSources/pgAuthSourceAlt/manage_addpgAuthSourceForm.dtml
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/pgAuthSourceAlt/manage_addpgAuthSourceForm.dtml	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/pgAuthSourceAlt/manage_addpgAuthSourceForm.dtml	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,44 @@
+<dtml-var "DialogHeader(_.None,_,DialogTitle='Add Postgresql Authentication Source')">
+<FORM ACTION="&dtml-URL;" METHOD="POST">
+<dtml-in "REQUEST.form.keys()">
+<input type="HIDDEN" name="<dtml-var sequence-item>" value="<dtml-var "REQUEST[_.getitem('sequence-item',0)]">">
+</dtml-in>
+<input type="HIDDEN" name="doProp" value="1">
+<TABLE CELLSPACING="2">
+<tr><th>Database Connection:</th>
+    <td>
+        <select name="pgauth_connection">
+        <dtml-in "SQLConnectionIDs()">
+            <option value="<dtml-var sequence-item>">
+            <dtml-var sequence-key></option>
+        </dtml-in>
+        </select>
+     </td>
+</tr>
+<tr>
+	<th>User Table Name:</th>
+	<td><input type="text" name="pgauth_userTable" value="security_user"></td>
+</tr>
+<tr>
+	<th>Username Column:</th>
+	<td><input type="text" name="pgauth_usernameColumn" value="username"></td>
+</tr>
+<tr>
+	<th>Password Column:</th>
+	<td><input type="text" name="pgauth_passwordColumn" value="password"></td>
+</tr>
+<tr>
+	<th>Roles Table:</th>
+	<td><input type="text" name="pgauth_roleTable" value="security_userrole"></td>
+</tr>
+<tr>
+	<th>Role Column:</th>
+	<td><input type="text" name="pgauth_roleColumn" value="rolename"></td>
+</tr>
+<TR>
+<TD></TD>
+<TD><BR><INPUT TYPE="SUBMIT" VALUE="Add"></TD>
+</TR>
+</TABLE>
+</FORM>
+<dtml-var DialogFooter>

Added: trunk/ZopeProducts/exUserFolder/AuthSources/pgAuthSourceAlt/manage_editpgAuthSourceForm.dtml
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/pgAuthSourceAlt/manage_editpgAuthSourceForm.dtml	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/pgAuthSourceAlt/manage_editpgAuthSourceForm.dtml	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,41 @@
+<dtml-var "DialogHeader(_.None,_,DialogTitle='Postgresql Authentication Source',dialog_width='100%')">
+<dtml-var manage_tabs>
+<FORM ACTION="manage_editAuthSource" METHOD="POST">
+<TABLE CELLSPACING="2">
+<tr><th>Database Connection:</th>
+    <td>
+        <select name="pgauth_connection">
+        <dtml-in "SQLConnectionIDs()">
+			<option value="<dtml-var sequence-item>"<dtml-if "currentAuthSource.connection==_['sequence-item']"> SELECTED</dtml-if>>
+            <dtml-var sequence-key></option>
+        </dtml-in>
+        </select>
+     </td>
+</tr>
+<tr>
+	<th>User Table Name:</th>
+	<td><input type="text" name="pgauth_userTable" value="<dtml-var "currentAuthSource.userTable">"></td>
+</tr>
+<tr>
+	<th>Username Column:</th>
+	<td><input type="text" name="pgauth_usernameColumn" value="<dtml-var "currentAuthSource.usernameColumn">"></td>
+</tr>
+<tr>
+	<th>Password Column:</th>
+	<td><input type="text" name="pgauth_passwordColumn" value="<dtml-var "currentAuthSource.passwordColumn">"></td>
+</tr>
+<tr>
+	<th>Role Table:</th>
+	<td><input type="text" name="pgauth_roleTable" value="<dtml-var "currentAuthSource.roleTable">"></td>
+</tr>
+<tr>
+	<th>Role Column:</th>
+	<td><input type="text" name="pgauth_roleColumn" value="<dtml-var "currentAuthSource.roleColumn">"></td>
+</tr>
+<TR>
+<TD></TD>
+<TD><BR><INPUT TYPE="SUBMIT" VALUE=" Edit "></TD>
+</TR>
+</TABLE>
+</FORM>
+<dtml-var DialogFooter>

Added: trunk/ZopeProducts/exUserFolder/AuthSources/pgAuthSourceAlt/pgAuthSource-Alternate.sql
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/pgAuthSourceAlt/pgAuthSource-Alternate.sql	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/pgAuthSourceAlt/pgAuthSource-Alternate.sql	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,24 @@
+CREATE TABLE "security_user" (
+        "username" VARCHAR(64) PRIMARY KEY NOT NULL,
+        "password" VARCHAR(64) NOT NULL,
+);
+
+
+CREATE TABLE "security_role" (
+        "rolename" VARCHAR(64) PRIMARY KEY NOT NULL
+);
+
+
+CREATE TABLE "security_userrole" (
+        "username" VARCHAR(64) NOT NULL,
+        "rolename" VARCHAR(64) NOT NULL,
+        CONSTRAINT "userrole_pkey" PRIMARY KEY ("username", "rolename"),
+        CONSTRAINT "username_fkey" FOREIGN KEY ("username")
+                REFERENCES "security_user" ("username"),
+        CONSTRAINT "rolename_fkey" FOREIGN KEY ("rolename")
+                REFERENCES "security_role" ("rolename")
+);
+
+
+CREATE INDEX "user_username_index" on "security_user" ("username");
+CREATE INDEX "userrole_username_index" on "security_userrole" ("username");

Added: trunk/ZopeProducts/exUserFolder/AuthSources/pgAuthSourceAlt/pgAuthSource.py
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/pgAuthSourceAlt/pgAuthSource.py	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/pgAuthSourceAlt/pgAuthSource.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,373 @@
+#
+# Extensible User Folder
+# 
+# Postgres Authentication Source for exUserFolder
+#
+# (C) Copyright 2000,2001 The Internet (Aust) Pty Ltd
+# ACN: 082 081 472  ABN: 83 082 081 472
+# All Rights Reserved
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# Author: Andrew Milton <akm at theinternet.com.au>
+# $Id: pgAuthSource.py,v 1.1 2004/11/10 14:15:36 akm Exp $
+#
+# This class only authenticates users, it stores no properties.
+#
+
+import string,Acquisition,md5
+
+from Globals import HTMLFile, MessageDialog, INSTANCE_HOME
+
+from OFS.Folder import Folder
+
+from Products.ZSQLMethods.SQL import SQL
+
+from Products.exUserFolder.exUserFolder import exUserFolder
+from Products.exUserFolder.Plugins import PluginRegister
+
+try:
+	from crypt import crypt
+except:
+	from Products.exUserFolder.fcrypt.fcrypt import crypt
+
+
+def manage_addpgAuthSource(self, REQUEST):
+	""" Add a Postgres Auth Source """
+
+	connection=REQUEST['pgauth_connection']
+	userTable=REQUEST['pgauth_userTable']
+	usernameColumn=REQUEST['pgauth_usernameColumn']
+	passwordColumn=REQUEST['pgauth_passwordColumn']
+	roleTable=REQUEST['pgauth_roleTable']
+	roleColumn=REQUEST['pgauth_roleColumn']
+	o = pgAuthSource(connection, userTable, usernameColumn, passwordColumn,
+					 roleTable, roleColumn)
+	self._setObject('pgAuthSource', o, None, None, 0)
+	o=getattr(self,'pgAuthSource')
+	if hasattr(o, 'postInitialisation'):
+		o.postInitialisation(REQUEST)
+	
+	self.currentAuthSource=o
+	return ''
+
+manage_addpgAuthSourceForm=HTMLFile('manage_addpgAuthSourceForm', globals())
+manage_editpgAuthSourceForm=HTMLFile('manage_editpgAuthSourceForm', globals())
+
+class pgAuthSource(Folder):
+	""" Authenticate Users against a Postgres Database """
+
+	meta_type='Authentication Source'
+	title='Advanced Postgresql Authentication'
+
+	icon ='misc_/exUserFolder/exUserFolderPlugin.gif'
+
+	manage_tabs=Acquisition.Acquired
+
+	manage_editForm=manage_editpgAuthSourceForm
+
+	#
+	# You can define this to go off and do the authentication instead of
+	# using the basic one inside the User Object
+	#
+	remoteAuthMethod=None
+	
+	def __init__(self, connection, userTable, usernameColumn, passwordColumn,
+				 roleTable, roleColumn):
+		self.id='pgAuthSource'
+		self.connection=connection
+		self.userTable=userTable
+		self.usernameColumn=usernameColumn
+		self.passwordColumn=passwordColumn
+		self.roleTable=roleTable
+		self.roleColumn=roleColumn
+		self.addSQLQueries()
+
+	def manage_editAuthSource(self, REQUEST):
+		""" Edit a Postgres Auth Source """
+
+		self.connection=REQUEST['pgauth_connection']
+		self.userTable=REQUEST['pgauth_userTable']
+		self.usernameColumn=REQUEST['pgauth_usernameColumn']
+		self.passwordColumn=REQUEST['pgauth_passwordColumn']
+		self.roleTable=REQUEST['pgauth_roleTable']
+		self.roleColumn=REQUEST['pgauth_roleColumn']
+		self.delSQLQueries()
+		self.addSQLQueries() # Re-add queries with new parameters
+
+	def createUser(self, username, password, roles):
+		""" Add A Username """
+
+		if type(roles) != type([]):
+			if roles:
+				roles=list(roles)
+			else:
+				roles=[]
+
+		secret=self.cryptPassword(username, password)
+		self.sqlInsertUser(username=username, password=secret)
+
+		for n in roles:
+			self.insertUserRole(username, n)
+
+
+	def insertUserRole(self, username, rolename):
+		""" Add User Role """
+
+		self.sqlInsertUserRole(username=username, rolename=rolename)
+
+	def deleteUserRoles(self, username):
+		""" Delete User Roles """
+
+		self.sqlDeleteUserRoles(username=username)
+
+	def updateUser(self, username, password, roles):
+		if type(roles) != type([]):
+			if roles:
+				roles=list(roles)
+			else:
+				roles=[]
+		
+		# Don't change passwords if it's null
+		if password:
+			secret=self.cryptPassword(username, password)
+			self.sqlUpdateUserPassword(username=username, password=secret)
+
+		self.deleteUserRoles(username)
+
+		for n in roles:
+			self.insertUserRole(username, n)
+			
+	def delSQLQueries(self):
+		sqllist=self.objectIds('Z SQL Method')
+		self.manage_delObjects(ids=sqllist)
+
+	def addSQLQueries(self):
+		sqlListUsers=SQL(
+			'sqlListUsers',
+			'List All Users',
+			self.connection,
+			'userTable=%s'%(self.userTable),
+			_sqlListUsers)
+
+		self._setObject('sqlListUsers', sqlListUsers)
+	
+		sqlListOneUser=SQL(
+			'sqlListOneUser',
+			'List ONE User',
+			self.connection,
+			'userTable=%s usernameColumn=%s username:string'%(
+			self.userTable, self.usernameColumn),
+			_sqlListOneUser)
+
+		self._setObject('sqlListOneUser', sqlListOneUser)
+
+		sqlListUserRoles=SQL(
+			'sqlListUserRoles',
+			'List User Roles',
+			self.connection,
+			'roleTable=%s usernameColumn=%s username:string'%(
+			self.roleTable, self.usernameColumn),
+			_sqlListUserRoles)
+
+		self._setObject('sqlListUserRoles', sqlListUserRoles)
+
+		sqlDeleteOneUser=SQL(
+			'sqlDeleteOneUser',
+			'Delete One User',
+			self.connection,
+			'userTable=%s usernameColumn=%s username:string'%(
+			self.userTable,self.usernameColumn),
+			_sqlDeleteOneUser)
+
+		self._setObject('sqlDeleteOneUser', sqlDeleteOneUser)
+
+		sqlDeleteUserRoles=SQL(
+			'sqlDeleteUserRoles',
+			'Delete User Roles',
+			self.connection,
+			'roleTable=%s usernameColumn=%s username:string'%(
+			self.roleTable,self.usernameColumn),
+			_sqlDeleteUserRoles)
+
+		self._setObject('sqlDeleteUserRoles', sqlDeleteUserRoles)
+
+		sqlInsertUser=SQL(
+			'sqlInsertUser',
+			'Insert One User',
+			self.connection,
+			'userTable=%s usernameColumn=%s passwordColumn=%s username:string password:string'%(
+			self.userTable, self.usernameColumn, self.passwordColumn),
+			_sqlInsertUser)
+
+		self._setObject('sqlInsertUser', sqlInsertUser)
+
+		sqlInsertUserRole=SQL(
+			'sqlInsertUserRole',
+			'Insert User Role',
+			self.connection,
+			'roleTable=%s usernameColumn=%s roleColumn=%s username:string rolename:string'%(
+			self.roleTable, self.usernameColumn, self.roleColumn),
+			_sqlInsertUserRole)
+
+		self._setObject('sqlInsertUserRole', sqlInsertUserRole)
+
+		sqlUpdateUserPassword=SQL(
+			'sqlUpdateUserPassword',
+			'Update just the password',
+			self.connection,
+			'userTable=%s usernameColumn=%s passwordColumn=%s username:string password:string'%(self.userTable, self.usernameColumn, self.passwordColumn),
+			_sqlUpdateUserPassword)
+
+		self._setObject('sqlUpdateUserPassword', sqlUpdateUserPassword)
+
+# Original cryptPassword function
+
+	def cryptPassword_old(self, username, password):
+		salt =username[:2]
+		secret = crypt(password, salt)
+		return secret
+
+# Alternate cryptPassword function, returns md5 hash of the password
+#	def cryptPassword_old(self, username, password):
+#		passhash = md5.new(password)
+#		secret = passhash.hexdigest()
+#		return secret
+
+# Alternate cryptPassword function, returns plain text of the password.
+#	def cryptPassword_old(self, username, password):
+#		return password
+
+	def deleteUsers(self, userids):
+		for uid in userids:
+			self.sqlDeleteUserRoles(username=uid)
+			self.sqlDeleteOneUser(username=uid)
+
+	def listUserNames(self):
+		"""Returns a real list of user names """
+		users = []
+		result=self.sqlListUsers()
+		for n in result:
+			username=sqlattr(n,self.usernameColumn)
+			users.append(username)
+		return users
+
+	def listUsers(self):
+		"""Returns a list of user names or [] if no users exist"""
+		users = []
+		result=self.sqlListUsers()
+		for n in result:
+			username=sqlattr(n,self.usernameColumn)
+			N={'username':username}
+			users.append(N)
+		return users
+
+	def listOneUser(self,username):
+		users = []
+		result=self.sqlListOneUser(username=username)
+		for n in result:
+			username=sqlattr(n,self.usernameColumn)
+			password=sqlattr(n,self.passwordColumn)
+			roles=self.listUserRoles(username)
+			N={'username':username, 'password':password, 'roles':roles}
+			users.append(N)
+		return users
+
+	def listUserRoles(self,username):
+		roles = []
+		result = self.sqlListUserRoles(username=username)
+		for n in result:
+			role=sqlattr(n, self.roleColumn)
+			N=role
+			roles.append(N)
+		return roles
+
+	def getUsers(self):
+		"""Return a list of user objects or [] if no users exist"""
+		data=[]
+		try:    items=self.listusers()
+		except: return data
+		for people in items:
+			roles=string.split(people['roles'],',')
+			user=User(people['username'], roles, '')
+			data.append(user)
+		return data
+
+	def postInitialisation(self, REQUEST):
+		pass
+
+pgAuthReg=PluginRegister('pgAuthSourceAdv', 'Advanced Postgresql Authentication Source',
+						 pgAuthSource, manage_addpgAuthSourceForm,
+						 manage_addpgAuthSource,
+						 manage_editpgAuthSourceForm)
+exUserFolder.authSources['pgAuthSourceAdv']=pgAuthReg
+
+from string import upper, lower
+import Missing
+mt=type(Missing.Value)
+
+def typeconv(val):
+    if type(val)==mt:
+        return ''
+    return val
+
+def sqlattr(ob, attr):
+    name=attr
+    if hasattr(ob, attr):
+        return typeconv(getattr(ob, attr))
+    attr=upper(attr)
+    if hasattr(ob, attr):
+        return typeconv(getattr(ob, attr))
+    attr=lower(attr)
+    if hasattr(ob, attr):
+        return typeconv(getattr(ob, attr))
+    raise NameError, name
+
+_sqlListUsers="""
+SELECT * FROM <dtml-var userTable>
+"""
+
+_sqlListOneUser="""
+SELECT * FROM <dtml-var userTable>
+where <dtml-var usernameColumn>=<dtml-sqlvar username type=string>
+"""
+
+_sqlListUserRoles="""
+SELECT * FROM <dtml-var roleTable>
+where <dtml-var usernameColumn>=<dtml-sqlvar username type=string>
+"""
+
+_sqlDeleteOneUser="""
+DELETE FROM <dtml-var userTable>
+where <dtml-var usernameColumn>=<dtml-sqlvar username type=string>
+"""
+
+_sqlDeleteUserRoles="""
+DELETE FROM <dtml-var roleTable>
+where <dtml-var usernameColumn>=<dtml-sqlvar username type=string>
+"""
+
+_sqlInsertUser="""
+INSERT INTO <dtml-var userTable> (<dtml-var usernameColumn>, <dtml-var passwordColumn>)
+VALUES (<dtml-sqlvar username type=string>, <dtml-sqlvar password type=string>)
+"""
+
+_sqlInsertUserRole="""
+INSERT INTO <dtml-var roleTable> (<dtml-var usernameColumn>, <dtml-var roleColumn>)
+VALUES (<dtml-sqlvar username type=string>, <dtml-sqlvar rolename type=string>)
+"""
+
+_sqlUpdateUserPassword="""
+UPDATE <dtml-var userTable> set <dtml-var passwordColumn>=<dtml-sqlvar password type=string>
+WHERE <dtml-var usernameColumn>=<dtml-sqlvar username type=string>
+"""

Added: trunk/ZopeProducts/exUserFolder/AuthSources/radiusAuthSource/.cvsignore
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/radiusAuthSource/.cvsignore	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/radiusAuthSource/.cvsignore	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,4 @@
+*.pyc
+*.pyo
+*~
+.*.swp

Added: trunk/ZopeProducts/exUserFolder/AuthSources/radiusAuthSource/Makefile
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/radiusAuthSource/Makefile	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/radiusAuthSource/Makefile	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,11 @@
+DEST = /www/zope/zope2/lib/python/Products/ZRadius
+
+INSTOPTS = -g zope -o zope 
+
+all:
+	echo Stoopid
+
+install:
+	install -d ${INSTOPTS} -m 750 ${DEST}
+	install ${INSTOPTS} -m 640 *.py *.txt *.dtml *.gif ${DEST}
+

Added: trunk/ZopeProducts/exUserFolder/AuthSources/radiusAuthSource/README.txt
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/radiusAuthSource/README.txt	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/radiusAuthSource/README.txt	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,8 @@
+Radius
+
+    This product implements simple Radius authentication. If you need
+    to authenticate Zope users from a Radius server, this product will
+    plug into the GenericUserFolder product.
+
+    © Copywrite 1999 Stuart Bishop <zen at cs.rmit.edu.au>
+

Added: trunk/ZopeProducts/exUserFolder/AuthSources/radiusAuthSource/ZRadius.gif
===================================================================
(Binary files differ)


Property changes on: trunk/ZopeProducts/exUserFolder/AuthSources/radiusAuthSource/ZRadius.gif
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/ZopeProducts/exUserFolder/AuthSources/radiusAuthSource/ZRadius.py
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/radiusAuthSource/ZRadius.py	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/radiusAuthSource/ZRadius.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,125 @@
+#!/usr/bin/env python
+
+__doc__ = '''
+$Id: ZRadius.py,v 1.1 2004/11/10 14:15:36 akm Exp $
+
+Extremly basic RADIUS authentication. Bare minimum required to authenticate
+a user, yet remain RFC2138 compliant (I hope).
+
+(c) 1999 Stuart Bishop <zen at cs.rmit.edu.au>
+'''
+
+__version__ = '$Revision: 1.1 $'
+
+from Globals import HTMLFile,MessageDialog,Persistent
+import OFS.SimpleItem
+import Acquisition
+import AccessControl.Role
+
+from radius import Radius
+
+manage_addZRadiusForm = HTMLFile('manage_addZRadiusForm',globals())
+
+def manage_addZRadius(self,id,title,host,port,secret,retries,timeout,\
+	REQUEST=None):
+    'Create a new ZRadius instance'
+    self._setObject(id, ZRadius(id,title,host,port,secret,retries,timeout))
+    if REQUEST is not None:
+	return self.manage_main(self,REQUEST)
+
+class ZRadius(
+    OFS.SimpleItem.Item,
+    Persistent,
+    Acquisition.Implicit,
+    AccessControl.Role.RoleManager):
+    'A Radius Authenticator'
+
+    meta_type = 'ZRadius'
+
+    manage_options = (
+	    {'label':'Test',		'action':''},
+	    {'label':'Properties',	'action':'manage_main'},
+	    {'label':'Security',	'action':'manage_access'}
+	)
+
+    __ac_permissions__ = (
+	    ('ZRadius authenticate',
+		('authenticate', 'manage_test', 'index_html','__call__')),
+	    ('Manage properties',
+		('manage_main','host','port','retries','timeout',
+		 'manage_edit')),
+	)
+
+    _v_radius = None
+
+    def __init__(self,id,title,host,port,secret,retries,timeout):
+	self.id = id
+	self.title = title
+
+	self.manage_main = HTMLFile('manage_main',globals())
+	self.index_html = HTMLFile('index',globals())
+
+	self._host = host
+	self._port = port
+	self._secret = secret
+
+	self._retries = int(retries)
+	self._timeout = float(timeout)
+    
+    def host(self): return self._host
+    def port(self): return self._port
+    def retries(self): return self._retries
+    def timeout(self): return self._timeout
+
+    def manage_edit(self,title,REQUEST=None):
+	'''Handle output of manage_main - change ZRadius instance properties.
+	    If REQUEST.secret is None, old secret will be used.'''
+
+	self.title = title
+	self._host = REQUEST.host
+	self._port = int(REQUEST.port)
+	if hasattr(REQUEST,'secret') and len(REQUEST.secret) > 0: 
+	    # So we don't code it in form source
+	    self._secret = REQUEST.secret 
+	self._retries = int(REQUEST.retries)
+	self._timeout = float(REQUEST.timeout)
+
+	# Reset the Radius object so new values take effect. This is
+	# why we don't allow direct access to the attributes
+	self._v_radius = None
+
+	if REQUEST is not None:
+	    return self.MessageDialog(self,REQUEST=REQUEST,
+		title = 'Edited',
+		message = "Properties for %s changed." % self.id,
+		action = './manage_main')
+
+    def manage_test(self,REQUEST):
+	'Handle submission from index_html'
+	username = REQUEST.username
+	password = REQUEST.password
+	if self.authenticate(username,password):
+	    return self.MessageDialog(self,REQUEST=REQUEST,
+		title = 'Succeded',
+		message = "Successfully authenticated '%s'" % username,
+		action = './index_html')
+	else:
+	    return self.MessageDialog(self,REQUEST=REQUEST,
+		title = 'Failed',
+		message = "Failed to authenticate '%s'" % username,
+		action = './index_html')
+
+    def __call__(self,username,password):
+	'Call authenticate'
+	return self.authenticate(username,password)
+
+    def authenticate(self,username,password):
+	'Authenticate a username/password combination against the Radius server'
+
+	if self._v_radius is None:
+	    self._v_radius = Radius(self._secret,self._host,self._port)
+	    self._v_radius.retries = int(self._retries)
+	    self._v_radius.timeout = self._timeout
+
+	return self._v_radius.authenticate(username,password)
+

Added: trunk/ZopeProducts/exUserFolder/AuthSources/radiusAuthSource/__init__.py
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/radiusAuthSource/__init__.py	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/radiusAuthSource/__init__.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,4 @@
+__doc__ = '''$Id: __init__.py,v 1.1 2004/11/10 14:15:36 akm Exp $'''
+__version__ = '$Revision: 1.1 $'
+
+import radiusAuthSource

Added: trunk/ZopeProducts/exUserFolder/AuthSources/radiusAuthSource/index.dtml
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/radiusAuthSource/index.dtml	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/radiusAuthSource/index.dtml	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,22 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
+<HTML lang="en">
+<HEAD>
+<TITLE>Test Radius Authentication</TITLE>
+</HEAD>
+<BODY BGCOLOR="#FFFFFF" LINK="#000099" VLINK="#555555">
+<dtml-var manage_tabs>
+
+<h1>Test <dtml-var title_or_id></h1>
+
+<form action="manage_test">
+<table>
+<tr><th>username</th>		
+<td><input name=username></td></tr>
+
+<tr><th>password</th>		
+<td><input name=password type=password></td></tr>
+
+<tr><td colspan=2><input name=Test type=submit name=Test></td></tr>
+</table>
+</form>
+

Added: trunk/ZopeProducts/exUserFolder/AuthSources/radiusAuthSource/manage_addradiusAuthSourceForm.dtml
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/radiusAuthSource/manage_addradiusAuthSourceForm.dtml	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/radiusAuthSource/manage_addradiusAuthSourceForm.dtml	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,25 @@
+<dtml-var "DialogHeader(_.None,_,DialogTitle='Add RADIUS Authentication Source')">
+<FORM ACTION="&dtml-URL;" METHOD="POST">
+<dtml-in "REQUEST.form.keys()">
+<input type="HIDDEN" name="<dtml-var sequence-item>" value="<dtml-var "REQUEST[_.getitem('sequence-item',0)]">">
+</dtml-in>
+<input type="HIDDEN" name="doProp" value="1">
+<table cellspacing="2">
+<tr><th><dtml-babel src="'en'">Host</dtml-babel></th>	
+	<td><input name="radiusauth_host:required" value="radius"></td></tr>
+
+<tr><th><dtml-babel src="'en'">Port</dtml-babel></th>	
+	<td><input name="radiusauth_port:int:required" value=1812></td></tr>
+
+<tr><th><dtml-babel src="'en'">Secret</dtml-babel></th>
+	<td><input name="radiusauth_secret:required" type=password></td></tr>
+
+<tr><th><dtml-babel src="'en'">Retries</dtml-babel></th>	
+	<td><input name="radiusauth_retries:int:required" value=3></td></tr>
+
+<tr><th><dtml-babel src="'en'">Timeout</dtml-babel></th>	
+<td><input name="radiusauth_timeout:float:required" value=5></td></tr>
+<tr><td><br><input type="SUBMIT" value="<dtml-babel src="'en'">Add</dtml-babel>"></td></tr>
+</table>
+</form>
+<dtml-var DialogFooter>

Added: trunk/ZopeProducts/exUserFolder/AuthSources/radiusAuthSource/manage_editradiusAuthSourceForm.dtml
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/radiusAuthSource/manage_editradiusAuthSourceForm.dtml	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/radiusAuthSource/manage_editradiusAuthSourceForm.dtml	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,27 @@
+<dtml-var "DialogHeader(_.None,_,DialogTitle='RADIUS Authentication Source',dialog_width='100%')">
+<dtml-var manage_tabs>
+<FORM ACTION="manage_editAuthSource" METHOD="POST">
+<dtml-with currentAuthSource>
+<table>
+<tr><th><dtml-babel src="'en'">Host</dtml-babel></th>
+<td><input name="host:required" value="<dtml-var host>"></td></tr>
+
+<tr><th><dtml-babel src="'en'">Port</dtml-babel></th>
+<td><input name="port:int:required" value="<dtml-var port>"></td></tr> 
+
+<tr><th><dtml-babel src="'en'">Secret</dtml-babel></th>
+<td><input name="secret" type=password>
+<small>Leave blank for existing secret</small></td>
+</tr>
+
+<tr><th><dtml-babel src="'en'">Retries</dtml-babel></th>
+<td><input name="retries:int:required" value="<dtml-var retries>"></td></tr>
+
+<tr><th><dtml-babel src="'en'">Timeout</dtml-babel></th>
+<td><input name="timeout:float:required" value="<dtml-var timeout>"></td></tr>
+
+<tr><td colspan=2><input type=submit value=" <dtml-babel src="'en'">Edit</dtml-babel> "></td></tr>
+</table>
+</dtml-with>
+</form>
+<dtml-var DialogFooter>

Added: trunk/ZopeProducts/exUserFolder/AuthSources/radiusAuthSource/radius.py
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/radiusAuthSource/radius.py	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/radiusAuthSource/radius.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,214 @@
+#!/usr/bin/env python
+'''
+$Id: radius.py,v 1.1 2004/11/10 14:15:36 akm Exp $
+
+Extremly basic RADIUS authentication. Bare minimum required to authenticate
+a user, yet remain RFC2138 compliant (I hope). 
+
+Homepage at http://py-radius.sourceforge.net
+'''
+# Copyright (c) 1999, Stuart Bishop <zen at shangri-la.dropbear.id.au> 
+# All rights reserved.
+# 
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+# 
+#     Redistributions of source code must retain the above copyright
+#     notice, this list of conditions and the following disclaimer.
+# 
+#     Redistributions in binary form must reproduce the above copyright
+#     notice, this list of conditions and the following disclaimer in the
+#     documentation and/or other materials provided with the
+#     distribution.
+# 
+#     The name of Stuart Bishop may not be used to endorse or promote 
+#     products derived from this software without specific prior written 
+#     permission.
+# 
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+from select import select
+from struct import pack,unpack
+from whrandom import randint,random
+import md5
+import socket
+
+__version__ = '1.0.1'
+
+# Constants
+ACCESS_REQUEST	= 1
+ACCESS_ACCEPT	= 2
+ACCESSS_REJECT	= 3
+
+DEFAULT_RETRIES = 3
+DEFAULT_TIMEOUT = 5
+
+class Error(Exception): pass
+class NoResponse(Error): pass
+class SocketError(NoResponse): pass
+
+def authenticate(username,password,secret,host='radius',port=1645):
+    '''Return 1 for a successful authentication. Other values indicate
+       failure (should only ever be 0 anyway).
+
+       Can raise either NoResponse or SocketError'''
+
+    r = RADIUS(secret,host,port)
+    return r.authenticate(username,password)
+
+class RADIUS:
+
+    def __init__(self,secret,host='radius',port=1645):
+	self._secret = secret
+	self._host   = host
+	self._port   = port
+
+	self.retries = DEFAULT_RETRIES
+	self.timeout = DEFAULT_TIMEOUT
+	self._socket = None
+
+    def __del__(self):
+	self.closesocket()
+
+    def opensocket(self):
+	if self._socket == None:
+	    self._socket = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
+	    self._socket.connect((self._host,self._port))
+
+    def closesocket(self):
+	if self._socket is not None:
+	    try:
+		self._socket.close()
+	    except socket.error,x:
+		raise SocketError(x)
+	    self._socket = None
+
+    def generateAuthenticator(self):
+	'''A 16 byte random string'''
+	v = range(0,17)
+	v[0] = '16B'
+	for i in range(1,17):
+	    v[i] = randint(1,255)
+
+	return apply(pack,v)
+
+    def radcrypt(self,authenticator,text,pad16=0):
+	'''Encrypt a password with the secret'''
+
+	md5vec = md5.new(self._secret + authenticator).digest()
+	r = ''
+
+	# Encrypted text is just an xor with the above md5 hash,
+	# although it gets more complex if len(text) > 16
+	for i in range(0,len(text)):
+
+	    # Handle text > 16 characters acording to RFC
+	    if (i % 16) == 0 and i <> 0:
+		md5vec = md5.new(self._secret + r[-16:]).digest()
+
+	    r = r + chr( ord(md5vec[i]) ^ ord(text[i]) )
+
+	# When we encrypt passwords, we want to pad the encrypted text
+	# to a multiple of 16 characters according to the RFC
+	if pad16:
+	    for i in range(len(r),16):
+		r = r + md5vec[i]
+	return r
+
+    def authenticate(self,uname,passwd):
+	'''Attempt t authenticate with the given username and password.
+	   Returns 0 on failure
+	   Returns 1 on success
+	   Raises a NoResponse (or its subclass SocketError) exception if 
+		no responses or no valid responses are received'''
+
+	try:
+	    self.opensocket()
+	    id = randint(0,255)
+
+	    authenticator = self.generateAuthenticator()
+
+	    encpass = self.radcrypt(authenticator,passwd,1)
+	    
+	    msg = pack('!B B H 16s B B %ds B B %ds' \
+		    % (len(uname),len(encpass)),\
+		1,id,
+		len(uname)+len(encpass) + 24, # Length of entire message
+		authenticator,
+		1,len(uname)+2,uname,
+		2,len(encpass)+2,encpass)
+
+	    for i in range(0,self.retries):
+		self._socket.send(msg)
+
+		t = select( [self._socket,],[],[],self.timeout)
+		if len(t[0]) > 0:
+		    response = self._socket.recv(4096)
+		else:
+		    continue
+
+		if ord(response[1]) <> id:
+		    continue
+
+		# Verify the packet is not a cheap forgery or corrupt
+		checkauth = response[4:20]
+		m = md5.new(response[0:4] + authenticator + response[20:] 
+		    + self._secret).digest()
+
+		if m <> checkauth:
+		    continue
+
+		if ord(response[0]) == ACCESS_ACCEPT:
+		    return 1	
+		else:
+		    return 0
+
+	except socket.error,x: # SocketError
+	    try: self.closesocket()
+	    except: pass
+	    raise SocketError(x)
+
+	raise NoResponse
+
+# Don't break code written for radius.py distributed with the ZRadius
+# Zope product
+Radius = RADIUS
+
+if __name__ == '__main__':
+
+    from getpass import getpass
+
+    host = raw_input("Host? (default = 'radius')")
+    port = raw_input('Port? (default = 1645) ')
+
+    if not host: host = 'radius'
+
+    if port: port = int(port)
+    else: port = 1645
+    
+    secret = ''
+    while not secret: secret = getpass('RADIUS Secret? ')
+
+    r = RADIUS(secret,host,port)
+
+    uname,passwd = None,None
+
+    while not uname:  uname = raw_input("Username? ")
+    while not passwd: passwd = getpass("Password? ")
+
+    if r.authenticate(uname,passwd):
+	print "Authentication Succeeded"
+    else:
+	print "Authentication Failed"
+

Added: trunk/ZopeProducts/exUserFolder/AuthSources/radiusAuthSource/radiusAuthSource.py
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/radiusAuthSource/radiusAuthSource.py	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/radiusAuthSource/radiusAuthSource.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,159 @@
+#
+# Extensible User Folder
+# 
+# Postgres Authentication Source for exUserFolder
+#
+# (C) Copyright 2000,2001 The Internet (Aust) Pty Ltd
+# ACN: 082 081 472	ABN: 83 082 081 472
+# All Rights Reserved
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# Author: Andrew Milton <akm at theinternet.com.au>
+# $Id: radiusAuthSource.py,v 1.1 2004/11/10 14:15:36 akm Exp $
+
+# This code based on ZRadius by Stuart Bishop
+# Copyright 1999 Stuart Bishop zen at cs.rmit.edu.au
+
+import string,Acquisition
+
+from Globals import HTMLFile, MessageDialog, INSTANCE_HOME
+
+from OFS.Folder import Folder
+
+from Products.exUserFolder.exUserFolder import exUserFolder
+from Products.exUserFolder.Plugins import PluginRegister
+
+from radius import Radius
+
+def manage_addradiusAuthSource(self, REQUEST):
+	""" Add a radius Auth Source """
+
+	host=REQUEST['radiusauth_host']
+	port=REQUEST['radiusauth_port']
+	secret=REQUEST['radiusauth_secret']
+
+	retries=REQUEST['radiusauth_retries']
+	timeout=REQUEST['radiusauth_timeout']
+
+	ob=radiusAuthSource(host, int(port), secret, int(retries), float(timeout))
+	self._setObject('radiusAuthSource', ob, None, None, 0)
+	self.currentAuthSource=ob	
+
+
+manage_addradiusAuthSourceForm=HTMLFile('manage_addradiusAuthSourceForm', globals())
+manage_editradiusAuthSourceForm=HTMLFile('manage_editradiusAuthSourceForm', globals())
+
+
+class radiusAuthSource(Folder):
+	""" """
+
+	meta_type='Authentication Source'
+	id		 ='radiusAuthSource'
+	title	 ='RADIUS Authentication'
+	icon ='misc_/exUserFolder/exUserFolderPlugin.gif'
+
+	manage_editForm=manage_editradiusAuthSourceForm
+	manage_tabs=Acquisition.Acquired
+
+	_v_radius = None
+
+	def __init__(self,host,port,secret,retries,timeout):
+
+		self._host = host
+		self._port = int(port)
+		self._secret = secret
+
+		self._retries = int(retries)
+		self._timeout = float(timeout)
+
+
+	def manage_editAuthSource(self,REQUEST):
+		'''Handle output of manage_main - change ZRadius instance properties.
+		If REQUEST.secret is None, old secret will be used.'''
+
+		self._host = REQUEST['host']
+		self._port = int(REQUEST['port'])
+		if hasattr(REQUEST,'secret') and len(REQUEST['secret']) > 0: 
+			# So we don't code it in form source
+			self._secret = REQUEST['secret']
+
+		self._retries = int(REQUEST['retries'])
+		self._timeout = float(REQUEST['timeout'])
+
+		# Reset the Radius object so new values take effect. This is
+		# why we don't allow direct access to the attributes
+		self._v_radius = None
+
+		if REQUEST is not None:
+			return self.MessageDialog(self,
+				REQUEST=REQUEST,
+				title = 'Edited',
+				message = "Properties for %s changed." % self.id,
+				action = 'manage_editAuthSourceForm')
+		
+	#
+	# We don't let you delete, create, or edit users
+	#
+	def deleteUsers(self, userids):
+		pass
+
+	def createUser(self, username, password, roles):
+		pass
+
+
+	def updateUser(self, username, password, roles):
+		self.currentPropSource.setUserProperty(username=username, key='_roles',
+											   value=roles)
+	
+	def listUserNames(self):
+		pass
+
+	def listUsers(self):
+		pass
+
+	def listOneUser(self, username):
+		roles=[]
+		if self.currentPropSource:
+			roles=self.currentPropSource.getUserProperty(username=username, key='_roles', default=[])
+
+		return [{'username':username, 'password':'',
+				 'roles':roles}]
+
+	def getUsers(self):
+		pass
+	
+	def authenticate(self,username,password):
+		'Authenticate a username/password combination against the Radius server'
+
+		if self._v_radius is None:
+			self._v_radius = Radius(self._secret,self._host,self._port)
+			self._v_radius.retries = int(self._retries)
+			self._v_radius.timeout = self._timeout
+
+		return self._v_radius.authenticate(username,password)
+	
+	remoteAuthMethod=authenticate
+	
+	def host(self): return self._host
+	def port(self): return self._port
+	def retries(self): return self._retries
+	def timeout(self): return self._timeout
+
+
+radiusAuthReg=PluginRegister('radiusAuthSource',
+							 'RADIUS Authentication Source',
+							 radiusAuthSource, manage_addradiusAuthSourceForm,
+							 manage_addradiusAuthSource,
+							 manage_editradiusAuthSourceForm)
+exUserFolder.authSources['radiusAuthSource']=radiusAuthReg

Added: trunk/ZopeProducts/exUserFolder/AuthSources/smbAuthSource/.cvsignore
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/smbAuthSource/.cvsignore	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/smbAuthSource/.cvsignore	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,4 @@
+*.pyc
+*.pyo
+*~
+.*.swp

Added: trunk/ZopeProducts/exUserFolder/AuthSources/smbAuthSource/__init__.py
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/smbAuthSource/__init__.py	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/smbAuthSource/__init__.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,2 @@
+# $Id: __init__.py,v 1.1 2004/11/10 14:15:36 akm Exp $
+import smbAuthSource

Added: trunk/ZopeProducts/exUserFolder/AuthSources/smbAuthSource/manage_addsmbAuthSourceForm.dtml
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/smbAuthSource/manage_addsmbAuthSourceForm.dtml	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/smbAuthSource/manage_addsmbAuthSourceForm.dtml	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,21 @@
+<dtml-var "DialogHeader(_.None,_,DialogTitle='Add SMB Authentication Source')">
+<FORM ACTION="&dtml-URL;" METHOD="POST">
+<dtml-in "REQUEST.form.keys()">
+<input type="HIDDEN" name="<dtml-var sequence-item>" value="<dtml-var "REQUEST[_.getitem('sequence-item',0)]">">
+</dtml-in>
+<input type="HIDDEN" name="doProp" value="1">
+<table cellspacing="2">
+<tr><th><dtml-babel src="'en'">Host</dtml-babel></th>	
+	<td><input name="smbauth_host:required"></td></tr>
+
+<tr><th><dtml-babel src="'en'">Windows Domain</dtml-babel></th>	
+	<td><input name="smbauth_domain:required"></td></tr>
+
+<tr><th><dtml-babel src="'en'">WINS Server IP Address</dtml-babel><br />
+        <dtml-babel src="'en'">(optional, leave empty for broadcast)</dtml-babel></th>	
+	<td><input name="smbauth_winsserver"></td></tr>
+
+<tr><td><br><input type="SUBMIT" value="<dtml-babel src="'en'">Add</dtml-babel>"></td></tr>
+</table>
+</form>
+<dtml-var DialogFooter>

Added: trunk/ZopeProducts/exUserFolder/AuthSources/smbAuthSource/manage_editsmbAuthSourceForm.dtml
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/smbAuthSource/manage_editsmbAuthSourceForm.dtml	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/smbAuthSource/manage_editsmbAuthSourceForm.dtml	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,17 @@
+<dtml-var "DialogHeader(_.None,_,DialogTitle='Add SMB Authentication Source')">
+<dtml-var manage_tabs>
+<FORM ACTION="manage_editAuthSource" METHOD="POST">
+<dtml-with currentAuthSource>
+<table cellspacing="2">
+<tr><th><dtml-babel src="'en'">Host</dtml-babel></th>	
+	<td><input name="host:required" value="<dtml-var host>"></td></tr>
+<tr><th><dtml-babel src="'en'">Windows Domain</dtml-babel></th>	
+	<td><input name="domain:required" value="<dtml-var domain>"></td></tr>
+<tr><th><dtml-babel src="'en'">WINS Server IP Address</dtml-babel><br />
+        <dtml-babel src="'en'">(optional, leave empty for broadcast)</dtml-babel></th>	
+	<td><input name="winsserver" value="<dtml-var winsserver>"></td></tr>
+<tr><td><br><input type="SUBMIT" value="<dtml-babel src="'en'">Change</dtml-babel>"></td></tr>
+</table>
+</dtml-with>
+</form>
+<dtml-var DialogFooter>

Added: trunk/ZopeProducts/exUserFolder/AuthSources/smbAuthSource/nmb.py
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/smbAuthSource/nmb.py	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/smbAuthSource/nmb.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,472 @@
+# -*- mode: python; tab-width: 4 -*-
+# $Id: nmb.py,v 1.1 2004/11/10 14:15:36 akm Exp $
+#
+# Copyright (C) 2001 Michael Teo <michaelteo at bigfoot.com>
+# nmb.py - NetBIOS library
+#
+# This software is provided 'as-is', without any express or implied warranty. 
+# In no event will the author be held liable for any damages arising from the 
+# use of this software.
+#
+# Permission is granted to anyone to use this software for any purpose, 
+# including commercial applications, and to alter it and redistribute it 
+# freely, subject to the following restrictions:
+#
+# 1. The origin of this software must not be misrepresented; you must not 
+#    claim that you wrote the original software. If you use this software 
+#    in a product, an acknowledgment in the product documentation would be
+#    appreciated but is not required.
+#
+# 2. Altered source versions must be plainly marked as such, and must not be 
+#    misrepresented as being the original software.
+#
+# 3. This notice cannot be removed or altered from any source distribution.
+#
+
+import os, sys, socket, string, re, select, errno
+from random import randint
+from struct import *
+
+
+
+CVS_REVISION = '$Revision: 1.1 $'
+
+# Taken from socket module reference
+INADDR_ANY = ''
+BROADCAST_ADDR = '<broadcast>'
+
+# Default port for NetBIOS name service
+NETBIOS_NS_PORT = 137
+# Default port for NetBIOS session service
+NETBIOS_SESSION_PORT = 139
+
+# Owner Node Type Constants
+NODE_B = 0x00
+NODE_P = 0x01
+NODE_M = 0x10
+NODE_RESERVED = 0x11
+
+# Name Type Constants
+TYPE_UNKNOWN = 0x01
+TYPE_WORKSTATION = 0x00
+TYPE_CLIENT = 0x03
+TYPE_SERVER = 0x20
+TYPE_MASTER_BROWSER = 0x1D
+TYPE_BROWSER = 0x1E
+
+NAME_TYPES = { TYPE_UNKNOWN: 'Unknown', TYPE_WORKSTATION: 'Workstation', TYPE_CLIENT: 'Client',
+               TYPE_SERVER: 'Server', TYPE_MASTER_BROWSER: 'Master Browser', TYPE_BROWSER: 'Browser Server' }
+
+
+
+def strerror(errclass, errcode):
+    if errclass == ERRCLASS_OS:
+        return 'OS Error', os.strerror(errcode)
+    elif errclass == ERRCLASS_QUERY:
+        return 'Query Error', QUERY_ERRORS.get(errcode, 'Unknown error')
+    elif errclass == ERRCLASS_SESSION:
+        return 'Session Error', SESSION_ERRORS.get(errcode, 'Unknown error')
+    else:
+        return 'Unknown Error Class', 'Unknown Error'
+    
+    
+
+class NetBIOSError(Exception): pass
+class NetBIOSTimeout(Exception): pass
+
+
+
+class NBHostEntry:
+
+    def __init__(self, nbname, nametype, ip):
+        self.__nbname = nbname
+        self.__nametype = nametype
+        self.__ip = ip
+
+    def get_nbname(self):
+        return self.__nbname
+
+    def get_nametype(self):
+        return self.__nametype
+
+    def get_ip(self):
+        return self.__ip
+
+    def __repr__(self):
+        return '<NBHostEntry instance: NBname="' + self.__nbname + '", IP="' + self.__ip + '">'
+
+
+
+class NBNodeEntry:
+    
+    def __init__(self, nbname, nametype, isgroup, nodetype, deleting, isconflict, isactive, ispermanent):
+        self.__nbname = nbname
+        self.__nametype = nametype
+        self.__isgroup = isgroup
+        self.__nodetype = nodetype
+        self.__deleting = deleting
+        self.__isconflict = isconflict
+        self.__isactive = isactive
+        self.__ispermanent = ispermanent
+
+    def get_nbname(self):
+        return self.__nbname
+
+    def get_nametype(self):
+        return self.__nametype
+
+    def is_group(self):
+        return self.__isgroup
+
+    def get_nodetype(self):
+        return self.__nodetype
+
+    def is_deleting(self):
+        return self.__deleting
+
+    def is_conflict(self):
+        return self.__isconflict
+
+    def is_active(self):
+        return self.__isactive
+
+    def is_permanent(self):
+        return self.__ispermanent
+
+    def __repr__(self):
+        s = '<NBNodeEntry instance: NBname="' + self.__nbname + '" NameType="' + NAME_TYPES[self.__nametype] + '"'
+        if self.__isactive:
+            s = s + ' ACTIVE'
+        if self.__isgroup:
+            s = s + ' GROUP'
+        if self.__isconflict:
+            s = s + ' CONFLICT'
+        if self.__deleting:
+            s = s + ' DELETING'
+        return s
+            
+
+
+class NetBIOS:
+
+    # Creates a NetBIOS instance without specifying any default NetBIOS domain nameserver.
+    # All queries will be sent through the servport.
+    def __init__(self, servport = NETBIOS_NS_PORT):
+        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+        has_bind = 0
+        for i in range(0, 10):
+            # We try to bind to a port for 10 tries
+            try:
+                s.bind(( INADDR_ANY, randint(10000, 60000) ))
+                s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
+                has_bind = 1
+            except socket.error, ex:
+                pass
+        if not has_bind:
+            raise NetBIOSError, ( 'Cannot bind to a good UDP port', ERRCLASS_OS, errno.EAGAIN )
+
+        self.__sock = s
+        self.__servport = NETBIOS_NS_PORT
+        self.__nameserver = None
+        self.__broadcastaddr = BROADCAST_ADDR
+
+    # Set the default NetBIOS domain nameserver.
+    def set_nameserver(self, nameserver):
+        self.__nameserver = nameserver
+
+    # Return the default NetBIOS domain nameserver, or None if none is specified.
+    def get_nameserver(self):
+        return self.__nameserver
+
+    # Set the broadcast address to be used for query.
+    def set_broadcastaddr(self, broadcastaddr):
+        self.__broadcastaddr = broadcastaddr
+
+    # Return the broadcast address to be used, or BROADCAST_ADDR if default broadcast address is used.   
+    def get_broadcastaddr(self):
+        return self.__broadcastaddr
+
+    # Returns a list of NBHostEntry instances containing the host information for nbname.
+    # If a NetBIOS domain nameserver has been specified, it will be used for the query.
+    # Otherwise, the query is broadcasted on the broadcast address.
+    def gethostbyname(self, nbname, type = TYPE_WORKSTATION, scope = None, timeout = 1):
+        return self.__queryname(nbname, self.__nameserver, type, scope, timeout)
+
+    # Returns a list of NBNodeEntry instances containing node status information for nbname.
+    # If destaddr contains an IP address, then this will become an unicast query on the destaddr.
+    # Raises NetBIOSTimeout if timeout (in secs) is reached.
+    # Raises NetBIOSError for other errors
+    def getnodestatus(self, nbname, destaddr = None, type = TYPE_WORKSTATION, scope = None, timeout = 1):
+        if destaddr:
+            return self.__querynodestatus(nbname, destaddr, type, scope, timeout)
+        else:
+            return self.__querynodestatus(nbname, self.__nameserver, type, scope, timeout)
+    
+    def __queryname(self, nbname, destaddr, type, scope, timeout):
+        netbios_name = string.upper(nbname)
+        trn_id = randint(1, 32000)
+        qn_label = encode_name(netbios_name, type, scope)
+
+        if destaddr:
+            req = pack('>HHHHHH', trn_id, 0x0100, 0x01, 0x00, 0x00, 0x00) + qn_label + pack('>HH', 0x20, 0x01)
+        else:
+            req = pack('>HHHHHH', trn_id, 0x0110, 0x01, 0x00, 0x00, 0x00) + qn_label + pack('>HH', 0x20, 0x01)
+            destaddr = self.__broadcastaddr
+
+        wildcard_query = netbios_name == '*'
+
+        self.__sock.sendto(req, 0, ( destaddr, self.__servport ))
+        
+        addrs = [ ]
+        tries = 3
+        while 1:
+            try:
+                ready, _, _ = select.select([ self.__sock.fileno() ], [ ] , [ ], timeout)
+                if not ready:
+                    if tries and not wildcard_query:
+                        # Retry again until tries == 0
+                        self.__sock.sendto(req, 0, ( destaddr, self.__servport ))
+                        tries = tries - 1
+                    elif wildcard_query:
+                        return addrs
+                    else:
+                        raise NetBIOSTimeout
+                else:
+                    data, _ = self.__sock.recvfrom(65536, 0)
+                    if unpack('>H', data[:2])[0] == trn_id:
+                        rcode = ord(data[3]) & 0x0f
+                        if rcode:
+                            if rcode == 0x03:
+                                # Name error. Name was not registered on server.
+                                return None
+                            else:
+                                raise NetBIOSError, ( 'Negative name query response', ERRCLASS_QUERY, rcode )
+                            
+                        qn_length, qn_name, qn_scope = decode_name(data[12:])
+                        offset = 20 + qn_length
+                        num_records = (unpack('>H', data[offset:offset + 2])[0] - 2) / 4
+                        offset = offset + 4
+                        for i in range(0, num_records):
+                            # In Python2, we can use socket.inet_ntoa(data[58 + i * 4:62 + i * 4]) to convert
+                            addrs.append(NBHostEntry(string.rstrip(qn_name[:-1]) + qn_scope, ord(qn_name[-1]), '%d.%d.%d.%d' % unpack('4B', (data[offset:offset + 4]))))
+                            offset = offset + 4
+
+                        if not wildcard_query:
+                            return addrs
+            except select.error, ex:
+                if ex[0] != errno.EINTR and ex[0] != errno.EAGAIN:
+                    raise NetBIOSError, ( 'Error occurs while waiting for response', ERRCLASS_OS, ex[0] )
+            except socket.error, ex:
+                pass
+
+    def __querynodestatus(self, nbname, destaddr, type, scope, timeout):
+        netbios_name = string.upper(nbname)
+        trn_id = randint(1, 32000)
+        qn_label = encode_name(netbios_name, type, scope)
+
+        if destaddr:
+            req = pack('>HHHHHH', trn_id, 0x0100, 0x01, 0x00, 0x00, 0x00) + qn_label + pack('>HH', 0x21, 0x01)
+        else:
+            req = pack('>HHHHHH', trn_id, 0x0110, 0x01, 0x00, 0x00, 0x00) + qn_label + pack('>HH', 0x21, 0x01)
+            destaddr = self.__broadcastaddr
+
+        tries = 3
+        while 1:
+            try:
+                self.__sock.sendto(req, 0, ( destaddr, self.__servport ))
+                ready, _, _ = select.select([ self.__sock.fileno() ], [ ] , [ ], timeout)
+                if not ready:
+                    if tries:
+                        # Retry again until tries == 0
+                        tries = tries - 1
+                    else:
+                        raise NetBIOSTimeout
+                else:
+                    data, _ = self.__sock.recvfrom(65536, 0)
+                    if unpack('>H', data[:2])[0] == trn_id:
+                        rcode = ord(data[3]) & 0x0f
+                        if rcode:
+                            if rcode == 0x03:
+                                # Name error. Name was not registered on server.
+                                return None
+                            else:
+                                raise NetBIOSError, ( 'Negative name query response', ERRCLASS_QUERY, rcode )
+                            
+                        nodes = [ ]
+                        num_names = ord(data[56])
+                        for i in range(0, num_names):
+                            rec_start = 57 + i * 18
+                            name = re.sub(chr(0x20) + '*$', '', data[rec_start:rec_start + 15])
+                            type, flags = unpack('>BH', data[rec_start + 15: rec_start + 18])
+                            nodes.append(NBNodeEntry(name, type, flags & 0x8000, flags & 0x6000,
+                                                     flags & 0x1000, flags & 0x0800, flags & 0x0400,
+                                                     flags & 0x0200))
+                             
+                        return nodes
+            except select.error, ex:
+                if ex[0] != errno.EINTR and ex[0] != errno.EAGAIN:
+                    raise NetBIOSError, ( 'Error occurs while waiting for response', ERRCLASS_OS, ex[0] )
+            except socket.error, ex:
+                pass
+        
+
+
+# Perform first and second level encoding of name as specified in RFC 1001 (Section 4)
+def encode_name(name, type, scope):
+    if name == '*':
+        name = name + '\0' * 15
+    elif len(name) > 15:
+        name = name[:15] + chr(type)
+    else:
+        name = string.ljust(name, 15) + chr(type)
+        
+    encoded_name = chr(len(name) * 2) + re.sub('.', _do_first_level_encoding, name)
+    if scope:
+        encoded_scope = ''
+        for s in string.split(scope, '.'):
+            encoded_scope = encoded_scope + chr(len(s)) + s
+        return encoded_name + encoded_scope + '\0'
+    else:
+        return encoded_name + '\0'
+
+# Internal method for use in encode_name()
+def _do_first_level_encoding(m):
+    s = ord(m.group(0))
+    return string.uppercase[s >> 4] + string.uppercase[s & 0x0f]
+
+def decode_name(name):
+    name_length = ord(name[0])
+    assert name_length == 32
+
+    decoded_name = re.sub('..', _do_first_level_decoding, name[1:33])
+    if name[33] == '\0':
+        return 34, decoded_name, ''
+    else:
+        decoded_domain = ''
+        offset = 34
+        while 1:
+            domain_length = ord(name[offset])
+            if domain_length == 0:
+                break
+            decoded_domain = '.' + name[offset:offset + domain_length]
+            offset = offset + domain_length
+        return offset + 1, decoded_name, decoded_domain
+
+def _do_first_level_decoding(m):
+    s = m.group(0)
+    return chr(((ord(s[0]) - ord('A')) << 4) | (ord(s[1]) - ord('A')))
+
+
+
+class NetBIOSSession:
+
+    def __init__(self, myname, remote_name, remote_host, host_type = TYPE_SERVER, sess_port = NETBIOS_SESSION_PORT):
+        if len(myname) > 15:
+            self.__myname = string.upper(myname[:15])
+        else:
+            self.__myname = string.upper(myname)
+
+        assert remote_name
+        if len(remote_name) > 15:
+            self.__remote_name = string.upper(remote_name[:15])
+        else:
+            self.__remote_name = string.upper(remote_name)
+
+        self.__remote_host = remote_host
+        self.__sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        self.__sock.connect(( remote_host, sess_port ))
+        self.__request_session(host_type)
+
+    def get_myname(self):
+        return self.__myname
+
+    def get_remote_host(self):
+        return self.__remote_host
+
+    def get_remote_name(self):
+        return self.__remote_name
+
+    def close(self):
+        self.__sock.shutdown(2)
+        self.__sock.close()
+
+    def send_packet(self, data):
+        self.__sock.send('\x00\x00' + pack('>H', len(data)) + data)
+
+    def recv_packet(self, timeout = None):
+        type, flags, data = self.__read(timeout)
+        if type == 0x00:
+            return data
+        else:
+            return None
+
+    def __request_session(self, host_type, timeout = None):
+        remote_name = encode_name(self.__remote_name, host_type, '')
+        myname = encode_name(self.__myname, TYPE_WORKSTATION, '')
+        
+        self.__sock.send('\x81\x00' + pack('>H', len(remote_name) + len(myname)) + remote_name + myname)
+        while 1:
+            type, flags, data = self.__read(timeout)
+            if type == 0x83:
+                raise NetBIOSError, ( 'Cannot request session', ERRCLASS_SESSION, ord(data[0]) )
+            elif type == 0x82:
+                break
+            else:
+                # Ignore all other messages, most probably keepalive messages
+                pass
+
+    def __read(self, timeout = None):
+        read_len = 4
+        data = ''
+
+        while read_len > 0:
+            try:
+                ready, _, _ = select.select([ self.__sock.fileno() ], [ ], [ ], timeout)
+                if not ready:
+                    raise NetBIOSTimeout
+                
+                data = data + self.__sock.recv(read_len)
+                read_len = 4 - len(data)
+            except select.error, ex:
+                if ex[0] != errno.EINTR and ex[0] != errno.EAGAIN:
+                    raise NetBIOSError, ( 'Error occurs while reading from remote', ERRCLASS_OS, ex[0] )
+                
+        type, flags, length = unpack('>ccH', data)
+        if ord(flags) & 0x01:
+            length = length | 0x10000
+            
+        read_len = length
+        data = ''
+        while read_len > 0:
+            try:
+                ready, _, _ = select.select([ self.__sock.fileno() ], [ ], [ ], timeout)
+                if not ready:
+                    raise NetBIOSTimeout
+
+                data = data + self.__sock.recv(read_len)
+                read_len = length - len(data)
+            except select.error, ex:
+                if ex[0] != errno.EINTR and ex[0] != errno.EAGAIN:
+                    raise NetBIOSError, ( 'Error while reading from remote', ERRCLASS_OS, ex[0] )
+                
+        return ord(type), ord(flags), data
+
+
+
+ERRCLASS_QUERY = 0x00
+ERRCLASS_SESSION = 0xf0
+ERRCLASS_OS = 0xff
+
+QUERY_ERRORS = { 0x01: 'Request format error. Please file a bug report.',
+                 0x02: 'Internal server error',
+                 0x03: 'Name does not exist',
+                 0x04: 'Unsupported request',
+                 0x05: 'Request refused'
+                 }
+
+SESSION_ERRORS = { 0x80: 'Not listening on called name',
+                   0x81: 'Not listening for calling name',
+                   0x82: 'Called name not present',
+                   0x83: 'Sufficient resources',
+                   0x8f: 'Unspecified error'
+                   }

Added: trunk/ZopeProducts/exUserFolder/AuthSources/smbAuthSource/pysmb-0.2.0.tar.gz
===================================================================
(Binary files differ)


Property changes on: trunk/ZopeProducts/exUserFolder/AuthSources/smbAuthSource/pysmb-0.2.0.tar.gz
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/ZopeProducts/exUserFolder/AuthSources/smbAuthSource/smb.py
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/smbAuthSource/smb.py	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/smbAuthSource/smb.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,795 @@
+# -*- mode: python; tab-width: 4 -*-
+# $Id: smb.py,v 1.1 2004/11/10 14:15:36 akm Exp $
+#
+# Copyright (C) 2001 Michael Teo <michaelteo at bigfoot.com>
+# smb.py - SMB/CIFS library
+#
+# This software is provided 'as-is', without any express or implied warranty. 
+# In no event will the author be held liable for any damages arising from the 
+# use of this software.
+#
+# Permission is granted to anyone to use this software for any purpose, 
+# including commercial applications, and to alter it and redistribute it 
+# freely, subject to the following restrictions:
+#
+# 1. The origin of this software must not be misrepresented; you must not 
+#    claim that you wrote the original software. If you use this software 
+#    in a product, an acknowledgment in the product documentation would be
+#    appreciated but is not required.
+#
+# 2. Altered source versions must be plainly marked as such, and must not be 
+#    misrepresented as being the original software.
+#
+# 3. This notice cannot be removed or altered from any source distribution.
+#
+
+import os, sys, socket, string, re, select, errno
+import nmb
+from random import randint
+from struct import *
+
+# Try to load mxCrypto's DES module to perform password encryption if required.
+# Password will not be encrypted if mxCrypto's DES module is not loaded.
+try:
+    from Crypto.Ciphers import DES
+except ImportError:
+	DES = None
+
+CVS_REVISION = '$Revision: 1.1 $'
+
+# Shared Device Type
+SHARED_DISK = 0x00
+SHARED_PRINT_QUEUE = 0x01
+SHARED_DEVICE = 0x02
+SHARED_IPC = 0x03
+
+# Extended attributes mask
+ATTR_ARCHIVE = 0x020
+ATTR_COMPRESSED = 0x800
+ATTR_NORMAL = 0x080
+ATTR_HIDDEN = 0x002
+ATTR_READONLY = 0x001
+ATTR_TEMPORARY = 0x100
+ATTR_DIRECTORY = 0x010
+ATTR_SYSTEM = 0x004
+
+# SMB Command Codes
+SMB_COM_CREATE_DIR = 0x00
+SMB_COM_DELETE_DIR = 0x01
+SMB_COM_CLOSE = 0x04
+SMB_COM_DELETE = 0x06
+SMB_COM_RENAME = 0x07
+SMB_COM_CHECK_DIR = 0x10
+SMB_COM_READ_RAW = 0x1a
+SMB_COM_WRITE_RAW = 0x1d
+SMB_COM_TRANSACTION = 0x25
+SMB_COM_TRANSACTION2 = 0x32
+SMB_COM_OPEN_ANDX = 0x2d
+SMB_COM_READ_ANDX = 0x2e
+SMB_COM_WRITE_ANDX = 0x2f
+SMB_COM_TREE_DISCONNECT = 0x71
+SMB_COM_NEGOTIATE = 0x72
+SMB_COM_SESSION_SETUP_ANDX = 0x73
+SMB_COM_TREE_CONNECT_ANDX = 0x75
+
+# Service Type
+SERVICE_DISK = 'A:'
+SERVICE_PRINTER = 'LPT1:'
+SERVICE_IPC = 'IPC'
+SERVICE_COMM = 'COMM'
+SERVICE_ANY = '?????'
+
+# Options values for SMB.stor_file and SMB.retr_file
+SMB_O_CREAT = 0x10   # Create the file if file does not exists. Otherwise, operation fails.
+SMB_O_EXCL = 0x00    # When used with SMB_O_CREAT, operation fails if file exists. Cannot be used with SMB_O_OPEN.
+SMB_O_OPEN = 0x01    # Open the file if the file exists
+SMB_O_TRUNC = 0x02   # Truncate the file if the file exists
+
+# Share Access Mode
+SMB_SHARE_COMPAT = 0x00
+SMB_SHARE_DENY_EXCL = 0x10
+SMB_SHARE_DENY_WRITE = 0x20
+SMB_SHARE_DENY_READEXEC = 0x30
+SMB_SHARE_DENY_NONE = 0x40
+SMB_ACCESS_READ = 0x00
+SMB_ACCESS_WRITE = 0x01
+SMB_ACCESS_READWRITE = 0x02
+SMB_ACCESS_EXEC = 0x03
+
+
+
+def strerror(errclass, errcode):
+    if errclass == 0x01:
+        return 'OS error', ERRDOS.get(errcode, 'Unknown error')
+    elif errclass == 0x02:
+        return 'Server error', ERRSRV.get(errcode, 'Unknown error')
+    elif errclass == 0x03:
+        return 'Hardware error', ERRHRD.get(errcode, 'Unknown error')
+    elif errclass == 0xff:
+        return 'Bad command', 'Bad command. Please file bug report'
+    else:
+        return 'Unknown error', 'Unknown error'
+
+    
+
+class SessionError(Exception): pass
+
+# Contains information about a SMB shared device/service
+class SharedDevice:
+
+    def __init__(self, name, type, comment):
+        self.__name = name
+        self.__type = type
+        self.__comment = comment
+
+    def get_name(self):
+        return self.__name
+
+    def get_type(self):
+        return self.__type
+
+    def get_comment(self):
+        return self.__comment
+
+    def __repr__(self):
+        return '<SharedDevice instance: name=' + self.__name + ', type=' + str(self.__type) + ', comment="' + self.__comment + '">'
+
+
+
+# Contains information about the shared file/directory
+class SharedFile:
+
+    def __init__(self, ctime, atime, mtime, filesize, allocsize, attribs, shortname, longname):
+        self.__ctime = ctime
+        self.__atime = atime
+        self.__mtime = mtime
+        self.__filesize = filesize
+        self.__allocsize = allocsize
+        self.__attribs = attribs
+        self.__shortname = shortname
+        self.__longname = longname
+
+    def get_ctime(self):
+        return self.__ctime
+
+    def get_mtime(self):
+        return self.__mtime
+
+    def get_atime(self):
+        return self.__atime
+
+    def get_filesize(self):
+        return self.__filesize
+
+    def get_allocsize(self):
+        return self.__allocsize
+
+    def get_attributes(self):
+        return self.__attribs
+
+    def is_archive(self):
+        return self.__attribs & ATTR_ARCHIVE
+
+    def is_compressed(self):
+        return self.__attribs & ATTR_COMPRESSED
+
+    def is_normal(self):
+        return self.__attribs & ATTR_NORMAL
+
+    def is_hidden(self):
+        return self.__attribs & ATTR_HIDDEN
+
+    def is_readonly(self):
+        return self.__attribs & ATTR_READONLY
+
+    def is_temporary(self):
+        return self.__attribs & ATTR_TEMPORARY
+
+    def is_directory(self):
+        return self.__attribs & ATTR_DIRECTORY
+
+    def is_system(self):
+        return self.__attribs & ATTR_SYSTEM
+
+    def get_shortname(self):
+        return self.__shortname
+
+    def get_longname(self):
+        return self.__longname
+
+    def __repr__(self):
+        return '<SharedFile instance: shortname="' + self.__shortname + '", longname="' + self.__longname + '", filesize=' + str(self.__filesize) + '>'
+
+    
+
+# Represents a SMB session
+class SMB:
+
+    def __init__(self, remote_name, remote_host, my_name = None, host_type = nmb.TYPE_SERVER, sess_port = nmb.NETBIOS_SESSION_PORT):
+        # The uid attribute will be set when the client calls the login() method
+        self.__uid = 0
+        self.__remote_name = string.upper(remote_name)
+        
+        if not my_name:
+            my_name = socket.gethostname()
+            i = string.find(my_name, '.')
+            if i > -1:
+                my_name = my_name[:i]
+            
+        self.__sess = nmb.NetBIOSSession(my_name, remote_name, remote_host, host_type, sess_port)
+        _, self.__login_required, self.__max_transmit_size, rawmode, self.__enc_key = self.__neg_session()
+        self.__can_read_raw = rawmode & 0x01
+        self.__can_write_raw = rawmode & 0x02
+
+    def __del__(self):
+        self.__sess.close()
+
+    def __decode_smb(self, data):
+        _, cmd, err_class, _, err_code, flags1, flags2, _, tid, pid, uid, mid, wcount = unpack('<4sBBBHBH12sHHHHB', data[:33])
+        param_end = 33 + wcount * 2
+        return cmd, err_class, err_code, flags1, flags2, tid, uid, mid, data[33:param_end], data[param_end + 2:]
+
+    def __decode_trans(self, params, data):
+        totparamcnt, totdatacnt, _, paramcnt, paramoffset, paramds, datacnt, dataoffset, datads, setupcnt = unpack('<HHHHHHHHHB', params[:19])
+        if paramcnt + paramds < totparamcnt or datacnt + datads < totdatacnt:
+            has_more = 1
+        else:
+            has_more = 0
+        paramoffset = paramoffset - 55 - setupcnt * 2
+        dataoffset = dataoffset - 55 - setupcnt * 2
+        return has_more, params[20:20 + setupcnt * 2], data[paramoffset:paramoffset + paramcnt], data[dataoffset:dataoffset + datacnt]
+
+    def __send_smb_packet(self, cmd, status, flags, flags2, tid, mid, params = '', data = ''):
+        wordcount = len(params)
+        assert wordcount & 0x1 == 0
+        
+        self.__sess.send_packet(pack('<4sBLBH12sHHHHB', '\xffSMB', cmd, status, flags, flags2, '\0' * 12, tid, os.getpid(), self.__uid, mid, wordcount / 2) + params + pack('<H', len(data)) + data)
+
+    def __neg_session(self, timeout = None):
+        self.__send_smb_packet(SMB_COM_NEGOTIATE, 0, 0, 0, 0, 0, data = '\x02PC NETWORK PROGRAM 1.0\x00\x02MICROSOFT NETWORKS 3.0\x00\x02LANMAN1.0\x00\x02LM1.2X002\x00')
+
+        while 1:
+            data = self.__sess.recv_packet(timeout)
+            if data:
+                cmd, err_class, err_code, flags1, flags2, _, _, mid, params, d = self.__decode_smb(data)
+                if cmd == SMB_COM_NEGOTIATE:
+                    if err_class == 0x00 and err_code == 0x00:
+                        sel_dialect, auth, max_buf_size, _, rawmode = unpack('<HHH4sH', params[:12])
+                        if len(d) >= 8:
+                            return sel_dialect, auth, max_buf_size, rawmode, d[:8]
+                        else:
+                            return sel_dialect, auth, max_buf_size, rawmode, None
+                    else:
+                        raise SessionError, ( "Cannot neg dialect. (ErrClass: %d and ErrCode: %d)" % ( err_class, err_code ), err_class, err_code )
+            
+    def __connect_tree(self, path, service, timeout = None):
+        self.__send_smb_packet(SMB_COM_TREE_CONNECT_ANDX, 0, 0x08, 0, 0, 0, pack('<BBHHH', 0xff, 0, 0, 0, 1), '\0' + string.upper(path) + '\0' + service + '\0')
+
+        while 1:
+            data = self.__sess.recv_packet(timeout)
+            if data:
+                cmd, err_class, err_code, flags1, flags2, tid, _, mid, params, d = self.__decode_smb(data)
+                if cmd == SMB_COM_TREE_CONNECT_ANDX:
+                    if err_class == 0x00 and err_code == 0x00:
+                        return tid
+                    else:
+                        raise SessionError, ( "Cannot connect tree. (ErrClass: %d and ErrCode: %d)" % ( err_class, err_code ), err_class, err_code )
+
+    def __disconnect_tree(self, tid):
+        self.__send_smb_packet(SMB_COM_TREE_DISCONNECT, 0, 0, 0, tid, 0, '', '')
+
+    def __open_file(self, tid, filename, open_mode, access_mode, timeout = None):
+        self.__send_smb_packet(SMB_COM_OPEN_ANDX, 0, 0x08, 0, tid, 0, pack('<BBHHHHHLHLLL', 0xff, 0, 0, 0, access_mode, ATTR_READONLY | ATTR_HIDDEN | ATTR_ARCHIVE, 0, 0, open_mode, 0, 0, 0), filename + '\x00')
+        
+        while 1:
+            data = self.__sess.recv_packet(timeout)
+            if data:
+                cmd, err_class, err_code, flags1, flags2, _, _, mid, params, d = self.__decode_smb(data)
+                if cmd == SMB_COM_OPEN_ANDX:
+                    if err_class == 0x00 and err_code == 0x00:
+                        offset = unpack('<H', params[2:4])[0]
+                        fid, attrib, lastwritetime, datasize, grantedaccess, filetype, devicestate, action, serverfid = unpack('<HHLLHHHHL', params[4+offset:28+offset])
+                        return fid, attrib, lastwritetime, datasize, grantedaccess, filetype, devicestate, action, serverfid
+                    else:
+                        raise SessionError, ( 'Open file failed. (ErrClass: %d and ErrCode: %d)' % ( err_class, err_code ), err_class, err_code )
+        
+    def __close_file(self, tid, fid):
+        self.__send_smb_packet(SMB_COM_CLOSE, 0, 0, 0, tid, 0, pack('<HL', fid, 0), '')
+
+    def __trans(self, tid, setup, name, param, data, timeout = None):
+        data_len = len(data)
+        name_len = len(name)
+        param_len = len(param)
+        setup_len = len(setup)
+
+        assert setup_len & 0x01 == 0
+
+        param_offset = name_len + setup_len + 63
+        data_offset = param_offset + param_len
+            
+        self.__send_smb_packet(SMB_COM_TRANSACTION, 0, 0, 0, tid, 0, pack('<HHHHBBHLHHHHHBB', param_len, data_len, 1024, 65504, 0, 0, 0, 0, 0, param_len, param_offset, data_len, data_offset, setup_len / 2, 0) + setup, name + param + data)
+
+    def __trans2(self, tid, setup, name, param, data, timeout = None):
+        data_len = len(data)
+        name_len = len(name)
+        param_len = len(param)
+        setup_len = len(setup)
+
+        assert setup_len & 0x01 == 0
+
+        param_offset = name_len + setup_len + 63
+        data_offset = param_offset + param_len
+            
+        self.__send_smb_packet(SMB_COM_TRANSACTION2, 0, 0, 0, tid, 0, pack('<HHHHBBHLHHHHHBB', param_len, data_len, 1024, 65504, 0, 0, 0, 0, 0, param_len, param_offset, data_len, data_offset, setup_len / 2, 0) + setup, name + param + data)
+
+    def __nonraw_retr_file(self, tid, fid, offset, datasize, callback, timeout = None):
+        max_buf_size = self.__max_transmit_size & ~0x3ff  # Read in multiple KB blocks
+        read_offset = offset
+        while read_offset < datasize:
+            self.__send_smb_packet(SMB_COM_READ_ANDX, 0, 0, 0, tid, 0, pack('<BBHHLHHLH', 0xff, 0, 0, fid, read_offset, max_buf_size, max_buf_size, 0, 0), '')
+            while 1:
+                data = self.__sess.recv_packet(timeout)
+                if data:
+                    cmd, err_class, err_code, flags1, flags2, _, _, mid, params, d = self.__decode_smb(data)
+                    if cmd == SMB_COM_READ_ANDX:
+                        if err_class == 0x00 and err_code == 0x00:
+                            offset = unpack('<H', params[2:4])[0]
+                            data_len, dataoffset = unpack('<HH', params[10+offset:14+offset])
+                            if data_len == len(d):
+                                callback(d)
+                            else:
+                                callback(d[dataoffset - 59:dataoffset - 59 + data_len])
+                                read_offset = read_offset + data_len
+                            break
+                        else:
+                            raise SessionError, ( 'Non-raw retr file failed. (ErrClass: %d and ErrCode: %d)' % ( err_class, err_code ), err_class, err_code )
+
+    def __raw_retr_file(self, tid, fid, offset, datasize, callback, timeout = None):
+        read_offset = offset
+        while read_offset < datasize:
+            self.__send_smb_packet(SMB_COM_READ_RAW, 0, 0, 0, tid, 0, pack('<HLHHLH', fid, read_offset, 0xffff, 0, 0, 0), '')
+            data = self.__sess.recv_packet(timeout)
+            if data:
+                callback(data)
+                read_offset = read_offset + len(data)
+            else:
+                # No data returned. Need to send SMB_COM_READ_ANDX to find out what is the error.
+                self.__send_smb_packet(SMB_COM_READ_ANDX, 0, 0, 0, tid, 0, pack('<BBHHLHHLH', 0xff, 0, 0, fid, read_offset, max_buf_size, max_buf_size, 0, 0), '')
+                while 1:
+                    data = self.__sess.recv_packet(timeout)
+                    if data:
+                        cmd, err_class, err_code, flags1, flags2, _, _, mid, params, d = self.__decode_smb(data)
+                        if cmd == SMB_COM_READ_ANDX:
+                            if err_class == 0x00 and err_code == 0x00:
+                                offset = unpack('<H', params[2:4])[0]
+                                data_len, dataoffset = unpack('<HH', params[10+offset:14+offset])
+                                if data_len == 0:
+                                    # Premature EOF!
+                                    return
+                                # By right we should not have data returned in the reply.
+                                elif data_len == len(d):
+                                    callback(d)
+                                else:
+                                    callback(d[dataoffset - 59:dataoffset - 59 + data_len])
+                                read_offset = read_offset + data_len
+                                break
+                            else:
+                                raise SessionError, ( 'Raw retr file failed. (ErrClass: %d and ErrCode: %d)' % ( err_class, err_code ), err_class, err_code )
+
+    def __nonraw_stor_file(self, tid, fid, offset, datasize, callback, timeout = None):
+        max_buf_size = self.__max_transmit_size & ~0x3ff  # Write in multiple KB blocks
+        write_offset = offset
+        while 1:
+            data = callback(max_buf_size)
+            if not data:
+                break
+            
+            self.__send_smb_packet(SMB_COM_WRITE_ANDX, 0, 0, 0, tid, 0, pack('<BBHHLLHHHHH', 0xff, 0, 0, fid, write_offset, 0, 0, 0, 0, len(data), 59), data)
+            
+            while 1:
+                data = self.__sess.recv_packet(timeout)
+                if data:
+                    cmd, err_class, err_code, flags1, flags2, _, _, mid, params, d = self.__decode_smb(data)
+                    if cmd == SMB_COM_WRITE_ANDX:
+                        if err_class == 0x00 and err_code == 0x00:
+                            offset = unpack('<H', params[2:4])[0]
+                            write_offset = write_offset + unpack('<H', params[4+offset:6+offset])[0]
+                            break
+                        else:
+                            raise SessionError, ( 'Non-raw store file failed. (ErrClass: %d and ErrCode: %d)' % ( err_class, err_code ), err_class, err_code )
+
+    def __raw_stor_file(self, tid, fid, offset, datasize, callback, timeout = None):
+        write_offset = offset
+        while 1:
+            read_data = callback(65535)
+            if not read_data:
+                break
+
+            read_len = len(read_data)
+            self.__send_smb_packet(SMB_COM_WRITE_RAW, 0, 0, 0, tid, 0, pack('<HHHLLHLHH', fid, read_len, 0, write_offset, 0, 0, 0, 0, 59), '')
+            while 1:
+                data = self.__sess.recv_packet(timeout)
+                if data:
+                    cmd, err_class, err_code, flags1, flags2, _, _, mid, params, d = self.__decode_smb(data)
+                    if cmd == SMB_COM_WRITE_RAW:
+                        if err_class == 0x00 and err_code == 0x00:
+                            self.__sess.send_packet(read_data)
+                            write_offset = write_offset + read_len
+                            break
+                        else:
+                            raise SessionError, ( 'Raw store file failed. (ErrClass: %d and ErrCode: %d)' % ( err_class, err_code ), err_class, err_code )
+
+        # We need to close fid to check whether the last raw packet is written successfully
+        self.__send_smb_packet(SMB_COM_CLOSE, 0, 0, 0, tid, 0, pack('<HL', fid, 0), '')
+        while 1:
+            data = self.__sess.recv_packet(timeout)
+            if data:
+                cmd, err_class, err_code, flags1, flags2, _, _, mid, params, d = self.__decode_smb(data)
+                if cmd == SMB_COM_CLOSE:
+                    if err_class == 0x00 and err_code == 0x00:
+                        return
+                    else:
+                        raise SessionError, ( 'Raw store file failed. (ErrClass: %d and ErrCode: %d)' % ( err_class, err_code ), err_class, err_code )
+
+    def __expand_des_key(self, key):
+        # Expand the key from a 7-byte password key into a 8-byte DES key
+        s = chr(((ord(key[0]) >> 1) & 0x7f) << 1)
+        s = s + chr(((ord(key[0]) & 0x01) << 6 | ((ord(key[1]) >> 2) & 0x3f)) << 1)
+        s = s + chr(((ord(key[1]) & 0x03) << 5 | ((ord(key[2]) >> 3) & 0x1f)) << 1)
+        s = s + chr(((ord(key[2]) & 0x07) << 4 | ((ord(key[3]) >> 4) & 0x0f)) << 1)
+        s = s + chr(((ord(key[3]) & 0x0f) << 3 | ((ord(key[4]) >> 5) & 0x07)) << 1)
+        s = s + chr(((ord(key[4]) & 0x1f) << 2 | ((ord(key[5]) >> 6) & 0x03)) << 1)
+        s = s + chr(((ord(key[5]) & 0x3f) << 1 | ((ord(key[6]) >> 7) & 0x01)) << 1)
+        s = s + chr((ord(key[6]) & 0x7f) << 1)
+        return s
+
+    def __deshash(self, password):
+        # This is done according to Samba's encryption specification (docs/html/ENCRYPTION.html)
+        if len(password) > 14:
+            p14 = string.upper(password[:14])
+        else:
+            p14 = string.upper(password) + '\0' * (14 - len(password))
+
+		
+        p21 = DES(self.__expand_des_key(p14[:7])).encrypt('\x4b\x47\x53\x21\x40\x23\x24\x25') + DES(self.__expand_des_key(p14[7:])).encrypt('\x4b\x47\x53\x21\x40\x23\x24\x25') + '\0' * 5
+        return DES(self.__expand_des_key(p21[:7])).encrypt(self.__enc_key) + DES(self.__expand_des_key(p21[7:14])).encrypt(self.__enc_key) + DES(self.__expand_des_key(p21[14:])).encrypt(self.__enc_key)
+
+    def is_login_required(self):
+        return self.__login_required
+
+    def login(self, name, password, domain = '', timeout = None):
+        # Password is only encrypted if the server passed us an "encryption" during protocol dialect
+        # negotiation and mxCrypto's DES module is loaded.
+        if self.__enc_key and DES:
+            password = self.__deshash(password)
+            
+        self.__send_smb_packet(SMB_COM_SESSION_SETUP_ANDX, 0, 0, 0, 0, 0, pack('<ccHHHHLHL', '\xff', '\0', 0, 65535, 2, 0, 0, len(password), 0), password + name + '\0' + domain + '\0' + os.name + '\0' + 'pysmb\0')
+
+        while 1:
+            data = self.__sess.recv_packet(timeout)
+            if data:
+                cmd, err_class, err_code, flags1, flags2, _, uid, mid, params, d = self.__decode_smb(data)
+                if cmd == SMB_COM_SESSION_SETUP_ANDX:
+                    if err_class == 0x00 and err_code == 0x00:
+                        # We will need to use this uid field for all future requests/responses
+                        self.__uid = uid
+                        return 1
+                    else:
+                        raise SessionError, ( 'Authentication failed. (ErrClass: %d and ErrCode: %d)' % ( err_class, err_code ), err_class, err_code )
+
+    def list_shared(self, timeout = None):
+        tid = self.__connect_tree('\\\\' + self.__remote_name + '\\IPC$', SERVICE_ANY, timeout)
+        self.__trans(tid, '', '\\PIPE\\LANMAN\0', '\x00\x00WrLeh\0B13BWz\0\x01\x00\xe0\xff', '')
+
+        try:
+            share_list = [ ]
+            while 1:
+                data = self.__sess.recv_packet(timeout)
+                if data:
+                    cmd, err_class, err_code, flags1, flags2, _, _, mid, params, d = self.__decode_smb(data)
+                    if cmd == SMB_COM_TRANSACTION:
+                        if err_class == 0x00 and err_code == 0x00:
+                            has_more, _, transparam, transdata = self.__decode_trans(params, d)
+                            converter, numentries = unpack('<HH', transparam[2:6])
+                            maxlength = len(transdata)
+                            offset = 0
+                            for i in range(0, numentries):
+                                name = transdata[offset:string.find(transdata, '\0', offset)]
+                                type, commentoffset = unpack('<HH', transdata[offset + 14:offset + 18])
+                                if commentoffset > maxlength:
+                                    comment = ''
+                                else:
+                                    comment = transdata[commentoffset:string.find(transdata, '\0', commentoffset)]
+                                offset = offset + 20
+                                share_list.append(SharedDevice(name, type, comment))
+                            return share_list
+                        else:
+                            raise SessionError, ( 'List directory failed. (ErrClass: %d and ErrCode: %d)' % ( err_class, err_code ), err_class, err_code )
+        finally:
+            self.__disconnect_tree(tid)
+
+    def list_path(self, service, path = '*', timeout = None):
+        path = string.replace(path, '/', '\\')
+            
+        tid = self.__connect_tree('\\\\' + self.__remote_name + '\\' + service, SERVICE_ANY, timeout)
+        try:
+            self.__trans2(tid, '\x01\x00', '\x00', '\x16\x00\x00\x02\x06\x00\x04\x01\x00\x00\x00\x00\x5c' + path + '\x00', '')
+            while 1:
+                data = self.__sess.recv_packet(timeout)
+                if data:
+                    cmd, err_class, err_code, flags1, flags2, _, _, mid, params, d = self.__decode_smb(data)
+                    if cmd == SMB_COM_TRANSACTION2:
+                        if err_class == 0x00 and err_code == 0x00:
+                            has_more, _, transparam, transdata = self.__decode_trans(params, d)
+                            sid, searchcnt, eos, erroffset, lastnameoffset = unpack('<HHHHH', transparam)
+                            files = [ ]
+                            offset = 0
+                            data_len = len(transdata)
+                            while offset < data_len:
+                                nextentry, fileindex, lowct, highct, lowat, highat, lowmt, highmt, lowcht, hightcht, loweof, higheof, lowsz, highsz, attrib, longnamelen, easz, shortnamelen = unpack('<lL12LLlLB', transdata[offset:offset + 69])
+                                files.append(SharedFile(highct << 32 | lowct, highat << 32 | lowat, highmt << 32 | lowmt, higheof << 32 | loweof, highsz << 32 | lowsz, attrib, transdata[offset + 70:offset + 70 + shortnamelen], transdata[offset + 94:offset + 94 + longnamelen]))
+                                offset = offset + nextentry
+                            return files
+                        else:
+                            raise SessionError, ( 'List path failed. (ErrClass: %d and ErrCode: %d)' % ( err_class, err_code ), err_class, err_code )
+        finally:
+            self.__disconnect_tree(tid)
+
+    def retr_file(self, service, filename, callback, mode = SMB_O_OPEN, offset = 0, timeout = None):
+        filename = string.replace(filename, '/', '\\')
+
+        fid = -1
+        tid = self.__connect_tree('\\\\' + self.__remote_name + '\\' + service, SERVICE_ANY, timeout)
+        try:
+            fid, attrib, lastwritetime, datasize, grantedaccess, filetype, devicestate, action, serverfid = self.__open_file(tid, filename, mode, SMB_ACCESS_READ | SMB_SHARE_DENY_WRITE)
+
+            if self.__can_read_raw:
+                self.__raw_retr_file(tid, fid, offset, datasize, callback)
+            else:
+                self.__nonraw_retr_file(tid, fid, offset, datasize, callback, timeout)
+        finally:
+            if fid >= 0:
+                self.__close_file(tid, fid)
+            self.__disconnect_tree(tid)
+
+    def stor_file(self, service, filename, callback, mode = SMB_O_CREAT | SMB_O_TRUNC, offset = 0, timeout = None):
+        filename = string.replace(filename, '/', '\\')
+
+        fid = -1
+        tid = self.__connect_tree('\\\\' + self.__remote_name + '\\' + service, SERVICE_ANY, timeout)
+        try:
+            fid, attrib, lastwritetime, datasize, grantedaccess, filetype, devicestate, action, serverfid = self.__open_file(tid, filename, mode, SMB_ACCESS_WRITE | SMB_SHARE_DENY_WRITE)
+
+            # If the max_transmit buffer size is more than 16KB, upload process using non-raw mode is actually
+            # faster than using raw-mode.
+            if self.__max_transmit_size < 16384 and self.__can_write_raw:
+                # Once the __raw_stor_file returns, fid is already closed
+                self.__raw_stor_file(tid, fid, offset, datasize, callback, timeout)
+                fid = -1
+            else:
+                self.__nonraw_stor_file(tid, fid, offset, datasize, callback, timeout)
+        finally:
+            if fid >= 0:
+                self.__close_file(tid, fid)
+            self.__disconnect_tree(tid)
+
+    def copy(self, src_service, src_path, dest_service, dest_path, callback = None, write_mode = SMB_O_CREAT | SMB_O_TRUNC, timeout = None):
+        dest_path = string.replace(dest_path, '/', '\\')
+        src_path = string.replace(src_path, '/', '\\')
+        src_tid = self.__connect_tree('\\\\' + self.__remote_name + '\\' + src_service, SERVICE_ANY, timeout)
+
+        dest_tid = -1
+        try:
+            if src_service == dest_service:
+                dest_tid = src_tid
+            else:
+                dest_tid = self.__connect_tree('\\\\' + self.__remote_name + '\\' + dest_service, SERVICE_ANY, timeout)
+            
+            dest_fid = self.__open_file(dest_tid, dest_path, write_mode, SMB_ACCESS_WRITE | SMB_SHARE_DENY_WRITE)[0]
+            src_fid, _, _, src_datasize, _, _, _, _, _ = self.__open_file(src_tid, src_path, SMB_O_OPEN, SMB_ACCESS_READ | SMB_SHARE_DENY_WRITE)
+
+            if callback:
+                callback(0, src_datasize)
+
+            max_buf_size = (self.__max_transmit_size >> 10) << 10
+            read_offset = 0
+            write_offset = 0
+            while read_offset < src_datasize:
+                self.__send_smb_packet(SMB_COM_READ_ANDX, 0, 0, 0, src_tid, 0, pack('<BBHHLHHLH', 0xff, 0, 0, src_fid, read_offset, max_buf_size, max_buf_size, 0, 0), '')
+                while 1:
+                    data = self.__sess.recv_packet(timeout)
+                    if data:
+                        cmd, err_class, err_code, flags1, flags2, _, _, mid, params, d = self.__decode_smb(data)
+                        if cmd == SMB_COM_READ_ANDX:
+                            if err_class == 0x00 and err_code == 0x00:
+                                offset = unpack('<H', params[2:4])[0]
+                                data_len, dataoffset = unpack('<HH', params[10+offset:14+offset])
+                                if data_len == len(d):
+                                    self.__send_smb_packet(SMB_COM_WRITE_ANDX, 0, 0, 0, dest_tid, 0, pack('<BBHHLLHHHHH', 0xff, 0, 0, dest_fid, write_offset, 0, 0, 0, 0, data_len, 59), d)
+                                else:
+                                    self.__send_smb_packet(SMB_COM_WRITE_ANDX, 0, 0, 0, dest_tid, 0, pack('<BBHHLLHHHHH', 0xff, 0, 0, dest_fid, write_offset, 0, 0, 0, 0, data_len, 59), d[dataoffset - 59:dataoffset - 59 + data_len])
+                                while 1:
+                                    data = self.__sess.recv_packet(timeout)
+                                    if data:
+                                        cmd, err_class, err_code, flags1, flags2, _, _, mid, params, d = self.__decode_smb(data)
+                                        if cmd == SMB_COM_WRITE_ANDX:
+                                            if err_class == 0x00 and err_code == 0x00:
+                                                offset = unpack('<H', params[2:4])[0]
+                                                write_offset = write_offset + unpack('<H', params[4+offset:6+offset])[0]
+                                                break
+                                            else:
+                                                raise SessionError, ( 'Copy (write) failed. (ErrClass: %d and ErrCode: %d)' % ( err_class, err_code ), err_class, err_code )
+                                read_offset = read_offset + data_len
+                                if callback:
+                                    callback(read_offset, src_datasize)
+                                break
+                            else:
+                                raise SessionError, ( 'Copy (read) failed. (ErrClass: %d and ErrCode: %d)' % ( err_class, err_code ), err_class, err_code )
+                
+        finally:
+            self.__disconnect_tree(src_tid)
+            if dest_tid > -1 and src_service != dest_service:
+                self.__disconnect_tree(dest_tid)
+
+    def check_dir(self, service, path, timeout = None):
+        tid = self.__connect_tree('\\\\' + self.__remote_name + '\\' + service, SERVICE_ANY, timeout)
+        try:
+            self.__send_smb_packet(SMB_COM_CHECK_DIR, 0, 0x08, 0, tid, 0, '', '\x04' + path + '\x00')
+
+            while 1:
+                data = self.__sess.recv_packet(timeout)
+                if data:
+                    cmd, err_class, err_code, flags1, flags2, _, _, mid, params, d = self.__decode_smb(data)
+                    if cmd == SMB_COM_CHECK_DIR:
+                        if err_class == 0x00 and err_code == 0x00:
+                            return
+                        else:
+                            raise SessionError, ( 'Check directory failed. (ErrClass: %d and ErrCode: %d)' % ( err_class, err_code ), err_class, err_code )
+        finally:
+            self.__disconnect_tree(tid)
+
+    def remove(self, service, path, timeout = None):
+        # Perform a list to ensure the path exists
+        self.list_path(service, path, timeout)
+
+        tid = self.__connect_tree('\\\\' + self.__remote_name + '\\' + service, SERVICE_ANY, timeout)
+        try:
+            self.__send_smb_packet(SMB_COM_DELETE, 0, 0x08, 0, tid, 0, pack('<H', ATTR_HIDDEN | ATTR_SYSTEM | ATTR_ARCHIVE), '\x04' + path + '\x00')
+
+            while 1:
+                data = self.__sess.recv_packet(timeout)
+                if data:
+                    cmd, err_class, err_code, flags1, flags2, _, _, mid, params, d = self.__decode_smb(data)
+                    if cmd == SMB_COM_DELETE:
+                        if err_class == 0x00 and err_code == 0x00:
+                            return
+                        else:
+                            raise SessionError, ( 'Delete file failed. (ErrClass: %d and ErrCode: %d)' % ( err_class, err_code ), err_class, err_code )
+        finally:
+            self.__disconnect_tree(tid)
+
+    def rmdir(self, service, path, timeout = None):
+        # Check that the directory exists
+        self.check_dir(service, path, timeout)
+
+        tid = self.__connect_tree('\\\\' + self.__remote_name + '\\' + service, SERVICE_ANY, timeout)
+        try:
+            self.__send_smb_packet(SMB_COM_DELETE_DIR, 0, 0x08, 0, tid, 0, '', '\x04' + path + '\x00')
+
+            while 1:
+                data = self.__sess.recv_packet(timeout)
+                if data:
+                    cmd, err_class, err_code, flags1, flags2, _, _, mid, params, d = self.__decode_smb(data)
+                    if cmd == SMB_COM_DELETE_DIR:
+                        if err_class == 0x00 and err_code == 0x00:
+                            return
+                        else:
+                            raise SessionError, ( 'Delete directory failed. (ErrClass: %d and ErrCode: %d)' % ( err_class, err_code ), err_class, err_code )
+        finally:
+            self.__disconnect_tree(tid)
+
+    def mkdir(self, service, path, timeout = None):
+        tid = self.__connect_tree('\\\\' + self.__remote_name + '\\' + service, SERVICE_ANY, timeout)
+        try:
+            self.__send_smb_packet(SMB_COM_CREATE_DIR, 0, 0x08, 0, tid, 0, '', '\x04' + path + '\x00')
+
+            while 1:
+                data = self.__sess.recv_packet(timeout)
+                if data:
+                    cmd, err_class, err_code, flags1, flags2, _, _, mid, params, d = self.__decode_smb(data)
+                    if cmd == SMB_COM_CREATE_DIR:
+                        if err_class == 0x00 and err_code == 0x00:
+                            return
+                        else:
+                            raise SessionError, ( 'Create directory failed. (ErrClass: %d and ErrCode: %d)' % ( err_class, err_code ), err_class, err_code )
+        finally:
+            self.__disconnect_tree(tid)
+
+    def rename(self, service, old_path, new_path, timeout = None):
+        tid = self.__connect_tree('\\\\' + self.__remote_name + '\\' + service, SERVICE_ANY, timeout)
+        try:
+            self.__send_smb_packet(SMB_COM_RENAME, 0, 0x08, 0, tid, 0, pack('<H', ATTR_SYSTEM | ATTR_HIDDEN | ATTR_DIRECTORY), '\x04' + old_path + '\x00\x04' + new_path + '\x00')
+
+            while 1:
+                data = self.__sess.recv_packet(timeout)
+                if data:
+                    cmd, err_class, err_code, flags1, flags2, _, _, mid, params, d = self.__decode_smb(data)
+                    if cmd == SMB_COM_RENAME:
+                        if err_class == 0x00 and err_code == 0x00:
+                            return 
+                        else:
+                            raise SessionError, ( 'Rename failed. (ErrClass: %d and ErrCode: %d)' % ( err_class, err_code ), err_class, err_code )
+        finally:
+            self.__disconnect_tree(tid)
+
+
+
+ERRDOS = { 1: 'Invalid function',
+           2: 'File not found',
+           3: 'Invalid directory',
+           4: 'Too many open files',
+           5: 'Access denied',
+           6: 'Invalid file handle. Please file a bug report.',
+           7: 'Memory control blocks destroyed',
+           8: 'Out of memory',
+           9: 'Invalid memory block address',
+           10: 'Invalid environment',
+           11: 'Invalid format',
+           12: 'Invalid open mode',
+           13: 'Invalid data',
+           15: 'Invalid drive',
+           16: 'Attempt to remove server\'s current directory',
+           17: 'Not the same device',
+           18: 'No files found',
+           32: 'Sharing mode conflicts detected',
+           33: 'Lock request conflicts detected',
+           80: 'File already exists'
+           }
+
+ERRSRV = { 1: 'Non-specific error',
+           2: 'Bad password',
+           4: 'Access denied',
+           5: 'Invalid tid. Please file a bug report.',
+           6: 'Invalid network name',
+           7: 'Invalid device',
+           49: 'Print queue full',
+           50: 'Print queue full',
+           51: 'EOF on print queue dump',
+           52: 'Invalid print file handle',
+           64: 'Command not recognized. Please file a bug report.',
+           65: 'Internal server error',
+           67: 'Invalid path',
+           69: 'Invalid access permissions',
+           71: 'Invalid attribute mode',
+           81: 'Server is paused',
+           82: 'Not receiving messages',
+           83: 'No room to buffer messages',
+           87: 'Too many remote user names',
+           88: 'Operation timeout',
+           89: 'Out of resources',
+           91: 'Invalid user handle. Please file a bug report.',
+           250: 'Temporarily unable to support raw mode for transfer',
+           251: 'Temporarily unable to support raw mode for transfer',
+           252: 'Continue in MPX mode',
+           65535: 'Unsupported function'
+           }
+
+ERRHRD = { 19: 'Media is write-protected',
+           20: 'Unknown unit',
+           21: 'Drive not ready',
+           22: 'Unknown command',
+           23: 'CRC error',
+           24: 'Bad request',
+           25: 'Seek error',
+           26: 'Unknown media type',
+           27: 'Sector not found',
+           28: 'Printer out of paper',
+           29: 'Write fault',
+           30: 'Read fault',
+           31: 'General failure',
+           32: 'Open conflicts with an existing open',
+           33: 'Invalid lock request',
+           34: 'Wrong disk in drive',
+           35: 'FCBs not available',
+           36: 'Sharing buffer exceeded'
+           }

Added: trunk/ZopeProducts/exUserFolder/AuthSources/smbAuthSource/smbAuthSource.py
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/smbAuthSource/smbAuthSource.py	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/smbAuthSource/smbAuthSource.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,259 @@
+#
+# Extensible User Folder
+# 
+# SMB Authentication Source for exUserFolder
+#
+# (C) Copyright 2000,2001 The Internet (Aust) Pty Ltd
+# ACN: 082 081 472	ABN: 83 082 081 472
+# All Rights Reserved
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# Author: Andrew Milton <akm at theinternet.com.au>
+# $Id: smbAuthSource.py,v 1.1 2004/11/10 14:15:51 akm Exp $
+
+# Uses pysmb
+# Copyright (C) 2001 Michael Teo <michaelteo at bigfoot.com>
+
+import string, Acquisition
+
+from Globals import HTMLFile, MessageDialog, INSTANCE_HOME
+
+from OFS.Folder import Folder
+
+from Products.exUserFolder.exUserFolder import exUserFolder
+from Products.exUserFolder.Plugins import PluginRegister
+
+import smb, nmb, socket
+from smb import SessionError
+import sys
+
+import zLOG
+
+def manage_addsmbAuthSource(self, REQUEST):
+	""" Add a smb Auth Source """
+
+	host=REQUEST['smbauth_host']
+	domain=REQUEST['smbauth_domain']
+	winsserver=REQUEST['smbauth_winsserver']
+
+	ob=smbAuthSource(host, domain, winsserver)
+	self._setObject('smbAuthSource', ob, None, None, 0)
+	self.currentAuthSource=ob
+
+
+manage_addsmbAuthSourceForm=HTMLFile('manage_addsmbAuthSourceForm', globals())
+manage_editsmbAuthSourceForm=HTMLFile('manage_editsmbAuthSourceForm', globals())
+
+
+class smbAuthSource(Folder):
+	""" """
+
+	meta_type = 'Authentication Source'
+	id = 'smbAuthSource'
+	title = 'SMB Authentication'
+	icon = 'misc_/exUserFolder/exUserFolderPlugin.gif'
+
+	manage_editForm=manage_editsmbAuthSourceForm
+	manage_tabs=Acquisition.Acquired
+
+	_v_smb=None
+	_v_netbios=None
+	_v_address=None
+
+	def __init__(self, host, domain, winsserver):
+
+		self._setSMBData(host, domain, winsserver)
+
+	def manage_editAuthSource(self, REQUEST):
+		'''Handle output of manage_main - change ZSmb instance properties.
+		If REQUEST.secret is None, old secret will be used.'''
+
+		host=REQUEST['host']
+		domain=REQUEST['domain']
+		winsserver=REQUEST['winsserver']
+
+		self._setSMBData(host, domain, winsserver)
+
+		if REQUEST is not None:
+			return self.MessageDialog(self,REQUEST=REQUEST,
+				title = 'Edited',
+				message = "Properties for %s changed." % self.id,
+				action = 'manage_editAuthSourceForm')
+		
+	#
+	# We don't let you delete, create, or edit users
+	#
+	def deleteUsers(self, userids):
+		pass
+
+	def createUser(self, username, password, roles, groups=[]):
+		pass
+
+
+	def updateUser(self, username, password, roles, groups=[]):
+		self.currentPropSource.setUserProperty(username=username, key='_roles', value=roles)
+		self.currentPropSource.setUserProperty(username=username, key='_groups', value=groups)
+	
+	def listUserNames(self):
+		return []
+
+	def listUsers(self):
+		return []
+
+	def listOneUser(self, username):
+		roles=[]
+		groups=[]
+
+		username = string.lower(username)		
+
+		if self.currentPropSource:
+			roles=self.currentPropSource.getUserProperty(username=username, key='_roles', default=[])
+			groups=self.currentPropSource.getUserProperty(username=username, key='_groups', default=[])
+
+		if not roles:
+			roles=[] # make sure it's a list...
+		if not groups:
+			groups=[]
+
+		zLOG.LOG('smbAuthSource',
+				 zLOG.DEBUG,
+				 "listOneUser returning {username: '%s', password: '', roles: %s}" % (username, roles)
+				)
+		return [{'username':username,
+				 'password':'',
+				 'roles':	roles,
+				 'groups':	groups},]
+
+	def getUsers(self):
+		return []
+	
+	def authenticate(self, username, password):
+		'Authenticate a username/password combination against the Smb server'
+		if username == '':
+			zLOG.LOG('smbAuthSource',
+				 zLOG.DEBUG,
+				 'got null username; auth failed' )
+			return 0
+	
+		# Please don't try to make retries a knob without
+		# checking _authenticate_retry -- we assume it's 3 in
+		# there when we log retry-related events.  It shouldn't
+		# break anything, but it will make log entries
+		# inaccurate. --mb
+
+		return self._authenticate_retry(username, password, 3)
+
+	def _authenticate_retry(self, username, password, retries):
+		try:
+			self._getSMB().login(username, password, self._domain)
+			if retries < 3:
+				zLOG.LOG('smbAuthSource', zLOG.BLATHER,
+						 'authenticated %s\%s after %d retries' % (self.domain(), username, (3 - retries)) )
+			return 1
+
+		except SessionError, e:
+			# Happens when server answers "Authentication failed", at least under Win32
+			zLOG.LOG('smbAuthSource',
+				 zLOG.BLATHER,
+				 'SessionError (%s) for %s\%s (usually means login failure); auth failed' % (e, self.domain(), username),
+				 '\n',
+				 sys.exc_info() )
+			return 0
+
+		except (nmb.NetBIOSError, nmb.NetBIOSTimeout), e:
+			zLOG.LOG('smbAuthSource', zLOG.ERROR,
+					 'NetBIOS error (%s) for %s\%s; auth failed' % (e, self.domain(), username),
+					 '\n', sys.exc_info() )
+			return 0
+
+		except socket.error, (errno, strerror):
+			zLOG.LOG('smbAuthSource',
+				 zLOG.DEBUG,
+				 'socket error %s (%s) for %s\%s' % (errno, strerror, self.domain(), username),
+				 '\n',
+				 sys.exc_info() )
+			# TODO: It would be nice to check for appropriate errnos for different platforms.
+			# As for now, we just won't worry about it and act the same whatever happens
+			if retries > 0:
+				self._resetSMB()
+				return self._authenticate_retry(username, password, retries - 1)
+			else:
+				zLOG.LOG('smbAuthSource',
+					 zLOG.ERROR,
+					 'socket error %s (%s) for %s\%s; auth failed' % (errno, strerror, self.domain(), username),
+					 '\n',
+					 sys.exc_info() )
+				return 0 
+	
+	remoteAuthMethod=authenticate
+
+	def host(self): return self._host
+	def domain(self): return self._domain
+	def port(self): return self._port
+	def retries(self): return self._retries
+	def timeout(self): return self._timeout
+	def winsserver(self): return self._winsserver
+
+	def _getNetBIOS(self):
+		if not self._v_netbios:
+			self._v_netbios = nmb.NetBIOS()
+			# check if we should use WINS
+			if self._winsserver:
+				self._v_netbios.set_nameserver(self._winsserver)
+		return self._v_netbios
+
+	def _getAddress(self):
+		if not self._v_address:
+			addressList = self._getNetBIOS().gethostbyname(self._host)
+
+			# it seems lookup with a WINS server does not
+			# raise an error when not found, it just returns
+			# an empty list, so we fake an error --rochael
+			if not addressList:
+				raise nmb.NetBIOSError("smbAuthorization: Authentication server '%s' is not known to WINS server '%s'" %
+						       (self._host, self._winsserver) )
+                        self._v_address = addressList[0].get_ip()
+		return self._v_address
+
+	def _getSMB(self):
+		if not self._v_smb:
+			self._v_smb = smb.SMB(self._host, self._getAddress())
+		return self._v_smb
+		
+	def _setSMBData(self, host, domain, winsserver):
+
+		self._host = host
+		self._domain = domain
+		self._winsserver = winsserver
+
+		# Reset Smb objects so new values take effect. This is
+		# why we don't allow direct access to the attributes
+		self._resetSMB()
+
+	def _resetSMB(self):
+		self._v_netbios = None
+		self._v_smb = None
+		self._v_address = None
+
+		# if this doesn't raise an Exception, the smb data is valid
+		return self._getSMB()
+
+
+smbAuthReg=PluginRegister('smbAuthSource',
+			  'SMB Authentication Source',
+			  smbAuthSource, manage_addsmbAuthSourceForm,
+			  manage_addsmbAuthSource,
+			  manage_editsmbAuthSourceForm)
+
+exUserFolder.authSources['smbAuthSource']=smbAuthReg

Added: trunk/ZopeProducts/exUserFolder/AuthSources/usAuthSource/.cvsignore
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/usAuthSource/.cvsignore	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/usAuthSource/.cvsignore	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,4 @@
+*.pyc
+*.pyo
+*~
+.*.swp

Added: trunk/ZopeProducts/exUserFolder/AuthSources/usAuthSource/__init__.py
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/usAuthSource/__init__.py	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/usAuthSource/__init__.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,2 @@
+# $Id: __init__.py,v 1.1 2004/11/10 14:15:52 akm Exp $
+import usAuthSource

Added: trunk/ZopeProducts/exUserFolder/AuthSources/usAuthSource/manage_addusAuthSourceForm.dtml
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/usAuthSource/manage_addusAuthSourceForm.dtml	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/usAuthSource/manage_addusAuthSourceForm.dtml	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,10 @@
+<dtml-var "DialogHeader(_.None,_,DialogTitle='Add User Supplied Authentication Source')">
+<FORM ACTION="&dtml-URL;" METHOD="POST">
+<dtml-in "REQUEST.form.keys()">
+<input type="HIDDEN" name="<dtml-var sequence-item>" value="<dtml-var "REQUEST[_.getitem('sequence-item',0)]">">
+</dtml-in>
+<input type="HIDDEN" name="doProp" value="1">
+<b><dtml-babel src="'en'">This authentication source requires no user configuration items at this time.</dtml-babel></b><br>
+<INPUT TYPE="SUBMIT" VALUE=" <dtml-babel src="'en'">NEXT</dtml-babel> ">
+</FORM>
+<dtml-var DialogFooter>

Added: trunk/ZopeProducts/exUserFolder/AuthSources/usAuthSource/manage_editusAuthSourceForm.dtml
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/usAuthSource/manage_editusAuthSourceForm.dtml	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/usAuthSource/manage_editusAuthSourceForm.dtml	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,7 @@
+<dtml-var "DialogHeader(_.None,_,DialogTitle='User Supplied Authentication Source',dialog_width='100%')">
+<dtml-var manage_tabs>
+<FORM ACTION="manage_main" METHOD="POST">
+<b><dtml-babel src="'en'">This property source requires no user configuration items at this time.</dtml-babel></b><br>
+<INPUT TYPE="SUBMIT" VALUE=" <dtml-babel src="'en'">OK</dtml-babel> ">
+</FORM>
+<dtml-var DialogFooter>

Added: trunk/ZopeProducts/exUserFolder/AuthSources/usAuthSource/usAuthSource.py
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/usAuthSource/usAuthSource.py	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/usAuthSource/usAuthSource.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,157 @@
+#
+# Extensible User Folder
+# 
+# User Supplied Authentication Source for exUserFolder
+#
+# (C) Copyright 2001 The Internet (Aust) Pty Ltd
+# ACN: 082 081 472  ABN: 83 082 081 472
+# All Rights Reserved
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# Author: Andrew Milton <akm at theinternet.com.au>
+# $Id: usAuthSource.py,v 1.1 2004/11/10 14:15:52 akm Exp $
+
+#
+# This class only authenticates users, it stores no properties.
+#
+
+import string
+
+from Globals import HTMLFile, MessageDialog, INSTANCE_HOME
+
+from OFS.Folder import Folder
+
+from Products.ZSQLMethods.SQL import SQL
+
+from Products.exUserFolder.exUserFolder import exUserFolder
+from Products.exUserFolder.Plugins import PluginRegister
+
+try:
+	from crypt import crypt
+except:
+	from Products.exUserFolder.fcrypt.fcrypt import crypt
+
+def manage_addusAuthSource(self, REQUEST):
+	""" Add a Postgres Auth Source """
+
+	o = usAuthSource()
+	self._setObject('usAuthSource', o, None, None, 0)
+	o=getattr(self,'usAuthSource')
+	if hasattr(o, 'postInitialisation'):
+		o.postInitialisation(REQUEST)
+	
+	self.currentAuthSource=o
+	return ''
+
+manage_addusAuthSourceForm=HTMLFile('manage_addusAuthSourceForm', globals())
+manage_editusAuthSourceForm=HTMLFile('manage_editusAuthSourceForm', globals())
+
+#
+# This is a pretty good example of what functionality is required in order
+# to provide an Authentication Source -- of course it leaves it as an exercise
+# for the reader to provide that functionality.
+#
+
+class usAuthSource(Folder):
+	""" Authenticate Users against a User Supplied Set of Methods """
+
+	meta_type='Authentication Source'
+	title='User Supplied Authentication'
+	icon ='misc_/exUserFolder/exUserFolderPlugin.gif'
+	manage_editForm=manage_editusAuthSourceForm
+		
+	def __init__(self):
+		self.id='usAuthSource'
+
+	# Create a User to authenticate against
+	# username, password and roles
+	def createUser(self, username, password, roles):
+		""" Add A Username """
+		if type(roles) != type([]):
+			if roles:
+				roles=list(roles)
+			else:
+				roles=[]
+		if 'usCreateUser' in self.objectIds():
+			self.usCreateUser(username, password, roles)
+
+	# Update a user's roles and password
+	# An empty password means do not change passwords...
+	def updateUser(self, username, password, roles):
+		if type(roles) != type([]):
+			if roles:
+				roles=list(roles)
+			else:
+				roles=[]
+		if 'usUpdateUser' in self.objectIds():
+			self.usUpdateUser(username, password, roles)
+
+	# Encrypt a password
+	# If no 'crypt' method is supplied return the
+	# Password -- i.e. plaintext password
+	def cryptPassword_old(self, username, password):
+		if 'usCryptPassword' in self.objectIds():
+			return self.usCryptPassword(username, password)
+		else:
+			return password
+
+	# Delete a set of users
+	def deleteUsers(self, userids):
+		if 'usDeleteUsers' in self.objectIds():
+			self.usDeleteUsers(userids)
+
+
+	# Return a list of usernames
+	def listUserNames(self):
+		if 'usListUserNames' in self.objectIds():
+			return self.usListUserNames()
+		else:
+			return []
+		
+	# Return one user matching the username
+	# Should be a list of dictionaries (because we may allow multiple matching
+	# users at some point)
+	# [{'username':username, 'password':cryptedPassword, 'roles':list_of_roles},]
+	def listOneUser(self,username):
+		if 'usListOneUser' in self.objectIds():
+			return self.usListOneUser(username)
+
+	# Return a list of user dictionaries the same as listOnUser
+	def listUsers(self):
+		if 'usListUsers' in self.objectIds():
+			return self.usListUsers()
+		else:
+			return []
+
+	#
+	# You can define this to go off and do the authentication instead of
+	# using the basic one inside the User Object
+	#
+	remoteAuthMethod=None
+
+##	def remoteAuthMethod(self, username, password):
+##		""" Perform authentication 'our' way """
+##		if 'usRemoteAuthMethod' in self.objectIds():
+##			return self.usRemoteAuthMethod(username, password)
+		
+		
+
+	def postInitialisation(self, REQUEST):
+		pass
+
+usAuthReg=PluginRegister('usAuthSource', 'User Supplied Authentication Source',
+						 usAuthSource, manage_addusAuthSourceForm,
+						 manage_addusAuthSource,
+						 manage_editusAuthSourceForm)
+exUserFolder.authSources['usAuthSource']=usAuthReg

Added: trunk/ZopeProducts/exUserFolder/AuthSources/zodbAuthSource/.cvsignore
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/zodbAuthSource/.cvsignore	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/zodbAuthSource/.cvsignore	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,4 @@
+*.pyc
+*.pyo
+*~
+.*.swp

Added: trunk/ZopeProducts/exUserFolder/AuthSources/zodbAuthSource/README
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/zodbAuthSource/README	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/zodbAuthSource/README	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,13 @@
+ZODB Authentication Source for exUserFolder
+
+This is an auth source that works pretty much like the standard user folder provided by zope.
+It stores the usernames, roles and passwords on a ZODB persistent dictionary. 
+
+It doesn't require any configuration at all, just select it as your auth source and you're
+ready to add user accounts.
+
+
+Author: Alex Verstraeten (aka: zxc)
+Email: alex at quad.com.ar
+
+

Added: trunk/ZopeProducts/exUserFolder/AuthSources/zodbAuthSource/__init__.py
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/zodbAuthSource/__init__.py	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/zodbAuthSource/__init__.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,90 @@
+##############################################################################
+#
+# Zope Public License (ZPL) Version 0.9.4
+# ---------------------------------------
+# 
+# Copyright (c) Digital Creations.  All rights reserved.
+# 
+# Redistribution and use in source and binary forms, with or
+# without modification, are permitted provided that the following
+# conditions are met:
+# 
+# 1. Redistributions in source code must retain the above
+#    copyright notice, this list of conditions, and the following
+#    disclaimer.
+# 
+# 2. Redistributions in binary form must reproduce the above
+#    copyright notice, this list of conditions, and the following
+#    disclaimer in the documentation and/or other materials
+#    provided with the distribution.
+# 
+# 3. Any use, including use of the Zope software to operate a
+#    website, must either comply with the terms described below
+#    under "Attribution" or alternatively secure a separate
+#    license from Digital Creations.
+# 
+# 4. All advertising materials, documentation, or technical papers
+#    mentioning features derived from or use of this software must
+#    display the following acknowledgement:
+# 
+#      "This product includes software developed by Digital
+#      Creations for use in the Z Object Publishing Environment
+#      (http://www.zope.org/)."
+# 
+# 5. Names associated with Zope or Digital Creations must not be
+#    used to endorse or promote products derived from this
+#    software without prior written permission from Digital
+#    Creations.
+# 
+# 6. Redistributions of any form whatsoever must retain the
+#    following acknowledgment:
+# 
+#      "This product includes software developed by Digital
+#      Creations for use in the Z Object Publishing Environment
+#      (http://www.zope.org/)."
+# 
+# 7. Modifications are encouraged but must be packaged separately
+#    as patches to official Zope releases.  Distributions that do
+#    not clearly separate the patches from the original work must
+#    be clearly labeled as unofficial distributions.
+# 
+# Disclaimer
+# 
+#   THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS ``AS IS'' AND
+#   ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+#   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+#   FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT
+#   SHALL DIGITAL CREATIONS OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+#   DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+#   CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+#   PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+#   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+#   ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+#   LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+#   IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+#   THE POSSIBILITY OF SUCH DAMAGE.
+# 
+# Attribution
+# 
+#   Individuals or organizations using this software as a web site
+#   must provide attribution by placing the accompanying "button"
+#   and a link to the accompanying "credits page" on the website's
+#   main entry point.  In cases where this placement of
+#   attribution is not feasible, a separate arrangment must be
+#   concluded with Digital Creations.  Those using the software
+#   for purposes other than web sites must provide a corresponding
+#   attribution in locations that include a copyright using a
+#   manner best suited to the application environment.
+# 
+# This software consists of contributions made by Digital
+# Creations and many individuals on behalf of Digital Creations.
+# Specific attributions are listed in the accompanying credits
+# file.
+# 
+##############################################################################
+# $Id: __init__.py,v 1.1 2004/11/10 14:15:52 akm Exp $
+
+__doc__="""zodb User Folder Product
+"""
+
+import zodbAuthSource

Added: trunk/ZopeProducts/exUserFolder/AuthSources/zodbAuthSource/manage_addzodbAuthSourceForm.dtml
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/zodbAuthSource/manage_addzodbAuthSourceForm.dtml	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/zodbAuthSource/manage_addzodbAuthSourceForm.dtml	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,10 @@
+<dtml-var "DialogHeader(_.None,_,DialogTitle='Add User Supplied Authentication Source')">
+<FORM ACTION="&dtml-URL;" METHOD="POST">
+<dtml-in "REQUEST.form.keys()">
+<input type="HIDDEN" name="<dtml-var sequence-item>" value="<dtml-var "REQUEST[_.getitem('sequence-item',0)]">">
+</dtml-in>
+<input type="HIDDEN" name="doProp" value="1">
+<b><dtml-babel src="'en'">This authentication source requires no user configuration items at this time.</dtml-babel></b><br>
+<INPUT TYPE="SUBMIT" VALUE=" <dtml-babel src="'en'">NEXT</dtml-babel> ">
+</FORM>
+<dtml-var DialogFooter>

Added: trunk/ZopeProducts/exUserFolder/AuthSources/zodbAuthSource/manage_editzodbAuthSourceForm.dtml
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/zodbAuthSource/manage_editzodbAuthSourceForm.dtml	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/zodbAuthSource/manage_editzodbAuthSourceForm.dtml	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,7 @@
+<dtml-var "DialogHeader(_.None,_,DialogTitle='ZODB Auth Source',dialog_width='100%')">
+<dtml-var manage_tabs>
+<FORM ACTION="manage_main" METHOD="POST">
+<b><dtml-babel src="'en'">This Auth source requires no user configuration items at this time.</dtml-babel></b><br>
+<INPUT TYPE="SUBMIT" VALUE=" <dtml-babel src="'en'">OK</dtml-babel> ">
+</FORM>
+<dtml-var DialogFooter>

Added: trunk/ZopeProducts/exUserFolder/AuthSources/zodbAuthSource/zodbAuthSource.py
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/zodbAuthSource/zodbAuthSource.py	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/zodbAuthSource/zodbAuthSource.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,114 @@
+# $Id: zodbAuthSource.py,v 1.1 2004/11/10 14:15:52 akm Exp $
+
+import Acquisition
+
+from Globals import HTMLFile, MessageDialog, INSTANCE_HOME, Persistent, PersistentMapping
+
+from OFS.Folder import Folder
+try:
+	from crypt import crypt
+except:
+	from Products.exUserFolder.fcrypt.fcrypt import crypt
+
+from Products.exUserFolder.exUserFolder import exUserFolder
+from Products.exUserFolder.Plugins import PluginRegister
+
+from AccessControl.User import User
+
+
+def manage_addzodbAuthSource(self, REQUEST):
+
+	""" Add a ZODB Auth Source"""
+	o = zodbAuthSource()
+	self._setObject('zodbAuthSource',o)
+	o=getattr(self,'zodbAuthSource')
+	if hasattr(o,'postInitialisation'):
+		o.postInitialisation(REQUEST)
+	
+	self.currentAuthSource=o
+	return ''
+
+manage_addzodbAuthSourceForm=HTMLFile('manage_addzodbAuthSourceForm', globals())
+manage_editzodbAuthSourceForm=HTMLFile('manage_editzodbAuthSourceForm', globals())
+
+class zodbAuthSource(Folder):
+	""" Authenticate users against a ZODB dictionary"""
+
+	meta_type='Authentication Source'
+	title	 ='ZODB Authentication'
+	icon ='misc_/exUserFolder/exUserFolderPlugin.gif'
+
+	manage_properties=HTMLFile('properties', globals())
+
+	manage_editForm=manage_editzodbAuthSourceForm
+	manage_tabs=Acquisition.Acquired
+
+	#
+	# You can define this to go off and do the authentication instead of
+	# using the basic one inside the User Object
+	#
+	remoteAuthMethod=None
+
+	def __init__(self):
+		self.id = 'zodbAuthSource'
+		self.data=PersistentMapping()
+
+	def cryptPassword_old(self, username, password):
+			salt = username[:2]
+			secret = crypt(password, salt)
+			return secret
+
+	def deleteUsers(self, userids):
+		for name in userids:
+			del self.data[name]
+
+	def createUser(self, username, password, roles=[]):
+		""" Add a Username """
+		if type(roles) != type([]):
+			if roles:
+				roles=list(roles)
+			else:
+				roles=[]
+			
+			
+		secret=self.cryptPassword(username, password)
+		self.data[username]=PersistentMapping()
+		self.data[username].update({ 
+					'username': username,	
+					'password': secret, 
+					'roles': roles })
+
+	def updateUser(self, username, password, roles):
+		if type(roles) != type([]):
+			if roles:
+				roles=list(roles)
+			else:
+				roles=[]
+		
+		self.data[username]['roles'] = roles
+		if password:
+			secret = self.cryptPassword(username, password)
+			self.data[username]['password'] = secret
+
+	def listUserNames(self):
+		return list(self.data.keys())
+
+	def listUsers(self):
+		""" return a list of user names or [] if no users exist"""
+		return self.data.values()
+
+	def listOneUser(self, username):
+		users = []
+		data = self.data.get(username)
+		if data is not None:
+			users.append(data)
+		return users
+
+	def postInitialisation(self, REQUEST):
+		pass
+
+zodbAuthReg=PluginRegister('zodbAuthSource', 'ZODB Authentication Source',
+						   zodbAuthSource, manage_addzodbAuthSourceForm,
+						   manage_addzodbAuthSource,
+						   manage_editzodbAuthSourceForm)
+exUserFolder.authSources['zodbAuthSource']=zodbAuthReg

Added: trunk/ZopeProducts/exUserFolder/AuthSources/zodbBTreeAuthSource/.cvsignore
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/zodbBTreeAuthSource/.cvsignore	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/zodbBTreeAuthSource/.cvsignore	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,4 @@
+*.pyc
+*.pyo
+*~
+.*.swp

Added: trunk/ZopeProducts/exUserFolder/AuthSources/zodbBTreeAuthSource/README
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/zodbBTreeAuthSource/README	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/zodbBTreeAuthSource/README	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,13 @@
+ZODB Authentication Source for exUserFolder
+
+This is an auth source that works pretty much like the standard user folder provided by zope.
+It stores the usernames, roles and passwords on a ZODB persistent dictionary. 
+
+It doesn't require any configuration at all, just select it as your auth source and you're
+ready to add user accounts.
+
+
+Author: Alex Verstraeten (aka: zxc)
+Email: alex at quad.com.ar
+
+

Added: trunk/ZopeProducts/exUserFolder/AuthSources/zodbBTreeAuthSource/__init__.py
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/zodbBTreeAuthSource/__init__.py	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/zodbBTreeAuthSource/__init__.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,89 @@
+##############################################################################
+#
+# Zope Public License (ZPL) Version 0.9.4
+# ---------------------------------------
+# 
+# Copyright (c) Digital Creations.  All rights reserved.
+# 
+# Redistribution and use in source and binary forms, with or
+# without modification, are permitted provided that the following
+# conditions are met:
+# 
+# 1. Redistributions in source code must retain the above
+#    copyright notice, this list of conditions, and the following
+#    disclaimer.
+# 
+# 2. Redistributions in binary form must reproduce the above
+#    copyright notice, this list of conditions, and the following
+#    disclaimer in the documentation and/or other materials
+#    provided with the distribution.
+# 
+# 3. Any use, including use of the Zope software to operate a
+#    website, must either comply with the terms described below
+#    under "Attribution" or alternatively secure a separate
+#    license from Digital Creations.
+# 
+# 4. All advertising materials, documentation, or technical papers
+#    mentioning features derived from or use of this software must
+#    display the following acknowledgement:
+# 
+#      "This product includes software developed by Digital
+#      Creations for use in the Z Object Publishing Environment
+#      (http://www.zope.org/)."
+# 
+# 5. Names associated with Zope or Digital Creations must not be
+#    used to endorse or promote products derived from this
+#    software without prior written permission from Digital
+#    Creations.
+# 
+# 6. Redistributions of any form whatsoever must retain the
+#    following acknowledgment:
+# 
+#      "This product includes software developed by Digital
+#      Creations for use in the Z Object Publishing Environment
+#      (http://www.zope.org/)."
+# 
+# 7. Modifications are encouraged but must be packaged separately
+#    as patches to official Zope releases.  Distributions that do
+#    not clearly separate the patches from the original work must
+#    be clearly labeled as unofficial distributions.
+# 
+# Disclaimer
+# 
+#   THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS ``AS IS'' AND
+#   ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+#   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+#   FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT
+#   SHALL DIGITAL CREATIONS OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+#   DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+#   CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+#   PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+#   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+#   ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+#   LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+#   IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+#   THE POSSIBILITY OF SUCH DAMAGE.
+# 
+# Attribution
+# 
+#   Individuals or organizations using this software as a web site
+#   must provide attribution by placing the accompanying "button"
+#   and a link to the accompanying "credits page" on the website's
+#   main entry point.  In cases where this placement of
+#   attribution is not feasible, a separate arrangment must be
+#   concluded with Digital Creations.  Those using the software
+#   for purposes other than web sites must provide a corresponding
+#   attribution in locations that include a copyright using a
+#   manner best suited to the application environment.
+# 
+# This software consists of contributions made by Digital
+# Creations and many individuals on behalf of Digital Creations.
+# Specific attributions are listed in the accompanying credits
+# file.
+# 
+##############################################################################
+# $Id: __init__.py,v 1.1 2004/11/10 14:15:52 akm Exp $
+__doc__="""zodb BTree User Folder Product
+"""
+
+import zodbBTreeAuthSource


Property changes on: trunk/ZopeProducts/exUserFolder/AuthSources/zodbBTreeAuthSource/__init__.py
___________________________________________________________________
Added: svn:executable
   + *

Added: trunk/ZopeProducts/exUserFolder/AuthSources/zodbBTreeAuthSource/manage_addzodbBTreeAuthSourceForm.dtml
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/zodbBTreeAuthSource/manage_addzodbBTreeAuthSourceForm.dtml	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/zodbBTreeAuthSource/manage_addzodbBTreeAuthSourceForm.dtml	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,10 @@
+<dtml-var "DialogHeader(_.None,_,DialogTitle='Add ZODB BTree Authentication Source')">
+<FORM ACTION="&dtml-URL;" METHOD="POST">
+<dtml-in "REQUEST.form.keys()">
+<input type="HIDDEN" name="<dtml-var sequence-item>" value="<dtml-var "REQUEST[_.getitem('sequence-item',0)]">">
+</dtml-in>
+<input type="HIDDEN" name="doProp" value="1">
+<b><dtml-babel src="'en'">This authentication source requires no user configuration items at this time.</dtml-babel></b><br>
+<INPUT TYPE="SUBMIT" VALUE=" <dtml-babel src="'en'">NEXT</dtml-babel> ">
+</FORM>
+<dtml-var DialogFooter>


Property changes on: trunk/ZopeProducts/exUserFolder/AuthSources/zodbBTreeAuthSource/manage_addzodbBTreeAuthSourceForm.dtml
___________________________________________________________________
Added: svn:executable
   + *

Added: trunk/ZopeProducts/exUserFolder/AuthSources/zodbBTreeAuthSource/manage_editzodbBTreeAuthSourceForm.dtml
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/zodbBTreeAuthSource/manage_editzodbBTreeAuthSourceForm.dtml	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/zodbBTreeAuthSource/manage_editzodbBTreeAuthSourceForm.dtml	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,7 @@
+<dtml-var "DialogHeader(_.None,_,DialogTitle='ZODB Auth BTree Source',dialog_width='100%')">
+<dtml-var manage_tabs>
+<FORM ACTION="manage_main" METHOD="POST">
+<b><dtml-babel src="'en'">This Auth source has no user configuration items at this time.</dtml-babel></b><br>
+<INPUT TYPE="SUBMIT" VALUE=" <dtml-babel src="'en'">OK</dtml-babel> ">
+</FORM>
+<dtml-var DialogFooter>


Property changes on: trunk/ZopeProducts/exUserFolder/AuthSources/zodbBTreeAuthSource/manage_editzodbBTreeAuthSourceForm.dtml
___________________________________________________________________
Added: svn:executable
   + *

Added: trunk/ZopeProducts/exUserFolder/AuthSources/zodbBTreeAuthSource/zodbBTreeAuthSource.py
===================================================================
--- trunk/ZopeProducts/exUserFolder/AuthSources/zodbBTreeAuthSource/zodbBTreeAuthSource.py	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/AuthSources/zodbBTreeAuthSource/zodbBTreeAuthSource.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,129 @@
+# $Id: zodbBTreeAuthSource.py,v 1.1 2004/11/10 14:15:52 akm Exp $
+
+import Acquisition
+
+from Globals import HTMLFile, MessageDialog, INSTANCE_HOME, Persistent, PersistentMapping
+
+from OFS.Folder import Folder
+try:
+	from crypt import crypt
+except:
+	from Products.exUserFolder.fcrypt.fcrypt import crypt
+
+from Products.exUserFolder.exUserFolder import exUserFolder
+from Products.exUserFolder.Plugins import PluginRegister
+
+from BTrees.OOBTree import OOBTree
+from AccessControl.User import User
+
+
+def manage_addzodbBTreeAuthSource(self, REQUEST):
+
+	""" Add a ZODB Auth Source"""
+	o = zodbBTreeAuthSource()
+	self._setObject('zodbBTreeAuthSource',o)
+	o=getattr(self,'zodbBTreeAuthSource')
+	if hasattr(o,'postInitialisation'):
+		o.postInitialisation(REQUEST)
+	
+	self.currentAuthSource=o
+	return ''
+
+manage_addzodbBTreeAuthSourceForm=HTMLFile('manage_addzodbBTreeAuthSourceForm', globals())
+manage_editzodbBTreeAuthSourceForm=HTMLFile('manage_editzodbBTreeAuthSourceForm', globals())
+
+class zodbUser(Persistent):
+	# I don't really need to specify these here, but, it does state
+	# the intention of the class.
+	username=''
+	password=''
+	roles=[]
+
+class zodbBTreeAuthSource(Folder):
+	""" Authenticate users against a ZODB BTree """
+
+	meta_type='Authentication Source'
+	title	 ='ZODB BTree Authentication'
+	icon ='misc_/exUserFolder/exUserFolderPlugin.gif'
+
+	manage_properties=HTMLFile('properties', globals())
+
+	manage_editForm=manage_editzodbBTreeAuthSourceForm
+	manage_tabs=Acquisition.Acquired
+
+	#
+	# You can define this to go off and do the authentication instead of
+	# using the basic one inside the User Object
+	#
+	remoteAuthMethod=None
+
+	def __init__(self):
+		self.id = 'zodbBTreeAuthSource'
+		self.userBTree=OOBTree()
+
+	def cryptPassword_old(self, username, password):
+			salt = username[:2]
+			secret = crypt(password, salt)
+			return secret
+
+	def deleteUsers(self, userids):
+		for name in userids:
+			del self.userBTree[name]
+
+	def createUser(self, username, password, roles=[]):
+		""" Add a Username """
+		if type(roles) != type([]):
+			if roles:
+				roles=list(roles)
+			else:
+				roles=[]
+
+		secret=self.cryptPassword(username, password)			
+		myUser=zodbUser()
+		myUser.username=username
+		myUser.password=secret
+		myUser.roles=roles
+		self.userBTree[username]=myUser
+
+	def updateUser(self, username, password, roles):
+		if type(roles) != type([]):
+			if roles:
+				roles=list(roles)
+			else:
+				roles=[]
+		
+		self.userBTree[username].roles = roles
+		if password:
+			secret = self.cryptPassword(username, password)
+			self.userBTree[username].password = secret
+
+	def listUserNames(self):
+		return self.userBTree.keys()
+
+	def listUsers(self):
+		""" return a list of users or [] if no users exist"""
+		users=[]
+		for u in self.userBTree.keys():
+			n = self.userBTree[u]
+			N={'username':n.username, 'password':n.password, 'roles':n.roles}
+			users.append(N)
+		return users
+
+	def listOneUser(self, username):
+		users = []
+		try:
+			n = self.userBTree[username]
+			N={'username':n.username, 'password':n.password, 'roles':n.roles}
+			users.append(N)
+		except:
+			pass
+		return users
+
+	def postInitialisation(self, REQUEST):
+		pass
+
+zodbBTreeAuthReg=PluginRegister('zodbBTreeAuthSource', 'ZODB BTree Authentication Source',
+						   zodbBTreeAuthSource, manage_addzodbBTreeAuthSourceForm,
+						   manage_addzodbBTreeAuthSource,
+						   manage_editzodbBTreeAuthSourceForm)
+exUserFolder.authSources['zodbBTreeAuthSource']=zodbBTreeAuthReg


Property changes on: trunk/ZopeProducts/exUserFolder/AuthSources/zodbBTreeAuthSource/zodbBTreeAuthSource.py
___________________________________________________________________
Added: svn:executable
   + *

Added: trunk/ZopeProducts/exUserFolder/CHANGES.txt
===================================================================
--- trunk/ZopeProducts/exUserFolder/CHANGES.txt	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/CHANGES.txt	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,865 @@
+Changes for 0.50.1
+
+Add a README.Upgrading file to explain the impact of the 0.50.0 source
+restructure, since people don't seem to be reading this file. --akm
+
+Fix the default docLogin to use &dtml-URL as the default destination.
+I porked the fcrypt import. It obviously doesn't get imported here since
+I have a crypt module installed. -- akm
+
+Fixed; https://sourceforge.net/tracker/?func=detail&aid=1084903&group_id=36318&atid=416446
+thanks to vigine -- akm
+
+Changes for 0.50.0
+
+Restructured Source Tree. This will make this version incompatible with
+previous versions, as the classes have moved. This breaks upgrading existing
+installs unless you keep the old classes around. If you only use external
+Auth/Prop/Group sources, you will probably be unaffected.
+
+o Auth Sources moved to single directory
+o Prop Sources moved to single directory
+o Group Sources moved to single directory
+o Docs moved to doc directory 
+--akm
+
+Added Pluggable Crypto methods. Any authSource that contains a
+cryptPassword method, will have it's method called, otherwise the
+method selected by the user is called. --akm
+
+Removed the cryptPassword method from existing Auth Sources. --akm
+
+docLoginRedirect is no longer used. --akm
+
+Changes for 0.20.2
+BLAH! I missed some LDAP changes! --akm
+
+Changes for 0.20.1
+
+Fix import problem for pgPropSource --akm
+Add performance boost to pgAuthSource and pgPropSource --akm
+Make zodbAuthSource.listUsernames return a list. --akm
+Update some LDAP Auth source bugs. --akm
+Change references to "Authorisation" to "Authentication" since XUF 
+auth sources authenticate, they don't authorise. --akm
+Changed the <h3> tags to <b> tags in the manage_adds.
+
+Changes for 0.20.0
+
+Fix:
+https://sourceforge.net/tracker/index.php?func=detail&aid=547327&group_id=36318&atid=416446
+https://sourceforge.net/tracker/index.php?func=detail&aid=616485&group_id=36318&atid=416448
+https://sourceforge.net/tracker/index.php?func=detail&aid=594081&group_id=36318&atid=416448
+https://sourceforge.net/tracker/index.php?func=detail&aid=594526&group_id=36318&atid=416448
+
+Added LDAPAuthSource, based on the auth_ldap module for Apache
+(http://www.rudedog.org/auth_ldap/) and the NDS Auth Source of
+Phil Harris (AKA ftmpsh). This is only lightly tested, I don't have
+the LDAP resources here to test all the features. Binding using uid/
+cn and using various filters works (if the userPassword item is
+present). This needs more testing by people with better LDAP setups
+that I do. --akm
+
+Padded docLoginRedirect to prevent IE from displaying "Friendly" error
+messages when -D flag not present when running Zope --akm.
+
+Update UZG to contain entry for LDAPAuthSource. Reformat text 
+slightly. --akm
+
+Propogate "unable to auth" here requests up. This means the Manager
+doesn't get locked out in cookie mode after adding an XUF instance.
+It also means that people using a non-existant username at this level
+get thrown up a level higher. This might not be what people want to
+happen. --akm
+
+Added method makeRedirectPath which is called from docLoginRedirect.
+This makes the destination include any querystring that was present
+when needing to redirect. -- akm.
+
+Removed some Class globals from exUseFolder.py. These are now set 
+in __set_state__ if not present in the class so that upgrading users
+don't get a crash (hopefully). -- akm.
+
+pgPropSource was losing track of properties under heavy load. 
+Only noticable if you were setting and deleting a lot of temporary
+properties. There is a global property timeout for pgPropSource. --akm
+
+Jason Gibson <jason.gibson at sbcglobal.net> provided a nisAuthSource,
+I've added it here --akm.
+
+Refactored validate method to behave a lot more like BasicUserFolder.
+Among other things, this fixes the issue where a local role could not
+be granted to a user and granted permissions on the same object.  --mb
+
+Add NuxUserGroups support (previously on NuxUserGroups_support_branch)
+and group sources.  --bmh, mb
+
+Now passes authFailedCode to Membership Login Page, The Default Login
+Page as defined in the README.Membership will correctly display reason
+for login being required --cab
+
+Fixed Edit management pages for user-supplied auth and property
+sources --bmh
+
+Removed overriding of __len__ to return the number of users.  This was
+causing performance problems during authentication.  See
+http://sourceforge.net/mailarchive/message.php?msg_id=2230743 for
+details.  WARNING: this means using len(acl_users) to get the number
+of users will no longer work!  If you were using this trick, please
+use len(acl_users.listUsers()) instead.  --bmh 
+
+Make title property editable --bmh
+
+Make Group Sources changeable dynamically after the acl_users folder has
+been created --bmh
+
+Inital import of https Auth source.  Also, added a listUsers method
+to the zodbBTreeProps source to support listUsers. -- jsb <jonah at cloud9.net>
+
+Changes for 0.10.10
+
+Added mysql Auth and mysql Prop source and mysql.sql schema. Just a
+copy of the appropriate pg source with sql that works with myqsl -cab
+
+Fixed negative user cache lookup in std_validade so that it actually
+works for users being authenticated thru basic auth, especially if
+they're authenticating in outer user folders -- rochael
+
+Made smbAuthSource catch NetBIOSTimeout errors during authentication -- rochael
+
+Fixed dtml/mainUser.dtml to be virtualhost-sensitive when displaying user
+icons -- rochael
+
+Updated UZG per user request. Fixed numbering, added information about
+addition parameters like Negative Caching.
+
+Changes for 0.10.9
+
+Made dummyZBabelTag compatible to replace the NoBabel in OrderedFolder
+while keeping its functionality in XUF -- cab
+
+Changed _doAddUser, _doChangeUser to work with the public interface for
+userfolders introduced in Zope2.5. Optional keyword arguments can now
+be passed to _doAddUser and _doChangeUser.
+
+PropertySource: Please note that createUser and updateUser, when called
+from _doAddUser and _doChangeUser, will no longer be passed a REQUEST,
+but a mapping with items from REQUEST updated with those from the
+optional keyword arguments.  -- pj
+
+Fixed the problem with upgrading from 0.10.7 and below that didn't
+account for existing XUF's not having a MessageDialog in their
+contents. Now unless specificy replace it will use the MessageDialog
+provided. Added how to do that to FAQ and README.Membership --cab
+
+Made docLoginRedirect provide an absolute URL --bmh
+
+MessageDialog in common no longer uses mangage_page_header and 
+mangage_page_footer v--cab
+
+Changes for 0.10.8
+
+Added the ability for members to change properties, and a default page
+in the README.Membership to show how to do it --cab
+
+MessageDialog is now an object in the ZODB that can be changed to fit
+the site --cab
+
+Now with 100% guaranteed race-condition-free UserCache goodness!  Those
+subclassing XUFUser, you will have to change your code.  See User.py
+for details.  --mb
+
+zodbBTreePropSource was returning None instead of the requested
+default value, when called with (e.g.) someuser.getProperty('shoesize',13).
+(Other property sources didn't have that bug.)
+--davidc at debian.org
+
+The tutorial loginform was wrong for Membership in README.Membership
+
+Seems delProperty has never worked.. fixed --akm
+Seems delProperty for pgPropSource has never worked.. fixed --akm
+
+Fixed Basic Auth not auth problem. --akm
+Fixed Basic Auth not cache problem. --akm
+Fixed Cached Users bypassing some auth checks. --akm
+
+Added usPropSource, which allows users to supply property methods TTW.
+--bmh
+
+Changes for 0.10.7
+
+PropertyEditor had a typo in dtml and was casting int to None. --zxc
+
+BasicAuth is now broken the other way, it'll allow any user to validate
+with any password. --akm
+
+Negative cache checking move was bogus. --akm
+
+redirectToLogin didn't have a security declaration so 2.5.0 refused to
+work in cookie mode *sigh* --akm
+
+Fixed the 'None' object has no attribute 'load' setstate errors that
+could crop up on propSources, and preemptively took care of the
+authSources as well.  Also fixed some of the weirder bugs relating to
+user object acquisition context. --mb
+
+Bug fixes from sf applied. --akm
+
+Changes for 0.10.6
+
+dummyZBabelTag used the python 2 re, which broke installations using
+python 1.5 which still used the now deprecated regex, changed it to
+catch the exception and use regex instead for python 1.5, else still
+use re --cab
+
+The redirectToLogin without Membership had a little logic problem where it
+would basically garantee the existence of a query string, with at least a
+lonely question mark even when there was no query string in the original
+URL --rochael
+
+smbAuthSource needed to cast NULL role properties to an empty list --akm
+
+smbAuthSource had some dodgey zLOGing in it. --akm
+
+smbAuthSource had some methods that should return [] instead of None. --akm
+
+s/postgres/RADIUS/ in the radiusAuthSource DTML --akm
+
+cookie_validate no longer pulls you from the cache if you're 
+logging in (which means your cookie wouldn't get set). --akm
+
+Cookies are no longer expired if you're successfully authenticated but
+merely unauthorized. --mb
+
+Basic auth resynched with standard user folder, trying to fix
+some basic auth issues. --akm.
+
+Negative cache checking now performed outside of the two specific
+validate methods. --akm.
+
+A fairly innocuous print debug statement turned into a zLOG at error
+level, removed --akm.
+
+Clean up smbAuthSource log messages, and quieten.  Only truly
+exceptional cases are now logged above BLATHER. --mb
+
+Changes for 0.10.5
+
+Membership redirecting to login was still broken. It should be better
+now (twice) --akm
+
+logout() wasn't clearing the advanced cookie. --akm
+
+Negative Cache Value wasn't being passed through to the XUF constructor. --akm
+Log Users Out DTML code was broken, should work now. --akm
+
+The User object now contains the authSource as well as the propSource,
+making access to roles for custom User-objects possible. --dlk
+
+Following akm's advice, fixed manage_beforeDelete to use two separate 
+try:except blocks to ensure that if cache-removal fails, deleting 
+the container.__allow_groups__  property is attempted. This should
+fix the problem where deleted xuf instances remain as "ghost" products
+causing interference with newer versions of xuf, and also fixes the 
+problem where deleting a xuf acl_users in a folder makes that folder
+inaccessible. --dlk
+
+Fixed cache_delete that was missing the "self" parameter in the method
+defintion. --dlk
+
+Fixed xcache_delete that was missing the "self" parameter in the method
+definition --akm d8)
+
+These previous two fix the problems with manage_beforeDelete, but, it
+will stay the same for now --akm.
+
+Fixed cache_deleteCookieCache that was missing the "self" parameter in
+the method defintion. --dlk ;)
+
+Changes for 0.10.4
+
+The instructions for File Based Auth were incorrect in the UZG --akm
+
+redirectToLogin was totally wrong for membership... --akm
+docLogin was fixed for VHM use. --akm
+
+Advanced Cookie Mode has changed so that it no longer sends the username
+and password. Instead a hash is used as a key into a module level cache.
+This should be 100% more secure than standard cookie mode, and removes
+the stupid back doors I enabled in the previous version. This work was
+based on conversations I had with Stuart Bishop (I basically lifted
+the hashing scheme from GUF). This makes use of the Module level cache
+code. --akm
+
+There was a code cleanup and a slight reorganisation of some files. --akm
+
+The main User Object has migrated to XUFUser and simarly with the
+AnonUser. There is now an empty [Anon]User class that has XUFUser as
+it's base. This allows people to create custom User Objects without
+jumping through hoops (and simplifies maintaining patches) --akm
+
+Cache Code has changed again. Now there is a module level cache, so
+that auth data is shared between threads for a single XUF (thanks to
+Stuart Bishop for an enlightening discussion on this and other issues,
+and thanks to Chris McDonough for talking me through setting up module 
+level globals [and sending me some code to work from]) --akm
+
+A Negative User Cache now exists. This is only generally useful for
+use with remote auth sources where repeatedly trying to auth non-existant
+users is very expensive (where they are authed at a higher level).
+You can enable this on creation or from the parameters screen (positive
+time in seconds enables). --akm
+
+Domain checking code finally removed. --akm
+
+zodbBTreePropSource changed to be friendlier about users that exist
+in remote locations (i.e. aren't create as such through the ZMI). -- akm
+
+Changed some 'print's in the code to use zLOG.LOG
+instead. Files affected so far (more to follow): -- rochael
+
+  * exUserFolder.py
+  * basicMemberSource/basicMemberSource.py
+  * zodbBTreePropSource/zodbBTreePropSource.py
+  * zodbPropSource/zodbPropSource.py
+
+Changed a couple things in smbAuthSource.py: -- rbanffy
+
+  * Method _authenticate_retry now logs several kinds of information
+    for debugging and diagnostics.
+
+  * Modified socket.error handling in _authenticate_retry: changed
+    "raise" to "return 0".
+
+  * Since this generated more problems (failed authentications) than
+    it solved (our impression it was not right not to return 0 in an
+    auth fail even due to a communications malfunction), we also
+    changed socket.error handling to retry no mather what errno tells
+    us (it said different things for the same problem under Windows
+    and Linux).
+
+  * In order to prevent infinite retries, changed retry handling a
+    bit. It now retries 3 times. Real-use data will tell us if we
+    should increase or not retries. To better convey the meaning of
+    the parameter, changed "retry_depth" to "retries". I strongly
+    advise the use of credential caching with smbAuthSource, tough, as
+    it reduces socket errors and load on the domain controllers.
+
+Changes for 0.10.3.1
+
+Readded support for I18N without ZBabel installation, somehow missed
+during the transition to SF CVS.
+
+Some text changes as well as an update to the dictionary while we're
+at it.  No functional changes for this release though.
+
+Changes for 0.10.3
+
+Missed a few LoginRequireds.
+
+Fixed a bug with __allow_groups__ not being set after paste
+(probably also not after import).
+
+The sources are now sorted by name in the drop down box..
+
+a BTree version of zodbAuthSource
+a BTree version of zodbPropSource
+
+These aren't really all that different to the originals that were
+provided by Alex, but, they use BTrees instead of PersistentMappings,
+and try to avoid various persistence problems associated with dicts.
+Both versions will continue to be supported.
+
+Patches from SF applied.
+
+Advanced Cookie Mode added.
+This mode adds a rotor cipher around the cookie. A secret is provided
+in order to encode the cookie. The username and password are placed
+within a small class which is pickled and then encrypted and then
+base64 encoded for transport. There is also a timestamp inside the cookie,
+so the ultra-paranoid of you can rotate the cookie based on the timestamp
+inside.
+
+Abstracted out the setting and decoding of cookies.
+
+Changes for 0.10.2
+
+all raise 'LoginRequired' <- raise 'Unauthorized'
+
+Raising unauthorizes breaks a million things. CMF people can just
+put up with configuring their portal properly.
+
+Radius resynced with version from sourceforge.
+manage_tabs redone to be ZBabel'd and to look like standard tabs.
+
+German Language added to the ZBabel dictionary.
+
+
+Changes for 0.10.1
+
+all raise 'LoginRequired' -> raise 'Unauthorized'
+
+Bug in etcAuthSource listUsers fixed, 
+and cryptPassword also fixed to get the actual salt.
+
+Zope 2.4.3 has dicked with security settings again.. I've had a round
+of permission whacking.
+
+Buggy handling of empty role lists was fixed.
+
+Change to smbAuthSource to use string.lower on usernames for
+python 1.5.2 compatibility?
+
+
+Changes for 0.10.0
+
+Added explicit roles for manage_editUser and friends, to allow
+the "Manage users" permission to be useful to non-Manager Users.
+Thanks to Heimo Laukkanen <huima at fountainpark.org> for reporting this
+one.
+
+zodbAuthSource made more persistent <alex at quad.com.ar>
+zodbPropSource was blowing when deleting temporary properties.
+
+XUF is now ZBabel'd which means you can view XUF in different languages
+for logging in and installation, if your browser locale is set up.
+You will need the latest ZBabel installed. The translation file is in the
+I18N directory. 
+
+Import this (using Import/Export in ZODB) at the same level as your 
+ZBabelTower, and then import it from ZBabel. If you have ZBabel installed, 
+but, your application can't find a ZBabelTower, because of a bug in the 
+current dtml-fish tag, you might experience some problems. This ZBabel 
+bug should be fixed sometime soon. 
+
+You do not need ZBabel installed to run XUF, XUF installs a dummy 
+interface for ZBabel so that XUF can continue to run (sorry folks it
+defaults to Australian English).
+
+getUserNames() was returning the wrong stuff (notably affected TheJester's
+WorkOrders Product)
+
+There is a now an 'Advanced Postgres' Auth Source that uses a seperate
+Roles table and a 'more relational' layout. The schema is with the
+auth source in pgAuthSourceAlt. Contributed by 
+Adam Manock <abmanock at earthlink.net>
+
+If you had a membership source and had specified a login page, XUF was
+still using the stock docLogin instead of the membership specified page
+(for redirectToLogin, exceptions still raise the docLogin).
+
+I changed the icon to something a *little* less hideous
+
+Leonardo Rochael Almeida <leo at hiper.com.br> made the following changes
+to smbAuthSource
+
+* Added a 'winsserver' constructor parameter and a '_winsserver'
+   instance variable to the 'smbAuthSource' class. This variable should
+   be the empty string, meaning that the authenticaton host will be
+   looked up by broadcast, or an IP address string pointing to a WINS
+   server.
+
+* Modified the dtml templates to ask for the above mentioned WINS
+   server (and also to replace 'Add' with 'Change' in
+   'manage_editsmbAuthSourceForm').
+
+* Refactored the smbAuthSource class to isolate all smb interaction
+   inside well defined methods.
+
+
+Changes for 0.9.0
+
+Messages are now sent back to the docLogin form. There's a file called
+LoginRequiredMessages.py where the messages are kept for now (it might
+end up a run-time configurable thing later).
+
+There's a new docLogin.dtml file on disk that shows how to use the new
+messages. Because docLogin is in the ZODB this won't be automatically
+upgraded.
+
+Idle Session Timeouts are in (this is the reason for the minor bump).
+If you flick the switch, then users are forced back to the login form
+(with a message saying their session timed out), when they're removed
+from the cache.
+
+I made some adjustments to the tabs on the management interface because
+they were too big, and I cleaned it up a bit for times when they run
+together.
+
+The internal API was inconsistent, so that's been updated.
+AuthSources no longer need to provide getUsers(), it was never
+being called anyway since exUserFolder built it's own.
+listUsers now returns the same data as listOneUser, this is used in
+other places as if it were a list of listOneUser calls.
+
+Fixed pgAuthSource to deal with NULL rather than empty roles
+columns (legacy columns).
+
+Changed Home Directory creation to use copy & paste functions to
+copy the skeleton data.
+
+Changes for 0.8.5
+
+I forgot to update the schema file for userproperties to reflect
+the temporary properties flag.
+
+Checks for existing cache weren't being performed before removing users
+from it, when their data was updated.
+
+Reversed the order for checking in cookie_validate, to allow logging
+in as a new user, when session tracking was on. Also now you can
+login as a different user, without logging out first, which might
+be useful to some people.
+
+etcAuthSource now looks for the correct salt from the file for
+encrypting the user supplied password
+
+Changes for 0.8.4
+
+Activating Session Tracking and then adding a new user when there
+were none in the XUF was broken.
+
+Changes for 0.8.3
+
+The idle users are flushed from the cache when you ask for the list
+of cache users (since it's iterating over the whole list anyway). So
+you can manually clear your cache by looking at the Cache Stats page.
+
+If you display the list of logged in users on your site, then your cache
+will be flushed for you automagically.
+
+Allowed a destination to be sent to redirectToLogin to allow you to
+manually override the destination after logging in.
+
+Added in a __setstate__ for pgPropSource to deal with new ZSQL Methods
+being added.
+
+Changes for 0.8.2
+A number of bugs related to temp properties fixed in pgPropSource
+
+FTP Access to folders protected with cookie_mode has been fixed, it
+now reverts to std_auth (which handles the FTP connection fine), since
+FTP auths are handled by getting a "Basic" auth tag coming through, which
+should never happen in cookie mode. 
+
+This has the knock-on effect of authenticating users that auth from a 
+higher acl_users that doesn't use cookies, 'more' correctly now. Which is
+if you have a user defined above, and in XUF and the XUF user has less
+permissions, it'll 401 you if you don't have permissions locally
+(which is the correct behaviour). This bit me in the arse when I changed it, 
+and I'm still leaving it this way. d8)
+
+Users are now flushed from the cache when you edit them (in case you changed
+roles), so that new roles should take effect immediately.
+
+The credential cache now uses the (Zope) builtin BTree Module for caching 
+rather than the AVL Tree implementation. There was a nasty issue with users 
+appearing multiple times in the AVL Tree which sucked.
+
+There is a report of the Radius Auth Source being broken (most likely
+by me), if your radius source stops working, you can try copying the
+py-radius.py file from sourceforge over the top of radius.py. If someone
+gives me a traceback, I can fix it. I don't seem to be having problems,
+but, I don't have a full time RADIUS source either.
+
+
+Changes for 0.8.1
+
+A bug in _doAddUser was fixed
+A bug in the User Object unconditionally calling the prop source was fixed.
+
+
+Changes for 0.8.0
+
+Experimental "Session Tracking" added (why is it called that? we don't really
+track anything, just associate arbitrary data with anonymous users).
+This relies on the credential cache being active. Your session will 
+automatically expire when the anonymous user is so idle that they are 
+expired from the cache. This is not currently acceptable (to me), but,
+it might be to other people, I await feedback on how sessions should expire
+gracefully.
+
+Updated the README.txt file to point at the UZG and to explain the
+version numbering system.
+
+All this time you couldn't delete properties from a user... who knew?
+It's fixed now.
+
+Temporary properties now available, you can setTempProperty() on a 
+user object, and also flushTempProperties() on a user object.
+Temporary properties are accessed like normal properties, and can be
+deleted in the same way. flushTempProperties is there to do a quick
+flush of all the crap you might have inserted (useful for sessions).
+If your user is flushed from the cache, then all temp properties will
+also be removed at that point.
+
+Propsource providers should look at the new temp properties stuff and
+update accordingly.
+
+Alex provided a whole heap of patches to make basicMembership more usable,
+well make it actually work.
+
+Matt Behrens supplied patches to prevent null logins and to allow case
+insensitive logins for smbAuthSource
+
+Added a basic FAQ.
+
+
+Changes for 0.7.10
+
+Active Users type functionality was added. The new function is called
+getUserCacheUsers(). It returns a list of dicts;
+
+{'username': theusername, 'lastAccessed': float_value} 
+
+lastAccessed represents the last time the user touched something.
+The Cache Stats page shows an example usage showing idle time (very cool
+I think :-)
+
+The logout method was not correctly removing users from the cache,
+although the cookie was removed, so logins were still enforced. I'm not
+sure of any side-effects related to it, but, 
+
+Some permissions were a little too liberal, including allowing arbitrary
+users to set and get Properties on the acl_users folder.
+
+Copy/Paste support for pasting exUserFolders into the root was added.
+I'm not sure I like the way this is done. I haven't found any side effects
+so far, but, just be wary. Adding an exUserFolder to the root becomes
+semi-trivial now. Create one in a sub-folder. Login as the emergency user.
+CUT the exUserFolder. Delete the standard acl_users folder. Paste exUserFolder.
+You should be away. At least it worked fine for me... YMMV
+
+_doChangeUser and _doDelUsers added so users can be altered and deleted 
+like for Standard UserFolder.
+
+_createInitialUser added so there should always be your initUser (hopefully) 
+when you create your exUserFolder.
+
+Emergency User checking brought into line with Standard Folder
+
+__creatable_by_emergency_user_ added and returns 1 to explicitly allow this.
+
+Unenlightened Zopistas Guide updated to have a 'Recipe' like section.
+Currently contains a section about adding exUserFolders from python.
+
+
+Changes for 0.7.9
+
+RADIUS authSource had a problem with non-integers being extracted from
+REQUEST (I wish someone at DC would fix this already). I worked around
+this problem
+
+Default port for RADIUS is now 1812 in line with the IANA sanctioned list.
+
+Unenlightened Zopistas Guide to exUserFolder version 0.0 included, 
+covers installation and authentication sources, and the most common 
+configuration mistake (or misunderstanding).
+
+I almost released with the daggy management screens all Purple or SkyBlue,
+so consider yoursevles lucky. This would have been the "Blue" release.
+
+Changes for 0.7.8
+
+zodbPropSource had a bug that must have been there since 0.0.0 where
+_p_changed wasn't being called on create, update, or delete user.
+Thanks to Bouke Scheurwater for spotting that one.
+
+Alex provided a number of patched to fix a whole bunch of goofy stuff
+with Basic Member Source that was stupidly wrong.
+
+Matt Behrens provided a patch to allow emergency user to own exUserFolders
+and some of the sources. I've grudgingly updated all the sources to allow
+this. It's just a hey nonny nonny to people using it as a root authenticator
+now.
+
+Matt Behrens also provided a patch to fix 'broken pipe' problems with
+smbAuthSource.
+
+pySMB is now at 0.2 for smbAuthSource WARNING: This will try to use DES 
+encrypted passwords. Apparently it should be ok if your server doesn't want
+them. However if it breaks, unpack the pySMB distribution in the 
+smbAuthSource directory, there are registry examples there to turn
+it off. It unfortunately needs the mxCrypto tools for encrypted passwords
+to work. When I've got a bit more time, I'll see if I can make it use
+crypt or fcrypt if available instead.
+
+Explicit checks for the emergency user were placed into the cookie_validate
+routines. I suspect this may have been the cause of some grief with people
+doing weird things like trying to make it the root auth folder.
+
+Changes for 0.7.7
+
+Some Auth sources had problems coping with no roles being selected when
+a user was created from the management interface, the stock ones were fixed.
+
+I screwed up some of the DTML, and forgot to change the loading of two of
+the methods from the dtml directory.
+
+NO MORE TRACEBACKS ON LOGIN FORMS, there is a little redirector dtml file
+dtml/docLoginRedirect that redirects to acl_users/docLogin with destination
+set to take them back to where they were going. If you have a custom loginPage
+change the redirector dtml to point to your new page.
+
+standard_html swapped for manage_page on Management Pages. Hopefully
+this doesn't break someone with an old copy of Zope.
+
+Credential Caching is now available by default for all Authentication Sources,
+upgrading installs will get this defaulted to 0 for no caching. You can alter
+the cache level from the Parameters Tab. Authors of external sources should
+remove any internal auth caching they're doing, and allow the user to decide
+how long to cache the credentials for.
+
+
+Changes for 0.7.6
+
+smbAuthSource included. Doesn't require any external libraries, or compiling.
+Uses pySMB from Micheal Teo <michaelteo at bigfoot.com>
+
+Changes for 0.7.5
+The Management Interface now batches the user list by 10. This isn't
+configurable at the moment (just change the dtml).
+
+The code was re-organised slightly, with all the DTML moving into its
+own directory for core.
+
+radiusAuthSource added, but, is so far untested. It is a direct port of
+ZRadius for GUF, but, I haven't had a chance to setup a RADIUS server to
+test it out.
+
+You can add properties to a user from the management interface.
+
+List Properties on users can be added and edited, if I can work out a decent 
+way to edit Dicts/Mappings, I'll add that feature in.
+
+This paves the way for defining a set of properties in the Membership 
+source, so it can create a Signup and Edit page for you automatically. 
+You will also be able to specify which properties the user can edit, or 
+roles required to edit a property, this will be in a later release though.
+
+pgPropSource was updated to take into account non-scalar types, and now
+pickles all data going into the database, this means ints will stay as ints,
+et al. 
+There is code in there to cope with older properties coming out as strings.
+The Schema remains the same.
+
+Changes for 0.7.2
+Changes to make it work with older version of python
+Some minor bug fixes for membership.
+
+Changes for 0.7.1
+DTML Change for cmfPropSource
+
+Changes for 0.7.0
+exUserFolder was a little too liberal in removing its cruft, this is now
+fixed.
+
+cmfPropSource was provided by Alan Runyan which is a layer around the CMF
+property stuff. It's conditionally imported, so if you don't have CMF
+installed you don't need to worry that'll it'll break.
+
+Property Sources are optional, and there is a NULL Property Source for this
+purpose.
+
+Membership hooks, and a rough start at membership (basicMemberSource),
+which has some usable functionality (you MUST read README.Membership before
+using this).
+
+Membership Sources are optional and there is a NULL Membership Source for
+this purpose.
+
+
+Changes for 0.6.2
+exUserFolder was leaving cruft around when it was being deleted from
+Folders. The cruft should now be obliterated if you delete an exUserFolder.
+
+Changes for 0.6.1
+Ownership tab enabled, for those sick monkeys that want to use it as a root
+Folder (there are some).
+
+fcrypt got the __init__.py that was missing from the 0.6.0 release
+zodbAuthSource updated to pull in fcrypt if crypt was missing.
+
+Changes for 0.6.0
+
+Updated for 2.4.1 / Python 2.1
+Bug in pgPropSource not deleting users from the property cache fixed.
+Bug with Local Roles not getting what it expected fixed.
+Alex Verstraeten provided zodbAuthSource, there's a README.zodbAuthSource,
+and the same README inside the zodbAuthSource directory.
+fcrypt is now included and used if crypt cannot be imported. More information 
+on fcrypt can be found at http://home.clear.net.nz/pages/c.evans/sw/. This
+should help particularly Windows users a lot.
+Rudimentary API doc included.
+
+Changes for 0.5.0
+
+A serious bug in zodbPropSource was fixed.
+
+There is now the option of providing a 'Remote Auth' function for
+validating. This allows things like IMAP/LDAP auth sources to do their
+authentication, since they don't return passwords you can use in general.
+
+There's already a 3rd Party solution that provides IMAP/POP3 authentication,
+using the new API.
+
+Changes for 0.4.6
+
+Minor dtml hacks
+
+Changes for 0.4.5
+
+Hooks for 'editing' Authentication and Property Sources were added, along
+with the relevant methods in each of the sources.
+
+The management interfaces got a little overhaul, just to make them 
+a little different (yes I know everything I do looks the same). The two
+I didn't want to mess with still have the acquired management interfaces.
+
+A fix for the ZODB Property Source which was missing a few methods.
+
+Changes for 0.4.0
+
+Based on an idea from Martin von Loewis, I added in support for defining
+roles for etcAuthSource. This basically uses the current Prop source to
+store a 'roles' property. The default role is still there as well for
+those of you who might be using it.
+
+Changes for 0.3.0
+
+Adrien Hernot noticed that properties for new users using zodbPropSource
+were causing havoc, and that the version.txt file was completely wrong.
+Andreas also noticed the version.txt was wrong. 
+
+I've been bugged enough by the pair of them to change the single += 
+into 1.5.2 compliant syntax. 
+
+I don't make any claims about it working under 1.5.2 though.
+
+Changes for 0.2.0
+
+Even more embarassment...
+
+Andreas Heckel provided fixes for some stupid things I left out including;
+
+o Fixing the way I was handling multiple roles coming out of the database
+o The wrong icon in the user display
+o Alerting me to the fact that pgPropSource didn't actually have a
+  deleteUsers hook
+o Providing a schema for automatically deleting properties in postgres
+  if you delete a user from the auth source (you have to be using both
+  pg sources for this to work, and they'd have to be in the same database)
+  I've put Andreas schema into the distribution, if you want to use 
+  exUserFolder as a straight pgUserFolder, you'll also need to edit
+  exUserFolder.py and comment out the line indicated in deleteUsers()
+
+Changes for 0.1.0
+
+Pretty embarassing really.
+
+M. Adam Kendall (DaJoker) found some stupid things in the 0.0.0 release 
+including the fact you couldn't edit user properties, or update them, 
+or actually change a user in anyway.
+
+I also discovered I was resetting the password to empty if you left it
+empty.. 

Added: trunk/ZopeProducts/exUserFolder/CryptoSources/__init__.py
===================================================================
--- trunk/ZopeProducts/exUserFolder/CryptoSources/__init__.py	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/CryptoSources/__init__.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,4 @@
+import pass_crypt
+import pass_md5
+import pass_sha
+import pass_plain

Added: trunk/ZopeProducts/exUserFolder/CryptoSources/fcrypt/.cvsignore
===================================================================
--- trunk/ZopeProducts/exUserFolder/CryptoSources/fcrypt/.cvsignore	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/CryptoSources/fcrypt/.cvsignore	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,4 @@
+*.pyc
+*.pyo
+*~
+.*.swp

Added: trunk/ZopeProducts/exUserFolder/CryptoSources/fcrypt/ChangeLog
===================================================================
--- trunk/ZopeProducts/exUserFolder/CryptoSources/fcrypt/ChangeLog	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/CryptoSources/fcrypt/ChangeLog	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,35 @@
+2001-05-05  Carey Evans  <careye at spamcop.net>
+
+	* fcrypt.py: Add module doc string for pydoc, and other globals
+	for pydoc as well.  Add __all__ for Python 2.1, and add
+	underscores to the front of private variables and functions.
+	(_set_key): Remove overly clever copying of globals into default
+	parameters, explicitly copying _shift2 and _skb before the loop.
+	(_body): Copy _SPtrans explicitly, as above.  Remove CR_ENCRYPT
+	inline function, and reroll unrolled loop using the contents of
+	this function.  Result: more readable code, and a 400% speedup!
+	(crypt): Add doc string for pydoc and doctest.
+	(_test): New function for doctest.
+
+	* setup.py: Add fields for PKG-INFO metadata.
+
+	* README: Add URL of distutils installation manual.
+
+	* LICENSE: Add note about license on fcrypt.py being the union of
+	my license on the Python code and Eric Young's on the original C.
+
+2001-03-24  Carey Evans  <careye at spamcop.net>
+
+	* setup.py: Move license to separate file.  Change email address
+	to SpamCop forwardder.  Update version to 1.1.
+
+	* fcrypt.py: Update license text and email address.
+	(crypt): Fix bug where passwords longer than eight characters were
+	not truncated.
+
+	* README: Update crypt module URL.  Remove license text, and add
+	pointer to LICENSE file.  Update email address.
+
+	* MANIFEST.in: Add LICENSE, ChangeLog and MANIFEST.in.
+
+	* LICENSE: New file.

Added: trunk/ZopeProducts/exUserFolder/CryptoSources/fcrypt/LICENSE
===================================================================
--- trunk/ZopeProducts/exUserFolder/CryptoSources/fcrypt/LICENSE	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/CryptoSources/fcrypt/LICENSE	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,77 @@
+		   fcrypt.py copyrights and license
+		   --------------------------------
+
+
+The Python code by Carey Evans has the following license, which is the
+original Python license with the serial numbers filed off, and the
+restrictions on advertising removed.
+
+  Copyright (C) 2001, 2001  Carey Evans  <careye at spamcop.net>
+
+  Permission to use, copy, modify, and distribute this software and its
+  documentation for any purpose and without fee is hereby granted,
+  provided that the above copyright notice appear in all copies and that
+  both that copyright notice and this permission notice appear in
+  supporting documentation.
+
+  CAREY EVANS DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+  INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
+  EVENT SHALL CAREY EVANS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+  CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF
+  USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+  OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+  PERFORMANCE OF THIS SOFTWARE.
+
+
+The original C code on which this module was based has the following
+more restrictive license, so the source for fcrypt.py should be
+considered to be covered by the union of my license and Eric Young's.
+
+  This library is free for commercial and non-commercial use as long as
+  the following conditions are aheared to.  The following conditions
+  apply to all code found in this distribution, be it the RC4, RSA,
+  lhash, DES, etc., code; not just the SSL code.  The SSL documentation
+  included with this distribution is covered by the same copyright terms
+  except that the holder is Tim Hudson (tjh at mincom.oz.au).
+  
+  Copyright remains Eric Young's, and as such any Copyright notices in
+  the code are not to be removed.
+  If this package is used in a product, Eric Young should be given attribution
+  as the author of the parts of the library used.
+  This can be in the form of a textual message at program startup or
+  in documentation (online or textual) provided with the package.
+  
+  Redistribution and use in source and binary forms, with or without
+  modification, are permitted provided that the following conditions
+  are met:
+  1. Redistributions of source code must retain the copyright
+     notice, this list of conditions and the following disclaimer.
+  2. Redistributions in binary form must reproduce the above copyright
+     notice, this list of conditions and the following disclaimer in the
+     documentation and/or other materials provided with the distribution.
+  3. All advertising materials mentioning features or use of this software
+     must display the following acknowledgement:
+     "This product includes cryptographic software written by
+      Eric Young (eay at mincom.oz.au)"
+     The word 'cryptographic' can be left out if the rouines from the library
+     being used are not cryptographic related :-).
+  4. If you include any Windows specific code (or a derivative thereof) from 
+     the apps directory (application code) you must include an acknowledgement:
+     "This product includes software written by Tim Hudson (tjh at mincom.oz.au)"
+  
+  THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND
+  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+  ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+  OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+  HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+  OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+  SUCH DAMAGE.
+  
+  The licence and distribution terms for any publically available version or
+  derivative of this code cannot be changed.  i.e. this code cannot simply be
+  copied and put under another distribution licence
+  [including the GNU Public Licence.]

Added: trunk/ZopeProducts/exUserFolder/CryptoSources/fcrypt/MANIFEST.in
===================================================================
--- trunk/ZopeProducts/exUserFolder/CryptoSources/fcrypt/MANIFEST.in	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/CryptoSources/fcrypt/MANIFEST.in	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1 @@
+include LICENSE ChangeLog MANIFEST.in

Added: trunk/ZopeProducts/exUserFolder/CryptoSources/fcrypt/PKG-INFO
===================================================================
--- trunk/ZopeProducts/exUserFolder/CryptoSources/fcrypt/PKG-INFO	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/CryptoSources/fcrypt/PKG-INFO	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,13 @@
+Metadata-Version: 1.0
+Name: fcrypt
+Version: 1.2
+Summary: The Unix password crypt function.
+Home-page: http://home.clear.net.nz/pages/c.evans/sw/
+Author: Carey Evans
+Author-email: careye at spamcop.net
+License: BSD
+Description: A pure Python implementation of the Unix DES password crypt function,
+        based on Eric Young's fcrypt.c.  It works with any version of Python
+        from version 1.5 or higher, and because it's pure Python it doesn't
+        need a C compiler to install it.
+Platform: UNKNOWN

Added: trunk/ZopeProducts/exUserFolder/CryptoSources/fcrypt/README
===================================================================
--- trunk/ZopeProducts/exUserFolder/CryptoSources/fcrypt/README	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/CryptoSources/fcrypt/README	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,33 @@
+			      fcrypt.py
+			      ---------
+
+This is a pure Python implementation of the Unix DES password crypt
+function.  It was ported from C code by Eric Young (eay at mincom.oz.au).
+See the file LICENSE for copyright and license details.
+
+This module is packaged with Distutils.  If you have this installed,
+or it came with your version of Python, you can install it by typing:
+
+    python setup.py install
+
+If not, you can just copy `fcrypt.py' into a directory on your Python
+library path, or into the same directory as the program that wants to
+use it.
+
+For more information, see the documentation for Python's built-in
+crypt module at:
+
+    http://www.python.org/doc/current/lib/module-crypt.html
+
+Eric Young's fcrypt.c is available from:
+
+    ftp://ftp.psy.uq.oz.au/pub/Crypto/DES/
+
+For more Distutils information, see:
+
+    http://www.python.org/doc/current/inst/inst.html
+    http://www.python.org/sigs/distutils-sig/
+
+-- 
+Carey Evans  <careye at spamcop.net>
+5 May 2001

Added: trunk/ZopeProducts/exUserFolder/CryptoSources/fcrypt/__init__.py
===================================================================
--- trunk/ZopeProducts/exUserFolder/CryptoSources/fcrypt/__init__.py	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/CryptoSources/fcrypt/__init__.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1 @@
+import fcrypt

Added: trunk/ZopeProducts/exUserFolder/CryptoSources/fcrypt/fcrypt.py
===================================================================
--- trunk/ZopeProducts/exUserFolder/CryptoSources/fcrypt/fcrypt.py	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/CryptoSources/fcrypt/fcrypt.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,602 @@
+# fcrypt.py
+
+"""Unix crypt(3) password hash algorithm.
+
+This is a port to Python of the standard Unix password crypt function.
+It's a single self-contained source file that works with any version
+of Python from version 1.5 or higher.  The code is based on Eric
+Young's optimised crypt in C.
+
+Python fcrypt is intended for users whose Python installation has not
+had the crypt module enabled, or whose C library doesn't include the
+crypt function.  See the documentation for the Python crypt module for
+more information:
+
+  http://www.python.org/doc/current/lib/module-crypt.html
+
+The crypt() function is a one-way hash function, intended to hide a
+password such that the only way to find out the original password is
+to guess values until you get a match.  If you need to encrypt and
+decrypt data, this is not the module for you.
+
+There are at least two packages providing Python cryptography support:
+M2Crypto at <http://www.pobox.org.sg/home/ngps/m2/>, and amkCrypto at
+<http://www.amk.ca/python/code/crypto.html>.
+
+Functions:
+
+  crypt() -- return hashed password
+"""
+
+__author__ = 'Carey Evans <careye at spamcop.net>'
+__version__ = '1.2'
+__date__ = '6 May 2001'
+__credits__ = '''michal j wallace for inspiring me to write this.
+Eric Young for the C code this module was copied from.'''
+
+__all__ = ['crypt']
+
+
+# Copyright (C) 2000, 2001  Carey Evans  <careye at spamcop.net>
+#
+# Permission to use, copy, modify, and distribute this software and
+# its documentation for any purpose and without fee is hereby granted,
+# provided that the above copyright notice appear in all copies and
+# that both that copyright notice and this permission notice appear in
+# supporting documentation.
+#
+# CAREY EVANS DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
+# EVENT SHALL CAREY EVANS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF
+# USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+# OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+# Based on C code by Eric Young (eay at mincom.oz.au), which has the
+# following copyright.  Especially note condition 3, which imposes
+# extra restrictions on top of the standard Python license used above.
+#
+# The fcrypt.c source is available from:
+#     ftp://ftp.psy.uq.oz.au/pub/Crypto/DES/
+
+# ----- BEGIN fcrypt.c LICENSE -----
+#
+# This library is free for commercial and non-commercial use as long as
+# the following conditions are aheared to.  The following conditions
+# apply to all code found in this distribution, be it the RC4, RSA,
+# lhash, DES, etc., code; not just the SSL code.  The SSL documentation
+# included with this distribution is covered by the same copyright terms
+# except that the holder is Tim Hudson (tjh at mincom.oz.au).
+# 
+# Copyright remains Eric Young's, and as such any Copyright notices in
+# the code are not to be removed.
+# If this package is used in a product, Eric Young should be given attribution
+# as the author of the parts of the library used.
+# This can be in the form of a textual message at program startup or
+# in documentation (online or textual) provided with the package.
+# 
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the copyright
+#    notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+# 3. All advertising materials mentioning features or use of this software
+#    must display the following acknowledgement:
+#    "This product includes cryptographic software written by
+#     Eric Young (eay at mincom.oz.au)"
+#    The word 'cryptographic' can be left out if the rouines from the library
+#    being used are not cryptographic related :-).
+# 4. If you include any Windows specific code (or a derivative thereof) from 
+#    the apps directory (application code) you must include an acknowledgement:
+#    "This product includes software written by Tim Hudson (tjh at mincom.oz.au)"
+# 
+# THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+# 
+# The licence and distribution terms for any publically available version or
+# derivative of this code cannot be changed.  i.e. this code cannot simply be
+# copied and put under another distribution licence
+# [including the GNU Public Licence.]
+#
+# ----- END fcrypt.c LICENSE -----
+
+
+import string, struct
+
+
+_ITERATIONS = 16
+
+_SPtrans = (
+    # nibble 0
+    [ 0x00820200, 0x00020000, 0x80800000, 0x80820200,
+      0x00800000, 0x80020200, 0x80020000, 0x80800000,
+      0x80020200, 0x00820200, 0x00820000, 0x80000200,
+      0x80800200, 0x00800000, 0x00000000, 0x80020000,
+      0x00020000, 0x80000000, 0x00800200, 0x00020200,
+      0x80820200, 0x00820000, 0x80000200, 0x00800200,
+      0x80000000, 0x00000200, 0x00020200, 0x80820000,
+      0x00000200, 0x80800200, 0x80820000, 0x00000000,
+      0x00000000, 0x80820200, 0x00800200, 0x80020000,
+      0x00820200, 0x00020000, 0x80000200, 0x00800200,
+      0x80820000, 0x00000200, 0x00020200, 0x80800000,
+      0x80020200, 0x80000000, 0x80800000, 0x00820000,
+      0x80820200, 0x00020200, 0x00820000, 0x80800200,
+      0x00800000, 0x80000200, 0x80020000, 0x00000000,
+      0x00020000, 0x00800000, 0x80800200, 0x00820200,
+      0x80000000, 0x80820000, 0x00000200, 0x80020200 ],
+
+    # nibble 1
+    [ 0x10042004, 0x00000000, 0x00042000, 0x10040000,
+      0x10000004, 0x00002004, 0x10002000, 0x00042000,
+      0x00002000, 0x10040004, 0x00000004, 0x10002000,
+      0x00040004, 0x10042000, 0x10040000, 0x00000004,
+      0x00040000, 0x10002004, 0x10040004, 0x00002000,
+      0x00042004, 0x10000000, 0x00000000, 0x00040004,
+      0x10002004, 0x00042004, 0x10042000, 0x10000004,
+      0x10000000, 0x00040000, 0x00002004, 0x10042004,
+      0x00040004, 0x10042000, 0x10002000, 0x00042004,
+      0x10042004, 0x00040004, 0x10000004, 0x00000000,
+      0x10000000, 0x00002004, 0x00040000, 0x10040004,
+      0x00002000, 0x10000000, 0x00042004, 0x10002004,
+      0x10042000, 0x00002000, 0x00000000, 0x10000004,
+      0x00000004, 0x10042004, 0x00042000, 0x10040000,
+      0x10040004, 0x00040000, 0x00002004, 0x10002000,
+      0x10002004, 0x00000004, 0x10040000, 0x00042000 ],
+
+    # nibble 2
+    [ 0x41000000, 0x01010040, 0x00000040, 0x41000040,
+      0x40010000, 0x01000000, 0x41000040, 0x00010040,
+      0x01000040, 0x00010000, 0x01010000, 0x40000000,
+      0x41010040, 0x40000040, 0x40000000, 0x41010000,
+      0x00000000, 0x40010000, 0x01010040, 0x00000040,
+      0x40000040, 0x41010040, 0x00010000, 0x41000000,
+      0x41010000, 0x01000040, 0x40010040, 0x01010000,
+      0x00010040, 0x00000000, 0x01000000, 0x40010040,
+      0x01010040, 0x00000040, 0x40000000, 0x00010000,
+      0x40000040, 0x40010000, 0x01010000, 0x41000040,
+      0x00000000, 0x01010040, 0x00010040, 0x41010000,
+      0x40010000, 0x01000000, 0x41010040, 0x40000000,
+      0x40010040, 0x41000000, 0x01000000, 0x41010040,
+      0x00010000, 0x01000040, 0x41000040, 0x00010040,
+      0x01000040, 0x00000000, 0x41010000, 0x40000040,
+      0x41000000, 0x40010040, 0x00000040, 0x01010000 ],
+
+    # nibble 3
+    [ 0x00100402, 0x04000400, 0x00000002, 0x04100402,
+      0x00000000, 0x04100000, 0x04000402, 0x00100002,
+      0x04100400, 0x04000002, 0x04000000, 0x00000402,
+      0x04000002, 0x00100402, 0x00100000, 0x04000000,
+      0x04100002, 0x00100400, 0x00000400, 0x00000002,
+      0x00100400, 0x04000402, 0x04100000, 0x00000400,
+      0x00000402, 0x00000000, 0x00100002, 0x04100400,
+      0x04000400, 0x04100002, 0x04100402, 0x00100000,
+      0x04100002, 0x00000402, 0x00100000, 0x04000002,
+      0x00100400, 0x04000400, 0x00000002, 0x04100000,
+      0x04000402, 0x00000000, 0x00000400, 0x00100002,
+      0x00000000, 0x04100002, 0x04100400, 0x00000400,
+      0x04000000, 0x04100402, 0x00100402, 0x00100000,
+      0x04100402, 0x00000002, 0x04000400, 0x00100402,
+      0x00100002, 0x00100400, 0x04100000, 0x04000402,
+      0x00000402, 0x04000000, 0x04000002, 0x04100400 ],
+
+    # nibble 4
+    [ 0x02000000, 0x00004000, 0x00000100, 0x02004108,
+      0x02004008, 0x02000100, 0x00004108, 0x02004000,
+      0x00004000, 0x00000008, 0x02000008, 0x00004100,
+      0x02000108, 0x02004008, 0x02004100, 0x00000000,
+      0x00004100, 0x02000000, 0x00004008, 0x00000108,
+      0x02000100, 0x00004108, 0x00000000, 0x02000008,
+      0x00000008, 0x02000108, 0x02004108, 0x00004008,
+      0x02004000, 0x00000100, 0x00000108, 0x02004100,
+      0x02004100, 0x02000108, 0x00004008, 0x02004000,
+      0x00004000, 0x00000008, 0x02000008, 0x02000100,
+      0x02000000, 0x00004100, 0x02004108, 0x00000000,
+      0x00004108, 0x02000000, 0x00000100, 0x00004008,
+      0x02000108, 0x00000100, 0x00000000, 0x02004108,
+      0x02004008, 0x02004100, 0x00000108, 0x00004000,
+      0x00004100, 0x02004008, 0x02000100, 0x00000108,
+      0x00000008, 0x00004108, 0x02004000, 0x02000008 ],
+
+    # nibble 5
+    [ 0x20000010, 0x00080010, 0x00000000, 0x20080800,
+      0x00080010, 0x00000800, 0x20000810, 0x00080000,
+      0x00000810, 0x20080810, 0x00080800, 0x20000000,
+      0x20000800, 0x20000010, 0x20080000, 0x00080810,
+      0x00080000, 0x20000810, 0x20080010, 0x00000000,
+      0x00000800, 0x00000010, 0x20080800, 0x20080010,
+      0x20080810, 0x20080000, 0x20000000, 0x00000810,
+      0x00000010, 0x00080800, 0x00080810, 0x20000800,
+      0x00000810, 0x20000000, 0x20000800, 0x00080810,
+      0x20080800, 0x00080010, 0x00000000, 0x20000800,
+      0x20000000, 0x00000800, 0x20080010, 0x00080000,
+      0x00080010, 0x20080810, 0x00080800, 0x00000010,
+      0x20080810, 0x00080800, 0x00080000, 0x20000810,
+      0x20000010, 0x20080000, 0x00080810, 0x00000000,
+      0x00000800, 0x20000010, 0x20000810, 0x20080800,
+      0x20080000, 0x00000810, 0x00000010, 0x20080010 ],
+
+    # nibble 6
+    [ 0x00001000, 0x00000080, 0x00400080, 0x00400001,
+      0x00401081, 0x00001001, 0x00001080, 0x00000000,
+      0x00400000, 0x00400081, 0x00000081, 0x00401000,
+      0x00000001, 0x00401080, 0x00401000, 0x00000081,
+      0x00400081, 0x00001000, 0x00001001, 0x00401081,
+      0x00000000, 0x00400080, 0x00400001, 0x00001080,
+      0x00401001, 0x00001081, 0x00401080, 0x00000001,
+      0x00001081, 0x00401001, 0x00000080, 0x00400000,
+      0x00001081, 0x00401000, 0x00401001, 0x00000081,
+      0x00001000, 0x00000080, 0x00400000, 0x00401001,
+      0x00400081, 0x00001081, 0x00001080, 0x00000000,
+      0x00000080, 0x00400001, 0x00000001, 0x00400080,
+      0x00000000, 0x00400081, 0x00400080, 0x00001080,
+      0x00000081, 0x00001000, 0x00401081, 0x00400000,
+      0x00401080, 0x00000001, 0x00001001, 0x00401081,
+      0x00400001, 0x00401080, 0x00401000, 0x00001001 ],
+
+    # nibble 7
+    [ 0x08200020, 0x08208000, 0x00008020, 0x00000000,
+      0x08008000, 0x00200020, 0x08200000, 0x08208020,
+      0x00000020, 0x08000000, 0x00208000, 0x00008020,
+      0x00208020, 0x08008020, 0x08000020, 0x08200000,
+      0x00008000, 0x00208020, 0x00200020, 0x08008000,
+      0x08208020, 0x08000020, 0x00000000, 0x00208000,
+      0x08000000, 0x00200000, 0x08008020, 0x08200020,
+      0x00200000, 0x00008000, 0x08208000, 0x00000020,
+      0x00200000, 0x00008000, 0x08000020, 0x08208020,
+      0x00008020, 0x08000000, 0x00000000, 0x00208000,
+      0x08200020, 0x08008020, 0x08008000, 0x00200020,
+      0x08208000, 0x00000020, 0x00200020, 0x08008000,
+      0x08208020, 0x00200000, 0x08200000, 0x08000020,
+      0x00208000, 0x00008020, 0x08008020, 0x08200000,
+      0x00000020, 0x08208000, 0x00208020, 0x00000000,
+      0x08000000, 0x08200020, 0x00008000, 0x00208020 ] )
+
+_skb = (
+    # for C bits (numbered as per FIPS 46) 1 2 3 4 5 6
+    [ 0x00000000, 0x00000010, 0x20000000, 0x20000010,
+      0x00010000, 0x00010010, 0x20010000, 0x20010010,
+      0x00000800, 0x00000810, 0x20000800, 0x20000810,
+      0x00010800, 0x00010810, 0x20010800, 0x20010810,
+      0x00000020, 0x00000030, 0x20000020, 0x20000030,
+      0x00010020, 0x00010030, 0x20010020, 0x20010030,
+      0x00000820, 0x00000830, 0x20000820, 0x20000830,
+      0x00010820, 0x00010830, 0x20010820, 0x20010830,
+      0x00080000, 0x00080010, 0x20080000, 0x20080010,
+      0x00090000, 0x00090010, 0x20090000, 0x20090010,
+      0x00080800, 0x00080810, 0x20080800, 0x20080810,
+      0x00090800, 0x00090810, 0x20090800, 0x20090810,
+      0x00080020, 0x00080030, 0x20080020, 0x20080030,
+      0x00090020, 0x00090030, 0x20090020, 0x20090030,
+      0x00080820, 0x00080830, 0x20080820, 0x20080830,
+      0x00090820, 0x00090830, 0x20090820, 0x20090830 ],
+
+    # for C bits (numbered as per FIPS 46) 7 8 10 11 12 13
+    [ 0x00000000, 0x02000000, 0x00002000, 0x02002000,
+      0x00200000, 0x02200000, 0x00202000, 0x02202000,
+      0x00000004, 0x02000004, 0x00002004, 0x02002004,
+      0x00200004, 0x02200004, 0x00202004, 0x02202004,
+      0x00000400, 0x02000400, 0x00002400, 0x02002400,
+      0x00200400, 0x02200400, 0x00202400, 0x02202400,
+      0x00000404, 0x02000404, 0x00002404, 0x02002404,
+      0x00200404, 0x02200404, 0x00202404, 0x02202404,
+      0x10000000, 0x12000000, 0x10002000, 0x12002000,
+      0x10200000, 0x12200000, 0x10202000, 0x12202000,
+      0x10000004, 0x12000004, 0x10002004, 0x12002004,
+      0x10200004, 0x12200004, 0x10202004, 0x12202004,
+      0x10000400, 0x12000400, 0x10002400, 0x12002400,
+      0x10200400, 0x12200400, 0x10202400, 0x12202400,
+      0x10000404, 0x12000404, 0x10002404, 0x12002404,
+      0x10200404, 0x12200404, 0x10202404, 0x12202404 ],
+
+    # for C bits (numbered as per FIPS 46) 14 15 16 17 19 20
+    [ 0x00000000, 0x00000001, 0x00040000, 0x00040001,
+      0x01000000, 0x01000001, 0x01040000, 0x01040001,
+      0x00000002, 0x00000003, 0x00040002, 0x00040003,
+      0x01000002, 0x01000003, 0x01040002, 0x01040003,
+      0x00000200, 0x00000201, 0x00040200, 0x00040201,
+      0x01000200, 0x01000201, 0x01040200, 0x01040201,
+      0x00000202, 0x00000203, 0x00040202, 0x00040203,
+      0x01000202, 0x01000203, 0x01040202, 0x01040203,
+      0x08000000, 0x08000001, 0x08040000, 0x08040001,
+      0x09000000, 0x09000001, 0x09040000, 0x09040001,
+      0x08000002, 0x08000003, 0x08040002, 0x08040003,
+      0x09000002, 0x09000003, 0x09040002, 0x09040003,
+      0x08000200, 0x08000201, 0x08040200, 0x08040201,
+      0x09000200, 0x09000201, 0x09040200, 0x09040201,
+      0x08000202, 0x08000203, 0x08040202, 0x08040203,
+      0x09000202, 0x09000203, 0x09040202, 0x09040203 ],
+
+    # for C bits (numbered as per FIPS 46) 21 23 24 26 27 28
+    [ 0x00000000, 0x00100000, 0x00000100, 0x00100100,
+      0x00000008, 0x00100008, 0x00000108, 0x00100108,
+      0x00001000, 0x00101000, 0x00001100, 0x00101100,
+      0x00001008, 0x00101008, 0x00001108, 0x00101108,
+      0x04000000, 0x04100000, 0x04000100, 0x04100100,
+      0x04000008, 0x04100008, 0x04000108, 0x04100108,
+      0x04001000, 0x04101000, 0x04001100, 0x04101100,
+      0x04001008, 0x04101008, 0x04001108, 0x04101108,
+      0x00020000, 0x00120000, 0x00020100, 0x00120100,
+      0x00020008, 0x00120008, 0x00020108, 0x00120108,
+      0x00021000, 0x00121000, 0x00021100, 0x00121100,
+      0x00021008, 0x00121008, 0x00021108, 0x00121108,
+      0x04020000, 0x04120000, 0x04020100, 0x04120100,
+      0x04020008, 0x04120008, 0x04020108, 0x04120108,
+      0x04021000, 0x04121000, 0x04021100, 0x04121100,
+      0x04021008, 0x04121008, 0x04021108, 0x04121108 ],
+
+    # for D bits (numbered as per FIPS 46) 1 2 3 4 5 6
+    [ 0x00000000, 0x10000000, 0x00010000, 0x10010000,
+      0x00000004, 0x10000004, 0x00010004, 0x10010004,
+      0x20000000, 0x30000000, 0x20010000, 0x30010000,
+      0x20000004, 0x30000004, 0x20010004, 0x30010004,
+      0x00100000, 0x10100000, 0x00110000, 0x10110000,
+      0x00100004, 0x10100004, 0x00110004, 0x10110004,
+      0x20100000, 0x30100000, 0x20110000, 0x30110000,
+      0x20100004, 0x30100004, 0x20110004, 0x30110004,
+      0x00001000, 0x10001000, 0x00011000, 0x10011000,
+      0x00001004, 0x10001004, 0x00011004, 0x10011004,
+      0x20001000, 0x30001000, 0x20011000, 0x30011000,
+      0x20001004, 0x30001004, 0x20011004, 0x30011004,
+      0x00101000, 0x10101000, 0x00111000, 0x10111000,
+      0x00101004, 0x10101004, 0x00111004, 0x10111004,
+      0x20101000, 0x30101000, 0x20111000, 0x30111000,
+      0x20101004, 0x30101004, 0x20111004, 0x30111004 ],
+
+    # for D bits (numbered as per FIPS 46) 8 9 11 12 13 14
+    [ 0x00000000, 0x08000000, 0x00000008, 0x08000008,
+      0x00000400, 0x08000400, 0x00000408, 0x08000408,
+      0x00020000, 0x08020000, 0x00020008, 0x08020008,
+      0x00020400, 0x08020400, 0x00020408, 0x08020408,
+      0x00000001, 0x08000001, 0x00000009, 0x08000009,
+      0x00000401, 0x08000401, 0x00000409, 0x08000409,
+      0x00020001, 0x08020001, 0x00020009, 0x08020009,
+      0x00020401, 0x08020401, 0x00020409, 0x08020409,
+      0x02000000, 0x0A000000, 0x02000008, 0x0A000008,
+      0x02000400, 0x0A000400, 0x02000408, 0x0A000408,
+      0x02020000, 0x0A020000, 0x02020008, 0x0A020008,
+      0x02020400, 0x0A020400, 0x02020408, 0x0A020408,
+      0x02000001, 0x0A000001, 0x02000009, 0x0A000009,
+      0x02000401, 0x0A000401, 0x02000409, 0x0A000409,
+      0x02020001, 0x0A020001, 0x02020009, 0x0A020009,
+      0x02020401, 0x0A020401, 0x02020409, 0x0A020409 ],
+
+    # for D bits (numbered as per FIPS 46) 16 17 18 19 20 21
+    [ 0x00000000, 0x00000100, 0x00080000, 0x00080100,
+      0x01000000, 0x01000100, 0x01080000, 0x01080100,
+      0x00000010, 0x00000110, 0x00080010, 0x00080110,
+      0x01000010, 0x01000110, 0x01080010, 0x01080110,
+      0x00200000, 0x00200100, 0x00280000, 0x00280100,
+      0x01200000, 0x01200100, 0x01280000, 0x01280100,
+      0x00200010, 0x00200110, 0x00280010, 0x00280110,
+      0x01200010, 0x01200110, 0x01280010, 0x01280110,
+      0x00000200, 0x00000300, 0x00080200, 0x00080300,
+      0x01000200, 0x01000300, 0x01080200, 0x01080300,
+      0x00000210, 0x00000310, 0x00080210, 0x00080310,
+      0x01000210, 0x01000310, 0x01080210, 0x01080310,
+      0x00200200, 0x00200300, 0x00280200, 0x00280300,
+      0x01200200, 0x01200300, 0x01280200, 0x01280300,
+      0x00200210, 0x00200310, 0x00280210, 0x00280310,
+      0x01200210, 0x01200310, 0x01280210, 0x01280310 ],
+
+    # for D bits (numbered as per FIPS 46) 22 23 24 25 27 28
+    [ 0x00000000, 0x04000000, 0x00040000, 0x04040000,
+      0x00000002, 0x04000002, 0x00040002, 0x04040002,
+      0x00002000, 0x04002000, 0x00042000, 0x04042000,
+      0x00002002, 0x04002002, 0x00042002, 0x04042002,
+      0x00000020, 0x04000020, 0x00040020, 0x04040020,
+      0x00000022, 0x04000022, 0x00040022, 0x04040022,
+      0x00002020, 0x04002020, 0x00042020, 0x04042020,
+      0x00002022, 0x04002022, 0x00042022, 0x04042022,
+      0x00000800, 0x04000800, 0x00040800, 0x04040800,
+      0x00000802, 0x04000802, 0x00040802, 0x04040802,
+      0x00002800, 0x04002800, 0x00042800, 0x04042800,
+      0x00002802, 0x04002802, 0x00042802, 0x04042802,
+      0x00000820, 0x04000820, 0x00040820, 0x04040820,
+      0x00000822, 0x04000822, 0x00040822, 0x04040822,
+      0x00002820, 0x04002820, 0x00042820, 0x04042820,
+      0x00002822, 0x04002822, 0x00042822, 0x04042822 ] )
+
+_shifts2 = (0,0,1,1,1,1,1,1,0,1,1,1,1,1,1,0)
+
+_con_salt = [
+    0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,
+    0xDA,0xDB,0xDC,0xDD,0xDE,0xDF,0xE0,0xE1,
+    0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,
+    0xEA,0xEB,0xEC,0xED,0xEE,0xEF,0xF0,0xF1,
+    0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,
+    0xFA,0xFB,0xFC,0xFD,0xFE,0xFF,0x00,0x01,
+    0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,
+    0x0A,0x0B,0x05,0x06,0x07,0x08,0x09,0x0A,
+    0x0B,0x0C,0x0D,0x0E,0x0F,0x10,0x11,0x12,
+    0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1A,
+    0x1B,0x1C,0x1D,0x1E,0x1F,0x20,0x21,0x22,
+    0x23,0x24,0x25,0x20,0x21,0x22,0x23,0x24,
+    0x25,0x26,0x27,0x28,0x29,0x2A,0x2B,0x2C,
+    0x2D,0x2E,0x2F,0x30,0x31,0x32,0x33,0x34,
+    0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,0x3C,
+    0x3D,0x3E,0x3F,0x40,0x41,0x42,0x43,0x44 ]
+
+_cov_2char = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
+
+
+def _HPERM_OP(a):
+    """Clever bit manipulation."""
+    t = ((a << 18) ^ a) & 0xcccc0000
+    return a ^ t ^ ((t >> 18) & 0x3fff)
+
+def _PERM_OP(a,b,n,m):
+    """Cleverer bit manipulation."""
+    t = ((a >> n) ^ b) & m
+    b = b ^ t
+    a = a ^ (t << n)
+    return a,b
+
+
+def _set_key(password):
+    """Generate DES key schedule from ASCII password."""
+
+    c,d = struct.unpack('<ii', password)
+    c = (c & 0x7f7f7f7f) << 1
+    d = (d & 0x7f7f7f7f) << 1
+
+    d,c = _PERM_OP(d,c,4,0x0f0f0f0f)
+    c = _HPERM_OP(c)
+    d = _HPERM_OP(d)
+    d,c = _PERM_OP(d,c,1,0x55555555)
+    c,d = _PERM_OP(c,d,8,0x00ff00ff)
+    d,c = _PERM_OP(d,c,1,0x55555555)
+
+    # Any sign-extended bits are masked off.
+    d = (((d & 0x000000ff) << 16) | (d & 0x0000ff00) |
+         ((d & 0x00ff0000) >> 16) | ((c >> 4) & 0x0f000000))
+    c = c & 0x0fffffff
+
+    # Copy globals into local variables for loop.
+    shifts2 = _shifts2
+    skbc0, skbc1, skbc2, skbc3, skbd0, skbd1, skbd2, skbd3 = _skb
+
+    k = [0] * (_ITERATIONS * 2)
+
+    for i in range(_ITERATIONS):
+        # Only operates on top 28 bits.
+        if shifts2[i]:
+            c = (c >> 2) | (c << 26)
+            d = (d >> 2) | (d << 26)
+        else:
+            c = (c >> 1) | (c << 27)
+            d = (d >> 1) | (d << 27)
+        c = c & 0x0fffffff
+        d = d & 0x0fffffff
+
+        s = ( skbc0[  c      & 0x3f                    ] |
+              skbc1[((c>> 6) & 0x03) | ((c>> 7) & 0x3c)] |
+              skbc2[((c>>13) & 0x0f) | ((c>>14) & 0x30)] |
+              skbc3[((c>>20) & 0x01) |
+                    ((c>>21) & 0x06) | ((c>>22) & 0x38)] )
+
+        t = ( skbd0[  d      & 0x3f                    ] |
+              skbd1[((d>> 7) & 0x03) | ((d>> 8) & 0x3c)] |
+              skbd2[((d>>15) & 0x3f)                   ] |
+              skbd3[((d>>21) & 0x0f) | ((d>>22) & 0x30)] )
+
+        k[2*i] = ((t << 16) | (s & 0x0000ffff)) & 0xffffffff
+        s = (s >> 16) | (t & 0xffff0000)
+
+        # Top bit of s may be 1.
+        s = (s << 4) | ((s >> 28) & 0x0f)
+        k[2*i + 1] = s & 0xffffffff
+
+    return k
+
+
+def _body(ks, E0, E1):
+    """Use the key schedule ks and salt E0, E1 to create the password hash."""
+
+    # Copy global variable into locals for loop.
+    SP0, SP1, SP2, SP3, SP4, SP5, SP6, SP7 = _SPtrans
+
+    inner = range(0, _ITERATIONS*2, 2)
+    l = r = 0
+    for j in range(25):
+        l,r = r,l
+        for i in inner:
+            t = r ^ ((r >> 16) & 0xffff)
+            u = t & E0
+            t = t & E1
+            u = u ^ (u << 16) ^ r ^ ks[i]
+            t = t ^ (t << 16) ^ r ^ ks[i+1]
+            t = ((t >> 4) & 0x0fffffff) | (t << 28)
+
+            l,r = r,(SP1[(t    ) & 0x3f] ^ SP3[(t>> 8) & 0x3f] ^
+                     SP5[(t>>16) & 0x3f] ^ SP7[(t>>24) & 0x3f] ^
+                     SP0[(u    ) & 0x3f] ^ SP2[(u>> 8) & 0x3f] ^
+                     SP4[(u>>16) & 0x3f] ^ SP6[(u>>24) & 0x3f] ^ l)
+
+    l = ((l >> 1) & 0x7fffffff) | ((l & 0x1) << 31)
+    r = ((r >> 1) & 0x7fffffff) | ((r & 0x1) << 31)
+
+    r,l = _PERM_OP(r, l,  1, 0x55555555)
+    l,r = _PERM_OP(l, r,  8, 0x00ff00ff)
+    r,l = _PERM_OP(r, l,  2, 0x33333333)
+    l,r = _PERM_OP(l, r, 16, 0x0000ffff)
+    r,l = _PERM_OP(r, l,  4, 0x0f0f0f0f)
+
+    return l,r
+
+
+def crypt(password, salt):
+    """Generate an encrypted hash from the passed password.  If the password
+is longer than eight characters, only the first eight will be used.
+
+The first two characters of the salt are used to modify the encryption
+algorithm used to generate in the hash in one of 4096 different ways.
+The characters for the salt must be alphanumeric, '.' or '/'.
+
+The returned hash begins with the two characters of the salt, and
+should be passed as the salt to verify the password.
+
+Example:
+
+  >>> from fcrypt import crypt
+  >>> password = 'AlOtBsOl'
+  >>> salt = 'cE'
+  >>> hash = crypt(password, salt)
+  >>> hash
+  'cEpWz5IUCShqM'
+  >>> crypt(password, hash) == hash
+  1
+  >>> crypt('IaLaIoK', hash) == hash
+  0
+
+In practice, you would read the password using something like the
+getpass module, and generate the salt randomly:
+
+  >>> import random, string
+  >>> saltchars = string.letters + string.digits + './'
+  >>> salt = random.choice(saltchars) + random.choice(saltchars)
+"""
+
+    if len(salt) < 2:
+        salt = salt + 'AA'
+
+    Eswap0 = _con_salt[ord(salt[0])]
+    Eswap1 = _con_salt[ord(salt[1])] << 4
+
+    ks = _set_key((password + '\0\0\0\0\0\0\0\0')[:8])
+    out1,out2 = _body(ks, Eswap0, Eswap1)
+
+    # Convert numbers to big-endian...
+    be1, be2 = struct.unpack('>ii', struct.pack('<ii', out1, out2))
+    # then extract 24-bit subsets.
+    b24 = [(be1 >> 8) & 0xffffff,
+           ((be1 << 16) & 0xff0000) | ((be2 >> 16) & 0xffff),
+           (be2 << 8) & 0xffff00]
+
+    # Convert to ASCII encoding, 4 characters for each 24 bits.
+    res = [salt[0], salt[1]]
+    for b in b24:
+        for i in range(18, -6, -6):
+            res.append(_cov_2char[(b >> i) & 0x3f])
+
+    return string.join(res[:13], '')
+
+def _test():
+    """Run doctest on fcrypt module."""
+    import doctest, fcrypt
+    return doctest.testmod(fcrypt)
+
+if __name__ == '__main__':
+    _test()

Added: trunk/ZopeProducts/exUserFolder/CryptoSources/fcrypt/setup.py
===================================================================
--- trunk/ZopeProducts/exUserFolder/CryptoSources/fcrypt/setup.py	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/CryptoSources/fcrypt/setup.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,22 @@
+#!/usr/bin/env python
+
+# distutils setup script for fcrypt.
+#
+# Copyright (C) 2000, 2001  Carey Evans  <careye at spamcop.net>
+
+from distutils.core import setup
+
+setup( name = 'fcrypt',
+       version = '1.2',
+       description = 'The Unix password crypt function.',
+       author = 'Carey Evans',
+       author_email = 'careye at spamcop.net',
+       url = 'http://home.clear.net.nz/pages/c.evans/sw/',
+       licence = 'BSD',
+       long_description = """\
+A pure Python implementation of the Unix DES password crypt function,
+based on Eric Young's fcrypt.c.  It works with any version of Python
+from version 1.5 or higher, and because it's pure Python it doesn't
+need a C compiler to install it.""",
+
+       py_modules = ['fcrypt'] )

Added: trunk/ZopeProducts/exUserFolder/CryptoSources/pass_crypt.py
===================================================================
--- trunk/ZopeProducts/exUserFolder/CryptoSources/pass_crypt.py	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/CryptoSources/pass_crypt.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,44 @@
+#
+# Extensible User Folder
+#
+# (C) Copyright 2000-2004 The Internet (Aust) Pty Ltd
+# ACN: 082 081 472  ABN: 83 082 081 472
+# All Rights Reserved
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# Author: Andrew Milton <akm at theinternet.com.au>
+# $Id: pass_crypt.py,v 1.3 2004/11/18 09:24:46 akm Exp $
+
+from Products.exUserFolder.exUserFolder import exUserFolder
+from Products.exUserFolder.Plugins import CryptoPluginRegister
+
+try:
+	from crypt import crypt
+except:
+	from fcrypt.fcrypt import crypt
+
+
+def cryptPassword(authSource, username, password):
+	u = authSource.listOneUser(username)
+	if not u:
+		salt = username[:2]
+	else:
+		salt=u[0]['password'][:2]
+
+	secret = crypt(password, salt)
+	return secret
+	
+
+CryptPlugin=CryptoPluginRegister('Crypt', 'crypt', 'Crypt', cryptPassword)
+exUserFolder.cryptoSources['Crypt']=CryptPlugin

Added: trunk/ZopeProducts/exUserFolder/CryptoSources/pass_md5.py
===================================================================
--- trunk/ZopeProducts/exUserFolder/CryptoSources/pass_md5.py	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/CryptoSources/pass_md5.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,47 @@
+#
+# Extensible User Folder
+#
+# (C) Copyright 2000-2004 The Internet (Aust) Pty Ltd
+# ACN: 082 081 472  ABN: 83 082 081 472
+# All Rights Reserved
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# Author: Andrew Milton <akm at theinternet.com.au>
+# $Id: pass_md5.py,v 1.1 2004/11/10 14:15:52 akm Exp $
+
+import md5, base64, string
+
+from Products.exUserFolder.exUserFolder import exUserFolder
+from Products.exUserFolder.Plugins import CryptoPluginRegister
+
+# Simple digest
+def cryptPassword(authSource, username, password):
+	digest = md5.new()
+	digest.update(password)
+	digest = digest.digest()
+	secret = string.strip(base64.encodestring(digest))
+	return secret
+
+# Digest includes username
+# So two passwords for different users hash differently
+def cryptPassword2(authSource, username, password):
+	newPass = username+':'+password
+	return cryptPassword(authSource, username, newPass)
+
+
+MD5Plugin1=CryptoPluginRegister('MD51', 'MD5', 'MD5 Password Only', cryptPassword)
+exUserFolder.cryptoSources['MD51']=MD5Plugin1
+
+MD5Plugin2=CryptoPluginRegister('MD52', 'MD5', 'MD5 Username + Password', cryptPassword2)
+exUserFolder.cryptoSources['MD52']=MD5Plugin2

Added: trunk/ZopeProducts/exUserFolder/CryptoSources/pass_plain.py
===================================================================
--- trunk/ZopeProducts/exUserFolder/CryptoSources/pass_plain.py	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/CryptoSources/pass_plain.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,31 @@
+#
+# Extensible User Folder
+#
+# (C) Copyright 2000-2004 The Internet (Aust) Pty Ltd
+# ACN: 082 081 472  ABN: 83 082 081 472
+# All Rights Reserved
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# Author: Andrew Milton <akm at theinternet.com.au>
+# $Id: pass_plain.py,v 1.1 2004/11/10 14:15:52 akm Exp $
+
+from Products.exUserFolder.exUserFolder import exUserFolder
+from Products.exUserFolder.Plugins import CryptoPluginRegister
+
+# Simple digest
+def cryptPassword(authSource, username, password):
+	return password
+
+PlainPlugin=CryptoPluginRegister('Plaintext', 'Plaintext', 'No Encryption', cryptPassword)
+exUserFolder.cryptoSources['Plaintext']=PlainPlugin

Added: trunk/ZopeProducts/exUserFolder/CryptoSources/pass_sha.py
===================================================================
--- trunk/ZopeProducts/exUserFolder/CryptoSources/pass_sha.py	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/CryptoSources/pass_sha.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,41 @@
+#
+# Extensible User Folder
+#
+# (C) Copyright 2000-2004 The Internet (Aust) Pty Ltd
+# ACN: 082 081 472  ABN: 83 082 081 472
+# All Rights Reserved
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# Author: Andrew Milton <akm at theinternet.com.au>
+# $Id: pass_sha.py,v 1.1 2004/11/10 14:15:52 akm Exp $
+
+import sha
+from base64 import encodestring
+
+from Products.exUserFolder.exUserFolder import exUserFolder
+from Products.exUserFolder.Plugins import CryptoPluginRegister
+
+
+def cryptPassword(authSource, username, password):
+	return encodestring(sha.new(password).digest())
+
+def cryptPassword2(authSource, username, password):
+	newPass = username+':'+password
+	return cryptPassword(authSource, username, newPass)
+
+SHAPlugin1=CryptoPluginRegister('SHA1', 'SHA', 'SHA Password Only', cryptPassword)
+exUserFolder.cryptoSources['SHA1']=SHAPlugin1
+
+SHAPlugin2=CryptoPluginRegister('SHA2', 'SHA', 'SHA Username + Password', cryptPassword2)
+exUserFolder.cryptoSources['SHA2']=SHAPlugin2

Added: trunk/ZopeProducts/exUserFolder/Extensions/.cvsignore
===================================================================
--- trunk/ZopeProducts/exUserFolder/Extensions/.cvsignore	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/Extensions/.cvsignore	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,4 @@
+*.pyc
+*.pyo
+*~
+.*.swp

Added: trunk/ZopeProducts/exUserFolder/Extensions/getOldGroups.py
===================================================================
--- trunk/ZopeProducts/exUserFolder/Extensions/getOldGroups.py	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/Extensions/getOldGroups.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,20 @@
+# This script interrogates the old-skool NuxUserGroups_support_branch
+# group structure and outputs a tab-delimited file you can send to
+# loadOldGroups.  Just in case anyone is using it. :-)
+#
+# Matt Behrens <matt.behrens at kohler.com>
+
+def getOldGroups(self):
+    "Reconstruct a group list from the old-style _groups property"
+    from string import join
+    props = self.currentPropSource.userProperties
+    groups = {}
+    for username in props.keys():
+	for groupname in props[username].getProperty('_groups', ()):
+	    if not groups.has_key(groupname):
+		groups[groupname] = []
+	    groups[groupname].append(username)
+    out = ''
+    for groupname in groups.keys():
+	out = out + '%s	%s\n' % (groupname, join(groups[groupname], '	'))
+    return out

Added: trunk/ZopeProducts/exUserFolder/Extensions/loadOldGroups.py
===================================================================
--- trunk/ZopeProducts/exUserFolder/Extensions/loadOldGroups.py	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/Extensions/loadOldGroups.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,26 @@
+# This takes 'old_groups.txt' from var (create it using getOldGroups)
+# and sets up all the groups therein using NuxUserGroups calls.  This
+# will load a group source if you need to do such a thing.
+#
+# Matt Behrens <matt.behrens at kohler.com>
+
+def loadOldGroups(self):
+    from os.path import join as pathJoin
+    from string import split, strip
+
+    groups_file = open(pathJoin(CLIENT_HOME, 'old_groups.txt'), 'r')
+    out = ''
+    for group_line in groups_file.readlines():
+	group_line_elements = split(strip(group_line), '	')
+	group_name = group_line_elements[0]
+	group_members = group_line_elements[1:]
+
+	if self.getGroupById(group_name, default=None) is None:
+	    out = out + 'adding group %s\n' % group_name
+	    self.userFolderAddGroup(group_name)
+
+	out = out + 'setting group %s membership to %s\n' % (group_name, group_members)
+	self.setUsersOfGroup(group_members, group_name)
+
+    return out
+

Added: trunk/ZopeProducts/exUserFolder/Extensions/usAuthSourceMethods.py
===================================================================
--- trunk/ZopeProducts/exUserFolder/Extensions/usAuthSourceMethods.py	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/Extensions/usAuthSourceMethods.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,140 @@
+# (C) Copyright 2000,2001 The Internet (Aust) Pty Ltd
+# ACN: 082 081 472  ABN: 83 082 081 472
+# All Rights Reserved
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# Author: Andrew Milton <akm at theinternet.com.au>
+# $Id: usAuthSourceMethods.py,v 1.3 2001/12/01 08:40:04 akm Exp $
+#
+########################################################################
+#
+# This is an example of an Extension Module to provide User Supplied 
+# Authentication Methods.
+# 
+# It mimics the behaviour of the pgAuthSource Module, and the sql queries
+# Used here would be added as ZSQLMethods in the usAuthSource Folder.
+# (you can basically cut and paste them from the bottom of this .py file
+# into the ZSQL Method Template Area
+#
+# It's not complete, but, you do get the idea...
+#
+# Each function becomes usFunctionName
+#
+# e.g. listOneUser -> usListOneUser
+#
+import string
+from crypt import crypt
+
+def listOneUser(self,username):
+	users = []
+	result=self.sqlListOneUser(username=username)
+	for n in result:
+		username=sqlattr(n,'username')
+		password=sqlattr(n,'password')
+		roles=string.split(sqlattr(n,'roles'))
+		N={'username':username, 'password':password, 'roles':roles}
+		users.append(N)
+	return users
+
+def listUsers(self):
+	"""Returns a list of user names or [] if no users exist"""		
+	users = []
+	result=self.sqlListUsers()
+	for n in result:
+		username=sqlattr(n,'username')
+		N={'username':username}
+		users.append(N)
+	return users	
+
+def getUsers(self):
+	"""Return a list of user objects or [] if no users exist"""
+	data=[]
+	try:    items=self.listusers()
+	except: return data
+	for people in items:
+		roles=string.split(people['roles'],',')
+		user=User(people['username'], roles, '')
+		data.append(user)
+	return data
+
+def cryptPassword(self, username, password):
+		salt =username[:2]
+		secret = crypt(password, salt)
+		return secret
+
+def deleteUsers(self, userids):
+	for uid in userids:
+		self.sqlDeleteOneUser(userid=uid)
+
+
+# Helper Functions...
+from string import upper, lower
+import Missing
+mt=type(Missing.Value)
+
+def typeconv(val):
+    if type(val)==mt:
+        return ''
+    return val
+
+def sqlattr(ob, attr):
+    name=attr
+    if hasattr(ob, attr):
+        return typeconv(getattr(ob, attr))
+    attr=upper(attr)
+    if hasattr(ob, attr):
+        return typeconv(getattr(ob, attr))
+    attr=lower(attr)
+    if hasattr(ob, attr):
+        return typeconv(getattr(ob, attr))
+    raise NameError, name
+
+
+########################################################################
+# SQL METHODS USED ABOVE
+# PASTE INTO ZSQL METHODS
+# take note of what parameters are used in each query
+########################################################################
+
+_sqlListUsers="""
+SELECT * FROM passwd
+"""
+
+_sqlListOneUser="""
+SELECT * FROM passwd
+where username=<dtml-sqlvar username type=string>
+"""
+
+_sqlDeleteOneUser="""
+DELETE FROM passwd
+where uid=<dtml-sqlvar userid type=int>
+"""
+
+_sqlInsertUser="""
+INSERT INTO passwd (username, password, roles)
+VALUES (<dtml-sqlvar username type=string>,
+        <dtml-sqlvar password type=string>,
+		<dtml-sqlvar roles type=string>)
+"""
+
+_sqlUpdateUserPassword="""
+UPDATE passwd set password=<dtml-sqlvar password type=string>
+WHERE username=<dtml-sqlvar username type=string>
+"""
+
+_sqlUpdateUser="""
+UPDATE passwd set roles=<dtml-sqlvar roles type=string>
+WHERE username=<dtml-sqlvar username type=string>
+"""
+

Added: trunk/ZopeProducts/exUserFolder/GroupSource/.cvsignore
===================================================================
--- trunk/ZopeProducts/exUserFolder/GroupSource/.cvsignore	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/GroupSource/.cvsignore	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,4 @@
+*.pyc
+*.pyo
+*~
+.*.swp

Added: trunk/ZopeProducts/exUserFolder/GroupSource/GroupSource.py
===================================================================
--- trunk/ZopeProducts/exUserFolder/GroupSource/GroupSource.py	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/GroupSource/GroupSource.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,32 @@
+#
+# Extensible User Folder
+# 
+# Null Group Source for exUserFolder
+#
+# Author: Brent Hendricks <bmh at users.sourceforge.net>
+# $Id: GroupSource.py,v 1.1 2002/12/02 23:20:49 bmh Exp $
+from Globals import DTMLFile
+
+
+manage_addGroupSourceForm=DTMLFile('manage_addGroupSourceForm', globals(), __name__='manage_addGroupSourceForm')
+
+
+def manage_addGroupSource(dispatcher, REQUEST):
+	""" Add a Group Source """
+
+	# Get the XUF object we're being added to
+	xuf = dispatcher.Destination()
+	
+	groupId = REQUEST.get('groupId', None)
+	if groupId:
+		# Invoke the add method for this plugin
+		xuf.groupSources[groupId].manage_addMethod(xuf, REQUEST)
+	else:
+		raise "BadRequest", "Required parameter 'groupId' omitted"
+
+	dispatcher.manage_main(dispatcher, REQUEST)
+	
+
+class GroupSource:
+	pass
+

Added: trunk/ZopeProducts/exUserFolder/GroupSource/__init__.py
===================================================================
--- trunk/ZopeProducts/exUserFolder/GroupSource/__init__.py	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/GroupSource/__init__.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,2 @@
+# $Id: __init__.py,v 1.1 2002/12/02 23:20:49 bmh Exp $
+import GroupSource

Added: trunk/ZopeProducts/exUserFolder/GroupSource/manage_addGroupSourceForm.dtml
===================================================================
--- trunk/ZopeProducts/exUserFolder/GroupSource/manage_addGroupSourceForm.dtml	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/GroupSource/manage_addGroupSourceForm.dtml	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,33 @@
+<dtml-var manage_page_header>
+  <dtml-if currentGroupSource>
+    <dtml-var "MessageDialog(title='Group Source Exists', message='Error: There is already a group source here.  Please delete it first', action='manage_main')">
+  <dtml-elif allDone>
+    <dtml-var expr="manage_addGroupSource(REQUEST)">
+  <dtml-elif groupId>
+    <dtml-call "REQUEST.set('groupForm',doGroupSourceForm(groupId=groupId))">
+    <dtml-var "groupForm(mapping=_)">
+  <dtml-else>
+    <dtml-var "DialogHeader(_.None,_,DialogTitle='Add eXtensible User Folder Group Source')">
+    <form action="&dtml-URL;" method="post">
+      <table cellspacing="2">
+	<tr>
+	  <td align="left" valign="top">
+	    <b><dtml-babel src="'en'">Group Source</dtml-babel></b>
+	  </td>
+	  <td>
+	    <select name="groupId">
+	      <dtml-in getGroupSources sort="name">
+		<option value="<dtml-var "_['sequence-item'].name">"><dtml-var description></option>
+	      </dtml-in>
+	    </select>
+	  </td>
+	</tr>
+	<tr>
+	  <td></td>
+	  <td><br><input type="submit" value=" <dtml-babel src="'en'">Add</dtml-babel> "></td>
+	</tr>
+      </table>
+    </form>
+    <dtml-var DialogFooter>
+  </dtml-if>
+<dtml-var manage_page_footer>
\ No newline at end of file

Added: trunk/ZopeProducts/exUserFolder/GroupSources/__init__.py
===================================================================
--- trunk/ZopeProducts/exUserFolder/GroupSources/__init__.py	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/GroupSources/__init__.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,31 @@
+#
+# Extensible User Folder
+# 
+# (C) Copyright 2000-2004 The Internet (Aust) Pty Ltd
+# ACN: 082 081 472  ABN: 83 082 081 472
+# All Rights Reserved
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# Author: Andrew Milton <akm at theinternet.com.au>
+# $Id: __init__.py,v 1.1 2004/11/10 14:15:53 akm Exp $
+
+import nullGroupSource
+
+# If this fails due to NUG being absent, just skip it
+try:
+	import zodbGroupSource
+except ImportError:
+	pass
+
+

Added: trunk/ZopeProducts/exUserFolder/GroupSources/nullGroupSource/.cvsignore
===================================================================
--- trunk/ZopeProducts/exUserFolder/GroupSources/nullGroupSource/.cvsignore	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/GroupSources/nullGroupSource/.cvsignore	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,4 @@
+*.pyc
+*.pyo
+*~
+.*.swp

Added: trunk/ZopeProducts/exUserFolder/GroupSources/nullGroupSource/__init__.py
===================================================================
--- trunk/ZopeProducts/exUserFolder/GroupSources/nullGroupSource/__init__.py	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/GroupSources/nullGroupSource/__init__.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,2 @@
+# $Id: __init__.py,v 1.1 2004/11/10 14:15:53 akm Exp $
+import nullGroupSource

Added: trunk/ZopeProducts/exUserFolder/GroupSources/nullGroupSource/manage_addNullPluginSourceForm.dtml
===================================================================
--- trunk/ZopeProducts/exUserFolder/GroupSources/nullGroupSource/manage_addNullPluginSourceForm.dtml	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/GroupSources/nullGroupSource/manage_addNullPluginSourceForm.dtml	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,21 @@
+<dtml-var "DialogHeader(_.None, _, DialogTitle='Add Basic Group Source', dialog_width='')">
+<FORM ACTION="&dtml-URL;" METHOD="POST">
+		<dtml-in "REQUEST.form.keys()">
+			<dtml-if "getVariableType(REQUEST[_['sequence-item']]) == 'List'">
+				<dtml-let listVar=sequence-item>
+					<dtml-in "REQUEST[listVar]">
+						<input type="HIDDEN" name="<dtml-var listVar>:list" value="<dtml-var sequence-item>">
+					</dtml-in>
+				</dtml-let>
+			<dtml-else>
+				<input type="HIDDEN" name="<dtml-var sequence-item>" value="<dtml-var "REQUEST[_.getitem('sequence-item',0)]">">
+			</dtml-if>
+
+		</dtml-in>
+
+<input type="HIDDEN" name="allDone" value="1">
+<b><dtml-babel src="'en'">This Group Source has no configuration Items</dtml-babel></b><br>
+<br>
+<input type="SUBMIT" value="<dtml-babel src="'en'">Add</dtml-babel>">
+</form>
+<dtml-var DialogFooter>

Added: trunk/ZopeProducts/exUserFolder/GroupSources/nullGroupSource/nullGroupSource.py
===================================================================
--- trunk/ZopeProducts/exUserFolder/GroupSources/nullGroupSource/nullGroupSource.py	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/GroupSources/nullGroupSource/nullGroupSource.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,34 @@
+#
+# Extensible User Folder
+# 
+# Null Group Source for exUserFolder
+#
+# Author: Brent Hendricks <bmh at users.sourceforge.net>
+# $Id: nullGroupSource.py,v 1.1 2004/11/10 14:15:53 akm Exp $
+from Globals import HTMLFile, INSTANCE_HOME
+
+from OFS.Folder import Folder
+
+from Products.exUserFolder.exUserFolder import exUserFolder
+from Products.exUserFolder.Plugins import PluginRegister
+from Products.exUserFolder.nullPlugin import nullPlugin
+
+def manage_addNullGroupSource(self, REQUEST):
+	""" Add a Group Source """
+	self.currentGroupSource=None
+	return ''
+
+
+manage_addNullGroupSourceForm=HTMLFile('manage_addNullPluginSourceForm',globals())
+manage_editNullGroupSourceForm=None
+
+		
+nullGroupReg=PluginRegister('nullGroupSource',
+						   'Null Group Source',
+						   nullPlugin,
+						   manage_addNullGroupSourceForm,
+						   manage_addNullGroupSource,
+						   manage_editNullGroupSourceForm)
+
+exUserFolder.groupSources['nullGroupSource']=nullGroupReg
+

Added: trunk/ZopeProducts/exUserFolder/GroupSources/zodbGroupSource/.cvsignore
===================================================================
--- trunk/ZopeProducts/exUserFolder/GroupSources/zodbGroupSource/.cvsignore	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/GroupSources/zodbGroupSource/.cvsignore	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,4 @@
+*.pyc
+*.pyo
+*~
+.*.swp

Added: trunk/ZopeProducts/exUserFolder/GroupSources/zodbGroupSource/__init__.py
===================================================================
--- trunk/ZopeProducts/exUserFolder/GroupSources/zodbGroupSource/__init__.py	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/GroupSources/zodbGroupSource/__init__.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,2 @@
+# $Id: __init__.py,v 1.1 2004/11/10 14:15:54 akm Exp $
+import zodbGroupSource

Added: trunk/ZopeProducts/exUserFolder/GroupSources/zodbGroupSource/manage_addzodbGroupSourceForm.dtml
===================================================================
--- trunk/ZopeProducts/exUserFolder/GroupSources/zodbGroupSource/manage_addzodbGroupSourceForm.dtml	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/GroupSources/zodbGroupSource/manage_addzodbGroupSourceForm.dtml	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,21 @@
+<dtml-var "DialogHeader(_.None,_,DialogTitle='Add ZODB Group Source')">
+
+<FORM ACTION="&dtml-URL;" METHOD="POST">
+		<dtml-in "REQUEST.form.keys()">
+			<dtml-if "getVariableType(REQUEST[_['sequence-item']]) == 'List'">
+				<dtml-let listVar=sequence-item>
+					<dtml-in "REQUEST[listVar]">
+						<input type="HIDDEN" name="<dtml-var listVar>:list" value="<dtml-var sequence-item>">
+					</dtml-in>
+				</dtml-let>
+			<dtml-else>
+				<input type="HIDDEN" name="<dtml-var sequence-item>" value="<dtml-var "REQUEST[_.getitem('sequence-item',0)]">">
+			</dtml-if>
+
+		</dtml-in>
+
+<input type="HIDDEN" name="allDone" value="1">
+<b><dtml-babel src="'en'">This group source requires no user configuration items at this time.</dtml-babel></b><br>
+<INPUT TYPE="SUBMIT" VALUE=" <dtml-babel src="'en'">NEXT</dtml-babel> ">
+</FORM>
+<dtml-var DialogFooter>

Added: trunk/ZopeProducts/exUserFolder/GroupSources/zodbGroupSource/manage_editzodbGroupSourceForm.dtml
===================================================================
--- trunk/ZopeProducts/exUserFolder/GroupSources/zodbGroupSource/manage_editzodbGroupSourceForm.dtml	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/GroupSources/zodbGroupSource/manage_editzodbGroupSourceForm.dtml	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,7 @@
+<dtml-var "DialogHeader(_.None,_,DialogTitle='ZODB Group Source',dialog_width='100%')">
+<dtml-var manage_tabs>
+<FORM ACTION="manage_main" METHOD="POST">
+<b><dtml-babel src="'en'">This group source requires no user configuration items at this time.</dtml-babel></b><br>
+<INPUT TYPE="SUBMIT" VALUE=" <dtml-babel src="'en'">OK</dtml-babel> ">
+</FORM>
+<dtml-var DialogFooter>

Added: trunk/ZopeProducts/exUserFolder/GroupSources/zodbGroupSource/zodbGroupSource.py
===================================================================
--- trunk/ZopeProducts/exUserFolder/GroupSources/zodbGroupSource/zodbGroupSource.py	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/GroupSources/zodbGroupSource/zodbGroupSource.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,177 @@
+#
+# Extensible User Folder
+# 
+# ZODB Group Source for exUserFolder
+#
+# Author: Brent Hendricks <mh at users.sourceforge.net>
+# $Id: zodbGroupSource.py,v 1.1 2004/11/10 14:15:54 akm Exp $
+from Globals import HTMLFile, MessageDialog, INSTANCE_HOME,Acquisition, PersistentMapping
+
+from OFS.Folder import Folder
+
+from Products.ZSQLMethods.SQL import SQL
+
+from Products.exUserFolder.exUserFolder import exUserFolder
+from Products.exUserFolder.Plugins import PluginRegister
+from Products.NuxUserGroups.UserFolderWithGroups import Group, _marker
+
+import time
+import zLOG
+import sys
+
+manage_addGroupSourceForm=HTMLFile('manage_addzodbGroupSourceForm', globals())
+
+def manage_addzodbGroupSource(self, REQUEST):
+	""" Add a ZODB Group Source """
+
+	o = zodbGroupSource()
+	self._setObject('zodbGroupSource', o, None, None, 0)
+	o = getattr(self, 'zodbGroupSource')
+
+	# Allow Prop Source to setup default users...
+	if hasattr(o, 'postInitialisation'):
+		o.postInitialisation(REQUEST)
+	self.currentGroupSource=o
+
+manage_addzodbGroupSourceForm=HTMLFile('manage_addzodbGroupSourceForm', globals())
+manage_editzodbGroupSourceForm=HTMLFile('manage_editzodbGroupSourceForm', globals())
+
+#
+# Very very simple thing, used as an example of how to write a property source
+# Not recommended for large scale production sites...
+#
+
+class zodbGroupSource(Folder):
+	""" Store Group Data inside ZODB, the simplistic way """
+
+	meta_type='Group Source'
+	title='Simplistic ZODB Groups'
+	icon ='misc_/exUserFolder/exUserFolderPlugin.gif'	
+	manage_editForm=manage_editzodbGroupSourceForm
+	manage_tabs=Acquisition.Acquired
+	
+	def __init__(self):
+		self.id='zodbGroupSource'
+		self.groups=PersistentMapping()
+
+
+	def addGroup(self, groupname, title='', users=(), **kw):
+		"""Creates a group"""
+		if self.groups.has_key(groupname):
+			raise ValueError, 'Group "%s" already exists' % groupname
+		a = 'before: groupname %s groups %s' % (groupname, self.groups)
+		group = apply(Group, (groupname,), kw)
+		group.setTitle(title)
+		group._setUsers(users)
+		self.groups[groupname] = group
+
+
+	def getGroup(self, groupname, default=_marker):
+		"""Returns the given group"""
+		try:
+			group = self.groups[groupname]
+		except KeyError:
+			if default is _marker: raise
+			return default
+		return group
+
+	
+	def delGroup(self, groupname):
+		"""Deletes the given group"""
+		usernames = self.groups[groupname].getUsers()
+		#self.delUsersFromGroup(usernames, groupname)
+		del self.groups[groupname]
+
+	
+	def listGroups(self):
+		"""Returns a list of group names"""
+		return tuple(self.groups.keys())
+	
+
+	def getGroupsOfUser(self, username):
+		"Get a user's groups"
+		groupnames = []
+		allnames = self.listGroups()
+		groupnames = filter(lambda g, u=username, self=self: u in self.groups[g].getUsers(), allnames)
+		return tuple(groupnames)
+
+	
+	def setGroupsOfUser(self, groupnames, username):
+		"Set a user's groups"
+		oldGroups = self.getGroupsOfUser(username)
+		self.delGroupsFromUser(oldGroups, username)
+		self.addGroupsToUser(groupnames, username)
+
+
+	def addGroupsToUser(self, groupnames, username):
+		"Add groups to a user"
+		for name in groupnames:
+			group = self.groups[name]
+			if not username in group.getUsers():
+				group._addUsers([username])
+
+
+	def delGroupsFromUser(self, groupnames, username):
+		"Delete groups from a user"
+		for name in groupnames:
+			group = self.groups[name]
+			if username in group.getUsers():
+				group._delUsers([username])
+		
+	
+	def getUsersOfGroup(self, groupname):
+		"Get the users in a group"
+		return self.groups[groupname].getUsers()
+
+	
+	def setUsersOfGroup(self, usernames, groupname):
+		"Set the users in a group"
+		# uniquify
+		dict = {}
+		for u in usernames: dict[u] = None
+		usernames = dict.keys()
+
+		self.groups[groupname]._setUsers(usernames)
+
+
+	def addUsersToGroup(self, usernames, groupname):
+		"Add users to a group"
+		# uniquify
+		dict = {}
+		for u in usernames: dict[u] = None
+		usernames = dict.keys()
+
+		self.groups[groupname]._addUsers(usernames)
+		
+
+	def delUsersFromGroup(self, usernames, groupname):
+		"Delete users from a group"
+		# uniquify
+		dict = {}
+		for u in usernames: dict[u] = None
+		usernames = dict.keys()
+
+		self.groups[groupname]._delUsers(usernames)
+
+
+	def deleteUsers(self, usernames):
+		"Delete a list of users"
+		for user in usernames:
+			groups = self.getGroupsOfUser(user)
+			self.delGroupsFromUser(groups, user)
+
+
+	def postInitialisation(self, REQUEST):
+		pass
+
+
+        def manage_beforeDelete(self, item, container):
+                # Notify the exUserFolder that it doesn't have a group source anymore
+                container.currentGroupSource=None
+
+
+zodbGroupReg=PluginRegister('zodbGroupSource','Simplistic ZODB Group Source',
+						   zodbGroupSource, manage_addzodbGroupSourceForm,
+						   manage_addzodbGroupSource,
+						   manage_editzodbGroupSourceForm)
+exUserFolder.groupSources['zodbGroupSource']=zodbGroupReg

Added: trunk/ZopeProducts/exUserFolder/I18N/ZBabelDictionary_txt.zexp
===================================================================
(Binary files differ)


Property changes on: trunk/ZopeProducts/exUserFolder/I18N/ZBabelDictionary_txt.zexp
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/ZopeProducts/exUserFolder/LICENSE
===================================================================
--- trunk/ZopeProducts/exUserFolder/LICENSE	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/LICENSE	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,91 @@
+XUF as a whole is covered by the BSD License, however it uses software 
+covered by other compatible licenses (see below)
+
+------------------------------------------------------------------------
+
+All of the documentation and software included in the exUserFolder
+Releases is copyrighted by The Internet (Aust) Pty Ltd and contributors
+ACN: 082 081 472  ABN: 83 082 081 472
+
+Copyright 2001, 2002 The Internet (Aust) Pty Ltd
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+1. Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGE.
+
+------------------------------------------------------------------------
+
+This product includes software developed by Digital Creations for use in 
+the Z Object Publishing Environment (http://www.zope.org/) 
+
+Portions of smbAuthSource Copyright (C) 2001 Michael Teo 
+
+Portions of radiusAuthSource Copyright (C) 1999 Stuart Bishop 
+
+fcrypt is Copyright (C) 2001, 2001 Carey Evans 
+
+This product includes cryptographic software written by Eric Young 
+(eay at mincom.oz.au)
+
+------------------------------------------------------------------------
+
+Brief discussion of what the license means to you, not meant to be
+all encompassing, but, to give you the general idea. This editorial does
+not need to be distributed d8)
+
+If you want to incorporate this product (or parts of it) into a commercial 
+product that's fine.
+
+If you want to modify this product that's fine.
+
+If you want to modify and distribute this product that's fine (even in
+commercial products).
+
+If you want to incorporate this into a larger work that's fine (even
+if that work has a different license).
+
+None of the previous items place any obligation of notification, compensation,
+or return of code to us. In fact we don't care if you do these things. Go
+forth and prosper. Basically as long as you recognise that this doesn't
+belong to you, you can do what you want with it even charge money for it.
+
+Note: If you do distribute this as source, then the XUF components are
+removable and distributable independently of your license as a whole
+(although that's a lot of trouble to go to when they could just download it
+from the same place you did).
+
+What you can't do, is claim it's yours, and this one thing encompasses a lot
+of things, here's a few.
+
+If it's not yours you can't;
+
+Change the license even if you change the code since the copyright
+of the modified files remains with the original copyright holders.
+
+Use bits of it inside products that require the license to change, because
+only the copyright holders have the right to modify the license (not a
+concern for commercial projects, only some other Free/Open Source licenses).
+
+Assign the copyright or other IP to any other party of the whole or any
+part (even if you change the code), because it's not yours to give away or
+sell to a 3rd party.
+
+If the fact you can almost do whatever you want with this code isn't 
+liberal enough for you, contact us and we'll see what we can arrange.

Added: trunk/ZopeProducts/exUserFolder/LoginRequiredMessages.py
===================================================================
--- trunk/ZopeProducts/exUserFolder/LoginRequiredMessages.py	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/LoginRequiredMessages.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,27 @@
+#
+# Extensible User Folder
+#
+# (C) Copyright 2000,2001 The Internet (Aust) Pty Ltd
+# ACN: 082 081 472  ABN: 83 082 081 472
+# All Rights Reserved
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# Author: Andrew Milton <akm at theinternet.com.au>
+# $Id: LoginRequiredMessages.py,v 1.2 2001/12/01 08:40:03 akm Exp $
+
+LoginRequiredMessages={
+	'session_expired':'Your Session has Expired',
+	'unauthorized':'Please Login',
+	'login_failed':'Login Failed',
+	}

Added: trunk/ZopeProducts/exUserFolder/MembershipSources/__init__.py
===================================================================
--- trunk/ZopeProducts/exUserFolder/MembershipSources/__init__.py	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/MembershipSources/__init__.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,25 @@
+#
+# Extensible User Folder
+# 
+# (C) Copyright 2000-2005 The Internet (Aust) Pty Ltd
+# ACN: 082 081 472  ABN: 83 082 081 472
+# All Rights Reserved
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# Author: Andrew Milton <akm at theinternet.com.au>
+# $Id:
+
+import basicMemberSource
+import nullMemberSource
+

Added: trunk/ZopeProducts/exUserFolder/MembershipSources/basicMemberSource/.cvsignore
===================================================================
--- trunk/ZopeProducts/exUserFolder/MembershipSources/basicMemberSource/.cvsignore	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/MembershipSources/basicMemberSource/.cvsignore	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,4 @@
+*.pyc
+*.pyo
+*~
+.*.swp

Added: trunk/ZopeProducts/exUserFolder/MembershipSources/basicMemberSource/PasswordForm.dtml
===================================================================
--- trunk/ZopeProducts/exUserFolder/MembershipSources/basicMemberSource/PasswordForm.dtml	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/MembershipSources/basicMemberSource/PasswordForm.dtml	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,22 @@
+<dtml-var "DialogHeader(DialogTitle='Change Password', dialog_width='')">
+<form action="acl_users/manage_changePassword" method="POST">
+<table>
+<tr>
+	<td align="right"><b><dtml-babel src="'en'">Old Password</dtml-babel></b></td>
+    <td><input type="password" name="current_password"></td>
+<tr>
+        <td align="right"><b><dtml-babel src="'en'">Password</dtml-babel></b></td>
+        <td><input type="password" name="password"></td>
+</tr>
+        <td align="right"><b><dtml-babel src="'en'">Confirm Password</dtml-babel></b></td>
+        <td><input type="password" name="password_confirm"></td>
+</tr>
+<dtml-if "forgottenPasswords=='hint'">
+<tr><td align="right"><b><dtml-babel src="'en'">Password Hint</dtml-babel></b></td>
+        <td><input type="text" name="user_hint" value="&dtml.missing-user_hint;"></td>
+</tr>
+</dtml-if>
+</table>
+<input type="submit" value=" <dtml-babel src="'en'">Change Password</dtml-babel> ">
+</form>
+<dtml-var DialogFooter>

Added: trunk/ZopeProducts/exUserFolder/MembershipSources/basicMemberSource/SignupForm.dtml
===================================================================
--- trunk/ZopeProducts/exUserFolder/MembershipSources/basicMemberSource/SignupForm.dtml	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/MembershipSources/basicMemberSource/SignupForm.dtml	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,31 @@
+<dtml-var "DialogHeader(DialogTitle='Signup', dialog_width='')">
+<form action="acl_users/manage_signupUser" method="POST">
+<table>
+<tr>
+	<td align="right"><b><dtml-babel src="'en'">Username</dtml-babel></td>
+	<td><input name="username" type="text" value="&dtml.missing-username;"></td>
+</tr>
+<dtml-if "passwordPolicy=='user'">
+<tr>
+        <td align="right"><b><dtml-babel src="'en'">Password</dtml-babel></b></td>
+        <td><input type="password" name="password" value="&dtml.missing-password;"></td>
+</tr>
+        <td align="right"><b><dtml-babel src="'en'">Confirm Password</dtml-babel></b></td>
+        <td><input type="password" name="password_confirm"></td>
+</tr>
+<dtml-if "forgottenPasswords=='hint'">
+<tr><td align="right"><b><dtml-babel src="'en'">Password Hint</dtml-babel></b></td>
+        <td><input type="text" name="user_hint" value="&dtml.missing-user_hint;"></td>
+</tr>
+</dtml-if>
+</dtml-if>
+<tr><td align="right"><b><dtml-babel src="'en'">Real Name</dtml-babel></b></td>
+        <td><input type="text" name="user_realname" value="&dtml.missing-user_realname;"></td>
+</tr>
+<tr><td align="right"><b><dtml-babel src="'en'"><dtml-babel src="'en'">Email</dtml-babel></dtml-babel></b></td>
+        <td><input type="text" name="user_email" value="&dtml.missing-user_email;"></td>
+</tr>
+</table>
+<input type="submit" value=" <dtml-babel src="'en'">Signup</dtml-babel> ">
+</form>
+<dtml-var DialogFooter>

Added: trunk/ZopeProducts/exUserFolder/MembershipSources/basicMemberSource/__init__.py
===================================================================
--- trunk/ZopeProducts/exUserFolder/MembershipSources/basicMemberSource/__init__.py	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/MembershipSources/basicMemberSource/__init__.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,2 @@
+# $Id: __init__.py,v 1.1 2004/11/10 14:15:55 akm Exp $
+import basicMemberSource

Added: trunk/ZopeProducts/exUserFolder/MembershipSources/basicMemberSource/basicMemberSource.py
===================================================================
--- trunk/ZopeProducts/exUserFolder/MembershipSources/basicMemberSource/basicMemberSource.py	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/MembershipSources/basicMemberSource/basicMemberSource.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,629 @@
+#
+# Extensible User Folder
+# 
+# Basic Membership Source for exUserFolder
+#
+# (C) Copyright 2000,2001 The Internet (Aust) Pty Ltd
+# ACN: 082 081 472  ABN: 83 082 081 472
+# All Rights Reserved
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# Author: Andrew Milton <akm at theinternet.com.au>
+# $Id: basicMemberSource.py,v 1.1 2004/11/10 14:15:55 akm Exp $
+
+#
+# Basically membership is a layer between the signup/login form, and
+# the authentication layer, it uses the prop source of the users to
+# store additional information about a user i.e. doesn't impact on the
+# authentication source.
+#
+# Some membership features imply some extra properties for the user will
+# be available; specifically at this time an email property.
+#
+# You also need a MailHost setup and ready to go for emailing stuff to users
+#
+
+import string,Acquisition
+from random import choice
+	
+
+from Globals import HTMLFile, INSTANCE_HOME
+
+from OFS.Folder import Folder
+from OFS.DTMLMethod import DTMLMethod
+
+from Products.exUserFolder.exUserFolder import exUserFolder
+from Products.exUserFolder.Plugins import PluginRegister
+
+from base64 import encodestring
+from urllib import quote
+
+import zLOG
+
+"""
+Password Policy enforcement (min/max length, caps etc)
+Create Password, or User Chooses.
+Timing out of passwords...
+Empty Password force change on login...
+Create Home Directory
+Copy files from Skelton Directory
+EMail password hint to user (forgot my password)
+Reset password and email user (needs plugin?)
+Redirect on login to fixed or varying per username location.
+Automatically add users, or manually approve of users.
+"""
+
+# Stupid little things for making a password
+# Don't hassle me, it's supposed to be basic.
+
+nouns=['ace', 'ant', 'arc', 'arm', 'axe',
+	   'bar', 'bat', 'bee', 'bib', 'bin',
+	   'can', 'cap', 'car', 'cat', 'cob',
+	   'day', 'den', 'dog', 'dot', 'dux',
+	   'ear', 'eel', 'egg', 'elf', 'elk',
+	   'fad', 'fan', 'fat', 'fig', 'fez',
+	   'gag', 'gas', 'gin', 'git', 'gum',
+	   'hag', 'hat', 'hay', 'hex', 'hub']
+
+pastConjs = [ 'did', 'has', 'was' ]
+suffixes  = [ 'ing', 'es', 'ed', 'ious', 'ily']
+
+def manage_addBasicMemberSource(self, REQUEST):
+	""" Add a Membership Source """
+
+	pvfeatures=[]
+	minLength=0
+	passwordPolicy=''
+	createHomedir=0
+	homeRoot=''
+	copyFilesFrom=''
+	postLogin=''
+	postSignup=''
+	forgottenPasswords=''
+	defaultRoles=[]
+	usersCanChangePasswords=0
+	baseURL=''
+	loginPage=''
+	signupPage=''
+	passwordPage=''
+	mailHost=''
+	fixedDest=''
+	
+	if REQUEST.has_key('basicmember_pvfeatures'):
+		pvfeatures=REQUEST['basicmember_pvfeatures']
+
+	if REQUEST.has_key('basicmember_roles'):
+		defaultRoles=REQUEST['basicmember_roles']
+
+	if not defaultRoles:
+		defaultRoles=['Member']
+
+	if 'minlength' in pvfeatures:
+		minLength=REQUEST['basicmember_minpasslen']
+
+	if REQUEST.has_key('basicmember_passwordpolicy'):
+		passwordPolicy=REQUEST['basicmember_passwordpolicy']
+
+	if REQUEST.has_key('basicmember_createhomedir'):
+		homeRoot=REQUEST['basicmember_homeroot']
+		createHomedir=1
+
+	if REQUEST.has_key('basicmember_copyfiles'):
+		copyFilesFrom=REQUEST['basicmember_copyfiles']
+
+	if REQUEST.has_key('basicmember_changepasswords'):
+		usersCanChangePasswords=1
+
+	if REQUEST.has_key('basicmember_fixeddest'):
+		fixedDest=''
+
+	forgottenPasswords=REQUEST['basicmember_forgottenpasswords']
+	postLogin=REQUEST['basicmember_postlogin']
+
+	baseURL=REQUEST['basicmember_baseurl']
+	loginPage=REQUEST['basicmember_loginpage']
+	signupPage=REQUEST['basicmember_signuppage']
+	passwordPage=REQUEST['basicmember_passwordpage']
+	siteEmail=REQUEST['basicmember_siteemail']
+	siteName=REQUEST['basicmember_sitename']
+
+	mailHost=REQUEST['basicmember_mailhost']
+	
+	# postSignup=REQUEST['basicmember_postsignup']
+
+	#
+	# Yep this is obscene
+	#
+	o = BasicMemberSource(pvfeatures, minLength, passwordPolicy,
+						  createHomedir, copyFilesFrom, postLogin,
+						  homeRoot, forgottenPasswords, defaultRoles,
+						  usersCanChangePasswords, baseURL, loginPage,
+						  signupPage, passwordPage, mailHost,
+						  siteName, siteEmail, fixedDest)
+
+	self._setObject('basicMemberSource', o, None, None, 0)
+	o = getattr(self, 'basicMemberSource')
+
+	if hasattr(o, 'postInitialisation'):
+		o.postInitialisation(REQUEST)
+
+	self.currentMembershipSource=o
+	return ''
+
+
+manage_addBasicMemberSourceForm=HTMLFile('manage_addBasicMemberSourceForm',
+										 globals())
+manage_editBasicMemberSourceForm=HTMLFile('manage_editBasicMemberSourceForm',
+										 globals())
+
+#
+# Crap, I don't know why I called this basic, I'd hate to see a
+# complicated one.
+#
+class BasicMemberSource(Folder):
+	""" Provide High Level User Management """
+	meta_type="Membership Source"
+	title="Basic Membership Source"
+	icon ='misc_/exUserFolder/exUserFolderPlugin.gif'
+	manage_tabs=Acquisition.Acquired
+	manage_editForm=manage_editBasicMemberSourceForm
+
+	# Ugh...
+	def __init__(self, pvFeatures=[], minLength=0, passwordPolicy='',
+				 createHomeDir=0, copyFilesFrom='', postLogin='', homeRoot='',
+				 forgottenPasswords='', defaultRoles=[], usersCanChangePasswords=0,
+				 baseURL='', loginPage='', signupPage='', passwordPage='',
+				 mailHost='', siteName='', siteEmail='', fixedDest=''):
+		
+		self.id='basicMemberSource'
+		self.pvFeatures=pvFeatures
+		self.minLength=int(minLength)
+		self.passwordPolicy=passwordPolicy
+		self.createHomeDir=createHomeDir
+		self.copyFilesFrom=copyFilesFrom
+		self.postLogin=postLogin
+		self.homeRoot=homeRoot
+		self.forgottenPasswords=forgottenPasswords
+		self.defaultRoles=defaultRoles
+		self.usersCanChangePasswords=usersCanChangePasswords
+		self.baseURL=baseURL
+		self.loginPage=loginPage
+		self.signupPage=signupPage
+		self.passwordPage=passwordPage
+		self.siteName=siteName
+		self.siteEmail=siteEmail
+		self.fixedDest=fixedDest
+		
+		_SignupForm=HTMLFile('SignupForm', globals())
+		SignupForm=DTMLMethod()
+		SignupForm.manage_edit(data=_SignupForm, title='Signup Form')
+		self._setObject('SignupForm', SignupForm)
+
+		_PasswordForm=HTMLFile('PasswordForm', globals())
+		PasswordForm=DTMLMethod()
+		PasswordForm.manage_edit(data=_PasswordForm,
+								 title='Change Password')
+		self._setObject('PasswordForm', PasswordForm)
+
+		self.mailHost=mailHost
+
+		_newPasswordEmail=HTMLFile('newPasswordEmail', globals())
+		newPasswordEmail=DTMLMethod()
+		newPasswordEmail.manage_edit(data=_newPasswordEmail,
+									 title='Send New Password')
+		self._setObject('newPasswordEmail', newPasswordEmail)
+
+		_forgotPasswordEmail=HTMLFile('forgotPasswordEmail', globals())
+		forgotPasswordEmail=DTMLMethod()
+		forgotPasswordEmail.manage_edit(data=_forgotPasswordEmail,
+										title='Send Forgotten Password')
+		self._setObject('forgotPasswordEmail', forgotPasswordEmail)
+
+		_passwordHintEmail=HTMLFile('passwordHintEmail', globals())
+		passwordHintEmail=DTMLMethod()
+		passwordHintEmail.manage_edit(data=_passwordHintEmail,
+										title='Send Forgotten Password Hint')
+		self._setObject('passwordHintEmail', passwordHintEmail)
+
+	def postInitialisation(self, REQUEST):
+		if self.createHomeDir and self.homeRoot:
+			self.findHomeRootObject()
+		else:
+			self.homeRootObj=None
+			
+		if self.copyFilesFrom:
+			self.findSkelRootObject()
+		else:
+			self.homeSkelObj=None
+
+		# The nice sendmail tag doesn't allow expressions for
+		# the mailhost
+		self.mailHostObject=getattr(self, self.mailHost)
+
+	def manage_editMembershipSource(self, REQUEST):
+		""" Edit a basic Membership Source """
+		if REQUEST.has_key('pvfeatures'):
+			self.pvFeatures=REQUEST['pvfeatures']
+		else:
+			self.pvFeatures=[]
+			
+		if REQUEST.has_key('minpasslength'):
+			self.minLength=REQUEST['minpasslength']
+
+		if REQUEST.has_key('createhomedir'):
+			createHomeDir=1
+		else:
+			createHomeDir=0
+
+		if createHomeDir:
+			self.copyFilesFrom=REQUEST['copyfiles']
+			if self.copyFilesFrom:
+				self.findSkelRootObject()
+			else:
+				self.homeRoot=REQUEST['homeroot']
+			self.findHomeRootObject()
+		
+		if REQUEST.has_key('memberroles'):
+			self.defaultRoles=REQUEST['memberroles']
+		if REQUEST.has_key('changepasswords'):
+			self.usersCanChangePasswords=1
+		else:
+			self.usersCanChangePasswords=0
+
+		self.postLogin=REQUEST['postlogin']
+		if REQUEST.has_key('fixeddest'):
+			self.fixedDest=REQUEST['fixeddest']
+
+		self.baseURL=REQUEST['baseurl']
+		self.loginPage=REQUEST['loginpage']
+		self.signupPage=REQUEST['signuppage']
+		self.passwordPage=REQUEST['passwordpage']
+		self.siteName=REQUEST['sitename']
+		self.siteEmail=REQUEST['siteemail']
+		return self.MessageDialog(self,
+				title  ='Updated!', 
+				message="Membership was Updated",
+				action ='manage_editMembershipSourceForm',
+				REQUEST=REQUEST)
+
+		
+
+	def forgotPassword(self, REQUEST):
+		username=REQUEST['username']
+		curUser=self.getUser(username)
+		if not curUser:
+			return self.MessageDialog(self,
+				title  ='No such user', 
+				message="No users matching that username were found.",
+				action ='%s/%s'%(self.baseURL, self.loginPage),
+				REQUEST=REQUEST)			
+
+			
+		userEmail=curUser.getProperty('email')
+		userName=curUser.getProperty('realname')
+		if self.forgottenPasswords == "hint":
+			passwordHint=curUser.getProperty('passwordhint')
+			self.passwordHintEmail(self,
+								   REQUEST=REQUEST,
+								   username=username,
+								   hint=passwordHint,
+								   realname=userName,
+								   email=userEmail)
+		else:
+			# make a new password, and mail it to the user
+			password = self.generatePassword()
+			curCrypt=self.currentAuthSource.cryptPassword(username,password)
+
+			# Update the user
+			bogusREQUEST={}
+			#bogusREQUEST['username']=username
+			bogusREQUEST['password']=password
+			bogusREQUEST['password_confirm']=password
+			bogusREQUEST['roles']=curUser.roles
+			self.manage_editUser(username, bogusREQUEST)
+			
+			self.forgotPasswordEmail(self,
+									REQUEST=REQUEST,
+									username=username,
+									password=password,
+									realname=userName,
+									email=userEmail)
+		return self.MessageDialog(self,
+				title  ='Sent!', 
+				message="Password details have been emailed to you",
+				action ='%s/%s'%(self.baseURL, self.loginPage),
+				REQUEST=REQUEST)			
+
+
+	def changeProperties(self, REQUEST):
+ 
+		curUser=self.listOneUser(REQUEST['AUTHENTICATED_USER'].getUserName())
+		curUser=curUser[0]
+		if not curUser:
+			return self.MessageDialog(self,
+				title  ='Erm!', 
+				message="You don't seem to be logged in",
+				action ='%s/%s'%(self.baseURL, self.passwordPage),
+				REQUEST=REQUEST)
+			
+
+		
+		self.currentPropSource.updateUser(curUser['username'],REQUEST)
+		
+		return self.MessageDialog(self,
+				   title  ='Properties updated',
+				   message="Your properties have been updated",
+				   action =self.baseURL,
+				   REQUEST=REQUEST,
+				   )
+
+	
+	def changePassword(self, REQUEST):
+		if not self.usersCanChangePasswords:
+			return ''
+
+		curUser=self.listOneUser(REQUEST['AUTHENTICATED_USER'].getUserName())
+		curUser=curUser[0]
+		if not curUser:
+			return self.MessageDialog(
+				title  ='Erm!', 
+				message="You don't seem to be logged in",
+				action ='%s/%s'%(self.baseURL, self.passwordPage),
+				REQUEST=REQUEST)
+			
+		curCrypt=self.currentAuthSource.cryptPassword(curUser['username'],REQUEST['current_password'])
+		if curCrypt != curUser['password']:
+			return self.MessageDialog(self,
+				title  ='Password Mismatch', 
+				message="Password is incorrect",
+				action ='%s/%s'%(self.baseURL, self.passwordPage),
+				REQUEST=REQUEST)
+
+		if REQUEST['password'] != REQUEST['password_confirm']:
+			return self.MessageDialog(self,
+				title  ='Password Mismatch', 
+				message="Passwords do not match",
+				action ='%s/%s'%(self.baseURL, self.passwordPage),
+				REQUEST=REQUEST)
+
+		# OK the old password matches the one the user provided
+		# Both new passwords match...
+		# Time to validate against our normal set of rules...
+		#
+		if not self.validatePassword(REQUEST['password'], curUser['username']):
+			return self.MessageDialog(self,
+				title  ='Password problem', 
+				message="Your password is invalid, please choose another",
+				action ='%s/%s'%(self.baseURL, self.passwordPage),
+				REQUEST=REQUEST)
+
+		if self.passwordPolicy=='hint':
+			if not hasattr(REQUEST,'user_passwordhint'):
+				return self.MessageDialog(self,
+					title  ='Password requires hint', 
+					message='You must choose a password hint',
+					action ='%s/%s'%(self.baseURL, self.passwordPage),
+					REQUEST=REQUEST)		
+
+		bogusREQUEST={}
+
+		bogusREQUEST['password']=REQUEST['password']
+		bogusREQUEST['password_confirm']=REQUEST['password']
+		bogusREQUEST['roles']=curUser['roles']
+		self.manage_editUser(curUser['username'],bogusREQUEST)
+		# update the cookie so he doesnt have to re-login:
+		if self.cookie_mode:
+			token='%s:%s' %(curUser['username'], REQUEST['password'])
+			token=encodestring(token)
+			token=quote(token)
+			REQUEST.response.setCookie('__ac', token, path='/')
+			REQUEST['__ac']=token
+
+		return self.MessageDialog(self,
+			title  ='Password updated', 
+			message="Your password has been updated",
+			action =self.baseURL,
+			REQUEST=REQUEST)
+		
+
+	def goHome(self, REQUEST, RESPONSE):
+		redirectstring="%s/%s/%s/manage_main"%(self.baseURL, self.homeRoot, REQUEST.AUTHENTICATED_USER.getUserName())
+		RESPONSE.redirect(redirectstring)
+		return ''
+	
+	# Tell exUserFolder where we want to go...
+	def getLoginDestination(self, REQUEST):
+		script=''
+		pathinfo=''
+		querystring=''
+		redirectstring=''
+		if self.postLogin=="destination":
+			script=REQUEST['SCRIPT_NAME']
+			pathinfo=REQUEST['PATH_INFO']
+		elif self.postLogin=="varied":
+			script=self.baseURL
+			pathinfo="/acl_users/goHome"
+			
+		elif self.postLogin=="fixed":
+			pathinfo="%s"%(self.fixedDest)
+
+		if REQUEST.has_key('QUERY_STRING'):
+			querystring='?'+REQUEST['QUERY_STRING']
+
+		redirectstring=script+pathinfo
+		if querystring:
+			redirectstring=redirectstring+querystring		
+
+		return redirectstring
+	
+	def validatePassword(self, password, username):
+		if 'minlength' in self.pvFeatures:
+			if len(password) < self.minLength:
+				return 0
+
+		if 'mixedcase' in self.pvFeatures:
+			lower = 0
+			upper = 0
+			for c in password:
+				if c in string.lowercase:
+					lower = 1
+				if c in string.uppercase:
+					upper = 1
+			if not upper and lower:
+				return 0
+			
+		if 'specialchar' in self.pvFeatures:
+			special = 0
+			for c in password:
+				if c in string.punctuation:
+					special = 1
+					break
+				elif c in string.digits:
+					special = 1
+					break
+			if not special:
+				return 0
+
+		#
+		# XXX Move this somewhere else
+		#
+			
+		if 'notstupid' in self.pvFeatures:
+			email=''
+			# We try some permutations here...
+			curUser=self.getUser(username)
+			if curUser:
+				email = curUser.getProperty('email')
+			elif hasattr(self, 'REQUEST'):
+				if self.REQUEST.has_key('user_email'): # new signup
+					email=self.REQUEST['user_email']
+				elif self.REQUEST.has_key('email'):
+					email=self.REQUEST['email']
+
+			if ((string.find(password, username)>=0) or
+				( email and
+				  (string.find(password,
+							   string.split(email,'@')[0]) >=0))):
+				return 0
+		return 1
+
+	# These next two look the same (and they are for now), but, the reason I
+	# Don't use one single method, is I think that SkelObj might migrate to
+	# using full paths, not relative paths.
+	
+	def findSkelRootObject(self):
+		# Parent should be acl_users
+		parent = getattr(self, 'aq_parent')
+
+		# This should be the root...
+		root = getattr(parent, 'aq_parent')
+		searchPaths = string.split(self.copyFilesFrom, '/')
+		for o in searchPaths:
+			if not getattr(root, o):
+				break
+			root = getattr(root, o)
+
+		self.homeSkelObj=root		
+	
+	def findHomeRootObject(self):
+		# Parent should be acl_users
+		parent = getattr(self, 'aq_parent')
+
+		# This should be the root...
+		root = getattr(parent, 'aq_parent')
+
+		searchPaths = string.split(self.homeRoot, '/')
+		for o in searchPaths:
+			if o not in root.objectIds():
+				root.manage_addFolder(id=o, title=o, createPublic=0, createUserF=0)
+			root = getattr(root, o)
+
+		self.homeRootObj=root
+		
+	def makeHomeDir(self, username):
+		if not self.homeRootObj:
+			return
+		
+		self.homeRootObj.manage_addFolder(id=username, title=username, createPublic=0, createUserF=0)
+		home = getattr(self.homeRootObj, username)
+		
+
+		# Allow user to be in charge of their own destiny
+		# XXXX WARNING THIS IS A NORMAL FOLDER *SO USERS CAN ADD ANYTHING*
+		# YOU NEED TO CHANGE THE TYPE OF OBJECT ADDED FOR A USER UNLESS
+		# THIS IS WHAT YOU WANT TO HAPPEN
+		home.manage_addLocalRoles(userid=username, roles=['Manager'])
+
+		if self.copyFilesFrom and self.homeSkelObj and self.homeSkelObj.objectIds():
+			cp=self.homeSkelObj.manage_copyObjects(
+				self.homeSkelObj.objectIds())
+			home.manage_pasteObjects(cp)
+
+		# Fix it so the user owns their stuff
+		curUser=self.getUser(username).__of__(self.aq_parent)
+		home.changeOwnership(curUser, recursive=1)
+		
+	def generatePassword(self):
+		password = (choice(nouns) + choice(pastConjs) +
+					choice(nouns) + choice(suffixes))
+		return password
+			
+	def createUser(self, REQUEST):
+		if self.passwordPolicy == 'user':
+			if not self.validatePassword(REQUEST['password'], REQUEST['username']):
+				return self.MessageDialog(self,
+					title  ='Password problem', 
+					message='Your password is invalid, please choose another',
+					action ='%s/%s'%(self.baseURL, self.signupPage),
+					REQUEST=REQUEST)
+
+			if self.passwordPolicy=='hint':
+				if not hasattr(REQUEST,'user_passwordhint'):
+					return self.MessageDialog(self,
+						title  ='Password requires hint', 
+						message='You must choose a password hint',
+						action ='%s/%s'%(self.baseURL, self.signupPage),
+						REQUEST=REQUEST)
+
+		elif self.passwordPolicy == 'system':
+			REQUEST['password']=self.generatePassword()
+			REQUEST['password_confirm']=REQUEST['password']
+
+			# Email the password.
+			self.newPasswordEmail(self, REQUEST)
+
+		zLOG.LOG("exUserFolder.basicMemberSource", zLOG.BLATHER,
+                         "Creating user",
+                         "Passed all tests -- creating [%s]" % REQUEST['username'])
+		REQUEST['roles']=self.defaultRoles
+		self.manage_addUser(REQUEST) # Create the User...
+		if self.createHomeDir:
+			self.makeHomeDir(REQUEST['username'])
+
+		return self.MessageDialog(self,
+			title  ='You have signed up', 
+			message='You have been signed up succesfully',
+			action ='%s'%(self.baseURL),
+			REQUEST=REQUEST)
+		
+
+		
+basicMemberReg=PluginRegister('basicMemberSource',
+							  'Basic Membership Source',
+							  BasicMemberSource,
+							  manage_addBasicMemberSourceForm,
+							  manage_addBasicMemberSource,
+							  manage_editBasicMemberSourceForm)
+exUserFolder.membershipSources['basicMemberSource']=basicMemberReg
+

Added: trunk/ZopeProducts/exUserFolder/MembershipSources/basicMemberSource/forgotPasswordEmail.dtml
===================================================================
--- trunk/ZopeProducts/exUserFolder/MembershipSources/basicMemberSource/forgotPasswordEmail.dtml	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/MembershipSources/basicMemberSource/forgotPasswordEmail.dtml	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,15 @@
+<dtml-sendmail mailhost=mailHostObject>
+To: <dtml-var realname> <<dtml-var email>>
+From: <dtml-var siteName> <<dtml-var siteEmail>>
+Subject: You forgot your password for <dtml-var siteName>
+
+Dear <dtml-var realname>,
+
+Your username is <dtml-var username> and your password is now
+<dtml-var password>.
+
+You should have tested this first, and now that you've tested it, you'll
+see you need to customise this method.
+
+</dtml-sendmail>
+

Added: trunk/ZopeProducts/exUserFolder/MembershipSources/basicMemberSource/manage_addBasicMemberSourceForm.dtml
===================================================================
--- trunk/ZopeProducts/exUserFolder/MembershipSources/basicMemberSource/manage_addBasicMemberSourceForm.dtml	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/MembershipSources/basicMemberSource/manage_addBasicMemberSourceForm.dtml	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,143 @@
+<dtml-var "DialogHeader(_.None, _, DialogTitle='Add Basic Membership Source', dialog_width='')">
+<b><dtml-babel src="'en'">Membership requires a valid property source, you cannot use this with NULL Property Source</dtml-babel></b>
+<FORM ACTION="&dtml-URL;" METHOD="POST">
+		<dtml-in "REQUEST.form.keys()">
+			<dtml-if "getVariableType(REQUEST[_['sequence-item']]) == 'List'">
+				<dtml-let listVar=sequence-item>
+					<dtml-in "REQUEST[listVar]">
+						<input type="HIDDEN" name="<dtml-var listVar>:list" value="<dtml-var sequence-item>">
+					</dtml-in>
+				</dtml-let>
+			<dtml-else>
+				<input type="HIDDEN" name="<dtml-var sequence-item>" value="<dtml-var "REQUEST[_.getitem('sequence-item',0)]">">
+			</dtml-if>
+
+		</dtml-in>
+
+<input type="HIDDEN" name="doGroup" value="1">
+<table cellspacing="2">
+<tr><td align="right"><b><dtml-babel src="'en'">Site Name (used in emails)</dtml-babel></b></td>
+	<td><input type="text" name="basicmember_sitename">
+	</td>
+</tr>
+
+<tr><td align="right"><b><dtml-babel src="'en'">Site Email (used for emails)</dtml-babel></b></td>
+	<td><input type="text" name="basicmember_siteemail">
+	</td>
+</tr>
+
+<tr><td align="right"><b><dtml-babel src="'en'">Mail Host</dtml-babel></b></td>
+	<td>
+        <select name="basicmember_mailhost">
+        <dtml-in "MailHostIDs()">
+            <option value="<dtml-var sequence-item>">
+            <dtml-var sequence-key></option>
+        </dtml-in>
+        </select>
+	</td>
+</tr>
+<tr><td align="right"><b><dtml-babel src="'en'">Site Base</dtml-babel></b></td>
+	<td><input type="text" name="basicmember_baseurl"
+			value="<dtml-var "absolute_url()">">
+	</td>
+</tr>
+
+<tr><td align="right"><b><dtml-babel src="'en'">Relative Path (from base) of Login Page</dtml-babel></b></td>
+	<td><input type="text" name="basicmember_loginpage"	value="LoginForm">
+	</td>
+</tr>
+
+<tr><td align="right"><b><dtml-babel src="'en'">Relative Path (from base) of Signup Page</dtml-babel></b></td>
+	<td><input type="text" name="basicmember_signuppage" value="SignupForm">
+	</td>
+</tr>
+
+<tr><td align="right"><b><dtml-babel src="'en'">Relative Path (from base) of Change Password Page</dtml-babel></b></td>
+	<td><input type="text" name="basicmember_passwordpage" value="ChangePasswordForm">
+	</td>
+</tr>
+<tr><td align="right"><b><dtml-babel src="'en'">Password Validation Features</dtml-babel></b></td>
+	<td>
+		<select name="basicmember_pvfeatures:list" multiple>
+		<option value="minlength">Minimum Length</option>
+		<option value="mixedcase">Must have Mixed Case</option>
+		<option value="specichar">Must have Special Chars</option>
+		<option value="notstupid">Not Stupid (username/email/part of name)</option>
+		</select>
+	</td>
+</tr>
+<tr><td align="right"><b><dtml-babel src="'en'">Minimum Length (0 if not required)</dtml-babel></b></td>
+	<td>
+		<input type="text" name="basicmember_minpasslen:int" value="0">
+	</td>
+</tr>
+<tr><td align="right"><b><dtml-babel src="'en'">Password Policy</dtml-babel></b></td>
+	<td>
+		<select name="basicmember_passwordpolicy">
+		<option value="user">User Chooses</option>
+		<option value="system">System Chooses and emails User</option>
+		</select>
+	</td>
+</tr>
+<tr><td align="right"><b><dtml-babel src="'en'">Forgotten Passwords</dtml-babel></b></td>
+	<td>
+		<select name="basicmember_forgottenpasswords">
+		<option value="hint"><dtml-babel src="'en'">Email a Hint</dtml-babel></option>
+		<option value="reset"><dtml-babel src="'en'">Reset and Email New password</dtml-babel></option>
+		</select>
+	</td>
+</tr>
+<tr><td align="right"><b><dtml-babel src="'en'">Allow users to change passwords</dtml-babel></b></td>
+	<td>
+		<input type="checkbox" name="basicmember_changepasswords" checked><dtml-babel src="'en'">Yes</dtml-babel>
+	</td>
+</tr>
+<tr><td align="right"><b><dtml-babel src="'en'">Create 'Home Directory'</dtml-babel></b></td>
+	<td>
+		<input type="checkbox" name="basicmember_createhomedir"><dtml-babel src="'en'">Yes</dtml-babel>
+	</td>
+</tr>
+<tr><td align="right"><b><dtml-babel src="'en'">Relative Path to 'Home Directory' Root</dtml-babel></b></td>
+	<td>
+		<input type="text" name="basicmember_homeroot" value="Members">
+	</td>
+</tr>
+<tr><td align="right"><b><dtml-babel src="'en'">Copy initial 'Home Directory' files from...(empty=No Copy)</dtml-babel></b></td>
+	<td>
+		<input type="text" name="basicmember_copyfiles", value="<dtml-var "_.string.join(getPhysicalPath()[1:], '/')">">
+	</td>
+</tr>
+<tr><td align="right"><b><dtml-babel src="'en'">After login....</dtml-babel></b></td>
+	<td>
+	<select name="basicmember_postlogin">
+	<option value="destination"><dtml-babel src="'en'">Go to intended destination</dtml-babel></option>
+	<option value="fixed"><dtml-babel src="'en'">Go to fixed destination</dtml-babel></option>
+	<option value="varied"><dtml-babel src="'en'">Go to Home Directory</dtml-babel></option>
+	</select>
+	</td>
+</tr>
+<tr><td align="right"><b><dtml-babel src="'en'">Fixed Destination</dtml-babel></b></td>
+	<td>
+		<input type="text" name="basicmember_fixeddest">
+	</td>
+</tr>
+<tr>
+  <td valign="top" align="right"><b><dtml-babel src="'en'">Default Roles</dtml-babel></b></td>
+  <td align="left" valign="top">
+  <select name="basicmember_roles:list" size="5" multiple>
+  <dtml-in valid_roles>
+  <dtml-if expr="_vars['sequence-item'] != 'Anonymous'">
+  <dtml-if expr="_vars['sequence-item'] != 'Authenticated'">
+  <dtml-if expr="_vars['sequence-item'] != 'Shared'">
+  <option value="<dtml-var sequence-item html_quote>"><dtml-var sequence-item>
+  </dtml-if>
+  </dtml-if>
+  </dtml-if>
+  </dtml-in valid_roles>
+  </select>
+</td>
+</tr>
+</table>
+<input type="SUBMIT" value="<dtml-babel src="'en'">Add</dtml-babel>">
+</form>
+<dtml-var DialogFooter>

Added: trunk/ZopeProducts/exUserFolder/MembershipSources/basicMemberSource/manage_editBasicMemberSourceForm.dtml
===================================================================
--- trunk/ZopeProducts/exUserFolder/MembershipSources/basicMemberSource/manage_editBasicMemberSourceForm.dtml	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/MembershipSources/basicMemberSource/manage_editBasicMemberSourceForm.dtml	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,116 @@
+<dtml-var "DialogHeader(_.None, _, DialogTitle='Edit Basic Membership Source', dialog_width='')">
+<dtml-var manage_tabs>
+<FORM ACTION="manage_editMembershipSource" METHOD="POST">
+<dtml-with currentMembershipSource>
+<table cellspacing="2">
+<tr><td align="right"><b><dtml-babel src="'en'">Site Name (used in emails)</dtml-babel></b></td>
+	<td><input type="text" name="sitename" value="&dtml.missing-siteName;">
+	</td>
+</tr>
+
+<tr><td align="right"><b><dtml-babel src="'en'">Site Email (used for emails)</dtml-babel></b></td>
+	<td><input type="text" name="siteemail" value="&dtml.missing-siteEmail;">
+	</td>
+</tr>
+
+<tr><td align="right"><b><dtml-babel src="'en'">Mail Host</dtml-babel></b></td>
+	<td>
+        <select name="mailhost">
+        <dtml-in "MailHostIDs()">
+            <option value="<dtml-var sequence-item>"<dtml-if "mailHost==_.getitem('sequence-item',0)"> selected</dtml-if>><dtml-var sequence-key></option>
+        </dtml-in>
+        </select>
+	</td>
+</tr>
+<tr><td align="right"><b><dtml-babel src="'en'">Site Base</dtml-babel></b></td>
+	<td><input type="text" name="baseurl"
+			value="&dtml.missing-baseURL;">
+	</td>
+</tr>
+
+<tr><td align="right"><b><dtml-babel src="'en'">Relative Path (from base) of Login Page</dtml-babel></b></td>
+	<td><input type="text" name="loginpage"	value="&dtml.missing-loginPage;">
+	</td>
+</tr>
+
+<tr><td align="right"><b><dtml-babel>Relative Path (from base) of Signup Page</dtml-babel></b></td>
+	<td><input type="text" name="signuppage" value="&dtml.missing-signupPage;">
+	</td>
+</tr>
+
+<tr><td align="right"><b><dtml-babel src="'en'">Relative Path (from base) of Change Password Page</dtml-babel></b></td>
+	<td><input type="text" name="passwordpage" value="&dtml.missing-passwordPage;">
+	</td>
+</tr>
+<tr><td align="right"><b><dtml-babel src="'en'">Password Validation Features</dtml-babel></b></td>
+	<td>
+		<select name="pvfeatures:list" multiple>
+		<option value="minlength" <dtml-if "'minlength' in pvFeatures"> selected</dtml-if>><dtml-babel src="'en'">Minimum Length</dtml-babel></option>
+		<option value="mixedcase" <dtml-if "'mixedcase' in pvFeatures"> selected</dtml-if>><dtml-babel src="'en'">Must have Mixed Case</dtml-babel></option>
+		<option value="specichar" <dtml-if "'specichar' in pvFeatures"> selected</dtml-if>><dtml-babel src="'en'">Must have Special Chars</dtml-babel></option>
+		<option value="notstupid" <dtml-if "'notstupid' in pvFeatures"> selected</dtml-if>><dtml-babel src="'en'">Not Stupid (username/email/part of name)</dtml-babel></option>
+		</select>
+	</td>
+</tr>
+<tr><td align="right"><b><dtml-babel src="'en'">Minimum Length (if required)</dtml-babel></b></td>
+	<td>
+		<input type="text" name="minpasslen:int" value="&dtml.missing-minLength;">
+	</td>
+</tr>
+<tr><td align="right"><b><dtml-babel src="'en'">Allow users to change passwords</dtml-babel></b></td>
+	<td>
+		<input type="checkbox" name="changepasswords"<dtml-if usersCanChangePasswords> checked</dtml-if>><dtml-babel src="'en'">Yes</dtml-babel>
+	</td>
+</tr>
+<tr><td align="right"><b><dtml-babel src="'en'">Create 'Home Directory'</dtml-babel></b></td>
+	<td>
+		<input type="checkbox" name="createhomedir"<dtml-if createHomeDir> checked</dtml-if>><dtml-babel src="'en'">Yes</dtml-babel>
+	</td>
+</tr>
+<tr><td align="right"><b><dtml-babel src="'en'">Path to 'Home Directory' Root</dtml-babel></b></td>
+	<td>
+		<input type="text" name="homeroot" value="&dtml.missing-homeRoot;">
+	</td>
+</tr>
+<tr><td align="right"><b><dtml-babel src="'en'">Copy initial 'Home Directory' files from...(empty=No Copy)</dtml-babel></b></td>
+	<td>
+		<input type="text" name="copyfiles" value="&dtml.missing-copyFilesFrom;">
+	</td>
+</tr>
+
+<tr><td align="right"><b><dtml-babel src="'en'">After login....</dtml-babel></b></td>
+	<td>
+	<select name="postlogin">
+	<option value="destination"<dtml-if "postLogin=='destination'"> selected</dtml-if>><dtml-babel src="'en'">Go to intended destination</dtml-babel></option>
+	<option value="fixed"<dtml-if "postLogin=='fixed'"> selected</dtml-if>><dtml-babel src="'en'">Go to fixed destination</dtml-babel></option>
+	<option value="varied"<dtml-if "postLogin=='varied'"> selected</dtml-if>><dtml-babel src="'en'">Go to Home Directory</dtml-babel></option>
+	</select>
+	</td>
+</tr>
+<tr><td align="right"><b><dtml-babel src="'en'">Fixed Destination</dtml-babel></b></td>
+	<td>
+		<input type="text" name="fixeddest" value="&dtml.missing-fixedDest;">
+	</td>
+</tr>
+<tr>
+  <td valign="top" align="right"><b><dtml-babel src="'en'">Default Roles</dtml-babel></b></td>
+  <td align="left" valign="top">
+  <select name="memberroles:list" size="5" multiple>
+  <dtml-in valid_roles>
+  <dtml-if expr="_vars['sequence-item'] != 'Anonymous'">
+  <dtml-if expr="_vars['sequence-item'] != 'Authenticated'">
+  <dtml-if expr="_vars['sequence-item'] != 'Shared'">
+  <option value="<dtml-var sequence-item html_quote>"<dtml-if "_['sequence-item'] in defaultRoles"> selected</dtml-if>><dtml-var sequence-item>
+  </dtml-if>
+  </dtml-if>
+  </dtml-if>
+  </dtml-in valid_roles>
+  </select>
+</td>
+</tr>
+</table>
+<input type="SUBMIT" value=" <dtml-babel src="'en'">Update</dtml-babel> ">
+</dtml-with>
+</form>
+
+<dtml-var DialogFooter>

Added: trunk/ZopeProducts/exUserFolder/MembershipSources/basicMemberSource/newPasswordEmail.dtml
===================================================================
--- trunk/ZopeProducts/exUserFolder/MembershipSources/basicMemberSource/newPasswordEmail.dtml	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/MembershipSources/basicMemberSource/newPasswordEmail.dtml	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,16 @@
+<dtml-sendmail mailhost=mailHostObject>
+To: <dtml-var user_realname> <<dtml-var user_email>>
+From: <dtml-var siteName> <<dtml-var siteEmail>>
+Subject: Welcome to <dtml-var siteName>
+
+Dear <dtml-var user_realname>,
+
+Welcome to <dtml-var siteName>.
+
+Your username is <dtml-var username> and your password is <dtml-var password>.
+
+You should have tested this first, and now that you've tested it, you'll
+see you need to customise this method.
+
+</dtml-sendmail>
+

Added: trunk/ZopeProducts/exUserFolder/MembershipSources/basicMemberSource/passwordHintEmail.dtml
===================================================================
--- trunk/ZopeProducts/exUserFolder/MembershipSources/basicMemberSource/passwordHintEmail.dtml	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/MembershipSources/basicMemberSource/passwordHintEmail.dtml	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,15 @@
+<dtml-sendmail mailhost=mailHostObject>
+To: <dtml-var realname> <<dtml-var email>>
+From: <dtml-var siteName> <<dtml-var siteEmail>>
+Subject: Hint for <dtml-var siteName>
+
+Dear <dtml-var realname>,
+
+Your username is <dtml-var username> and your password hint was;
+<dtml-var hint>.
+
+You should have tested this first, and now that you've tested it, you'll
+see you need to customise this method.
+
+</dtml-sendmail>
+

Added: trunk/ZopeProducts/exUserFolder/MembershipSources/nullMemberSource/.cvsignore
===================================================================
--- trunk/ZopeProducts/exUserFolder/MembershipSources/nullMemberSource/.cvsignore	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/MembershipSources/nullMemberSource/.cvsignore	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,4 @@
+*.pyc
+*.pyo
+*~
+.*.swp

Added: trunk/ZopeProducts/exUserFolder/MembershipSources/nullMemberSource/__init__.py
===================================================================
--- trunk/ZopeProducts/exUserFolder/MembershipSources/nullMemberSource/__init__.py	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/MembershipSources/nullMemberSource/__init__.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,2 @@
+# $Id: __init__.py,v 1.1 2004/11/10 14:15:55 akm Exp $
+import nullMemberSource

Added: trunk/ZopeProducts/exUserFolder/MembershipSources/nullMemberSource/manage_addNullPluginSourceForm.dtml
===================================================================
--- trunk/ZopeProducts/exUserFolder/MembershipSources/nullMemberSource/manage_addNullPluginSourceForm.dtml	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/MembershipSources/nullMemberSource/manage_addNullPluginSourceForm.dtml	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,21 @@
+<dtml-var "DialogHeader(_.None, _, DialogTitle='Add Basic Membership Source', dialog_width='')">
+<FORM ACTION="&dtml-URL;" METHOD="POST">
+		<dtml-in "REQUEST.form.keys()">
+			<dtml-if "getVariableType(REQUEST[_['sequence-item']]) == 'List'">
+				<dtml-let listVar=sequence-item>
+					<dtml-in "REQUEST[listVar]">
+						<input type="HIDDEN" name="<dtml-var listVar>:list" value="<dtml-var sequence-item>">
+					</dtml-in>
+				</dtml-let>
+			<dtml-else>
+				<input type="HIDDEN" name="<dtml-var sequence-item>" value="<dtml-var "REQUEST[_.getitem('sequence-item',0)]">">
+			</dtml-if>
+
+		</dtml-in>
+
+<input type="HIDDEN" name="doGroup" value="1">
+<b><dtml-babel src="'en'">This Membership Source has no configuration Items</dtml-babel></b><br>
+<br>
+<input type="SUBMIT" value="<dtml-babel src="'en'">Add</dtml-babel>">
+</form>
+<dtml-var DialogFooter>

Added: trunk/ZopeProducts/exUserFolder/MembershipSources/nullMemberSource/nullMemberSource.py
===================================================================
--- trunk/ZopeProducts/exUserFolder/MembershipSources/nullMemberSource/nullMemberSource.py	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/MembershipSources/nullMemberSource/nullMemberSource.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,49 @@
+#
+# Extensible User Folder
+# 
+# Null Membership Source for exUserFolder
+#
+# (C) Copyright 2000,2001 The Internet (Aust) Pty Ltd
+# ACN: 082 081 472  ABN: 83 082 081 472
+# All Rights Reserved
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# Author: Andrew Milton <akm at theinternet.com.au>
+# $Id: nullMemberSource.py,v 1.1 2004/11/10 14:15:55 akm Exp $
+from Globals import HTMLFile, INSTANCE_HOME
+
+from OFS.Folder import Folder
+
+from Products.exUserFolder.exUserFolder import exUserFolder
+from Products.exUserFolder.Plugins import PluginRegister
+from Products.exUserFolder.nullPlugin import nullPlugin
+
+def manage_addNullMemberSource(self, REQUEST):
+	""" Add a Membership Source """
+	self.currentMembershipSource=None
+	return ''
+
+
+manage_addNullMemberSourceForm=HTMLFile('manage_addNullPluginSourceForm',globals())
+manage_editNullMemberSourceForm=None
+
+		
+nullMemberReg=PluginRegister('nullMemberSource',
+							  'Null Membership Source',
+							 nullPlugin,
+							 manage_addNullMemberSourceForm,
+							 manage_addNullMemberSource,
+							 manage_editNullMemberSourceForm)
+exUserFolder.membershipSources['nullMemberSource']=nullMemberReg
+

Added: trunk/ZopeProducts/exUserFolder/Plugins.py
===================================================================
--- trunk/ZopeProducts/exUserFolder/Plugins.py	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/Plugins.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,46 @@
+#
+#
+# (C) Copyright 2001 The Internet (Aust) Pty Ltd
+# ACN: 082 081 472  ABN: 83 082 081 472
+# All Rights Reserved
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# Author: Andrew Milton <akm at theinternet.com.au>
+# $Id: Plugins.py,v 1.5 2004/11/10 14:15:33 akm Exp $
+
+import App, Globals, OFS
+import string
+import time
+
+from Globals import ImageFile, HTMLFile, HTML, MessageDialog, package_home
+from OFS.Folder import Folder
+
+class PluginRegister:
+	def __init__(self, name, description, pluginClass,
+				 pluginStartForm, pluginStartMethod,
+				 pluginEditForm=None, pluginEditMethod=None):
+		self.name=name #No Spaces please...
+		self.description=description
+		self.plugin=pluginClass
+		self.manage_addForm=pluginStartForm
+		self.manage_addMethod=pluginStartMethod
+		self.manage_editForm=pluginEditForm
+		self.manage_editMethod=pluginEditMethod
+
+class CryptoPluginRegister:
+	def __init__(self, name, crypto, description, pluginMethod):
+		self.name = name #No Spaces please...
+		self.cryptoMethod = crypto 
+		self.description = description
+		self.plugin = pluginMethod

Added: trunk/ZopeProducts/exUserFolder/PropSources/nullPropSource/.cvsignore
===================================================================
--- trunk/ZopeProducts/exUserFolder/PropSources/nullPropSource/.cvsignore	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/PropSources/nullPropSource/.cvsignore	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,4 @@
+*.pyc
+*.pyo
+*~
+.*.swp

Added: trunk/ZopeProducts/exUserFolder/PropSources/nullPropSource/__init__.py
===================================================================
--- trunk/ZopeProducts/exUserFolder/PropSources/nullPropSource/__init__.py	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/PropSources/nullPropSource/__init__.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,2 @@
+# $Id: __init__.py,v 1.1 2004/11/10 14:15:56 akm Exp $
+import nullPropSource

Added: trunk/ZopeProducts/exUserFolder/PropSources/nullPropSource/manage_addNullPluginSourceForm.dtml
===================================================================
--- trunk/ZopeProducts/exUserFolder/PropSources/nullPropSource/manage_addNullPluginSourceForm.dtml	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/PropSources/nullPropSource/manage_addNullPluginSourceForm.dtml	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,21 @@
+<dtml-var "DialogHeader(_.None, _, DialogTitle='Add Basic Property Source', dialog_width='')">
+<FORM ACTION="&dtml-URL;" METHOD="POST">
+		<dtml-in "REQUEST.form.keys()">
+			<dtml-if "getVariableType(REQUEST[_['sequence-item']]) == 'List'">
+				<dtml-let listVar=sequence-item>
+					<dtml-in "REQUEST[listVar]">
+						<input type="HIDDEN" name="<dtml-var listVar>:list" value="<dtml-var sequence-item>">
+					</dtml-in>
+				</dtml-let>
+			<dtml-else>
+				<input type="HIDDEN" name="<dtml-var sequence-item>" value="<dtml-var "REQUEST[_.getitem('sequence-item',0)]">">
+			</dtml-if>
+
+		</dtml-in>
+
+<input type="HIDDEN" name="doMember" value="1">
+<b><dtml-babel src="'en'">This Property Source has no configuration Items</dtml-babel></b><br>
+<br>
+<input type="SUBMIT" value="<dtml-babel src="'en'">Add</dtml-babel>">
+</form>
+<dtml-var DialogFooter>

Added: trunk/ZopeProducts/exUserFolder/PropSources/nullPropSource/nullPropSource.py
===================================================================
--- trunk/ZopeProducts/exUserFolder/PropSources/nullPropSource/nullPropSource.py	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/PropSources/nullPropSource/nullPropSource.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,50 @@
+#
+# Extensible User Folder
+# 
+# Null Membership Source for exUserFolder
+#
+# (C) Copyright 2000,2001 The Internet (Aust) Pty Ltd
+# ACN: 082 081 472  ABN: 83 082 081 472
+# All Rights Reserved
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# Author: Andrew Milton <akm at theinternet.com.au>
+# $Id: nullPropSource.py,v 1.1 2004/11/10 14:15:56 akm Exp $
+from Globals import HTMLFile, INSTANCE_HOME
+
+from OFS.Folder import Folder
+
+from Products.exUserFolder.exUserFolder import exUserFolder
+from Products.exUserFolder.Plugins import PluginRegister
+from Products.exUserFolder.nullPlugin import nullPlugin
+
+def manage_addNullPropSource(self, REQUEST):
+	""" Add a Property Source """
+	self.currentPropSource=None
+	return ''
+
+
+manage_addNullPropSourceForm=HTMLFile('manage_addNullPluginSourceForm',globals())
+manage_editNullPropSourceForm=None
+
+		
+nullPropReg=PluginRegister('nullPropSource',
+						   'Null Property Source',
+						   nullPlugin,
+						   manage_addNullPropSourceForm,
+						   manage_addNullPropSource,
+						   manage_editNullPropSourceForm)
+
+exUserFolder.propSources['nullPropSource']=nullPropReg
+

Added: trunk/ZopeProducts/exUserFolder/PropertyEditor/.cvsignore
===================================================================
--- trunk/ZopeProducts/exUserFolder/PropertyEditor/.cvsignore	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/PropertyEditor/.cvsignore	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,4 @@
+*.pyc
+*.pyo
+*~
+.*.swp

Added: trunk/ZopeProducts/exUserFolder/PropertyEditor/PropertyEditor.py
===================================================================
--- trunk/ZopeProducts/exUserFolder/PropertyEditor/PropertyEditor.py	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/PropertyEditor/PropertyEditor.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,122 @@
+#
+# Extensible User Folder
+#
+# (C) Copyright 2000,2001 The Internet (Aust) Pty Ltd
+# ACN: 082 081 472  ABN: 83 082 081 472
+# All Rights Reserved
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# Author: Andrew Milton <akm at theinternet.com.au>
+# $Id: PropertyEditor.py,v 1.3 2002/01/29 17:42:02 alex_zxc Exp $
+
+from Globals import DTMLFile, MessageDialog, INSTANCE_HOME
+from string import join,strip,split,lower,upper,find
+from urllib import quote, unquote
+
+
+def editStringProperty( name, value):
+	""" """
+	return('<input type="TEXT" name="%s:string" value="%s">\n'%(name, value))
+
+def viewStringProperty( name, value):
+	""" """
+	return('<input type="HIDDEN" name="propValue:string" value="%s"><br>%s<br>\n'%(value, value))
+
+
+def editIntegerProperty( name, value):
+	""" """
+	return('<input type="TEXT" name="%s:int" value="%d">\n'%(name, value or 0))
+
+def viewIntegerProperty( name, value):
+	""" """
+	return('<input type="HIDDEN" name="propValue:int" value="%d"><br>%d<br>\n'%(value or 0 , value or 0))
+
+
+def editLongProperty( name, value):
+	""" """
+	return('<input type="TEXT" name="%s:int" value="%d">\n'%(name, value or 0))	
+
+def viewLongProperty( name, value):
+	""" """
+	return('<input type="HIDDEN" name="propValue:long" value="%d"><br>%d<br>\n'%(value or 0, value or 0))
+
+
+def editFloatProperty( name, value):
+	""" """
+	return('<input type="TEXT" name="%s:float" value="%d">\n'%(name, value))
+
+def viewFloatProperty( name, value):
+	""" """
+	return('<input type="HIDDEN" name="propValue:float" value="%f"><br>%f<br>\n'%(value, value))
+
+
+def editListProperty( name, value):
+	a=''
+	if value:
+		a = a + 'Select Items to keep<br>\n'
+		a = a + '<select name="%s:list" multiple>\n'%(name)
+		for i in value:
+			a = a + (
+				'<option value="%s" SELECTED>%s\n'%(i, i))
+		a = a + '</select>\n<br>'
+	a = a + 'Add an item\n<br>'
+	a = a + '<input type="TEXT" name="%s:list">'%(name)
+	return(a)
+
+def viewListProperty( name, value):
+	a=''
+	if value:
+		for i in value:
+			a = a + (
+				'<input type="HIDDEN" name="propValue:list" value="%s">\n'%(i))
+			a = a + '%s\n<br>'%(i)
+	return(a)
+
+
+def editDictProperty( name, value):
+	""" """
+	a=''
+	if value and value.keys():
+		for i in value.keys():
+			a = a + '%s : <input type="TEXT" name="%s.%s" value="%s">\n<br>'%(i, name, i, value[i])
+	return a
+
+
+def viewDictProperty( name, value):
+	""" """
+	a=''
+	if value and value.keys():
+		for i in value.keys():
+			a = a + '%s : <input type="HIDDEN" name="propValue.%s" value="%s">\n<br>'%(i, name, i, value[i])
+			a = a + '%s\n<br>'%(value[i])
+	return a
+
+EditMethods={'String':editStringProperty,
+			 'Integer':editIntegerProperty,
+			 'Long':editLongProperty,
+			 'Float':editFloatProperty,
+			 'List':editListProperty,
+			 'Dict':editDictProperty}
+			 
+
+ViewMethods={'String':viewStringProperty,
+			 'Integer':viewIntegerProperty,
+			 'Long':viewLongProperty,
+			 'Float':viewFloatProperty,
+			 'List':viewListProperty,
+			 'Dict':viewDictProperty}
+			 
+
+
+

Added: trunk/ZopeProducts/exUserFolder/PropertyEditor/__init__.py
===================================================================
--- trunk/ZopeProducts/exUserFolder/PropertyEditor/__init__.py	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/PropertyEditor/__init__.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,22 @@
+#
+# Extensible User Folder
+#
+# (C) Copyright 2000,2001 The Internet (Aust) Pty Ltd
+# ACN: 082 081 472  ABN: 83 082 081 472
+# All Rights Reserved
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# Author: Andrew Milton <akm at theinternet.com.au>
+# $Id: __init__.py,v 1.2 2001/12/01 08:40:04 akm Exp $
+import PropertyEditor

Added: trunk/ZopeProducts/exUserFolder/README.Upgrading
===================================================================
--- trunk/ZopeProducts/exUserFolder/README.Upgrading	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/README.Upgrading	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,14 @@
+If you are upgrading an existing site from < 0.50
+
+I have restructured the source tree. This will make this version
+incompatible with previous versions, as the classes have moved. This
+breaks upgrading existing installs unless you keep the old classes
+around. If you only use external Auth/Prop/Group sources, you will
+probably be unaffected.
+
+This means for those of you using SQL or LDAP or any non-ZODB sources,
+you can remove and then re-add your XUF acl_users to get going again.
+
+If you are using a ZODB source, then you need to keep the old classes
+and the old paths around (e.g. symlink zodbAuthSource to
+AuthSources/zodbAuthSource).

Added: trunk/ZopeProducts/exUserFolder/User.py
===================================================================
--- trunk/ZopeProducts/exUserFolder/User.py	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/User.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,243 @@
+#
+# Extensible User Folder
+#
+# (C) Copyright 2000,2001 The Internet (Aust) Pty Ltd
+# ACN: 082 081 472  ABN: 83 082 081 472
+# All Rights Reserved
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# Author: Andrew Milton <akm at theinternet.com.au>
+# $Id: User.py,v 1.10 2004/12/14 05:30:29 akm Exp $
+
+##############################################################################
+#
+# Zope Public License (ZPL) Version 0.9.4
+# ---------------------------------------
+# 
+# Copyright (c) Digital Creations.  All rights reserved.
+# 
+# Redistribution and use in source and binary forms, with or
+# without modification, are permitted provided that the following
+# conditions are met:
+# 
+# 1. Redistributions in source code must retain the above
+#    copyright notice, this list of conditions, and the following
+#    disclaimer.
+# 
+# 6. Redistributions of any form whatsoever must retain the
+#    following acknowledgment:
+# 
+#      "This product includes software developed by Digital
+#      Creations for use in the Z Object Publishing Environment
+#      (http://www.zope.org/)."
+# 
+# Disclaimer
+# 
+#   THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS ``AS IS'' AND
+#   ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+#   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+#   FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT
+#   SHALL DIGITAL CREATIONS OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+#   DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+#   CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+#   PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+#   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+#   ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+#   LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+#   IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+#   THE POSSIBILITY OF SUCH DAMAGE.
+#
+##############################################################################
+from AccessControl.User import BasicUser
+from string import join,strip,split,lower,upper,find
+
+class XUFUser(BasicUser):
+
+	icon='misc_/exUserFolder/exUser.gif'
+
+	# cacheable is a dict that must contain at least name, password,
+	# roles, and domains -- unless you're working with your own User class,
+	# in which case you need to override __init__ and define it yourself.
+	def __init__(self, cacheable, propSource, cryptPassword, authSource,
+				 groupSource=None):
+		self.name   =cacheable['name']
+		self.__     =cacheable['password']
+		if cacheable['roles']:
+			self.roles = filter(None, cacheable['roles'])
+		else:
+			self.roles = []
+		# domains may be passed as a string or a list
+		if type(cacheable['domains']) == type(''):
+			self.domains=filter(None, map(strip,
+										  split(cacheable['domains'], ',')))
+		else:
+			self.domains=cacheable['domains']
+		self._authSource=authSource
+		self._propSource=propSource
+		self._groupSource=groupSource		
+		self.cryptPassword=cryptPassword
+
+	def getUserName(self):
+		return self.name
+
+	def _getPassword(self):
+		return self.__
+
+	def getRoles(self):
+		return tuple(self.roles) + ('Authenticated',)
+    
+	def getDomains(self):
+		return self.domains
+
+	# Ultra generic way of getting, checking and setting properties
+	def getProperty(self, property, default=None):
+		if self._propSource:
+			return self._propSource.getUserProperty(property, self.name, default)
+
+	def hasProperty(self, property):
+		if self._propSource:
+			return self._propSource.hasProperty(property)
+
+	def setProperty(self, property, value):
+		if property[0]=='_':
+			return
+		if self._propSource:
+			return self._propSource.setUserProperty(property, self.name, value)
+
+	def setTempProperty(self, property, value):
+		if property[0]=='_':
+			return
+		if self._propSource:
+			return self._propSource.setTempProperty(property, value)
+
+	def flushTempProperties(self):
+		if self._propSource:
+			return self._propSource.flushTempProperties()
+
+	def delProperty(self, property):
+		if property[0]=='_':
+			return
+		if self._propSource:
+			return self._propSource.delUserProperty(property, self.name)
+		
+	def listProperties(self):
+		if self._propSource:
+			return self._propSource.listUserProperties(self.name)
+
+	# Try to allow User['property'] -- won't work for password d;)
+	def __getitem__(self, key):
+		# Don't return 'private' keys
+		if key[0] != '_':
+			if hasattr(self, key):
+				return getattr(self, key)
+			if self._propSource and self._propSource.hasProperty(key):
+
+				return self._propSource.getUserProperty(key, self.name)
+		raise KeyError, key
+
+	def __setitem__(self, key, value):
+		if key[0]=='_':
+			return
+		if self._propSource:
+			self._propSource.setUserProperty(key, self.name, value)
+		
+	# List one user is supplied by the Auth Source...
+	
+	def authenticate(self, listOneUser, password, request, remoteAuth=None):
+		result=listOneUser(username=self.name)
+		for people in result:
+			if remoteAuth:
+				return remoteAuth(self.name, password)
+			else:
+				secret=self.cryptPassword(self.name, password)
+				return secret==people['password']
+		return None
+
+	# You can set logout times or whatever here if you want to, the
+	# property source is still active.
+	def notifyCacheRemoval(self):
+		if self._propSource:
+			self._propSource.flushTempProperties()
+
+	# You must override this and __init__ if you are subclassing
+	# the user object, or your user object may not be reconstructed
+	# properly!  All values in this dict must be non-Persistent objects
+	# or types, and may not hold any references to Persistent objects,
+	# or the cache will break.
+	def _getCacheableDict(self):
+		return {'name':		self.name,
+				'password':	self.__,
+				'roles':	self.roles,
+				'domains':	self.domains}
+
+	def getGroups(self):
+		if self._groupSource:
+			return self._groupSource.getGroupsOfUser(self.name)
+		else:
+			return ()
+
+
+	def _setGroups(self, groupnames):
+		if self._groupSource:
+			return self._groupSource.setGroupsOfUser(groupnames, self.name)
+
+
+	def _addGroups(self, groupnames):
+		if self._groupSource:
+			return self._groupSource.addGroupsToUser(groupnames, self.name)
+
+	def _delGroups(self, groupnames):
+		if self._groupSource:
+			return self._groupSource.delGroupsFromUser(groupnames, self.name)
+
+	def getId(self):
+		if self._propSource and self._propSource.getUserProperty('userid', self.name):
+			return self._propSource.getUserProperty('userid', self.name)
+		return self.name
+
+
+#
+# An Anonymous User for session tracking...
+# Can set and get properties just like a normal user.
+#
+# These objects live in the cache, so, we have a __del__ method to
+# clean ourselves up.
+#
+
+class XUFAnonUser(XUFUser):
+	def __init__(self, name, roles, propSource):
+		self.name   =name
+		self.__     =''
+		self.roles  =filter(None, roles)
+		self._propSource=propSource
+
+	def getRoles(self):
+		return tuple(self.roles) + ('Anonymous',)
+
+	def authenticate(self, listOneUser, password, request, remoteAuth=None):
+		return 1
+	
+	def notifyCacheRemoval(self):
+		if self._propSource:
+			self._propSource.deleteUsers([self.name,])
+
+# We now set up a dummy classes so that people can extend the User objects
+# or override stuff with much less pain --akm
+
+class User(XUFUser):
+	pass
+
+class AnonUser(XUFAnonUser):
+	pass
+

Added: trunk/ZopeProducts/exUserFolder/UserCache/.cvsignore
===================================================================
--- trunk/ZopeProducts/exUserFolder/UserCache/.cvsignore	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/UserCache/.cvsignore	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,4 @@
+*.pyc
+*.pyo
+*~
+.*.swp

Added: trunk/ZopeProducts/exUserFolder/UserCache/UserCache.py
===================================================================
--- trunk/ZopeProducts/exUserFolder/UserCache/UserCache.py	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/UserCache/UserCache.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,406 @@
+#
+# Extensible User Folder
+#
+# (C) Copyright 2000,2001 The Internet (Aust) Pty Ltd
+# ACN: 082 081 472  ABN: 83 082 081 472
+# All Rights Reserved
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# Author: Andrew Milton <akm at theinternet.com.au>
+# $Id: UserCache.py,v 1.16 2003/07/10 21:11:26 akm Exp $
+
+# Module Level Caches for Various User Operations
+
+from time import time
+from BTrees.OOBTree import OOBTree
+import threading
+from Acquisition import aq_inner
+from Products.exUserFolder.User import User
+
+class UserCacheItem:
+	lastAccessed=0
+	def __init__(self, username, password, cacheable):
+		self.username=username
+		self.password=password
+		self.cacheable=cacheable
+		self.lastAccessed=time()
+
+	def touch(self):
+		self.lastAccessed=time()
+		
+	def __repr__(self):
+		return self.username
+
+class NegativeUserCacheItem(UserCacheItem):
+	def __init__(self, username):
+		self.username=username
+		self.lastAccessed=time()
+
+class AdvancedCookieCacheItem(UserCacheItem):
+	lastAccessed=0
+	def __init__(self, username, password):
+		self.username=username
+		self.password=password
+		self.lastAccessed=time()
+
+SessionExpiredException='User Session Expired'
+class UserCache:
+	def __init__(self, sessionLength):
+		self.sessionLength=sessionLength
+		self.cache=OOBTree()
+		self.hits=0
+		self.fail=0
+		self.nouser=0
+		self.attempts=0
+		self.timeouts=0
+		self.cacheStarted=time()
+		self.lock=threading.Lock()
+		
+	def addToCache(self, username, password, User):
+		self.lock.acquire()		
+		try:
+			if not self.sessionLength:
+				return
+
+			try:
+				u = self.cache.items(username)
+				if u:
+					for x in self.cache[username]:
+						self.cache.remove(x)
+			except:
+				pass
+
+			u = UserCacheItem(username, password, User._getCacheableDict())
+			self.cache[username]=u
+		finally:
+			self.lock.release()
+
+	def getUser(self, caller, username, password, checkpassword=1):
+		self.lock.acquire()
+		try:
+			if not self.sessionLength:
+				return None
+
+			self.attempts=self.attempts+1
+
+			u = None
+			try:
+				u = self.cache[username]
+			except KeyError:
+				self.nouser=self.nouser+1
+				return None
+
+			now = time()
+			if u:
+				if checkpassword and (u.password != password):
+					self.fail=self.fail+1
+					del self.cache[u.username]
+				elif self.sessionLength and (
+					(now - u.lastAccessed) > self.sessionLength):
+					del self.cache[u.username]
+					self.timeouts=self.timeouts+1
+					user_object=User(u.cacheable,
+									 caller.currentPropSource,
+									 caller.cryptPassword,
+									 caller.currentAuthSource,
+									 caller.currentGroupSource)
+					user_object.notifyCacheRemoval()
+					del u
+					raise SessionExpiredException
+				else:
+					u.touch()
+					self.hits=self.hits+1
+					return User(u.cacheable,
+								caller.currentPropSource,
+								caller.cryptPassword,
+								caller.currentAuthSource,
+								caller.currentGroupSource)
+
+			self.nouser=self.nouser+1
+			return None
+		finally:
+			self.lock.release()
+
+	def removeUser(self, username):
+		self.lock.acquire()
+		try:
+			if not self.sessionLength:
+				return
+			try:
+				if self.cache[username]:
+					del self.cache[username]
+			except:
+				pass
+		finally:
+			self.lock.release()
+
+	def getCacheStats(self):
+		self.lock.acquire()
+		try:
+			return (
+				{'attempts':self.attempts,
+				 'hits':self.hits,
+				 'fail':self.fail,
+				 'misses':self.nouser,
+				 'cachesize':len(self.cache),
+				 'time':self.cacheStarted,
+				 'timeouts':self.timeouts,
+				 'length':self.sessionLength})
+		finally:
+			self.lock.release()
+		
+	def getCurrentUsers(self, caller):
+		self.lock.acquire()
+		try:
+			x=[]
+			now = time()		
+			for z in self.cache.keys():
+				u = self.cache[z]
+				if self.sessionLength and (
+					(now - u.lastAccessed) > self.sessionLength):
+					del self.cache[u.username]
+					self.timeouts=self.timeouts+1
+					user_object=User(u.cacheable,
+									 caller.currentPropSource,
+									 caller.cryptPassword,
+									 caller.currentAuthSource,
+									 caller.currentGroupSource)
+					user_object.notifyCacheRemoval()
+					del u
+				else:
+					x.append({'username':u.username,
+							  'lastAccessed':u.lastAccessed})
+			return x
+		finally:
+			self.lock.release()
+
+class NegativeUserCache:
+	def __init__(self, sessionLength):
+		self.sessionLength=sessionLength
+		self.cache=OOBTree()
+		self.hits=0
+		self.cacheStarted=time()
+		self.lock=threading.Lock()
+		
+	def addToCache(self, username):
+		self.lock.acquire()		
+		try:
+			if not self.sessionLength:
+				return
+
+			try:
+				u = self.cache.items(username)
+				if u:
+					for x in self.cache[username]:
+						self.cache.remove(x)
+			except:
+				pass
+
+			u = NegativeUserCacheItem(username)
+			self.cache[username]=u
+		finally:
+			self.lock.release()
+
+	def getUser(self, username):
+		self.lock.acquire()
+		try:
+			if not self.sessionLength:
+				return 0
+
+			u = None
+			try:
+				u = self.cache[username]
+			except KeyError:
+				return 0
+
+			now = time()
+			if u:
+				if self.sessionLength and (
+					(now - u.lastAccessed) > self.sessionLength):
+					del self.cache[u.username]
+				else:
+					# We don't touch negative user caches
+					# u.touch()
+					self.hits=self.hits+1
+					return 1
+			return 0
+		finally:
+			self.lock.release()
+
+	def removeUser(self, username):
+		self.lock.acquire()
+		try:
+			if not self.sessionLength:
+				return
+			try:
+				del self.cache[username]
+			except:
+				pass
+		finally:
+			self.lock.release()					
+
+class CookieCache:
+	def __init__(self, sessionLength):
+		self.sessionLength=sessionLength
+		self.cache=OOBTree()
+		self.hits=0
+		self.cacheStarted=time()
+		self.lock=threading.Lock()
+		
+	def addToCache(self, username, password, key):
+		self.lock.acquire()		
+		try:
+			if not self.sessionLength:
+				return
+
+			try:
+				u = self.cache.items(key)
+				if u:
+					for x in self.cache[key]:
+						self.cache.remove(x)
+			except:
+				pass
+			u = AdvancedCookieCacheItem(username, password)
+			self.cache[key]=u
+		finally:
+			self.lock.release()
+
+	def getUser(self, key):
+		self.lock.acquire()
+		try:
+			if not self.sessionLength:
+				return None
+
+			u = None
+			try:
+				u = self.cache[key]
+			except KeyError:
+				return None
+
+			now = time()
+			if u:
+				if self.sessionLength and (
+					(now - u.lastAccessed) > self.sessionLength):
+					del self.cache[key]
+				else:
+					# We don't touch negative user caches
+					# u.touch()
+					self.hits=self.hits+1
+					return u.username, u.password
+			return None
+		finally:
+			self.lock.release()
+
+	def removeUser(self, key):
+		self.lock.acquire()
+		try:
+			if not self.sessionLength:
+				return
+			try:
+				del self.cache[key]
+			except:
+				pass
+		finally:
+			self.lock.release()
+
+class GlobalUserCache:
+	caches={}
+	def __init__(self):
+		self.lock = threading.Lock()
+
+	def createCache(self, who, sessionLength):
+		self.lock.acquire()
+		try:
+			self.caches[who]=UserCache(sessionLength)
+			return self.caches[who]
+		finally:
+			self.lock.release()
+
+	def getCache(self, who):
+		self.lock.acquire()
+		try:
+			if self.caches.has_key(who):
+				return self.caches[who]
+			else:
+				return None
+		finally:
+			self.lock.release()
+			
+	def deleteCache(self, who):
+		self.lock.acquire()
+		try:
+			del self.caches[who]
+		finally:
+			self.lock.release()
+
+class GlobalNegativeUserCache:
+	caches={}
+	def __init__(self):
+		self.lock = threading.Lock()
+
+	def createCache(self, who, sessionLength):
+		self.lock.acquire()
+		try:
+			self.caches[who]=NegativeUserCache(sessionLength)
+			return self.caches[who]
+		finally:
+			self.lock.release()
+
+	def getCache(self, who):
+		self.lock.acquire()
+		try:
+			if self.caches.has_key(who):
+				return self.caches[who]
+			else:
+				return None
+		finally:
+			self.lock.release()
+			
+	def deleteCache(self, who):
+		self.lock.acquire()
+		try:
+			del self.caches[who]
+		finally:
+			self.lock.release()
+
+class GlobalAdvancedCookieCache:
+	caches={}
+	def __init__(self):
+		self.lock = threading.Lock()
+
+	def createCache(self, who, sessionLength):
+		self.lock.acquire()
+		try:
+			self.caches[who]=CookieCache(sessionLength)
+			return self.caches[who]
+		finally:
+			self.lock.release()
+
+	def getCache(self, who):
+		self.lock.acquire()
+		try:
+			if self.caches.has_key(who):
+				return self.caches[who]
+			else:
+				return None
+		finally:
+			self.lock.release()
+			
+	def deleteCache(self, who):
+		self.lock.acquire()
+		try:
+			del self.caches[who]
+		finally:
+			self.lock.release()
+	

Added: trunk/ZopeProducts/exUserFolder/UserCache/__init__.py
===================================================================
--- trunk/ZopeProducts/exUserFolder/UserCache/__init__.py	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/UserCache/__init__.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,22 @@
+#
+# Extensible User Folder
+#
+# (C) Copyright 2000,2001 The Internet (Aust) Pty Ltd
+# ACN: 082 081 472  ABN: 83 082 081 472
+# All Rights Reserved
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# Author: Andrew Milton <akm at theinternet.com.au>
+# $Id: __init__.py,v 1.2 2001/12/01 08:40:04 akm Exp $
+import UserCache

Added: trunk/ZopeProducts/exUserFolder/__init__.py
===================================================================
--- trunk/ZopeProducts/exUserFolder/__init__.py	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/__init__.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,85 @@
+#
+# Extensible User Folder
+# 
+# (C) Copyright 2000-2005 The Internet (Aust) Pty Ltd
+# ACN: 082 081 472  ABN: 83 082 081 472
+# All Rights Reserved
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# Author: Andrew Milton <akm at theinternet.com.au>
+# $Id: __init__.py,v 1.18 2004/11/10 14:15:33 akm Exp $
+
+import exUserFolder
+
+import CryptoSources
+import AuthSources
+#import PropSources
+import MembershipSources
+import GroupSources
+
+from GroupSource import GroupSource
+
+from App.ImageFile import ImageFile
+import OFS
+
+#
+# Install a dummy ZBabel setup if we don't have ZBabel installed.
+#
+import dummyZBabelTag
+
+# Methods we need access to from any ObjectManager context
+legacy_methods = (
+	    ('manage_addexUserFolderForm', exUserFolder.manage_addexUserFolderForm),
+	    ('manage_addexUserFolder',     exUserFolder.manage_addexUserFolder),
+	    ('getAuthSources',             exUserFolder.getAuthSources),
+	    #('getPropSources',             exUserFolder.getPropSources),
+		('getCryptoSources',           exUserFolder.getCryptoSources),
+	    ('getMembershipSources',       exUserFolder.getMembershipSources),
+	    ('getGroupSources',            exUserFolder.getGroupSources),
+	    ('doAuthSourceForm',           exUserFolder.doAuthSourceForm),
+	    #('doPropSourceForm',           exUserFolder.doPropSourceForm),
+	    ('doMembershipSourceForm',     exUserFolder.doMembershipSourceForm),
+        #	    ('doGroupSourceForm',          exUserFolder.doGroupSourceForm),
+	    ('getVariableType',            exUserFolder.getVariableType),
+	    ('DialogHeader',               exUserFolder.exUserFolder.DialogHeader),
+	    ('DialogFooter',               exUserFolder.exUserFolder.DialogFooter),
+	    #('MailHostIDs',                exUserFolder.MailHostIDs),
+	    )
+
+# Image files to place in the misc_ object so they are accesible from misc_/exUserFolder
+misc_={'exUserFolder.gif': ImageFile('exUserFolder.gif', globals()),
+       'exUserFolderPlugin.gif': ImageFile('exUserFolderPlugin.gif', globals()),
+       'exUser.gif': ImageFile('exUser.gif', globals()),
+       }
+
+
+def initialize(context):
+    """
+    Register base classes
+    """
+    context.registerClass(exUserFolder.exUserFolder,
+			  meta_type="ex User Folder",
+			  permission="Add exUser Folder",
+			  constructors=(exUserFolder.manage_addexUserFolderForm,
+					exUserFolder.manage_addexUserFolder,),
+			  legacy=legacy_methods,
+			  icon="exUserFolder.gif")
+
+    context.registerClass(GroupSource.GroupSource,
+			  meta_type="ex User Folder Group Source",
+			  permission="Add exUser Folder",
+			  constructors=(GroupSource.manage_addGroupSourceForm,
+					GroupSource.manage_addGroupSource,),
+			  icon="exUserFolderPlugin.gif")
+

Added: trunk/ZopeProducts/exUserFolder/common/DialogFooter.dtml
===================================================================
--- trunk/ZopeProducts/exUserFolder/common/DialogFooter.dtml	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/common/DialogFooter.dtml	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,4 @@
+</td>
+</tr>
+</table>
+</td></tr></table>
\ No newline at end of file

Added: trunk/ZopeProducts/exUserFolder/common/DialogHeader.dtml
===================================================================
--- trunk/ZopeProducts/exUserFolder/common/DialogHeader.dtml	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/common/DialogHeader.dtml	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,7 @@
+<table bgcolor="#808080" width="&dtml.missing-dialog_width;"><tr><td align="center" valign="middle">
+<table width="100%" border="1" bgcolor="White" cellpadding="0" cellspacing="0">
+<tr>
+    <td align="center"><table width="100%" bgcolor="#dddddd" border="0" cellspacing="0"><tr><td align="center"><font color="Black" size=+1 face="Helvetica"><b><dtml-babel src="'en'" literal="1"><dtml-var DialogTitle></dtml-babel></b></font></td></tr></table></td>
+</tr>
+<tr>
+   <td>

Added: trunk/ZopeProducts/exUserFolder/common/MessageDialog.dtml
===================================================================
--- trunk/ZopeProducts/exUserFolder/common/MessageDialog.dtml	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/common/MessageDialog.dtml	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,41 @@
+<head><body>
+<FORM ACTION="<dtml-var action>" METHOD="POST" <dtml-if target>TARGET="<dtml-var target>"</dtml-if>>
+<dtml-in "REQUEST.form.keys()">
+	<dtml-if "getVariableType(REQUEST[_['sequence-item']]) == 'List'">
+		<dtml-let listVar=sequence-item>
+			<dtml-in "REQUEST[listVar]">
+				<input type="HIDDEN" name="<dtml-var listVar>:list" value="<dtml-var sequence-item>">
+			</dtml-in>
+		</dtml-let>
+	<dtml-else>
+		<input type="HIDDEN" name="<dtml-var sequence-item>" value="<dtml-var "REQUEST[_.getitem('sequence-item',0)]">">
+	</dtml-if>
+</dtml-in>
+<dtml-var "DialogHeader(DialogTitle=title, dialog_width='')">
+<TABLE BORDER="0" WIDTH="100%" CELLPADDING="10">
+<TR>
+  <TD VALIGN="TOP">
+  <BR>
+  <CENTER><B><FONT SIZE="+6" COLOR="#77003B">!</FONT></B></CENTER>
+  </TD>
+  <TD VALIGN="TOP">
+  <BR><BR>
+  <CENTER>
+  <dtml-babel src="'en'" literal="1"><dtml-var message></dtml-babel>
+  </CENTER>
+  </TD>
+</TR>
+<TR>
+  <TD VALIGN="TOP">
+  </TD>
+  <TD VALIGN="TOP">
+  <CENTER>
+  <INPUT TYPE="SUBMIT" VALUE="   <dtml-babel src="'en'">Ok</dtml-babel>   ">
+  </CENTER>
+  </TD>
+</TR>
+</TABLE>
+<dtml-var DialogFooter>
+</FORM>
+
+</head></body>

Added: trunk/ZopeProducts/exUserFolder/common/manage_tabs.dtml
===================================================================
--- trunk/ZopeProducts/exUserFolder/common/manage_tabs.dtml	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/common/manage_tabs.dtml	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,176 @@
+<dtml-with "_(manage_options=filtered_manage_options())">
+<dtml-if manage_options>
+<dtml-call "REQUEST.set('n_', _.len(manage_options)-1)">
+<dtml-call "REQUEST.set('a_', 0)">
+<dtml-in manage_options mapping>
+<dtml-if expr="URL[-(_.len(action)):]==action or
+                URL[-17:]=='/manage_workspace' and _['sequence-start']">
+<dtml-call "REQUEST.set('a_', _['sequence-index'])">
+</dtml-if>
+<dtml-if "_.has_key('management_view') and management_view==label">
+<dtml-call "REQUEST.set('a_', _['sequence-index'])">
+</dtml-if>
+</dtml-in>
+
+
+<table cellpadding="0" cellspacing="0" width="100%" border="0">
+
+<tr>
+  <td bgcolor="#000000" rowspan="5" width="10%" valign="bottom" 
+   align="left">  <img src="&dtml-BASEPATH1;/p_/sp" 
+   width="2" height="1" alt="" />
+  </td>
+  <td bgcolor="#000000" colspan="<dtml-var "4 * (n_ + 1)">"><img 
+   src="&dtml-BASEPATH1;/p_/sp" width="1" height="5" alt="" /></td>
+</tr>
+
+<tr>
+<dtml-in manage_options>
+<dtml-if "_['sequence-index']==a_">
+  <td bgcolor="#ffffff" rowspan="2" valign="top" 
+   align="left"><img src="&dtml-BASEPATH1;/p_/ltab" width="5" 
+   height="5" alt="" /></td>
+  <td bgcolor="#ffffff"><img src="&dtml-BASEPATH1;/p_/sp"
+   width="1" height="2" alt="" /></td>
+  <td bgcolor="#ffffff" rowspan="2" valign="top" 
+   align="right"><img src="&dtml-BASEPATH1;/p_/rtab" width="5" 
+   height="5" alt="" /></td>
+  <td bgcolor="#000000" rowspan="4"><img src="&dtml-BASEPATH1;/p_/sp"
+ width="2" height="1" alt="" /></td>
+<dtml-else>
+  <td bgcolor="#efefef" rowspan="2" valign="top" 
+   align="left"><img src="&dtml-BASEPATH1;/p_/ltab" width="5" 
+   height="5" alt="" /></td>
+  <td bgcolor="#efefef"><img src="&dtml-BASEPATH1;/p_/sp"
+ width="1" height="2" alt="" /></td>
+  <td bgcolor="#efefef" rowspan="2" valign="top" 
+   align="right"><img src="&dtml-BASEPATH1;/p_/rtab" width="5" 
+   height="5" alt="" /></td>
+  <td bgcolor="#000000" rowspan="4"><img src="&dtml-BASEPATH1;/p_/sp"
+ width="2" height="1" alt="" /></td>
+</dtml-if>
+</dtml-in>
+</tr>
+<tr>
+<dtml-in manage_options mapping>
+<dtml-if "_['sequence-index']==a_">
+  <td bgcolor="#ffffff" valign="bottom" class="tab-small" 
+   align="center"><font face="Verdana, Arial, Helvetica" 
+   size="1" color="#000000"> <a <dtml-if 
+   action>href="&dtml-action;"<dtml-else>href="&dtml-URL1;"</dtml-if
+   ><dtml-if target> target="&dtml-target;"</dtml-if
+   >><span style="color: #000000;"><strong><dtml-babel src="'en'" literal="1">
+	<dtml-var label></dtml-babel></strong></span></a> </font></td>
+<dtml-else>
+  <td bgcolor="#efefef" valign="bottom" class="tab-small" 
+   align="center"><font face="Verdana, Arial, Helvetica" 
+   size="1" color="#000000"> <a <dtml-if 
+   action>href="&dtml-action;"<dtml-else>href="&dtml-URL1;"</dtml-if
+   ><dtml-if target> target="&dtml-target;"</dtml-if
+   >><span style="color: #000000;"><strong>
+	<dtml-babel src="'en'" literal="1"><dtml-var label></dtml-babel></strong></span></a> </font></td>
+</dtml-if>
+</dtml-in>
+</tr>
+<tr>
+<dtml-in manage_options>
+<dtml-if "_['sequence-index']==a_">
+  <td colspan="3" bgcolor="#ffffff"><img src="&dtml-BASEPATH1;/p_/sp"
+ width="2" height="1" alt="" /></td>
+<dtml-else>
+  <td colspan="3" bgcolor="#efefef"><img src="&dtml-BASEPATH1;/p_/sp"
+ width="2" height="1" alt="" /></td>
+</dtml-if>
+</dtml-in>
+</tr>
+<tr>
+<dtml-in manage_options>
+<dtml-if "_['sequence-index']==a_">
+  <td colspan="3" bgcolor="#ffffff"><img src="&dtml-BASEPATH1;/p_/sp"
+ width="2" height="1" alt="" /></td>
+<dtml-else>
+  <td colspan="3" bgcolor="#c0c0c0"><img src="&dtml-BASEPATH1;/p_/sp"
+ width="2" height="1" alt="" /></td>
+</dtml-if>
+</dtml-in>
+</tr>
+</table>
+</dtml-if>
+
+<dtml-unless MANAGE_TABS_NO_BANNER>
+  <br />
+  <table width="100%" cellspacing="0" cellpadding="2" border="0">
+  <tr class="location-bar">
+    <td align="left" valign="top">
+    <div class="std-text">
+    <dtml-if icon>
+    <img src="&dtml-BASEPATH1;/&dtml-icon;" 
+         alt="&dtml-meta_type;" border="0" />
+    </dtml-if>
+    <strong>
+    <dtml-if meta_type>
+      <dtml-if class_manage_path>
+    <a href="&dtml-BASEPATH1;&dtml-class_manage_path;"
+       title="Manage the ZClass of this object">&dtml-meta_type;</a>
+      <dtml-else>
+    &dtml-meta_type;
+      </dtml-if>
+    <dtml-else>
+    Object
+    </dtml-if> 
+    at <dtml-var expr="tabs_path_default(REQUEST)">
+    </strong>
+    <dtml-if locked_in_version>
+      <dtml-if modified_in_version>
+        <img src="&dtml-BASEPATH1;/p_/locked"
+         alt="This item has been modified in this version" />
+      <dtml-else>
+        <img src="&dtml-BASEPATH1;/p_/lockedo"
+         alt="This item has been modified in another version" />
+              (<em><dtml-var locked_in_version html_quote></em>)
+      </dtml-if>
+    </dtml-if>
+    <dtml-if wl_isLocked>
+     <img src="&dtml-BASEPATH1;/p_/davlocked"
+      alt="This item has been locked by WebDAV"
+      title="This item has been locked by WebDAV" />
+    </dtml-if wl_isLocked>
+    </div>
+    </td>
+  <dtml-if "_.has_key('help_topic') and _.has_key('help_product')">
+  <td align="right" valign="top">
+  <div class="std-text">
+  <dtml-var "HelpSys.helpLink(help_product, help_topic)">
+  </div>
+  </td>
+  <dtml-else>
+  <dtml-if manage_options>
+  <dtml-with "_(option=manage_options[a_])">
+  <dtml-if "option.has_key('help')">
+  <td align="right" valign="top">
+  <div class="std-text">
+  <dtml-var "HelpSys.helpLink(option['help'][0], option['help'][1])">
+  </div>
+  </td>
+  </dtml-if>
+  </dtml-with>
+  </dtml-if>
+  </dtml-if>
+  </tr>
+  </table>
+
+<dtml-if Zope-Version>
+<div class="system-msg">
+<em>You are currently working in version <a href="&dtml-SERVER_URL;&dtml-Zope-Version;/manage_main"><dtml-var Zope-Version html_quote></a></em>
+</div>
+</dtml-if>
+</dtml-unless>
+
+<dtml-if manage_tabs_message>
+<div class="system-msg">
+<dtml-var manage_tabs_message> 
+(<dtml-var ZopeTime fmt="%Y-%m-%d %H:%M">)
+</div>
+</dtml-if>
+
+</dtml-with>

Added: trunk/ZopeProducts/exUserFolder/common/text_manage_tabs.dtml
===================================================================
--- trunk/ZopeProducts/exUserFolder/common/text_manage_tabs.dtml	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/common/text_manage_tabs.dtml	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,19 @@
+<dtml-with "_(manage_options=filtered_manage_options())">
+    <dtml-if manage_options>
+        <table cellpadding="0" cellspacing="0" width="100%" border="2">
+	<tr><td>
+        <table cellpadding="0" cellspacing="5" width="100%" border="0">
+        <tr>
+   	    <td valign="bottom" align="left" class="tab-small">
+            <dtml-in manage_options mapping>
+                    <b><a <dtml-if action>href="<dtml-var action>" 
+                       <dtml-else>href="<dtml-var URL1>" </dtml-if> 
+                       <dtml-if target> target="<dtml-var target>"</dtml-if>>
+                    [<dtml-babel src="'en'" literal="1"><dtml-var label></dtml-babel>]</a></b> 
+            </dtml-in>
+            </td>    
+        </tr>
+        </table>
+	</td></tr></table>
+    </dtml-if>
+</dtml-with>

Added: trunk/ZopeProducts/exUserFolder/doc/FAQ.txt
===================================================================
--- trunk/ZopeProducts/exUserFolder/doc/FAQ.txt	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/doc/FAQ.txt	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,119 @@
+Frequently Asked Questions
+
+1.  Why shouldn't I use Core Session Tracking + Login Manager?
+XUF serves a different set of users to the combination above. XUF
+aims to be a simple out of the box solution. Login Manager allows
+for very complex authorisation schemes that can query multiple user
+sources. We don't do that.
+
+2.  Why use XUF at all?
+In its simplest configuration, XUF provides the same functionality
+as the standard User Folder, but, is more secure. Passwords are
+stored encrypted, which is not the case for the standard User Folder.
+So even if you don't want to set properties on users, or any
+membership facilities, there is a benefit to running XUF.
+
+3.  Do I have to have all this other stuff?
+No. The only thing you need to enable is authentication. There is
+a null property source, and a null membership source. Everything
+other than authentication is optional.
+
+4.  Can I use it as a root folder?
+Some people have reported success in doing so. We don't recommend
+it for various reasons. The main one is that the internal Zope API
+can change without warning, which could break XUF and lock you out
+of your Zope. This can happen with any User Folder product. We
+recommend you look at VHM and other Site Access methods to allow
+you to store your main site in a sub-folder.
+
+5.  When will XUF support authentication against XYZ system?
+That depends. First the active developers need to have an interest
+in it, and more importantly they need to be able to test it. Writing
+your authentication method is very simple, so if you understand
+what you want to authenticate against, and know some python you
+could write one in an hour.  You can also use the usAuthSource to
+write one using PythonScripts, ExternalMethods, DTML, or any other
+callable method that Zope supports.
+
+6.  I wrote this cool authentication source can I get it into the main
+    distribution?
+Yes and No. If your authentication is Open Source, and has a
+compatible license with XUF, and doesn't require any external
+libraries, odds are it'll go into the main distribution. If it
+depends on external libraries, it's possible it can conditionally
+go into the main distribution. The nice thing about XUF is that
+Authentication, Property, and Membership sources are all packagable
+as independent products, so you can distribute it as a standalone
+product, and it'll work (without having to have the code drop into
+the XUF directory either).
+
+7.  Is XUF going to be part of the Core Zope?
+No idea. At the moment (0.10.5) XUF is probably not at a level that
+Zope Corporation would consider mature enough for core inclusion
+anyway.
+
+Actually the answer now, is probably not. At a minimum smbAuthSource,
+and radiusAuthSource would have to be stripped and distributed
+seperately.  Over and above that, I would have to assign Zope Corp
+co-ownership rights on the IP, which amongst other things gives
+them or anyone that buys them unlimited access to future derived
+works. I refuse to do this on principle, the liberal licensing of
+the product should be more than adequate for any (especially open
+source) endeavour.
+
+8.  What's with the Management Screens?
+It's a joke on the Zope World.
+
+9.  But they're really ugly I want to change them.
+That's fine, you do that, that's the point.
+
+10. Can I send you patches to put them back to standard Management
+    Screens?
+You can put patches into the tracker at Source Forge if you want to.
+
+11. HELP!!!! I tried to install XUF as my root folder, without
+    reading the FAQ, or really knowing what I'm doing, and now I'm
+    hosed!!!
+That's a shame.
+
+12. Will XUF work with ZEO?
+Unknown. However, it's almost certain that in its current form
+credential caching will not work across ZEO -- you will get a
+seperate User Cache for each Zope Client (which isn't really all
+that bad). However, it means that if you want to use Session Tracking,
+you need to lock users to one particular server. Most commercial
+Load Balancers do this for you anyhow. A persistent cache will form
+part of an upcoming release which will allow all of this to work
+transparently across ZEO farms.
+
+13. Shouldn't it be EUF?
+No, it should be XUF :-P
+
+14. How can I log in a user via a form submission?
+Yes, the key is sending the __ac_name and __ac_password (yes that's
+two underscores in front) as form variables to any object covered
+by the XUF.
+
+This form will take your users to the /index_html and log them in.
+You can place this anywhere in your site including /index_html.
+
+<form action="/index_html">
+Name: <input type="text" size="20" name="__ac_name"><br>
+Password: <input type="password" size="20" name="__ac_password">
+<input type="submit" value="Log in">
+</form>
+
+15. That Dialog box sure is ugly! How can I customize it so it looks
+    right in my site?
+Under the contents tab add an object called MessageDialog , it can
+be a dtml document, method, or even a page template.  Make it look
+how you want, it will acquire all objects from the folder the XUF
+is in so you can use the standard_html_header, call scripts, etc.
+
+16. Why can't I change the default crypto method?
+Because someone will change it, and all the existing passwords will cease
+to work. Then I'll get lots of hate-mail, or get slagged off on other mailing
+lists :-)
+
+17. Where is the Zen Master's Guide to exUserFolder?
+Everywhere and nowhere.

Added: trunk/ZopeProducts/exUserFolder/doc/README.API
===================================================================
--- trunk/ZopeProducts/exUserFolder/doc/README.API	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/doc/README.API	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,171 @@
+User Sources
+------------
+
+This is the list of functions your auth source should provide.
+
+createUser(self, username, password, roles)
+Create a User to authenticate against username, password and roles,
+should all be obvious.
+
+
+updateUser(self, username, password, roles)
+Update a user's roles and password.
+An empty password means do not change passwords, so at least at this time
+it's not possible to have passwordless accounts.
+
+
+cryptPassword(self, username, password)
+Encrypt a password
+If no 'crypt' method is supplied return the Password -- 
+i.e. plaintext password.
+
+
+deleteUsers(self, userids)
+Delete a set of users, userids is a list of usernames to delete.
+
+
+listUserNames(self)
+returns a list of usernames.
+
+listOneUser(self,username)
+Return one user matching the username
+Should be a dictionary; 
+	{'username':username, 'password':cryptedPassword, 'roles':list_of_roles}
+Once again, you can provide more information than this if you're customising
+the forms.
+
+listUsers(self)
+Return the list of users in the same format as listOneUser
+
+
+remoteAuthMethod(self, username, password)
+You can define this to go off and do the authentication instead of
+using the basic one inside the User Object. Useful for IMAP/LDAP auth
+type methods where the authentication is handled elsewhere and we just want
+to know success or failure. If you don't want to do this you should have;
+
+remoteAuthMethod=None
+
+in your AuthSource (to explicitly tell the exUserFolder that you don't).
+
+------------------------------------------------------------------------
+This is a skeleton class;
+
+manage_addfooAuthSourceForm=HTMLFile('manage_addfooAuthSourceForm', globals())
+manage_editfooAuthSourceForm=HTMLFile('manage_editfooAuthSourceForm', globals())
+
+class fooAuthSource(Folder):
+
+	meta_type='Authentication Source'
+	title='User Supplied Authentication'
+	icon ='misc_/exUserFolder/exUserFolderPlugin.gif'
+	manage_editForm=manage_editfooAuthSourceForm
+		
+	def __init__(self):
+		self.id='fooAuthSource'
+
+	# Create a User to authenticate against
+	# username, password and roles
+	def createUser(self, username, password, roles):
+		""" Add A Username """
+		pass
+
+	# Update a user's roles and password
+	# An empty password means do not change passwords...
+	def updateUser(self, username, password, roles):
+		pass
+
+	# Encrypt a password
+	# If no 'crypt' method is supplied return the
+	# Password -- i.e. plaintext password
+	def cryptPassword(self, username, password):
+		pass
+
+	# Delete a set of users
+	def deleteUsers(self, userids):
+		pass
+
+	# Return a list of usernames
+	def listUserNames(self):
+		pass
+		
+	# Return a list of user dictionaries with
+	# {'username':username} can be extended to pass back other
+	# information, but, we don't do that just now
+	def listUsers(self):
+		pass
+
+	# Return one user matching the username
+	# Should be a dictionary;
+	# {'username':username, 'password':cryptedPassword, 'roles':list_of_roles}
+	def listOneUser(self,username):
+		pass
+
+	#
+	# Return a list of users, dictionary format as for listOneUser
+	#
+	def getUsers(self):
+		pass
+
+	#
+	# You can define this to go off and do the authentication instead of
+	# using the basic one inside the User Object
+	#
+	remoteAuthMethod=None
+
+##	def remoteAuthMethod(self, username, password):
+##		pass
+
+	def postInitialisation(self, REQUEST):
+		pass
+
+fooAuthReg=PluginRegister('fooAuthSource', 'User Supplied Authentication Source',
+						 fooAuthSource, manage_addfooAuthSourceForm,
+						 manage_addfooAuthSource,
+						 manage_editfooAuthSourceForm)
+exUserFolder.authSources['fooAuthSource']=fooAuthReg
+
+------------------------------------------------------------------------
+
+Property Sources
+----------------
+
+Property Sources have only a few things they need to provide;
+
+hasProperty(self, key)
+Returns true if the current user has that property
+
+setProperty(self, key, value)
+Sets a property for the current user
+
+
+setUserProperty(self, key, username, value)
+Sets a property for the given user.
+
+
+getProperty(self, key, default=None)
+Returns the requested property or the default for the current user.
+
+
+getUserProperty(self, key, username, default=None)
+Returns the requested property or the default for the named user.
+
+
+listProperties(self)
+Returns a list of properties (just the properties not their values).
+
+
+listUserProperties(self, username)
+Returns a list of properties for the named user.
+
+createUser(self, username, REQUEST)
+Creates a new user, and adds in the properties in the REQUEST.
+New properties are preceded with "user_KEYNAME", so strip user_ to
+set the property.
+
+deleteUsers(self, userids)
+Delete the list of users (and their properties) contained within userids.
+
+updateUser(self, username, REQUEST)
+Change the list of properties for a user, the variables are formatted as for
+createUser.

Added: trunk/ZopeProducts/exUserFolder/doc/README.LDAPAuthSource
===================================================================
--- trunk/ZopeProducts/exUserFolder/doc/README.LDAPAuthSource	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/doc/README.LDAPAuthSource	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,20 @@
+This is a reimplementation of the auth_ldap auth source for apache, written by
+Dave Carrigan and others.
+
+You can find the original auth_ldap Apache code at
+http://www.rudedog.org/auth_ldap/
+
+This auth source is covered by the Apache license;
+
+Copyright (C) 1998, 1999 Enbridge Pipelines Inc. 
+Copyright (C) 1999-2001 Dave Carrigan
+Copyright (C) 2003 The Internet (Aust) Pty Ltd.
+All rights reserved.
+
+This module is free software; you can redistribute it and/or modify
+it under the same terms as Apache itself. This module is
+distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. The copyright holder of this
+module can not be held liable for any general, special, incidental
+or consequential damages arising out of the use of the module.

Added: trunk/ZopeProducts/exUserFolder/doc/README.Membership
===================================================================
--- trunk/ZopeProducts/exUserFolder/doc/README.Membership	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/doc/README.Membership	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,144 @@
+There are now membership hooks defined, and a basic membership
+source defined for exUserFolder. This is a first stab at this, so
+please be careful :-)
+
+Membership adds a level of complexity to everything, and basically is a
+controlled way of breaching your security.
+
+You will need to prepare a few things before trying to add an exUserFolder
+with Membership support;
+
+a) You will need a MailHost object available
+b) You will need to define some methods for Membership to use
+	  i) a Login Page
+	 ii) a Change Password Page
+	iii) a Signup Page
+	 iv) a Forgot My Password Page (optional)
+	  v) a change Properties Page (optional)
+   These should live at the same level as your acl_user (i.e. not inside).
+   These should be fairly simple, I've included some examples below.
+   These should just wrap the ones below acl_users. There will be methods
+   you can edit to get all your fields and layout done.
+c) If you want the results pages from Signup, Change Password, etc to fit 
+   your site, you'll need to add a MessageDialog document in the contents 
+   of the XUF. See FAQ 15 for more
+
+
+When you see the creation form, obviously some of the options are
+mutually exclusive.
+
+e.g. You can't choose system defined passwords, and have the system
+     email a hint, if they forgot their password. So try to pick sane
+     combinations of options.
+
+
+
+
+
+If you choose to have Home Directories, basicMemberSource will create
+the path you provide, so you don't need to do that in advance.
+
+If you want to have skeleton files copied to their homedir you'll need
+to have that directory (it can be empty) setup and ready to go, before
+the first user signs up.
+
+WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING 
+WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING 
+WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING 
+If you get basicMembershipSource to create Home Directories for your 
+users, it will create a 'Folder' and it will give the user management
+permissions on that Folder. This means that they will be able to add
+any object you can, just at a level below this. You should create/have
+a proper HomeFolder object that is restricted in what is available
+for adding, and change makeHomeDir() in basicMemberSource.py to create
+one of those.
+
+I will look at creating a restricted HomeDirectory Object in a later
+release, and allow you to add and remove meta_types from it.
+WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING 
+WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING 
+WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING 
+
+========================================================================
+
+
+
+------------------------------------------------------------------------
+LoginForm
+------------------------------------------------------------------------
+<dtml-with acl_users>
+<dtml-var docLogin>
+</dtml-with>
+------------------------------------------------------------------------
+
+
+
+------------------------------------------------------------------------
+ChangePasswordForm
+------------------------------------------------------------------------
+<dtml-var standard_html_header>
+<dtml-with acl_users>
+<dtml-with currentMembershipSource>
+<dtml-var PasswordForm>
+</dtml-with>
+</dtml-with>
+<dtml-var standard_html_footer>
+------------------------------------------------------------------------
+
+
+
+------------------------------------------------------------------------
+SignupForm
+------------------------------------------------------------------------
+<dtml-var standard_html_header>
+<dtml-with acl_users>
+<dtml-with currentMembershipSource>
+<dtml-var SignupForm>
+</dtml-with>
+</dtml-with>
+<dtml-var standard_html_footer>
+
+
+------------------------------------------------------------------------
+ForgotMyPassword
+------------------------------------------------------------------------
+<dtml-var standard_html_header>
+<form action="acl_users/manage_forgotPassword" method="POST">
+Username: <input type="text" name="username">
+</form>
+<dtml-var standard_html_footer>
+
+
+
+------------------------------------------------------------------------
+ChangePropertiesForm
+------------------------------------------------------------------------
+<dtml-var standard_html_header>
+
+<h2> Changing Properties for <dtml-var AUTHENTICATED_USER></h2>
+<dtml-with acl_users>
+
+<form action="acl_users/manage_changeProps" method="POST">
+
+
+                <hr>
+                <h2>Properties</h2>
+                <table border>
+                <tr>
+                        <th>Property Name</th><th>Value</th>
+                </tr>
+        <dtml-in "AUTHENTICATED_USER.listProperties()"> 
+        <tr>                    <td><dtml-var sequence-item></td>
+                <td><input name="user_<dtml-var sequence-item>"
+value="<dtml-var
+"AUTHENTICATED_USER.getProperty(_['sequence-item'])">"></td>
+                </tr>
+                </dtml-in>
+                </table>
+
+        <input type="submit" value=" Change ">
+        </form>
+
+</dtml-with>
+<dtml-var standard_html_footer>
+------------------------------------------------------------------------

Added: trunk/ZopeProducts/exUserFolder/doc/README.httpsAuthSource
===================================================================
--- trunk/ZopeProducts/exUserFolder/doc/README.httpsAuthSource	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/doc/README.httpsAuthSource	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,28 @@
+This plugin implements authentication from an https service.  
+
+Upon installation, the mangament forms allow you to configure:
+* the url to the service, 
+* the parameter that will contain the username 
+* the parameter that will contain the password
+* The expected authorization response regex (returned from the authorization service).
+* The default role that authorized users will be assinged upon their first login
+
+The https auth source posts a request over https to the named service with the username and 
+passowrd passed according to the parameters defined in the configuration.  It will attempt 
+to match the authorization pattern specified, and if the pattern is found, the user will be
+authenticated.
+
+Once a user has logged in, they will appear in xuf user list, and their roles can be updated.
+
+This auth source has been developed using the zodbBTreeProps plugin, and stores the user's
+roles in this property tool.
+
+A typical use case for this authorization service might be to authenticate against
+a legacy user directory for which no xuf auth plugin currently exists.  Hopefully, the 
+development of a the auth service on the other end will be trivial, or better yet,
+already exist.
+
+IMPORTANT NOTE: In order to use this plugin you must compile your python to include 
+ssl support.  The python that ships with zope 2.X does not have this enabled by default.
+
+Thanks to akm, bcsaller, runyaga for all their help.

Added: trunk/ZopeProducts/exUserFolder/doc/README.pgAuthSource-Alternate
===================================================================
--- trunk/ZopeProducts/exUserFolder/doc/README.pgAuthSource-Alternate	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/doc/README.pgAuthSource-Alternate	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,20 @@
+This alternate pgAuthSource was developed to allow Zope and jakarta-tomcat to 
+share common PostGreSQL auth tables. It's really just a mod of the original 
+pgAuthSource, with changes to the original kept to a minimum. This should help
+when it comes to cross porting improvements / maintenence changes between the 
+two versions.
+
+The only thing that's new is the table schema. This auth source uses:
+A user table 
+	Username, password
+
+A role table:
+	rolename
+
+and a associative userrole table for relating the two:
+	username, rolename
+
+ps. Use the Source, Luke!
+If you dig a little you will find a couple of different ways of crypting 
+passwords commented out (plain and MD5).
+

Added: trunk/ZopeProducts/exUserFolder/doc/README.radiusAuthSource
===================================================================
--- trunk/ZopeProducts/exUserFolder/doc/README.radiusAuthSource	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/doc/README.radiusAuthSource	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,12 @@
+I have converted ZRadius to work with exUserFolder as an AuthSource
+it should function at least as well as it functioned with GUF d8)
+-- akm
+
+Radius
+
+    This product implements simple Radius authentication. If you need
+    to authenticate Zope users from a Radius server, this product will
+    plug into the GenericUserFolder product.
+
+    © Copywrite 1999 Stuart Bishop <zen at cs.rmit.edu.au>
+

Added: trunk/ZopeProducts/exUserFolder/doc/README.smbAuthSource
===================================================================
--- trunk/ZopeProducts/exUserFolder/doc/README.smbAuthSource	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/doc/README.smbAuthSource	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,41 @@
+ 
+SMB Authentication Source for exUserFolder
+
+(C) Copyright 2000,2001 The Internet (Aust) Pty Ltd
+ACN: 082 081 472	ABN: 83 082 081 472
+All Rights Reserved
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGE.
+
+This smbAuthSource uses pySMB by Michael Teo whose copyright is as follows.
+
+Copyright (C) 2001 Michael Teo <michaelteo at bigfoot.com>
+
+This software is provided 'as-is', without any express or implied warranty. 
+In no event will the author be held liable for any damages arising from the 
+use of this software.
+
+Permission is granted to anyone to use this software for any purpose, 
+including commercial applications, and to alter it and redistribute it 
+freely, subject to the following restrictions:
+
+1. The origin of this software must not be misrepresented; you must not 
+   claim that you wrote the original software. If you use this software 
+   in a product, an acknowledgment in the product documentation would be
+   appreciated but is not required.
+
+2. Altered source versions must be plainly marked as such, and must not be 
+   misrepresented as being the original software.
+
+3. This notice cannot be removed or altered from any source distribution.
+

Added: trunk/ZopeProducts/exUserFolder/doc/README.txt
===================================================================
--- trunk/ZopeProducts/exUserFolder/doc/README.txt	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/doc/README.txt	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,127 @@
+A lot of this stuff is now covered in the Unenlightened Zopistas Guide to
+exUserFolder, so this document is slightly redundant (but not completely).
+It's also shockingly out of date...
+
+A note on the version numbering... there's none of that odd/even 
+numbering nonsense you find in Lin*x land. The numbers are based on
+
+Major.Minor.Micro
+
+A bump in Major means a milestone or set of milestones has been reached.
+A bump in Minor means some piece of functionality has been added, or
+a major bug was fixed.
+A bump in Micro usually means bug fixes.
+
+These numbers go from 0-99, and are not necessarily continuous or
+monotonically increasing (but they are increasing).
+
+What you consider major and what I consider major are probably two
+different things.
+
+Release candidates before a Major bump start at 99.. so;
+
+0.99.0  is the first Release Candidate prior to 1.0.0
+0.99.1  is the next Release Candidate
+1.0.0 is the 'final' release.
+
+It's possible that there will be no changes between final release candidate 
+and release.
+
+Sometimes due to the nature of the changes a release will be marked 
+development. This usually means some core functionality was changed.
+
+Extensible User Folder
+
+Extensible User Folder is a user folder that requires the authentication 
+of users to be removed from the storage of properties for users.
+
+Writing new authentication or property sources is (almost) a trivial operation
+and require no authentication knowledge to write, most simply return lists
+of attributes, or dictionaries. You don't need to incorporate them into
+the base exUserFolder code, they can be written as normal Zope Products,
+(i.e. you can distribute new sources independently of exUserFolder).
+
+There are three authentication sources provided OOTB;
+
+o pgAuthSource -- Postgresql Authentication Source
+Actually this is pretty generic, and could be used for most SQL databases,
+the schema isn't all that advanced either.
+
+This source allows you to specify the table, and the name of each of the
+columns (username, password, roles), so you can use an existing database.
+
+
+All ZSQL Methods are available inside the pgAuthSource folder for editing.
+You need to have a DB Connection already in place for use.
+
+o usAuthSource -- User Supplied Authentication
+This is similar to Generic User Folder, or Generic User Sources for
+Login Manager. You provide a set of methods;
+
+createUser    -- if you want to create users.
+cryptPassword -- if you want to encrypt passwords.
+listUsers     -- return a list of userIds.
+listOneUser   -- return a dictionary containing the username, password, and 
+                 a list of roles
+getUsers      -- return a list of users ala listOneUser but lots of them.
+
+that's it. listOneUser is mandatory.
+
+There is an example of ExternalMethods you could use to create an 'sql'
+user source in the Extensions directory.
+
+o etcAuthSource -- File Based Authentication
+This is etcUserFolder reworked to be a plugin for this. Since I used
+etcUserFolder as a base for this product, I might as well add the functionality
+in.
+
+Each of the plugins has a 'manage_addForm' that is called when the User Folder
+is added, so that parameters can be garnered from the user (pg*Source e.g.
+get the dbconnection)
+
+etcAuthSource doesn't allow roles to be set, although it does ask for
+a default role to be assigned to a user (which it dutifully does).
+
+
+There are two property sources provided:
+
+o pgPropertySource -- Postgresql Property Source
+A very naive sql implementation for properties, works fine, I wouldn't want
+to load it up too high though.
+
+o zodbProperySource -- ZODB Property Source
+This is a very simple property keeper, and is more available as an example
+of what functionality needs to be provided.
+
+There is a postUserCreate method which you can replace with anything really,
+if you want to do something once a user is created (send an email to someone,
+page someone...)
+
+You can mix-n-match authentication methods and property methods.
+
+You can have cookie or standard (Basic) authentication.
+
+docLogin and docLogout methods are present in the ZODB for editing, because
+you will hate the way the default login and logout pages look d8)
+
+The various plugins need some more configurable options atm, but, it 
+shouldn't be that much of a drama to add them in soon.
+
+Arbitrary properties can be set on a user (at least if the PropertySource
+is written correctly they can).
+
+<dtml-call "AUTHENTICATED_USER.setProperty(key, value)">
+<dtml-if "AUTHENTICATED_USER.hasProperty(key)">
+<dtml-var "AUTHENTICATED_USER.getProperty(key, defaultValue)">
+<dtml-var "AUTHENTICATED_USER['key']">
+
+Will all work (assuming the user is logged in).
+
+When creating a new user any fields with user_ will be set as properties.
+So 'user_email' field will create an 'email' property. You just have to
+provide the input form, and exUserFolder will do the rest.
+
+This has only been lightly tested, but, it seems to work just fine.
+
+
+

Added: trunk/ZopeProducts/exUserFolder/doc/README.zodbAuthSource
===================================================================
--- trunk/ZopeProducts/exUserFolder/doc/README.zodbAuthSource	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/doc/README.zodbAuthSource	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,13 @@
+ZODB Authentication Source for exUserFolder
+
+This is an auth source that works pretty much like the standard user folder provided by zope.
+It stores the usernames, roles and passwords on a ZODB persistent dictionary. 
+
+It doesn't require any configuration at all, just select it as your auth source and you're
+ready to add user accounts.
+
+
+Author: Alex Verstraeten (aka: zxc)
+Email: alex at quad.com.ar
+
+

Added: trunk/ZopeProducts/exUserFolder/doc/UnenlightenedZopistasGuide.txt
===================================================================
--- trunk/ZopeProducts/exUserFolder/doc/UnenlightenedZopistasGuide.txt	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/doc/UnenlightenedZopistasGuide.txt	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,671 @@
+The Unenlightened Zopistas Guide to exUserFolder.
+(C) 2001-2003 Andrew Milton <akm at theinternet.com.au>
+
+0. INTRODUCTION.
+
+exUserFolder is an extensible authentication product for the Zope
+Application Server. It allows a user to choose from a number of
+methods of authenticating their users, and allows the setting and
+fetching of arbitrary properties on a User object.
+
+Authentication methods, and Property methods do not have to use the
+same backing store, so it is possible to use legacy user sources, and
+still have configuration information stored about a user.
+
+exUserFolder supports HTTP Basic Authentication, and the so called
+Cookie Authentication schemes popular with most webservers.
+
+0.1 Audience.
+
+Everybody, and Nobody. If we build our product correctly, we shouldn't
+need user documentation, that's why Nobody. For some reason, normal
+sane people seem to lose all of their common sense when sitting in
+front of a computer. To the point where plain messages e.g. "Please
+Insert a Floppy", evoke a response of "What does that mean?" So that's
+why this document is for Everybody.
+
+This is not a guide for writing your own authentication, property, or
+membership sources. You need the Zen Masters Guide to exUserFolder
+document for that.
+
+
+1. GETTING STARTED
+
+exUserFolder requires Python 2.1 or above (may work with older
+pythons, but, this is not supported). And has been tested on Zope
+2.3.0 and above (including 2.4.3).
+
+exUserFolder comes as a source tarball, and it does not rely on any
+outside dependencies to be work. Some items may require additional
+products to be installed in your Zope tree, but, simply will not
+appear if these are not available. Some items may also require
+external products to add functionality to them, but, at no time should
+the Product not install because of these dependencies.
+
+
+1.1 Installing exUserFolder.
+
+Unpack exUserFolder in your Products directory. This can be achieved by
+executing the following on most UNIX systems.
+
+gzip -d -c exUserFolder-Ma_Mi_u.tgz | tar xvf -
+
+where exUserFolder-Ma_Mi_u.tgz is the version of exUserFolder you have
+downloaded.
+
+On systems that have the GNU tar this can be shortened to;
+
+tar zxvf exUserFolder-Ma_Mi_u.tgz
+
+You should restart Zope after unpacking. Installing the product will not
+affect your Zope installation in anyway.
+
+If you go to Folder inside your Zope Management Interface, you should see
+exUserFolder as a dropdown item near the bottom somewhere.
+
+Congratulations, it's installed.
+
+
+2. AUTHENTICATION SOURCES AND YOU.
+
+The only mandatory component of an exUserFolder installation, is
+choosing an Authentication Source. There are six Authentication
+Sources to choose from in the default install. There are other add-on
+Sources available from other parties.
+
+Each Authentication Source is different, and assumes at least some
+knowledge of that type of authentication scheme.
+
+Most if not all sources store the password encrypted in some
+manner. This means that discovering other people's passwords is going
+to be more difficult, than with the standard user folder that comes
+with Zope.
+
+By default crypt or fcrypt are used, which is are DES encryption
+methods.  While this is not the strongest, fastest, choose another
+superlative, of encryption techniques, it is certainly adequate for
+protecting webpages.
+
+In a later release exUserFolder will allow you to choose what method
+is used for password hashing.
+
+Some Authentication Sources can list the users that are available,
+some cannot (or will not). Some allow you to add users, and others do
+not.  What features are availble depend on the individual
+Authentication Source.
+
+
+2.1 ZODB Authentication Source
+
+The ZODB Authentication Source operates just like a normal User
+Folder. It stores its authentication items in the ZODB as the name
+suggests. This is the simplest folder to setup, requiring no
+parameters.
+
+Choosing ZODB Authentication Source is recommended for testing your install.
+
+
+2.2 File Based Authentication.
+
+File Based Authentication allows you to have a fixed set of users in a
+file with their encrypted passwords. The prerequisites for this are
+somewhat convoluted.
+
+In the root of your Zope Installation, on the actual file system (not
+in the ZODB), create a directory called exUsers. Make sure that Zope
+has access to that directory.
+
+This is the directory where you will create your files.
+
+This is a read only Authentication Source. You will not be able to
+create users, or modify their passwords. You can change their roles if you
+choose a Property Source.
+
+There are two parameters asked for;
+
+2.2.1 Password File
+
+This is the name of the file that contains your users and passwords.
+It should be of the format;
+
+username:cryptedPassword
+user2:otherCryptedPasswd
+
+I can contain other fields after the password also delimited by : but these
+will not be ussed.
+
+This file should exist inside the exUsers directory.
+
+2.2.2 Default Role
+
+This is the role that all users should be given when the log in. Because this
+is a Read Only authentication source, you may not be able to add Roles at a
+later date.
+
+
+2.3 Postgresql Authentication Source
+
+Postgresql Authentication source is an RDBMS backed user store. You
+can add, change, and list users. It requires a Postgresql Database
+Connection to be created before creating the User Folder.
+
+You should be familiar with databases, and with your schema before
+using this Authentication Source. If you don't already have a table
+structure in place, a default schema is provided called 'pgScheme.sql'
+in the exUserFolder distribution.
+
+The required schema is very simple. You need to store usernames,
+passwords, and roles. If your existing schema doesn't support a roles
+column you will have to add one.
+
+The configuration scheme looks daunting, but, it is setup to use the
+defaults for 'pgScheme.sql' so if you're using this you can safely
+continue.
+
+We will run through the items.
+
+2.3.1 Database Connection
+
+If you have any database connections, they will be listed in the drop
+down box. Choose the one that represents your connection to your users
+table.
+
+2.3.2 Table Name
+
+This is the name of the table containing your users. If you have a
+different table to the default, you should change it here.
+
+2.3.3 Username Column
+
+This is the name of the column inside your table that contains the
+usernames or logins of your users. This should contain exactly what
+the user needs to type in as their username.
+
+
+2.3.4 Password Column
+
+This is the name of the column inside your table that contains the
+encrypted passwords of your users.
+
+
+2.3.5 Roles Column
+
+This is where the roles are stored. These are used to provide access
+to items in Zope.
+
+
+2.4 User Supplied Authentication Source
+
+This allows you to create your methods in DTML, PythonScripts,
+External Methods, or any other callable Zope item for listing,
+authenticating, adding and changing your users.
+
+It is beyond the scope of this guide to describe how to do this, but,
+the API is quite well defined inside the source, and also in the
+README.API document.
+
+This Authentication Source has no configuration parameters.
+
+
+2.5 RADIUS Authentication Source
+
+This allows you to authenticate your users against a RADIUS server. If
+you don't know what this means, then this User Source is not for you
+:-)
+
+You will require a RADIUS server to be operating, and for the server
+that Zope is running on to have access to it. You will also need to
+know the secret key to access the RADIUS server.
+
+2.5.1 Host
+
+This is the host your RADIUS server is running on.
+
+2.5.2 Port
+
+This is the port your RADIUS server is running on. Older installs may
+require this to be 1645. The new 'blessed' port by IANA is 1812, and
+this is now the default port.
+
+2.5.3 Secret
+
+Every remote host has a secret key it has to share with the server in
+order to gain access to the authentication server. You need to know
+this.
+
+2.5.4 Retries
+
+Because this is a networked authentication service, errors can
+occur. This sets the number of times it will try to authenticate
+before giving up.
+
+2.5.5 Timeout
+
+This is how long the RADIUS authenticator will wait for a
+response. Because RADIUS operates over UDP, which is a connectionless
+protocol, answers may never come back, or never reach their
+destination in the first place.
+
+The default is 5 seconds which is actually quite a long time.
+
+
+2.6 SMB Authentication Source
+
+This source allows you to authenticate your users in a Microsoft
+Environment, using the SMB protocols. This is not the same as
+authenticating via Directory Services.
+
+If your SMB server requires passwords to be encrypted in transit, you'll 
+need to install mxCrypto.
+
+2.6.1 Host
+
+This is the host that your Authentication service is on, this is
+normally an NT or Win2K server, but, it can also be a UNIX box running
+Samba. This should be the NetBIOS name of the server.
+
+2.6.2 Domain
+
+This is the NT/Windows DOMAIN that the user is to authenticate
+against.
+
+2.6.3 WINS Server IP Address (optional)
+
+If provided, this should be the IP address of the WINS server to be
+queried to locate your auth host (see 2.5.1 above).
+
+If you leave this field empty, the location of the authentication host
+will be queried by broadcast, which works just fine if the Zope
+machine is on the same subnet as your auth host but not if the auth
+host is across a subnet link or if it's in the same machine as Zope
+(don't ask. Apparently, some braindmamaged creature at M$ decided that
+a machine shouldn't answer to its own broadcasts no matter what)
+
+Fill in this field if you are getting "NetBIOSTimeout" errors but you
+are sure that your auth host was specified correctly, or if Windows
+machines in your subnet also use a WINS server.
+
+
+2.7 LDAP Authentication
+
+This source allows you to authenticate your users against an LDAP
+server.  This code is based on the auth_ldap module for Apache. The
+documentation for these parameters is unashamedly lifted directly from
+the documentation of the Apache directives for auth_ldap.
+
+See: http://www.rudedog.org/auth_ldap/
+
+You must choose a property source when using LDAP Authentication, all
+of the properties associated with the LDAP user entry are stored as
+properties when they authenticate. Items with multiple entries are
+stored as a list of items.
+
+You will need the pyLDAP module installed to use this authsource.
+If you don't have it installed, you will not see an LDAP Auth Source available
+for use.
+
+2.7.1 URL
+
+An RFC 2255 URL which specifies the LDAP search parameters to use. The syntax
+of the URL is
+ldap://host:port/basedn?attribute?scope?filter
+
+ldap      For regular ldap, use the string ldap. For secure LDAP, use ldaps
+          instead. Secure LDAP is only available if auth_ldap was compiled with
+          SSL support.                                                         
+host:port The name/port of the ldap server (defaults to localhost:389 for ldap,
+          and localhost:636 for ldaps). 
+                        
+          Once a connection has been made to a server, that connection remains
+          active for the life of the Zope process, or until the LDAP server
+          goes down.
+                                                                          
+          If the LDAP server goes down and breaks an existing connection,
+          the Auth Source will attempt to re-connect
+                       
+basedn    The DN of the branch of the directory where all searches should start
+          from. At the very least, this must be the top of your directory tree,
+          but could also specify a subtree in the directory.
+
+attribute The attribute to search for. Although RFC 2255 allows a
+          comma-separated list of attributes, only the first attribute will be
+          used, no matter how many are provided. If no attributes are provided,
+          the default is to use uid. It's a good idea to choose an attribute
+          that will be unique across all entries in the subtree you will be
+          using.                                                              
+
+scope     The scope of the search. Can be either one or sub. Note that a scope
+          of base is also supported by RFC 2255, but is not supported by this
+          module. If the scope is not provided, or if base scope is specified,
+          the default is to use a scope of sub.
+
+filter    A valid LDAP search filter. If not provided, defaults to (objectClass
+          =*), which will search for all objects in the tree.
+
+When doing searches, the attribute, filter and username passed by the HTTP
+client are combined to create a search filter that looks like (&(filter)
+(attribute=username)).
+
+For example, consider an URL of ldap://ldap.xuf.com/o=XUF?cn?sub?(posixid
+=*). When a client attempts to connect using a username of The Jester, the
+resulting search filter will be (&(posixid=*)(cn=The Jester)).
+
+
+2.7.2 Bind DN
+
+An optional Distinguished Name user to bind to the server when searching
+for entries. If not provided an Anonymous bind will be used.
+
+2.7.3 Bind Password.
+A bind password to use in conjunction with the bind DN. Note that the bind
+password is probably sensitive data, and should be properly protected. You
+should only use the Bind DN and Bind Password if you absolutely
+need them to search the directory.
+
+2.7.4 Cert DB Path
+
+Specifies in which directory LDAP Auth Source should look for the certificate
+authorities database. There should be a file named cert7.db in that directory.
+
+2.7.5 Compare DN On Server
+
+When set, LDAP Auth Source will use the LDAP server to compare the
+DNs. This is the only foolproof way to compare DNs. LDAP Auth Source
+will search the directory for the DN specified with the require dn
+directive, then, retrieve the DN and compare it with the DN retrieved
+from the user entry. If this directive is not set, LDAP Auth Source
+simply does a string comparison. It is possible to get false negatives
+with this approach, but it is much faster. Note the LDAP Auth Source cache
+can speed up DN comparison in most situations.
+
+2.7.6 Dereference Aliases
+
+This directive specifies when LDAP Auth Source will de-reference
+aliases during LDAP operations. The default is always.
+
+2.7.7 Group Attribute is DN
+
+When set, this directive says to use the distinguished name of the
+client username when checking for group membership. Otherwise, the
+username will be used. For example, assume that the client sent the
+username tjester, which corresponds to the LDAP DN cn=The Jester,
+o=XUF. If this directive is set, LDAP Auth Source will check if the
+group has cn=The Jester, o=XUF as a member. If this directive is not
+set, then LDAP Auth Source will check if the group has tjester as a
+member.
+
+2.7.8 Compare Cache Size
+
+This specifies the size of the cache used to cache LDAP compare
+operations. The default is 1024 entries. Setting it to 0 disables
+operation caching.
+
+2.7.9 Compare Cache TTL
+
+Specifies the time (in seconds) that entries in the operation cache
+remain valid. The default is 600 seconds.
+
+2.7.10 Start TLS
+
+If this is set to Yes, LDAP Auth Source will start a secure TLS
+session after connecting to the LDAP server. This requires your LDAP
+server to support TLS.
+
+2.7.11 Require User (one per line)
+
+The require user directive specifies what usernames can access the
+resource.  Once LDAP Auth Source has retrieved a unique DN from the
+directory, it does an LDAP compare operation using the username
+specified in the require user to see if that username is part of the
+just-fetched LDAP entry. Multiple users can be granted access by
+putting multiple usernames in the box, separated with newlines. For
+example, with a AuthLDAPURL of ldap://ldap/o=XUF?cn (i.e., cn is used
+for searches), the following require entries could be used to restrict
+access: The Jester Fred User Joe Manager
+
+Because of the way that LDAP Auth Source handles this directive, The
+Jester could sign on as The Jester, Zen Jester or any other cn that he
+has in his LDAP entry. Only the single require user line is needed to
+support all values of the attribute in the user's entry.
+
+If the uid attribute was used instead of the cn attribute in the URL
+above, the above three lines could be;
+
+tj
+fred_u
+jmanager
+
+2.7.12 Require Group (one per line)
+
+This directive specifies an LDAP group whose members are allowed
+access. It takes the distinguished name of the LDAP group. For
+example, assume that the following entry existed in the LDAP
+directory:
+
+dn: cn=Administrators, o=XUF
+objectClass: groupOfUniqueNames
+uniqueMember: cn=The Jester, o=XUF
+uniqueMember: cn=Fred User, o=XUF
+
+The following directive would grant access to both Fred and Jester:
+
+require group cn=Administrators, o=XUF
+
+Behavior of this directive is modified by the Group Attribute and 
+Group Attribute Is DN options.
+
+2.7.13 Require DN
+
+The require dn option allows the administrator to grant access based
+on distinguished names. It specifies a DN that must match for access
+to be granted. If the distinguished name that was retrieved from the
+directory server matches the distinguished name in the require dn,
+then authorization is granted.
+
+The following directive would grant access to a specific DN:
+require dn cn=The Jester, o=XUF
+
+Behavior of this directive is modified by the Compare DN On Server option.
+
+2.7.14 Default Manager
+
+This allows you to specify the username of the Manager for this area.
+The manager will still need to meet auth requirements above, but, if
+they do they will get the 'Manager' role added to their list of roles.
+
+2.7.15 Default Role
+
+This is a role to be assigned to users when they auth correctly. This
+is to differentiate them from merely being 'authenticated'.
+
+2.7.16 Examples
+
+  * Grant access to anyone who exists in the LDAP directory, using their UID
+    for searches.
+    URL ldap://ldap1.zope.com:389/ou=People, o=XUF?uid?sub?(objectClass=*)
+
+  * The next example is similar to the previous one, but is uses the common
+    name instead of the UID. Note that this could be problematical if multiple
+    people in the directory share the same cn, because a search on cn must
+    return exactly one entry. That's why this approach is not recommended: it's
+    a better idea to choose an attribute that is guaranteed unique in your
+    directory, such as uid.
+    URL ldap://ldap.zope.com/ou=People, o=XUF?cn
+
+  * Grant access to anybody in the Administrators group. The users must
+    authenticate using their UID.
+    URL ldap://ldap.zope.com/o=XUF?uid
+    require group: 
+    cn=Administrators, o=XUF
+
+  * The next example assumes that everyone at XUF who carries an
+    alphanumeric pager will have an LDAP attribute of qpagePagerID. The example
+    will grant access only to people (authenticated via their UID) who have
+    alphanumeric pagers:
+    URL: ldap://ldap.zope.com/o=XUF?uid??(qpagePagerID=*)
+
+  * The next example demonstrates the power of using filters to accomplish
+    complicated administrative requirements. Without filters, it would have
+    been necessary to create a new LDAP group and ensure that the group's
+    members remain synchronized with the pager users. This becomes trivial with
+    filters. The goal is to grant access to anyone who has a filter, plus grant
+    access to Joe Manager, who doesn't have a pager, but does need to access
+    the same resource:
+    URL ldap://ldap.zope.com/o=XUF?uid??(|(qpagePagerID=*)(uid=jmanager))
+
+    This last may look confusing at first, so it helps to evaluate what the
+    search filter will look like based on who connects, as shown below.
+    If Fred User connects as fuser, the filter would look like
+   
+    (&(|(qpagePagerID=*)(uid=jmanager))(uid=fuser))
+   
+    The above search will only succeed if fuser has a pager. When Joe Manager
+    connects as jmanager, the filter looks like
+   
+    (&(|(qpagePagerID=*)(uid=jmanager))(uid=jmanager))
+
+    The above search will succeed whether jmanager has a pager or not.
+
+
+2.8 General Items.
+
+You can choose to use standard auth, or cookie auth, and you can
+decide how long you want to cache the users credentials before
+retrying.
+
+2.8.1 Authentication Type
+
+2.8.1.1 Standard Authentication
+
+This method causes the browser to pop up a dialog box to ask for the
+username and password.
+
+2.8.1.2 Cookie Authentication
+
+This method allows you to use a normal HTML form to get the username
+and password from the user. It also will present the default form to
+the user if they try to access an unauthorised area.
+
+
+2.8.1.3 Secure Cookie based Authentication
+
+This method, like Cookie Authentication allows you to use a HTML form
+to get the user details. However, the cookie it uses does not contain
+any login information. It is internally checked against a cache of
+hashes and the information is derived from that. This cache disappears
+if you restart Zope, so this is not a good option for people who want
+to persistently cache logins across sessions.
+
+
+2.8.2 Credential Cache Timeout in Seconds
+
+exUserFolder by default caches credential information, so that the
+authorisation source isn't hit *for every object and page* that has to
+be fetched. For remote authentication services this can slow things
+down quite considerably. Even setting this to a modest setting will
+quicken response times.
+
+Setting this too long could cause problems if you want to lock out a
+troublesome user. The credential cache is flushed if someone provides
+a password that doesn't match the one in the cache.
+
+
+2.8.3 Negative Credential Cache Timeout in Seconds
+
+exUserFolder allows you to cache login failures for users that do not
+exist. This means you don't have to go out to your auth source when
+you know for certain this user is never going to be able to
+authenticate.
+
+Due to the way some auth sources are designed, this doesn't work for
+auth sources like SMB Auth Source that lie initially about the user
+existing (because you can't verify the existence of a user without
+authenticating them), and then check the credentials later.
+
+It's possible to create a hybrid auth source that lets this work
+correctly for auth sources that can't list the users.
+
+
+2.8.4 Log out users who expire from cache?
+
+If you've got caching turned on, then this will force any user who has
+their session expire to login again. Some people like to do this.
+
+
+2.8.5 Activate Session Tracking for anoymous users?
+
+For any anonymous user, a new temporary user is created. This allows
+you to set/get properties for anonymous users too. Currently
+experimental.
+
+
+3.0 PROPERTY SOURCES
+
+4.0 MEMBERSHIP SOURCES
+
+5.0 TIPS FOR THE UNWARY
+
+Generally these things apply to Cookie Authentication models, since
+there is additional access required to present the login form.
+
+5.1 Too much protection.
+
+A lot of people try to protect a folder by placing an exUserFolder
+inside.  They then change the permissions on this folder to only allow
+Authenticated or some Local Role to have permission.
+
+5.1.1 The problem
+
+When you try to access the folder, instead of getting the form, you
+get a popup box, even though you chose Cookie Authentication. Even
+when you enter a username and password it doesn't work.
+
+
+5.1.2 What happened
+
+You tried to access an area you don't have access to. Zope found the
+closest user folder to the object you were trying to access. The user
+folder decided you were not authorised and tried to display the login
+form. You don't have access to view the login form, so Zope finds the
+nearest user folder to the login form, which is the user folder above
+the protected directory. It pops up the authentication dialog. If you
+put in a valid username and password for this top level, then lower
+level then displays the login form.
+
+5.1.3 Solution 1 (preferred).
+
+Place the user folder one level *above* the folder you want to protect,
+that is in the unprotected area. Everything should work fine.
+
+5.1.4. Solution 2 (not so preferred).
+
+Set the 'View' permission on the docLogin form inside the acl_users
+folder.  You can get there by Choosing 'Contents' on docLogin and
+scrolling down to the bottom.
+
+6.0 MISCELLANY
+
+6.1 Adding an exUserFolder from a product.
+
+You can add an exUserFolder from a Python product fairly easily, if
+not a tad messily.
+
+
+from Products.exUserFolder.exUserFolder import manage_addexUserFolder, eUserFolder
+
+manage_addexUserFolder(authId='zodbAuthSource', propId='zodbPropSource',
+                       memberId='basicMemberSource',
+                       cookie_mode=1, session_length=600, REQUEST)
+
+Obviously change authId, propId, and memberId to what you want.
+However, you'll need to ram in the appropriate form fields for the various
+source constructors into your REQUEST.
+
+6.2 Session Tracking.
+
+Session tracking (currently) relies on having the credential cache
+active, and a property source active. Your trackable user will only
+last as long as they are not expired from the cache. You should set
+the cache expiry length to be somewhat longer than normal if you plan
+to use Session Tracking, and you should also be prepared to check that
+the current session is valid.

Added: trunk/ZopeProducts/exUserFolder/doc/mysql.sql
===================================================================
--- trunk/ZopeProducts/exUserFolder/doc/mysql.sql	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/doc/mysql.sql	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,18 @@
+DROP TABLE IF EXISTS passwd;
+CREATE TABLE passwd (
+	username varchar(64) NOT NULL PRIMARY KEY,
+	password varchar(64) NOT NULL,
+	roles varchar(255)
+);
+
+DROP TABLE IF EXISTS UserProperties;
+CREATE TABLE UserProperties (
+	username varchar(64) NOT NULL,
+	prop_key varchar(128) NOT NULL,
+	value text NOT NULL,
+	istemporary int
+);
+
+CREATE UNIQUE INDEX username_prop_idx on UserProperties(username,prop_key );
+CREATE INDEX username_idx on UserProperties(username);
+

Added: trunk/ZopeProducts/exUserFolder/doc/pgAndreasScheme.sql
===================================================================
--- trunk/ZopeProducts/exUserFolder/doc/pgAndreasScheme.sql	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/doc/pgAndreasScheme.sql	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,14 @@
+CREATE TABLE "passwd" (
+	"username" character varying(64) UNIQUE NOT NULL,
+	"password" character varying(64) NOT NULL,
+	"roles" character varying(255),
+	Constraint "passwd_pkey" Primary Key ("username")
+);
+
+CREATE TABLE "userproperties" (
+	"username" character varying(64) NOT NULL REFERENCES passwd (username) ON DELETE CASCADE ON UPDATE CASCADE,
+	"key" character varying(128) NOT NULL,
+	"value" text NOT NULL	
+);
+
+CREATE  INDEX "username_idx" on "userproperties" using btree ( "username" "varchar_ops" );

Added: trunk/ZopeProducts/exUserFolder/doc/pgScheme.sql
===================================================================
--- trunk/ZopeProducts/exUserFolder/doc/pgScheme.sql	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/doc/pgScheme.sql	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,46 @@
+CREATE SEQUENCE "passwd_userid_seq" start 1 increment 1 maxvalue 2147483647 minvalue 1  cache 1 ;
+
+--
+-- TOC Entry ID 6 (OID 24949)
+--
+-- Name: passwd Type: TABLE Owner: akm
+--
+
+CREATE TABLE "passwd" (
+	"userid" integer DEFAULT nextval('"passwd_userid_seq"'::text) NOT NULL,
+	"username" character varying(64) NOT NULL,
+	"password" character varying(64) NOT NULL,
+	"roles" character varying(255),
+	Constraint "passwd_pkey" Primary Key ("userid")
+);
+
+--
+-- TOC Entry ID 4 (OID 24965)
+--
+-- Name: userproperties_propertyid_seq Type: SEQUENCE Owner: akm
+--
+
+CREATE SEQUENCE "userproperties_propertyid_seq" start 1 increment 1 maxvalue 2147483647 minvalue 1  cache 1 ;
+
+--
+-- TOC Entry ID 7 (OID 24984)
+--
+-- Name: userproperties Type: TABLE Owner: akm
+--
+
+CREATE TABLE "userproperties" (
+	"propertyid" integer DEFAULT nextval('"userproperties_propertyid_seq"'::text) NOT NULL,
+	"username" character varying(64) NOT NULL,
+	"key" character varying(128) NOT NULL,
+	"value" text NOT NULL,
+	"istemporary" integer,
+	Constraint "userproperties_pkey" Primary Key ("propertyid")
+);
+
+--
+-- TOC Entry ID 8 (OID 24984)
+--
+-- Name: "username_idx" Type: INDEX Owner: akm
+--
+
+CREATE  INDEX "username_idx" on "userproperties" using btree ( "username" "varchar_ops" );

Added: trunk/ZopeProducts/exUserFolder/dtml/docLogin.dtml
===================================================================
--- trunk/ZopeProducts/exUserFolder/dtml/docLogin.dtml	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/dtml/docLogin.dtml	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,49 @@
+<dtml-var standard_html_header>
+<center>
+<dtml-if authFailedCode>
+<dtml-call "REQUEST.set('loginTitle', getAuthFailedMessage(authFailedCode))">
+<dtml-else>
+<dtml-call "REQUEST.set('loginTitle', 'Login Required')">
+</dtml-if>
+<dtml-var "DialogHeader(_.None,_,DialogTitle=loginTitle)">
+<P>
+<dtml-if destination>
+<FORM ACTION="&dtml-destination;" METHOD="POST">
+<dtml-else>
+<FORM ACTION="&dtml-URL;" METHOD="POST">
+</dtml-if>
+
+<dtml-var "query_string_to_form_inputs(QUERY_STRING)"> <dtml-comment> Added by Emmanuel for ScoDoc</dtml-comment>
+
+
+<TABLE>
+<TR>
+  <TD ALIGN="LEFT" VALIGN="TOP">
+  <STRONG><dtml-babel src="'en'">Name</dtml-babel></STRONG>
+  </TD>
+  <TD ALIGN="LEFT" VALIGN="TOP">
+  <INPUT TYPE="TEXT" NAME="__ac_name" SIZE="20">
+  </TD>
+</TR>
+
+<TR>
+  <TD ALIGN="LEFT" VALIGN="TOP">
+  <STRONG><dtml-babel src="'en'">Password</dtml-babel></STRONG>
+  </TD>
+  <TD ALIGN="LEFT" VALIGN="TOP">
+  <INPUT TYPE="PASSWORD" NAME="__ac_password" SIZE="20">
+  </TD>
+</TR>
+<TR>
+  <TD ALIGN="LEFT" VALIGN="TOP">
+  </TD>
+</TR>
+</TABLE>
+<center>
+<INPUT TYPE="SUBMIT" NAME="submit" VALUE=" <dtml-babel src="'en'">Ok</dtml-babel> ">
+</center>
+</FORM>
+<br>
+<dtml-var DialogFooter>
+</center>
+<dtml-var standard_html_footer>

Added: trunk/ZopeProducts/exUserFolder/dtml/docLoginRedirect.dtml
===================================================================
--- trunk/ZopeProducts/exUserFolder/dtml/docLoginRedirect.dtml	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/dtml/docLoginRedirect.dtml	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,26 @@
+<HTML>
+<HEAD>
+<TITLE>Logging In</TITLE>
+<dtml-call makeRedirectPath>
+<META HTTP-EQUIV=Expires CONTENT="<dtml-var "ZopeTime()-1" fmt="%Y-%m-%d">">
+<META HTTP-EQUIV=Refresh CONTENT="0; URL=&dtml-URL1;/acl_users/redirectToLogin?destination=&dtml-URL;<dtml-if "REQUEST.has_key('authFailedCode')">&authFailedCode=&dtml-authFailedCode;</dtml-if>">
+</HEAD>
+<BODY>
+<!-- This is here to stop IE's default 512 byte limit from kicking in and
+     showing us "Friendly" error pages.
+
+The Unenlightened Zopistas Guide to exUserFolder.
+(C) 2001-2003 Andrew Milton <akm at theinternet.com.au>
+
+0. INTRODUCTION.
+
+exUserFolder is an extensible authentication product for the Zope
+Application Server. It allows a user to choose from a number of
+methods of authenticating their users, and allows the setting and
+fetching of arbitrary properties on a User object.
+
+Authentication methods, and Property methods do not have to use the
+same backing store, so it is possible to use legacy user sources, and
+-->
+</BODY></HTML>
+

Added: trunk/ZopeProducts/exUserFolder/dtml/docLogout.dtml
===================================================================
--- trunk/ZopeProducts/exUserFolder/dtml/docLogout.dtml	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/dtml/docLogout.dtml	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,16 @@
+<dtml-var standard_html_header>
+<P>
+<CENTER>
+<p>Vous \xEAtes d\xE9connect\xE9 de ScoDoc.
+</p>
+
+<p><a href="<dtml-var "BASE0">">revenir \xE0 l'accueil</a></p>
+
+<br/>
+<p><em style="color: red;">(Attention: si vous \xEAtes administrateur, vous ne pouvez vous d\xE9connecter compl\xE8tement qu'en relan\xE7ant votre navigateur)
+</em></p>
+</CENTER>
+
+
+
+<dtml-var standard_html_footer>

Added: trunk/ZopeProducts/exUserFolder/dummyZBabelTag.py
===================================================================
--- trunk/ZopeProducts/exUserFolder/dummyZBabelTag.py	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/dummyZBabelTag.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,164 @@
+try:
+	from Products.ZBabel import ZBabelTag
+except:
+
+	from DocumentTemplate.DT_String import String
+	from DocumentTemplate.DT_Util   import render_blocks, Eval, ParseError
+	import string, zLOG
+
+
+	# fake Babel/Fish Tags
+
+	class ZBabelTag:
+		'''ZBabel Tag class - The cool translation tag'''
+
+		# define the name of the tag; also tell the system it is a doublet
+		name = 'babel'
+		blockContinuations = ()
+
+
+		def __init__(self, blocks):
+			'''__init__(self, blocks) --> Initialize tag object; return None'''
+			(tname, args, section,) = blocks[0]
+
+			self.section = section
+
+
+		def render(self, md):
+			'''render(self, md) --> Do the Translation; return string'''
+			return render_blocks(self.section.blocks, md)
+		__call__=render
+	# register the DTML-BABEL tag
+	String.commands['babel'] = ZBabelTag
+
+	class FishTag:
+		'''Fish Tag class - Short-Cut version of the cool translation tag (babel)
+
+		   This tag is used to quickly translate menu-like text snippets, similar to
+		   the KDE translation.'''
+
+		# define the tag name
+		name = 'fish'
+
+		# define additional variables
+		literal = 1
+
+		src = 'label'
+		attrs = {'dst': None, 'label': '', 'data': None, 'towerId': None}
+
+		def __init__(self, args):
+			'''__init__(self, blocks) --> Initialize tag object; return None'''
+			self.section = None
+			args = parseTagParameters(args, tag=self.name)
+			self.args = self.validateArguments(args)
+			
+			for attr in self.attrs.keys(): 
+				setattr(self, attr, self.attrs[attr])
+				
+		def validateArguments(self, args):
+			'''validateArguments(self, args) --> try to evaluate the passed expression or try to get an object from the passed id; if all this fails, leave the string, it is probably cool!; return tuple of (name, value)'''
+			# I stole this from dtml-let...
+			# SR: Like he said: Always copy existing code to make you life easier (evben though
+			#	  I changed some variables around
+			for count in range(len(args)):
+				(name, expr,) = args[count]
+				if ((expr[:1] == '"') and ((expr[-1:] == '"') and (len(expr) > 1))):
+					expr = expr[1:-1]
+					try:
+
+						args[count] = (name, Eval(expr).eval)
+
+					except SyntaxError, v:
+						(m, (huh, l, c, src,),) = v
+						raise ParseError, (('<strong>Expression (Python) Syntax error</strong>:' +
+											'<pre>\012%s\012</pre>\012' % v[0]), 'babel')
+
+				elif ((expr[:1] == "'") and ((expr[-1:] == "'") and (len(expr) > 1))):
+					expr = expr[1:-1]
+					args[count] = (name, expr)
+
+			return args
+			
+		def render(self, md):
+			'''render(self, md) --> Do the Translation; return string'''
+			data = None
+			for name, expr in self.args:
+				if type(expr) is type(''):
+					try:
+						data = md[expr]
+					except:
+						data = expr
+				else:
+					data = expr(md)
+					
+				#zLOG.LOG("exUserFolder", zLOG.INFO, "rendering name=%s expr=%s data=%s"%(name,expr,data))
+				
+			print data
+			return str(data)
+					  	 
+		__call__=render
+
+
+	# register the DTML-FISH tag
+	String.commands['fish'] = FishTag
+
+
+
+try:
+	import re
+	parmre=re.compile('''([\000- ]*([^\000- ="']+)=([^\000- ="']+))''');#"))
+	dqparmre=re.compile('([\000- ]*([^\000- ="]+)="([^"]*)")')
+	sqparmre=re.compile('''([\000- ]*([^\000- =']+)='([^']*)')''')
+except:
+	import regex
+	parmre=regex.compile('''([\000- ]*([^\000- ="']+)=([^\000- ="']+))''');#"))
+ 	dqparmre=regex.compile('([\000- ]*([^\000- ="]+)="([^"]*)")')
+ 	sqparmre=regex.compile('''([\000- ]*([^\000- =']+)='([^']*)')''')
+	
+
+
+def parseTagParameters(paramText, result = None, tag = 'babel',
+					   parmre=parmre,
+					   dqparmre=dqparmre,
+					   sqparmre=sqparmre,
+					   **parms):
+	result = (result or [])
+
+	parsedParam	  = parmre.match(paramText)
+	dqParsedParam = dqparmre.match(paramText)
+	sqParsedParam = sqparmre.match(paramText)
+
+	# Parse parameters of the form: name=value
+	if parsedParam is not None:
+		name   = parsedParam.group(2)
+		value  = parsedParam.group(3)
+		length = len(parsedParam.group(1))
+
+	# Parse parameters of the form: name="value"
+	elif dqParsedParam is not None:
+		name = dqParsedParam.group(2)
+		value = ('"%s"' % dqParsedParam.group(3))
+		length = len(dqParsedParam.group(1))
+
+	# Parse parameters of the form: name='value'
+	elif sqParsedParam is not None:
+		name = sqParsedParam.group(2)
+		value = ('''"'%s'"''' % sqParsedParam.group(3))
+		length = len(sqParsedParam.group(1))
+
+	else:
+		# There are no more parameters to parse
+		if ((not paramText) or (not string.strip(paramText))):
+			return result
+		raise ParseError, (('invalid parameter: "%s"' % paramText), tag)
+
+	# add the parameter/value pait to the results
+	result.append((name, value))
+
+	# remove the found parameter from the paramText
+	paramText = string.strip(paramText[length:])
+
+	if paramText:
+		return apply(parseTagParameters, (paramText, result, tag), parms)
+	else:
+		return result

Added: trunk/ZopeProducts/exUserFolder/exUser.gif
===================================================================
(Binary files differ)


Property changes on: trunk/ZopeProducts/exUserFolder/exUser.gif
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/ZopeProducts/exUserFolder/exUserFolder.gif
===================================================================
(Binary files differ)


Property changes on: trunk/ZopeProducts/exUserFolder/exUserFolder.gif
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/ZopeProducts/exUserFolder/exUserFolder.py
===================================================================
--- trunk/ZopeProducts/exUserFolder/exUserFolder.py	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/exUserFolder.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,1363 @@
+# Zope User Folder for ScoDoc
+# Adapte de l'Extensible User Folder
+# simplifie pour les besoins de ScoDoc.
+# Emmanuel Viennet 2013
+
+#
+# Extensible User Folder
+#
+# (C) Copyright 2000,2001 The Internet (Aust) Pty Ltd
+# ACN: 082 081 472  ABN: 83 082 081 472
+# All Rights Reserved
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# Author: Andrew Milton <akm at theinternet.com.au>
+# $Id: exUserFolder.py,v 1.93 2004/11/10 14:15:33 akm Exp $
+
+##############################################################################
+#
+# Zope Public License (ZPL) Version 0.9.4
+# ---------------------------------------
+# 
+# Copyright (c) Digital Creations.  All rights reserved.
+# 
+# Redistribution and use in source and binary forms, with or
+# without modification, are permitted provided that the following
+# conditions are met:
+# 
+# 1. Redistributions in source code must retain the above
+#    copyright notice, this list of conditions, and the following
+#    disclaimer.
+# 
+# 6. Redistributions of any form whatsoever must retain the
+#    following acknowledgment:
+# 
+#      "This product includes software developed by Digital
+#      Creations for use in the Z Object Publishing Environment
+#      (http://www.zope.org/)."
+# 
+# Disclaimer
+# 
+#   THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS ``AS IS'' AND
+#   ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+#   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+#   FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT
+#   SHALL DIGITAL CREATIONS OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+#   DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+#   CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+#   PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+#   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+#   ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+#   LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+#   IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+#   THE POSSIBILITY OF SUCH DAMAGE.
+#
+##############################################################################
+
+# Portions Copyright (c) 2002 Nuxeo SARL <http://nuxeo.com>,
+#          Copyright (c) 2002 Florent Guillaume <mailto:fg at nuxeo.com>.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in
+#    the documentation and/or other materials provided with the
+#    distribution.
+# 
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+import Globals, App.Undo, socket, os, string, sha, whrandom, sys, zLOG
+
+from Globals import DTMLFile, PersistentMapping
+from string import join,strip,split,lower,upper,find
+
+from OFS.Folder import Folder
+from OFS.CopySupport import CopyContainer
+
+from base64 import decodestring, encodestring
+from urllib import quote, unquote
+
+from Acquisition import aq_base
+from AccessControl import ClassSecurityInfo
+from AccessControl.Role import RoleManager
+from AccessControl.User import BasicUser, BasicUserFolder, readUserAccessFile
+from AccessControl.PermissionRole import PermissionRole
+from AccessControl.ZopeSecurityPolicy import _noroles
+from OFS.DTMLMethod import DTMLMethod
+from time import time
+from OFS.ObjectManager import REPLACEABLE
+from Persistence import Persistent
+
+from PropertyEditor import *
+
+from User import User, AnonUser
+from UserCache.UserCache import GlobalUserCache, GlobalNegativeUserCache, GlobalAdvancedCookieCache, SessionExpiredException
+
+from LoginRequiredMessages import LoginRequiredMessages
+
+from AccessControl import Unauthorized
+
+
+
+# If there is no NUG Product just define a dummy class
+try:
+	from Products.NuxUserGroups.UserFolderWithGroups import BasicGroupFolderMixin, _marker
+except ImportError:
+	class BasicGroupFolderMixin:
+		pass
+	_marker = None
+
+# Little function to create temp usernames
+def createTempName():
+	t=time()
+	t1=time()
+	t2=time()
+	t3 = 0.0
+	t3 = (t + t1 + t2) / 3
+	un = "Anonymous %.0f"%(t3)
+	return(un)
+
+
+manage_addexUserFolderForm=DTMLFile('dtml/manage_addexUserFolder', globals(), __name__='manage_addexUserFolderForm')
+
+
+
+def manage_addexUserFolder(self, authId, propId, memberId,
+						   cookie_mode=0, session_length=0,
+						   not_session_length=0,
+						   sessionTracking=None, idleTimeout=None,
+						   REQUEST={}, groupId=None, cryptoId=None):
+	""" """
+	if hasattr(self.aq_base, 'acl_users'):
+		return Globals.MessageDialog(self,REQUEST,
+			title  ='Item Exists',
+			message='This object already contains a User Folder',
+			action ='%s/manage_main' % REQUEST['URL1'])
+	ob=exUserFolder(authId, propId, memberId, groupId, cryptoId, cookie_mode,
+					session_length, sessionTracking, idleTimeout,
+					not_session_length)
+			
+	self._setObject('acl_users', ob, None, None, 0)
+	self.__allow_groups__=self.acl_users
+	ob=getattr(self, 'acl_users')
+	ob.postInitialisation(REQUEST)
+
+	if REQUEST:
+		return self.manage_main(self, REQUEST)
+	return ''
+
+#
+# Module level caches
+#
+XUFUserCache=GlobalUserCache()
+XUFNotUserCache=GlobalNegativeUserCache()
+XUFCookieCache=GlobalAdvancedCookieCache()
+
+class exUserFolder(Folder,BasicUserFolder,BasicGroupFolderMixin,
+				   CopyContainer):
+	""" """
+
+	# HACK! We use this meta_type internally so we can be pasted into
+	# the root. We registered with 'exUserFolder' meta_type however, so
+	# our constructors work.
+	meta_type='User Folder'
+	id       ='acl_users'
+	title    ='Extensible User Folder'
+	icon     ='misc_/exUserFolder/exUserFolder.gif'
+
+	isPrincipiaFolderish=1
+	isAUserFolder=1
+	__allow_access_to_unprotected_subobjects__=1
+	authSources={}
+	propSources={}
+	cryptoSources={}
+	membershipSources={}
+	groupSources={} # UNUSED by ScoDoc
+
+	manage_options=(
+		{'label':'Users',      'action':'manage_main'},
+		{'label':'Groups',	   'action':'manage_userGroups'},
+		{'label':'Parameters', 'action':'manage_editexUserFolderForm'},
+		{'label':'Authentication Source','action':'manage_editAuthSourceForm'},
+		{'label':'Properties Source','action':'manage_editPropSourceForm'},
+		{'label':'Membership Source', 'action':'manage_editMembershipSourceForm'},
+		{'label':'Cache Data', 'action':'manage_showCacheData'},
+		{'label':'Security',   'action':'manage_access'},
+		{'label':'Contents',   'action':'manage_contents'},
+		{'label':'Ownership',  'action':'manage_owner'},
+		{'label':'Undo',       'action':'manage_UndoForm'},
+		)
+
+	__ac_permissions__=(
+		('View management screens', ('manage','manage_menu','manage_main',
+									 'manage_copyright', 'manage_tabs',
+									 'manage_properties', 'manage_UndoForm',
+									 'manage_edit', 'manage_contents',
+									 'manage_cutObjects','manage_copyObjects',
+									 'manage_pasteObjects',
+									 'manage_renameForm',
+									 'manage_renameObject',
+									 'manage_renameObjects', ),
+		 ('Manager',)),
+		
+		('Undo changes',            ('manage_undo_transactions',),
+		 ('Manager',)),
+		
+		('Change permissions',      ('manage_access',),
+		 ('Manager',)),
+		
+		('Manage users',            ('manage_users', 'manage_editUserForm',
+									 'manage_editUser', 'manage_addUserForm',
+									 'manage_addUser', 'manage_userActions',
+									 'userFolderAddGroup',
+									 'userFolderDelGroups',
+									 'getGroupNames',
+					                                 'getGroupById',
+								         'manage_userGroups',
+									 'manage_addGroup',
+									 'manage_showGroup',),
+		 ('Manager',)),
+		
+		('Change exUser Folders',   ('manage_edit',),
+		 ('Manager',)),
+		
+		('View',                    ('manage_changePassword',
+									 'manage_forgotPassword', 'docLogin','docLoginRedirect',
+									 'docLogout', 'logout', 'DialogHeader',
+									 'DialogFooter', 'manage_signupUser',
+									 'MessageDialog', 'redirectToLogin','manage_changeProps'),
+		 ('Anonymous', 'Authenticated', 'Manager')),
+		
+		('Manage properties',       ('manage_addProperty',
+									 'manage_editProperties',
+									 'manage_delProperties',
+									 'manage_changeProperties',
+									 'manage_propertiesForm',
+									 'manage_propertyTypeForm',
+									 'manage_changePropertyTypes',
+									 ),
+		 ('Manager',)),
+		('Access contents information', ('hasProperty', 'propertyIds',
+										 'propertyValues','propertyItems',
+										 'getProperty', 'getPropertyType',
+										 'propertyMap', 'docLogin','docLoginRedirect',
+										 'DialogHeader', 'DialogFooter',
+										 'MessageDialog', 'redirectToLogin',),
+		 ('Anonymous', 'Authenticated', 'Manager')),
+		)
+	manage_access=DTMLFile('dtml/access',globals())
+	manage_tabs=DTMLFile('common/manage_tabs',globals())
+	manage_properties=DTMLFile('dtml/properties', globals())
+	manage_main=DTMLFile('dtml/mainUser', globals())
+	manage_contents=Folder.manage_main
+	manage_showCacheData=DTMLFile('dtml/manage_showCacheData', globals())
+
+	# This is going away soon...
+	docLoginRedirect=DTMLFile('dtml/docLoginRedirect', globals())	
+
+	# Stupid crap
+	try:
+		manage_contents._setName('manage_contents')
+	except AttributeError:
+		pass
+
+
+	MessageDialog=DTMLFile('common/MessageDialog', globals())
+	MessageDialog.__replaceable__ = REPLACEABLE
+	
+	manage_addUserForm=DTMLFile('dtml/manage_addUserForm',globals())
+	manage_editUserForm=DTMLFile('dtml/manage_editUserForm',globals())
+
+	DialogHeader__roles__=()
+	DialogHeader=DTMLFile('common/DialogHeader',globals())
+	DialogFooter__roles__=()
+	DialogFooter=DTMLFile('common/DialogFooter',globals())
+
+	manage_editAuthSourceForm=DTMLFile('dtml/manage_editAuthSourceForm',globals())
+	manage_editPropSourceForm=DTMLFile('dtml/manage_editPropSourceForm',globals())
+	manage_editMembershipSourceForm=DTMLFile('dtml/manage_editMembershipSourceForm', globals())
+
+	manage_addPropertyForm=DTMLFile('dtml/manage_addPropertyForm', globals())
+	manage_createPropertyForm=DTMLFile('dtml/manage_createPropertyForm', globals())
+	manage_editUserPropertyForm=DTMLFile('dtml/manage_editUserPropertyForm', globals())
+
+	manage_editexUserFolderForm=DTMLFile('dtml/manage_editexUserFolderForm', globals())
+
+	manage_userGroups=DTMLFile('dtml/mainGroup',globals())
+
+
+	# Use pages from NUG if it's there, otherwise no group support
+	try:
+		manage_addGroup = BasicGroupFolderMixin.manage_addGroup
+		manage_showGroup = BasicGroupFolderMixin.manage_showGroup
+	except:
+		manage_addGroup = None
+		manage_showGroup = None
+
+	# No more class globals
+	
+	# sessionLength=0 # Upgrading users should get no caching.
+	# notSessionLength=0 # bad cache limit
+	# cookie_mode=0
+	# sessionTracking=None # Or session tracking.
+	# idleTimeout=0
+	
+	def __init__(self, authId, propId, memberId, groupId, cryptoId,
+				 cookie_mode=0, session_length=0, sessionTracking=None,
+				 idleTimeout=0, not_session_length=0):
+		self.cookie_mode=cookie_mode
+		self.sessionLength=session_length
+		self.notSessionLength=not_session_length
+		self.sessionTracking=sessionTracking
+		self.idleTimeout=idleTimeout
+		
+		_docLogin=DTMLFile('dtml/docLogin',globals())
+		_docLogout=DTMLFile('dtml/docLogout',globals())
+
+		docLogin=DTMLMethod(__name__='docLogin')
+		docLogin.manage_edit(data=_docLogin, title='Login Page')
+		self._setObject('docLogin', docLogin, None, None, 0)
+
+		docLogout=DTMLMethod(__name__='docLogout')
+		docLogout.manage_edit(data=_docLogout, title='Logout Page')
+		self._setObject('docLogout', docLogout, None, None, 0)
+
+		postUserCreate=DTMLMethod(__name__='postUserCreate')
+		postUserCreate.manage_edit(data=_postUserCreate, title='Post User Creation methods')
+		self._setObject('postUserCreate', postUserCreate, None, None, 0)
+
+		self.manage_addAuthSource=self.authSources[authId].manage_addMethod
+		self.manage_addPropSource=self.propSources[propId].manage_addMethod
+		self.manage_addMembershipSource=self.membershipSources[memberId].manage_addMethod
+
+		self.manage_addGroupSource=None # UNUSED by ScoDoc
+		self.currentGroupsSource=None
+
+		if cryptoId:
+			self.cryptoId = cryptoId
+		else:
+			self.cryptoId = 'Crypt'
+			
+	def __setstate__(self, state):
+		Persistent.__setstate__(self, state)
+		if not hasattr(self, 'currentGroupSource'):
+			self.currentGroupSource = None
+		if not hasattr(self, 'sessionLength'):
+			self.sessionLength = 0
+		if not hasattr(self, 'notSessionLength'):
+			self.notSessionLength = 0
+		if not hasattr(self, 'cookie_mode'):
+			self.cookie_mode = 0
+		if not hasattr(self, 'sessionTraining'):
+			self.sessionTracking = None
+		if not hasattr(self, 'idleTimeout'):
+			self.idleTimeout=0
+
+	def manage_beforeDelete(self, item, container):
+		zLOG.LOG("exUserFolder", zLOG.BLATHER, "Attempting to delete an exUserFolder instance")
+		if item is self:
+			try:
+				self.cache_deleteCache()
+				self.xcache_deleteCache()
+				zLOG.LOG("exUserFolder", zLOG.BLATHER, "-- Caches deleted")
+			except:
+				#pass
+				zLOG.LOG("exUserFolder", zLOG.BLATHER, "-- Cache deletion failed")
+
+			try:
+				del container.__allow_groups__
+				zLOG.LOG("exUserFolder", zLOG.BLATHER, "-- container.__allow_groups_ deleted")
+			except:
+				#pass
+				zLOG.LOG("exUserFolder", zLOG.BLATHER, "-- container.__allow_groups_ deletion failed")
+
+
+	def manage_afterAdd(self, item, container):
+		zLOG.LOG("exUserFolder", zLOG.BLATHER, "Adding an exUserFolder")
+                         
+		if item is self:
+			if hasattr(self, 'aq_base'): self=self.aq_base
+			container.__allow_groups__=self
+
+	def manage_editPropSource(self, REQUEST):
+		""" Edit Prop Source """
+		if self.currentPropSource:
+			self.currentPropSource.manage_editPropSource(REQUEST)
+		return self.manage_main(self, REQUEST)
+
+	def manage_editAuthSource(self, REQUEST):
+		""" Edit Auth Source """
+		self.currentAuthSource.manage_editAuthSource(REQUEST)
+		return self.manage_main(self, REQUEST)
+
+	def manage_editMembershipSource(self, REQUEST):
+		""" Edit Membership Source """
+		if self.currentMembershipSource:
+			return self.currentMembershipSource.manage_editMembershipSource(REQUEST)
+
+	def postInitialisation(self, REQUEST):
+		self.manage_addAuthSource(self=self,REQUEST=REQUEST)
+		self.manage_addPropSource(self=self,REQUEST=REQUEST)
+		self.manage_addMembershipSource(self=self,REQUEST=REQUEST)
+		self.currentGroupSource = None
+	
+	def addAuthSource(self, REQUEST={}):
+		return self.manage_addAuthSourceForm(self, REQUEST)
+
+	def addPropSource(self, REQUEST={}):
+		return self.manage_addPropSourceForm(self, REQUEST)
+
+	def addMembershipSource(self, REQUEST={}):
+		return self.manage_editMembershipSourceForm(self, REQUEST)
+
+	def listUserProperties(self, username):
+		if self.currentPropSource:
+			return self.currentPropSource.listUserProperties(username=username)
+
+	def getUserProperty(self, username, key):
+		if self.currentPropSource:
+			return self.currentPropSource.getUserProperty(key=key, username=username)
+	
+	def reqattr(self, request, attr, default=None):
+		try:    return request[attr]
+		except: return default
+
+	def getAuthFailedMessage(self, code):
+		""" Return a code """
+		if LoginRequiredMessages.has_key(code):
+			return LoginRequiredMessages[code]
+		return 'Login Required'
+
+	# Called when we are deleted
+	def cache_deleteCache(self):
+		pp = string.join(self.getPhysicalPath(), '/')
+		XUFUserCache.deleteCache(pp)
+		
+	def cache_addToCache(self, username, password, user):
+		if not self.sessionLength:
+			return
+		# fix by emmanuel
+		if username == self._emergency_user.getUserName():
+			return
+		# /fix
+		pp = string.join(self.getPhysicalPath(), '/')
+		x = XUFUserCache.getCache(pp)
+		if not x:
+			x = XUFUserCache.createCache(pp, self.sessionLength)
+		x.addToCache(username, password, user)
+
+	def cache_getUser(self, username, password, checkpassword=1):
+		if not self.sessionLength:
+			return None
+		pp = string.join(self.getPhysicalPath(), '/')
+		x = XUFUserCache.getCache(pp)
+		if not x:
+			return None
+		u = x.getUser(self, username, password, checkpassword)
+		if u is not None:
+			u = u.__of__(self)
+		return u
+
+	def cache_removeUser(self, username):
+		if not self.sessionLength:
+			return
+		pp = string.join(self.getPhysicalPath(), '/')
+		x = XUFUserCache.getCache(pp)
+		if x:
+			x.removeUser(username)
+
+	def cache_getCacheStats(self):
+		pp = string.join(self.getPhysicalPath(), '/')
+		x = XUFUserCache.getCache(pp)
+		if not x:
+			x = XUFUserCache.createCache(pp, self.sessionLength)			
+		if x:
+			return x.getCacheStats()
+
+	def cache_getCurrentUsers(self):
+		pp = string.join(self.getPhysicalPath(), '/')
+		x = XUFUserCache.getCache(pp)
+		if x:
+			return x.getCurrentUsers(self)
+
+	# negative cache functions
+	def xcache_deleteCache(self):
+		pp = string.join(self.getPhysicalPath(), '/')
+		XUFNotUserCache.deleteCache(pp)
+		
+	def xcache_addToCache(self, username):
+		if not self.notSessionLength:
+			return
+		pp = string.join(self.getPhysicalPath(), '/')
+		x = XUFNotUserCache.getCache(pp)
+		if not x:
+			x = XUFNotUserCache.createCache(pp, self.notSessionLength)
+		x.addToCache(username)
+
+	def xcache_getUser(self, username):
+		if not self.notSessionLength:
+			return None
+		pp = string.join(self.getPhysicalPath(), '/')
+		x = XUFNotUserCache.getCache(pp)
+		if not x:
+			return None
+		return x.getUser(username)
+
+	def xcache_removeUser(self, username):
+		if not self.notSessionLength:
+			return
+		pp = string.join(self.getPhysicalPath(), '/')
+		x = XUFNotUserCache.getCache(pp)
+		if x:
+			x.removeUser(username)
+
+	# Cookie Cache Functions
+	def cache_deleteCookieCache(self):
+		pp = string.join(self.getPhysicalPath(), '/')
+		XUFCookieCache.deleteCache(pp)
+
+	def cache_addToCookieCache(self, username, password, key):
+		pp = string.join(self.getPhysicalPath(), '/')
+		c = XUFCookieCache.getCache(pp)
+		if not c:
+			c = XUFCookieCache.createCache(pp, 86400)
+		c.addToCache(username, password, key)
+
+	def cache_getCookieCacheUser(self, key):
+		pp = string.join(self.getPhysicalPath(), '/')
+		c = XUFCookieCache.getCache(pp)
+		if not c:
+			return None
+		return c.getUser(key)
+
+	def cache_removeCookieCacheUser(self, key):
+		pp = string.join(self.getPhysicalPath(), '/')
+		c = XUFCookieCache.getCache(pp)
+		if c:
+			c.removeUser(key)
+    
+	def manage_editUser(self, username, REQUEST={}): # UNUSED by ScoDoc
+ 		""" Edit a User """
+		# username=self.reqattr(REQUEST,'username')
+		password=self.reqattr(REQUEST,'password')
+		password_confirm=self.reqattr(REQUEST,'password_confirm')
+		roles=self.reqattr(REQUEST,'roles', [])
+		groups=self.reqattr(REQUEST, 'groupnames', [])
+        
+		if not username:
+			return self.MessageDialog(self,REQUEST=REQUEST,
+				title  ='Illegal value', 
+				message='A username must be specified',
+				action ='manage_main')
+
+		if (password or password_confirm) and (password != password_confirm):
+			return self.MessageDialog(self,REQUEST=REQUEST,
+				title  ='Illegal value', 
+				message='Password and confirmation do not match',
+				action ='manage_main')
+		
+		self._doChangeUser(username, password, roles, domains='', groups=groups, REQUEST=REQUEST)
+		
+		return self.MessageDialog(self,REQUEST=REQUEST,
+			title = 'User Updated',
+			message= 'User %s was updated.'%(username),
+			action = 'manage_main')
+    
+    
+    # Methode special pour ScoDoc: evite le code inutile dans notre contexte
+    # et accede a la BD via le curseur psycopg2 fournie
+    # (facilitera la separation de Zope)
+	def scodoc_editUser(self, cursor, username, password=None, roles=[]):
+		"""Edit a ScoDoc user"""
+		roles = list(roles)
+		rolestring= ','.join(roles)
+		# Don't change passwords if it's null
+		if password:
+			secret=self.cryptPassword(username, password)
+			# Update just the password:			   
+			# self.sqlUpdateUserPassword(username=username, password=secret)
+			cursor.execute("UPDATE sco_users SET passwd=%(secret)s WHERE user_name=%(username)s",
+						   { 'secret':secret, 'username': username } )
+		
+		#self.sqlUpdateUser(username=username, roles=rolestring)
+		cursor.execute("UPDATE sco_users SET roles=%(rolestring)s WHERE user_name=%(username)s",
+					   { 'rolestring':rolestring, 'username': username } )
+		
+		self._v_lastUser={} # ? zope/exUserFolder specific		   
+		
+		# We may have updated roles or passwords... flush the user...
+		self.cache_removeUser(username)
+		self.xcache_removeUser(username)
+    
+	#
+	# Membership helper
+	#
+	def goHome(self, REQUEST, RESPONSE):
+		""" Go to home directory """
+		if self.currentMembershipSource:
+			self.currentMembershipSource.goHome(REQUEST, RESPONSE)
+
+
+	# 
+	# Membership method of changing user properties
+	# 
+
+	def manage_changeProps(self, REQUEST):
+		""" Change Properties """
+		if self.currentMembershipSource:
+			return self.currentMembershipSource.changeProperties(REQUEST)
+		else:
+			
+			return self.MessageDialog(self,REQUEST,
+				title = 'This is a test',
+				message= 'This was a test',
+				action = '..')
+ 
+
+	#
+	# Membership method of adding a new user.
+	# If everything goes well the membership plugin calls manage_addUser()
+	#
+	
+	def manage_signupUser(self, REQUEST):
+		""" Signup a new user """
+		""" This is seperate so you can add users using the normal """
+		""" interface w/o going through membership policy """
+
+		username=self.reqattr(REQUEST,'username')
+		roles=self.reqattr(REQUEST,'roles')
+
+		if not username:
+			return self.MessageDialog(self,REQUEST=REQUEST,
+				title  ='Illegal value', 
+				message='A username must be specified',
+				action ='manage_main')
+
+		if (self.getUser(username) or
+			(self._emergency_user and
+			 username == self._emergency_user.getUserName())):
+			return self.MessageDialog(self,REQUEST=REQUEST,
+				title  ='Illegal value', 
+				message='A user with the specified name already exists',
+				action ='manage_main')
+
+		if self.currentMembershipSource:
+			return self.currentMembershipSource.createUser(REQUEST)
+
+	#
+	# Membership method of changing passwords
+	#
+	def manage_changePassword(self, REQUEST):
+		""" Change a password """
+		if self.currentMembershipSource:
+			return self.currentMembershipSource.changePassword(REQUEST)
+		
+	#
+	# User says they can't remember their password
+	#
+	def manage_forgotPassword(self, REQUEST):
+		""" So something about forgetting your password """
+		if self.currentMembershipSource:
+			return self.currentMembershipSource.forgotPassword(REQUEST)
+		
+	def __creatable_by_emergency_user__(self): return 1
+
+	def manage_addUser(self, REQUEST):
+		""" Add a New User """
+		username=self.reqattr(REQUEST,'username')
+		password=self.reqattr(REQUEST,'password')
+		password_confirm=self.reqattr(REQUEST,'password_confirm')
+		roles=self.reqattr(REQUEST,'roles')
+		groups=self.reqattr(REQUEST, 'groupnames', [])
+
+		if not username:
+			return self.MessageDialog(self,REQUEST=REQUEST,
+				title  ='Illegal value', 
+				message='A username must be specified',
+				action ='manage_main')
+
+		if not password or not password_confirm:
+			return self.MessageDialog(self,REQUEST=REQUEST,
+				title  ='Illegal value', 
+				message='Password and confirmation must be specified',
+				action ='manage_main')
+
+		if (self.getUser(username) or
+			(self._emergency_user and
+			 username == self._emergency_user.getUserName())):
+			return self.MessageDialog(self,REQUEST=REQUEST,
+				title  ='Illegal value', 
+				message='A user with the specified name already exists',
+				action ='manage_main')
+
+		if (password or password_confirm) and (password != password_confirm):
+			return self.MessageDialog(self,REQUEST=REQUEST,
+				title  ='Illegal value', 
+				message='Password and confirmation do not match',
+				action ='manage_main')
+
+		self._doAddUser(username, password, roles, domains='', groups=groups, REQUEST=REQUEST)
+		#
+		# Explicitly check our contents, do not just acquire postUserCreate
+		#
+		if 'postUserCreate' in self.objectIds():
+			self.postUserCreate(self, REQUEST)
+		
+		return self.MessageDialog(self,REQUEST=REQUEST,
+			title = 'User Created',
+			message= 'User %s was created.'%(username),
+			action = 'manage_main')
+
+	def _doAddUser(self, name, password, roles, domains='', groups=(), **kw):
+		""" For programatically adding simple users """
+		self.currentAuthSource.createUser(name, password, roles)
+		if self.currentPropSource:
+			# copy items not in kw from REQUEST
+			REQUEST = kw.get('REQUEST', self.REQUEST)
+			map(kw.setdefault, REQUEST.keys(), REQUEST.values())
+			self.currentPropSource.createUser(name, kw)
+
+	def _doChangeUser(self, name, password, roles, domains='', groups=(), **kw):
+		self.currentAuthSource.updateUser(name, password, roles)
+		if self.currentPropSource:
+			# copy items not in kw from REQUEST
+			REQUEST = kw.get('REQUEST', self.REQUEST)
+			map(kw.setdefault, REQUEST.keys(), REQUEST.values())
+			self.currentPropSource.updateUser(name, kw)
+		# We may have updated roles or passwords... flush the user...
+		self.cache_removeUser(name)
+		self.xcache_removeUser(name)
+		
+	def _doDelUsers(self, names):
+		self.deleteUsers(names)
+
+	def _createInitialUser(self):
+		if len(self.getUserNames()) <= 1:
+			info = readUserAccessFile('inituser')
+			if info:
+				name, password, domains, remote_user_mode = info
+				self._doAddUser(name, password, ('Manager',), domains)
+
+
+	def getUsers(self):
+		"""Return a list of user objects or [] if no users exist"""
+		data=[]
+		try:
+			items=self.listUsers()
+			for people in items:
+				user=User({'name':		people['username'],
+						   'password':	people['password'],
+						   'roles':		people['roles'], 
+						   'domains':	''},
+						  self.currentPropSource,
+						  self.cryptPassword,
+						  self.currentAuthSource,
+						  self.currentGroupSource)
+				data.append(user)
+		except:
+			import traceback
+			traceback.print_exc()
+			pass
+			
+		return data
+
+	getUsers__roles__=('Anonymous','Authenticated')
+	
+	def getUser(self, name):
+		"""Return the named user object or None if no such user exists"""
+		user = self.cache_getUser(name, '', 0)
+		if user:
+			return user
+		try:
+			items=self.listOneUser(name)
+		except:
+			zLOG.LOG("exUserFolder", zLOG.ERROR,
+                                 "error trying to list user %s" % name,
+                                 '',
+                                 sys.exc_info())
+			return None
+
+		if not items:
+			return None
+		
+		for people in items:
+			user =  User({'name':    people['username'],
+						  'password':people['password'],
+						  'roles':   people['roles'],
+						  'domains':	''},
+						 self.currentPropSource,
+						 self.cryptPassword,
+						 self.currentAuthSource,
+						 self.currentGroupSource)
+			return user
+		return None
+		
+	def manage_userActions(self, submit=None, userids=None, REQUEST={}):
+		""" Do things to users """
+		if submit==' Add ':
+			if hasattr(self.currentAuthSource,'manage_addUserForm'):
+				return self.currentAuthSource.manage_addUserForm(self, REQUEST)
+			else:
+				return self.manage_addUserForm(self, REQUEST)
+		if submit==' Delete ':
+			self.deleteUsers(userids)
+			return self.MessageDialog(self,REQUEST=REQUEST,
+				title  ='Users Deleted',
+				message='Selected Users have been deleted',
+				action =REQUEST['URL1']+'/manage_main',
+				target ='manage_main')
+
+		if REQUEST:
+			return self.manage_main(self,REQUEST)
+		return ''
+
+	def identify(self, auth):
+		# Identify the username and password.  This is where new modes should
+		# be called from, and if pluggable modes ever take shape, here ya go!
+
+		if self.cookie_mode and not auth:
+			# The identify signature does not include the request, sadly.
+			# I think that's dumb.
+			request = self.REQUEST
+			response = request.RESPONSE
+	
+			if request.has_key('__ac_name') and request.has_key('__ac_password'):
+				return request['__ac_name'], request['__ac_password']
+			elif request.has_key('__ac') and self.cookie_mode == 1:
+				return self.decodeBasicCookie(request, response)
+			elif request.has_key('__aca') and self.cookie_mode == 2:
+				return self.decodeAdvancedCookie(request, response)
+
+		if auth and lower(auth[:6]) == 'basic ':
+				return tuple(split(decodestring(split(auth)[-1]), ':', 1))
+
+		return None, None
+
+	def decodeUserCookie(self, request, response):
+		return self.identify('')
+
+	def validate(self, request, auth='', roles=_noroles):
+		"""
+		Perform identification, authentication, and authorization.
+		"""
+
+		v = request['PUBLISHED']
+		a, c, n, v = self._getobcontext(v, request)
+
+		name, password = self.identify(auth)
+		zLOG.LOG('exUserFolder', zLOG.DEBUG, 'identify returned %s, %s' % (name, password))
+
+		response = request.RESPONSE
+		if name is not None:
+			try:
+				xcached_user = self.xcache_getUser(name)
+				if xcached_user:
+					return None
+			except:
+				zLOG.LOG('exUserFolder', zLOG.ERROR,
+						 "error while looking up '%s' on the xcache" % name,
+						 '',
+						 sys.exc_info())
+
+			user = self.authenticate(name, password, request)
+			if user is None:
+				# If it's none, because there's no user by that name,
+				# don't raise a login, allow it to go higher...
+				# This kinda breaks for people putting in the wrong username
+				# when the Folder above uses a different auth method.
+				# But it doesn't lock Manager users out inside Zope.
+				# Perhaps this should be a tunable.
+
+				# modified by Emmanuel
+				try:
+					lou = self.listOneUser(name) 
+				except:
+					lou = None
+				if lou:
+					self.challenge(request, response, 'login_failed', auth)
+				return None
+			self.remember(name, password, request)
+			self.cache_addToCache(name, password, user)
+			emergency = self._emergency_user
+			if emergency and user is emergency:
+				if self._isTop():
+					return emergency.__of__(self)
+				else:
+					return None
+			if self.authorize(user, a, c, n, v, roles):
+				return user.__of__(self)
+			if self._isTop() and self.authorize(self._nobody, a, c, n, v, roles):
+				return self._nobody.__of__(self)
+			self.challenge(request, response, 'unauthorized')
+			return None
+		else:
+			if self.sessionTracking and self.currentPropSource:
+				user = self.createAnonymousUser(request, response)
+				if self.authorize(user, a, c, n, v, roles):
+					return user.__of__(self)
+			if self.authorize(self._nobody, a, c, n, v, roles):
+				if self._isTop():
+					return self._nobody.__of__(self)
+				else:
+					return None
+			else:
+				self.challenge(request, response, None, auth)
+				return None
+	
+	def authenticate(self, name, password, request):
+		emergency = self._emergency_user
+		if emergency and name == emergency.getUserName():
+			return emergency
+		try:
+			user = self.cache_getUser(name, password)
+			if user:
+				return user
+		except SessionExpiredException:
+			if self.idleTimeout:
+				self.logout(request)
+				self.challenge(request, request.RESPONSE, 'session_expired')
+				return None
+		user = self.getUser(name)
+		if user is not None:
+			if user.authenticate(self.currentAuthSource.listOneUser,
+								 password,
+								 request,
+								 self.currentAuthSource.remoteAuthMethod):
+				return user
+		return None
+
+	def challenge(self, request, response, reason_code='unauthorized',
+				  auth=''):
+		# Give whatever mode we're in a chance to challenge the validation
+		# failure.  We do this to preserve LoginRequired behavior.  The
+		# other thing we could do is let the None propagate on up and patch
+		# the request's unauthorized method to 
+
+		if self.cookie_mode and not auth:
+			zLOG.LOG('exUserFolder', zLOG.DEBUG, 'raising LoginRequired for %s' % reason_code)
+			if reason_code == 'login_failed':
+				response.expireCookie('__ac', path='/')
+				response.expireCookie('__aca', path='/')
+			if reason_code:
+				request.set('authFailedCode', reason_code)
+			raise 'LoginRequired', self.docLogin(self, request)
+		else:
+			zLOG.LOG('exUserFolder', zLOG.DEBUG, 'not raising LoginRequired for %s' % reason_code)
+
+	def remember(self, name, password, request):
+		response = request.RESPONSE
+		if self.cookie_mode == 1:
+			self.setBasicCookie(name, password, request, response)
+		elif self.cookie_mode == 2:
+			self.setAdvancedCookie(name, password, request, response)
+
+		if self.cookie_mode:
+			try:
+				del request.form['__ac_name']
+				del request.form['__ac_password']
+			except KeyError:
+				pass
+
+	def makeRedirectPath(self):
+		REQUEST=self.REQUEST
+		if not REQUEST.has_key('destination'):
+			script=REQUEST['SCRIPT_NAME']
+			pathinfo=REQUEST['PATH_INFO']
+			redirectstring=script+pathinfo
+			if REQUEST.has_key('QUERY_STRING'):
+				querystring='?'+quote(REQUEST['QUERY_STRING'])
+				redirectstring=redirectstring+querystring
+
+			REQUEST['destination']=redirectstring
+		
+	def redirectToLogin(self, REQUEST):
+		""" Allow methods to call from Web """
+		script=''
+		pathinfo=''
+		querystring=''
+		redirectstring=''
+		authFailedCode=''
+		
+		if not REQUEST.has_key('destination'):
+			if self.currentMembershipSource:
+				redirectstring = self.currentMembershipSource.getLoginDestination(REQUEST)
+			else:
+				script=REQUEST['SCRIPT_NAME']
+				pathinfo=REQUEST['PATH_INFO']
+				redirectstring=script+pathinfo
+				if REQUEST.has_key('QUERY_STRING'):
+					querystring='?'+REQUEST['QUERY_STRING']
+					redirectstring=redirectstring+querystring
+
+			REQUEST['destination']=redirectstring
+
+		
+		if REQUEST.has_key('authFailedCode'):
+			authFailedCode='&authFailedCode='+REQUEST['authFailedCode']
+		
+			
+			
+		if self.currentMembershipSource and self.currentMembershipSource.loginPage:
+			try:
+				REQUEST.RESPONSE.redirect('%s/%s?destination=%s%s'%(self.currentMembershipSource.baseURL, self.currentMembershipSource.loginPage,REQUEST['destination'],authFailedCode))				
+				return
+			except:
+				pass
+		return self.docLogin(self,REQUEST)
+
+	def decodeBasicCookie(self, request, response):
+		c=request['__ac']
+		c=unquote(c)
+		try:
+			c=decodestring(c)
+		except:
+			response.expireCookie('__ac', path='/')
+			raise 'LoginRequired', self.docLogin(self, request)
+		
+		name,password=tuple(split(c, ':', 1))
+		return name, password
+		
+	def decodeAdvancedCookie(self, request, response):
+		c = ''
+		try:
+			c = request['__aca']
+			c = unquote(c)
+		except:
+			response.expireCookie('__aca', path='/')
+			response.expireCookie('__ac', path='/')	# Precaution
+			response.flush()
+			raise 'LoginRequired', self.docLogin(self, request)
+
+		u = self.cache_getCookieCacheUser(c)
+		if u:
+			return u
+
+		response.expireCookie('__aca', path='/')
+		response.expireCookie('__ac', path='/')	# Precaution
+		response.flush()
+		raise 'LoginRequired', self.docLogin(self, request)
+
+	def setBasicCookie(self, name, password, request, response):
+		token='%s:%s' % (name, password)
+		token=encodestring(token)
+		token=quote(token)
+		response.setCookie('__ac', token, path='/')
+		request['__ac']=token
+		
+	def setAdvancedCookie(self, name, password, request, response):
+		xufid = self._p_oid
+		hash = encodestring(sha.new('%s%s%f%f%s'%(
+			name, password, time(), whrandom.random(), str(request))).digest())
+		token=quote(hash)
+		response.setCookie('__aca', token, path='/')
+		response.flush()
+		request['__aca']=token
+		self.cache_addToCookieCache(name, password, hash)
+		
+	def setAnonCookie(self, name, request, resp):
+		token='%s:%s' % (name, '')
+		token=encodestring(token)
+		token=quote(token)
+		resp.setCookie('__ac', token, path='/')
+		request['__ac']=token
+
+	def createAnonymousUser(self, request, resp):
+		aName=createTempName()
+		bogusREQUEST={}
+		bogusREQUEST['user_realname']='Guest User'
+		self.currentPropSource.createUser(aName, bogusREQUEST)
+		ob = AnonUser(aName, [], self.currentPropSource)
+		ob = ob.__of__(self)
+		self.cache_addToCache(aName, '', ob)			
+		self.setAnonCookie(aName, request, resp)
+		return ob
+		
+	def manage_edit(self, cookie_mode, session_length, sessionTracking=None,
+					idleTimeout=0, not_session_length=0,
+			                title=None,
+					REQUEST=None):
+		"""Change properties"""
+
+		self.cookie_mode=cookie_mode
+		self.sessionLength=session_length
+		self.notSessionLength=not_session_length
+		self.sessionTracking=sessionTracking
+		self.idleTimeout=idleTimeout
+		if title:
+			self.title = title
+		
+		if REQUEST:
+			return self.MessageDialog(self,REQUEST=REQUEST,
+				title  ='exUserFolder Changed',
+				message='exUserFolder properties have been updated',
+				action =REQUEST['URL1']+'/manage_main',
+				target ='manage_main')
+
+	def logout(self, REQUEST):
+		"""Logout"""
+		try:
+			self.cache_removeUser(REQUEST['AUTHENTICATED_USER'].getUserName())
+		except:
+			pass
+		
+		REQUEST['RESPONSE'].expireCookie('__ac', path='/')
+		REQUEST.cookies['__ac']=''
+		try:
+			acc = REQUEST['__aca']
+			self.cache_removeCookieCacheUser(acc)
+			REQUEST.cookies['__aca']=''
+		except:
+			pass
+		REQUEST['RESPONSE'].expireCookie('__aca', path='/')
+
+		
+		
+		return self.docLogout(self, REQUEST)
+
+	#
+	# Methods to be supplied by Auth Source
+	#
+	def deleteUsers(self, userids):
+		self.currentAuthSource.deleteUsers(userids)
+
+		# Comment out to use Andreas' pgSchema
+		if self.currentPropSource:
+			self.currentPropSource.deleteUsers(userids)
+
+		if self.currentGroupSource:
+			self.currentGroupSource.deleteUsers(userids)
+			
+
+	def listUsers(self):
+		return self.currentAuthSource.listUsers()
+
+	def user_names(self):
+		return self.currentAuthSource.listUserNames()
+	
+	def getUserNames(self):
+		return self.currentAuthSource.listUserNames()
+
+	def listOneUser(self,username):
+		return self.currentAuthSource.listOneUser(username)
+
+	def cryptPassword(self, username, password):
+		if hasattr(aq_base(self.currentAuthSource), 'cryptPassword'):
+			return self.currentAuthSource.cryptPassword(username, password)
+
+		if hasattr(self, 'cryptoId'):
+			return self.cryptoSources[self.cryptoId].plugin(self, username, password)
+		return self.cryptoSources['Crypt'].plugin(self, username, password)
+
+	def PropertyEditor(self):
+		""" """
+		if self.REQUEST.has_key(self.REQUEST['propName']):
+			return PropertyEditor.EditMethods[self.REQUEST['propType']](self.REQUEST['propName'], self.REQUEST[self.REQUEST['propName']])
+		return PropertyEditor.EditMethods[self.REQUEST['propType']](self.REQUEST['propName'], None)
+
+	def PropertyView(self):
+		""" """
+		if self.REQUEST.has_key(self.REQUEST['propName']):
+			return PropertyEditor.ViewMethods[self.REQUEST['propType']](self.REQUEST['propName'], self.REQUEST[self.REQUEST['propName']])
+		return PropertyEditor.ViewMethods[self.REQUEST['propType']](self.REQUEST['propName'], None)
+
+	def manage_addUserProperty(self, username, propName, propValue, REQUEST):
+		""" add a new property """
+		self.currentPropSource.setUserProperty(propName, username, propValue)
+		if hasattr(self.currentAuthSource,'manage_editUserForm'):
+			return self.currentAuthSource.manage_editUserForm(self, REQUEST)
+		else:
+			return self.manage_editUserForm(self,REQUEST)
+
+	def getUserCacheStats(self):
+		""" Stats """
+		if self.sessionLength:
+			if self.cache_getCacheStats()['attempts']:
+				return self.cache_getCacheStats()
+		return None
+
+	def getUserCacheUsers(self):
+		""" Current Users """
+		if self.sessionLength:
+			return self.cache_getCurrentUsers()
+		return None
+
+	def userFolderAddGroup(self, groupname, title='', **kw):
+		"""Creates a group"""
+		if self.currentGroupSource:
+			apply(self.currentGroupSource.addGroup, (groupname, title), kw)
+	
+	def userFolderDelGroups(self, groupnames):
+		"""Deletes groups"""
+		if self.currentGroupSource:
+			for groupname in groupnames:
+				self.currentGroupSource.delGroup(groupname)
+
+	def getGroupNames(self):
+		"""Returns a list of group names"""
+		if self.currentGroupSource:
+			return self.currentGroupSource.listGroups()
+		else:
+			return []
+
+
+	def getGroupById(self, groupname, default=_marker):
+		"""Returns the given group"""
+		if self.currentGroupSource:
+			group = self.currentGroupSource.getGroup(groupname, default)
+			if group:
+				return group.__of__(self)
+			else:
+				return None
+
+	def setUsersOfGroup(self, usernames, groupname):
+		"""Sets the users of the group"""
+		if self.currentGroupSource:
+			return self.currentGroupSource.setUsersOfGroup(usernames, groupname)
+
+	def addUsersToGroup(self, usernames, groupname):
+		"""Adds users to a group"""
+		if self.currentGroupSource:
+			return self.currentGroupSource.addUsersToGroup(usernames, groupname)
+
+	def delUsersFromGroup(self, usernames, groupname):
+		"""Deletes users from a group"""
+		if self.currentGroupSource:
+			return self.currentGroupSource.delUsersFromGroup(usernames, groupname)
+
+	def setGroupsOfUser(self, groupnames, username):
+		"""Sets the groups of a user"""
+		if self.currentGroupSource:
+			return self.currentGroupSource.setGroupsOfUser(groupnames, username)
+
+	def addGroupsOfUser(self, groupnames, username):
+		"""Add groups to a user"""
+		if self.currentGroupSource:
+			return self.currentGroupSource.addGroupsToUser(groupnames, username)
+
+	def delGroupsOfUser(self, groupnames, username):
+		"""Deletes groups from a user"""
+		if self.currentGroupSource:
+			return self.currentGroupSource.delGroupsFromUser(groupnames, username)
+
+	# We lie.
+	def hasUsers(self):
+		return 1
+
+
+def doAuthSourceForm(self,authId):
+	""" la de da """
+	return exUserFolder.authSources[authId].manage_addForm
+
+def doPropSourceForm(self,propId):
+	""" la de da """
+	return exUserFolder.propSources[propId].manage_addForm
+
+def doMembershipSourceForm(self, memberId):
+	""" doot de doo """
+	return exUserFolder.membershipSources[memberId].manage_addForm
+
+#def doGroupSourceForm(self,groupId):
+#	""" la de da """
+#	return exUserFolder.groupSources[groupId].manage_addForm
+
+def getAuthSources(self):
+	""" Hrm I need a docstring """
+	l=[]
+	for o in exUserFolder.authSources.keys():
+		l.append(
+			exUserFolder.authSources[o]
+			)
+	return l
+
+def getPropSources(self):
+	""" Hrm I need a docstring """
+	l=[]
+	for o in exUserFolder.propSources.keys():
+		l.append(
+			exUserFolder.propSources[o]
+			)
+	return l
+
+def getMembershipSources(self):
+	""" Hrm I need a docstring """
+	l=[]
+	for o in exUserFolder.membershipSources.keys():
+		l.append(
+			exUserFolder.membershipSources[o]
+			)
+	return l
+
+def getGroupSources(self):
+	""" Hrm I need a docstring """
+	return [] # UNUSED by ScoDoc: empty
+
+def getCryptoSources(self):
+	""" Doc String """
+	l = []
+	for o in exUserFolder.cryptoSources.keys():
+		l.append(
+			exUserFolder.cryptoSources[o]
+			)
+	return l
+
+def MailHostIDs(self):
+    """Find SQL database connections in the current folder and above
+
+    This function return a list of ids.
+    """
+    return [] # UNUSED BY SCODOC
+
+from types import ListType, IntType, LongType, FloatType, NoneType, DictType, StringType
+
+def getVariableType(self, o):
+
+	if type(o) == ListType:
+		return 'List'
+	if type(o) == IntType:
+		return 'Int'
+	if type(o) == LongType:
+		return 'Long'
+	if type(o) == FloatType:
+		return 'Float'
+	if type(o) == NoneType:
+		return 'None'
+	if type(o) == DictType:
+		return 'Dict'
+	if type(o) == StringType:
+		return 'String'
+	return 'Unknown or Restricted'
+
+_postUserCreate='''
+<dtml-comment>
+Replace this method with whatever you want to do
+when a user is created, you can use a Python Script,
+or External Method, or keep it as a DTML Method if you
+want to
+</dtml-comment>
+'''

Added: trunk/ZopeProducts/exUserFolder/exUserFolder.py.debug
===================================================================
--- trunk/ZopeProducts/exUserFolder/exUserFolder.py.debug	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/exUserFolder.py.debug	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,1390 @@
+#
+# Extensible User Folder
+#
+# (C) Copyright 2000,2001 The Internet (Aust) Pty Ltd
+# ACN: 082 081 472  ABN: 83 082 081 472
+# All Rights Reserved
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# Author: Andrew Milton <akm at theinternet.com.au>
+# $Id: exUserFolder.py,v 1.93 2004/11/10 14:15:33 akm Exp $
+
+##############################################################################
+#
+# Zope Public License (ZPL) Version 0.9.4
+# ---------------------------------------
+# 
+# Copyright (c) Digital Creations.  All rights reserved.
+# 
+# Redistribution and use in source and binary forms, with or
+# without modification, are permitted provided that the following
+# conditions are met:
+# 
+# 1. Redistributions in source code must retain the above
+#    copyright notice, this list of conditions, and the following
+#    disclaimer.
+# 
+# 6. Redistributions of any form whatsoever must retain the
+#    following acknowledgment:
+# 
+#      "This product includes software developed by Digital
+#      Creations for use in the Z Object Publishing Environment
+#      (http://www.zope.org/)."
+# 
+# Disclaimer
+# 
+#   THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS ``AS IS'' AND
+#   ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+#   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+#   FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT
+#   SHALL DIGITAL CREATIONS OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+#   DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+#   CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+#   PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+#   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+#   ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+#   LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+#   IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+#   THE POSSIBILITY OF SUCH DAMAGE.
+#
+##############################################################################
+
+# Portions Copyright (c) 2002 Nuxeo SARL <http://nuxeo.com>,
+#          Copyright (c) 2002 Florent Guillaume <mailto:fg at nuxeo.com>.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in
+#    the documentation and/or other materials provided with the
+#    distribution.
+# 
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+import Globals, App.Undo, socket, os, string, sha, whrandom, sys, zLOG
+
+from Globals import DTMLFile, PersistentMapping
+from string import join,strip,split,lower,upper,find
+
+from OFS.Folder import Folder
+from OFS.CopySupport import CopyContainer
+
+from base64 import decodestring, encodestring
+from urllib import quote, unquote
+
+from Acquisition import aq_base
+from AccessControl import ClassSecurityInfo
+from AccessControl.Role import RoleManager
+from AccessControl.User import BasicUser, BasicUserFolder, readUserAccessFile
+from AccessControl.PermissionRole import PermissionRole
+from AccessControl.ZopeSecurityPolicy import _noroles
+from OFS.DTMLMethod import DTMLMethod
+from time import time
+from OFS.ObjectManager import REPLACEABLE
+from Persistence import Persistent
+
+from PropertyEditor import *
+
+from User import User, AnonUser
+from UserCache.UserCache import GlobalUserCache, GlobalNegativeUserCache, GlobalAdvancedCookieCache, SessionExpiredException
+
+from LoginRequiredMessages import LoginRequiredMessages
+
+from AccessControl import Unauthorized
+
+
+
+# If there is no NUG Product just define a dummy class
+try:
+	from Products.NuxUserGroups.UserFolderWithGroups import BasicGroupFolderMixin, _marker
+except ImportError:
+	class BasicGroupFolderMixin:
+		pass
+	_marker = None
+
+# Little function to create temp usernames
+def createTempName():
+	t=time()
+	t1=time()
+	t2=time()
+	t3 = 0.0
+	t3 = (t + t1 + t2) / 3
+	un = "Anonymous %.0f"%(t3)
+	return(un)
+
+
+manage_addexUserFolderForm=DTMLFile('dtml/manage_addexUserFolder', globals(), __name__='manage_addexUserFolderForm')
+
+
+
+def manage_addexUserFolder(self, authId, propId, memberId,
+						   cookie_mode=0, session_length=0,
+						   not_session_length=0,
+						   sessionTracking=None, idleTimeout=None,
+						   REQUEST={}, groupId=None, cryptoId=None):
+	""" """
+	if hasattr(self.aq_base, 'acl_users'):
+		return Globals.MessageDialog(self,REQUEST,
+			title  ='Item Exists',
+			message='This object already contains a User Folder',
+			action ='%s/manage_main' % REQUEST['URL1'])
+	ob=exUserFolder(authId, propId, memberId, groupId, cryptoId, cookie_mode,
+					session_length, sessionTracking, idleTimeout,
+					not_session_length)
+			
+	self._setObject('acl_users', ob, None, None, 0)
+	self.__allow_groups__=self.acl_users
+	ob=getattr(self, 'acl_users')
+	ob.postInitialisation(REQUEST)
+
+	if REQUEST:
+		return self.manage_main(self, REQUEST)
+	return ''
+
+#
+# Module level caches
+#
+XUFUserCache=GlobalUserCache()
+XUFNotUserCache=GlobalNegativeUserCache()
+XUFCookieCache=GlobalAdvancedCookieCache()
+
+class exUserFolder(Folder,BasicUserFolder,BasicGroupFolderMixin,
+				   CopyContainer):
+	""" """
+
+	# HACK! We use this meta_type internally so we can be pasted into
+	# the root. We registered with 'exUserFolder' meta_type however, so
+	# our constructors work.
+	meta_type='User Folder'
+	id       ='acl_users'
+	title    ='Extensible User Folder'
+	icon     ='misc_/exUserFolder/exUserFolder.gif'
+
+	isPrincipiaFolderish=1
+	isAUserFolder=1
+	__allow_access_to_unprotected_subobjects__=1
+	authSources={}
+	propSources={}
+	cryptoSources={}
+	membershipSources={}
+	groupSources={}
+
+	manage_options=(
+		{'label':'Users',      'action':'manage_main'},
+		{'label':'Groups',	   'action':'manage_userGroups'},
+		{'label':'Parameters', 'action':'manage_editexUserFolderForm'},
+		{'label':'Authentication Source','action':'manage_editAuthSourceForm'},
+		{'label':'Properties Source','action':'manage_editPropSourceForm'},
+		{'label':'Membership Source', 'action':'manage_editMembershipSourceForm'},
+		{'label':'Groups Source','action':'manage_editGroupSourceForm'},
+		{'label':'Cache Data', 'action':'manage_showCacheData'},
+		{'label':'Security',   'action':'manage_access'},
+		{'label':'Contents',   'action':'manage_contents'},
+		{'label':'Ownership',  'action':'manage_owner'},
+		{'label':'Undo',       'action':'manage_UndoForm'},
+		)
+
+	__ac_permissions__=(
+		('View management screens', ('manage','manage_menu','manage_main',
+									 'manage_copyright', 'manage_tabs',
+									 'manage_properties', 'manage_UndoForm',
+									 'manage_edit', 'manage_contents',
+									 'manage_cutObjects','manage_copyObjects',
+									 'manage_pasteObjects',
+									 'manage_renameForm',
+									 'manage_renameObject',
+									 'manage_renameObjects', ),
+		 ('Manager',)),
+		
+		('Undo changes',            ('manage_undo_transactions',),
+		 ('Manager',)),
+		
+		('Change permissions',      ('manage_access',),
+		 ('Manager',)),
+		
+		('Manage users',            ('manage_users', 'manage_editUserForm',
+									 'manage_editUser', 'manage_addUserForm',
+									 'manage_addUser', 'manage_userActions',
+									 'userFolderAddGroup',
+									 'userFolderDelGroups',
+									 'getGroupNames',
+					                                 'getGroupById',
+								         'manage_userGroups',
+									 'manage_addGroup',
+									 'manage_showGroup',),
+		 ('Manager',)),
+		
+		('Change exUser Folders',   ('manage_edit',),
+		 ('Manager',)),
+		
+		('View',                    ('manage_changePassword',
+									 'manage_forgotPassword', 'docLogin','docLoginRedirect',
+									 'docLogout', 'logout', 'DialogHeader',
+									 'DialogFooter', 'manage_signupUser',
+									 'MessageDialog', 'redirectToLogin','manage_changeProps'),
+		 ('Anonymous', 'Authenticated', 'Manager')),
+		
+		('Manage properties',       ('manage_addProperty',
+									 'manage_editProperties',
+									 'manage_delProperties',
+									 'manage_changeProperties',
+									 'manage_propertiesForm',
+									 'manage_propertyTypeForm',
+									 'manage_changePropertyTypes',
+									 ),
+		 ('Manager',)),
+		('Access contents information', ('hasProperty', 'propertyIds',
+										 'propertyValues','propertyItems',
+										 'getProperty', 'getPropertyType',
+										 'propertyMap', 'docLogin','docLoginRedirect',
+										 'DialogHeader', 'DialogFooter',
+										 'MessageDialog', 'redirectToLogin',),
+		 ('Anonymous', 'Authenticated', 'Manager')),
+		)
+	manage_access=DTMLFile('dtml/access',globals())
+	manage_tabs=DTMLFile('common/manage_tabs',globals())
+	manage_properties=DTMLFile('dtml/properties', globals())
+	manage_main=DTMLFile('dtml/mainUser', globals())
+	manage_contents=Folder.manage_main
+	manage_showCacheData=DTMLFile('dtml/manage_showCacheData', globals())
+
+	# This is going away soon...
+	docLoginRedirect=DTMLFile('dtml/docLoginRedirect', globals())	
+
+	# Stupid crap
+	try:
+		manage_contents._setName('manage_contents')
+	except AttributeError:
+		pass
+
+
+	MessageDialog=DTMLFile('common/MessageDialog', globals())
+	MessageDialog.__replaceable__ = REPLACEABLE
+	
+	manage_addUserForm=DTMLFile('dtml/manage_addUserForm',globals())
+	manage_editUserForm=DTMLFile('dtml/manage_editUserForm',globals())
+
+	DialogHeader__roles__=()
+	DialogHeader=DTMLFile('common/DialogHeader',globals())
+	DialogFooter__roles__=()
+	DialogFooter=DTMLFile('common/DialogFooter',globals())
+
+	manage_editAuthSourceForm=DTMLFile('dtml/manage_editAuthSourceForm',globals())
+	manage_editPropSourceForm=DTMLFile('dtml/manage_editPropSourceForm',globals())
+	manage_editMembershipSourceForm=DTMLFile('dtml/manage_editMembershipSourceForm', globals())
+	manage_editGroupSourceForm=DTMLFile('dtml/manage_editGroupSourceForm',globals())
+
+	manage_addPropertyForm=DTMLFile('dtml/manage_addPropertyForm', globals())
+	manage_createPropertyForm=DTMLFile('dtml/manage_createPropertyForm', globals())
+	manage_editUserPropertyForm=DTMLFile('dtml/manage_editUserPropertyForm', globals())
+
+	manage_editexUserFolderForm=DTMLFile('dtml/manage_editexUserFolderForm', globals())
+
+	manage_userGroups=DTMLFile('dtml/mainGroup',globals())
+
+
+	# Use pages from NUG if it's there, otherwise no group support
+	try:
+		manage_addGroup = BasicGroupFolderMixin.manage_addGroup
+		manage_showGroup = BasicGroupFolderMixin.manage_showGroup
+	except:
+		manage_addGroup = None
+		manage_showGroup = None
+
+	# No more class globals
+	
+	# sessionLength=0 # Upgrading users should get no caching.
+	# notSessionLength=0 # bad cache limit
+	# cookie_mode=0
+	# sessionTracking=None # Or session tracking.
+	# idleTimeout=0
+	
+	def __init__(self, authId, propId, memberId, groupId, cryptoId,
+				 cookie_mode=0, session_length=0, sessionTracking=None,
+				 idleTimeout=0, not_session_length=0):
+		self.cookie_mode=cookie_mode
+		self.sessionLength=session_length
+		self.notSessionLength=not_session_length
+		self.sessionTracking=sessionTracking
+		self.idleTimeout=idleTimeout
+		
+		_docLogin=DTMLFile('dtml/docLogin',globals())
+		_docLogout=DTMLFile('dtml/docLogout',globals())
+
+		docLogin=DTMLMethod(__name__='docLogin')
+		docLogin.manage_edit(data=_docLogin, title='Login Page')
+		self._setObject('docLogin', docLogin, None, None, 0)
+
+		docLogout=DTMLMethod(__name__='docLogout')
+		docLogout.manage_edit(data=_docLogout, title='Logout Page')
+		self._setObject('docLogout', docLogout, None, None, 0)
+
+		postUserCreate=DTMLMethod(__name__='postUserCreate')
+		postUserCreate.manage_edit(data=_postUserCreate, title='Post User Creation methods')
+		self._setObject('postUserCreate', postUserCreate, None, None, 0)
+
+		self.manage_addAuthSource=self.authSources[authId].manage_addMethod
+		self.manage_addPropSource=self.propSources[propId].manage_addMethod
+		self.manage_addMembershipSource=self.membershipSources[memberId].manage_addMethod
+		if groupId:
+			self.manage_addGroupSource=self.groupSources[groupId].manage_addMethod
+		else:
+			self.manage_addGroupSource=None
+			self.currentGroupsSource=None
+
+		if cryptoId:
+			self.cryptoId = cryptoId
+		else:
+			self.cryptoId = 'Crypt'
+			
+	def __setstate__(self, state):
+		Persistent.__setstate__(self, state)
+		if not hasattr(self, 'currentGroupSource'):
+			self.currentGroupSource = None
+		if not hasattr(self, 'sessionLength'):
+			self.sessionLength = 0
+		if not hasattr(self, 'notSessionLength'):
+			self.notSessionLength = 0
+		if not hasattr(self, 'cookie_mode'):
+			self.cookie_mode = 0
+		if not hasattr(self, 'sessionTraining'):
+			self.sessionTracking = None
+		if not hasattr(self, 'idleTimeout'):
+			self.idleTimeout=0
+
+	def manage_beforeDelete(self, item, container):
+		zLOG.LOG("exUserFolder", zLOG.BLATHER, "Attempting to delete an exUserFolder instance")
+		if item is self:
+			try:
+				self.cache_deleteCache()
+				self.xcache_deleteCache()
+				zLOG.LOG("exUserFolder", zLOG.BLATHER, "-- Caches deleted")
+			except:
+				#pass
+				zLOG.LOG("exUserFolder", zLOG.BLATHER, "-- Cache deletion failed")
+
+			try:
+				del container.__allow_groups__
+				zLOG.LOG("exUserFolder", zLOG.BLATHER, "-- container.__allow_groups_ deleted")
+			except:
+				#pass
+				zLOG.LOG("exUserFolder", zLOG.BLATHER, "-- container.__allow_groups_ deletion failed")
+
+
+	def manage_afterAdd(self, item, container):
+		zLOG.LOG("exUserFolder", zLOG.BLATHER, "Adding an exUserFolder")
+                         
+		if item is self:
+			if hasattr(self, 'aq_base'): self=self.aq_base
+			container.__allow_groups__=self
+
+	def manage_editPropSource(self, REQUEST):
+		""" Edit Prop Source """
+		if self.currentPropSource:
+			self.currentPropSource.manage_editPropSource(REQUEST)
+		return self.manage_main(self, REQUEST)
+
+	def manage_editAuthSource(self, REQUEST):
+		""" Edit Auth Source """
+		self.currentAuthSource.manage_editAuthSource(REQUEST)
+		return self.manage_main(self, REQUEST)
+
+	def manage_editMembershipSource(self, REQUEST):
+		""" Edit Membership Source """
+		if self.currentMembershipSource:
+			return self.currentMembershipSource.manage_editMembershipSource(REQUEST)
+
+
+	def manage_editGroupSource(self, REQUEST):
+		""" Edit Group Source """
+		if self.currentGroupSource:
+			self.currentGroupSource.manage_editGroupSource(REQUEST)
+		return self.manage_main(self, REQUEST)
+
+
+	def postInitialisation(self, REQUEST):
+		self.manage_addAuthSource(self=self,REQUEST=REQUEST)
+		self.manage_addPropSource(self=self,REQUEST=REQUEST)
+		self.manage_addMembershipSource(self=self,REQUEST=REQUEST)
+		if self.manage_addGroupSource:
+			self.manage_addGroupSource(self=self,REQUEST=REQUEST)
+		else:
+			self.currentGroupSource = None
+	
+	def addAuthSource(self, REQUEST={}):
+		return self.manage_addAuthSourceForm(self, REQUEST)
+
+	def addPropSource(self, REQUEST={}):
+		return self.manage_addPropSourceForm(self, REQUEST)
+
+	def addMembershipSource(self, REQUEST={}):
+		return self.manage_editMembershipSourceForm(self, REQUEST)
+
+	def addGroupSource(self, REQUEST={}):
+		return self.manage_addGroupSourceForm(self, REQUEST)
+
+	def listUserProperties(self, username):
+		if self.currentPropSource:
+			return self.currentPropSource.listUserProperties(username=username)
+
+	def getUserProperty(self, username, key):
+		if self.currentPropSource:
+			return self.currentPropSource.getUserProperty(key=key, username=username)
+	
+	def reqattr(self, request, attr, default=None):
+		try:    return request[attr]
+		except: return default
+
+	def getAuthFailedMessage(self, code):
+		""" Return a code """
+		if LoginRequiredMessages.has_key(code):
+			return LoginRequiredMessages[code]
+		return 'Login Required'
+
+	# Called when we are deleted
+	def cache_deleteCache(self):
+		pp = string.join(self.getPhysicalPath(), '/')
+		XUFUserCache.deleteCache(pp)
+		
+	def cache_addToCache(self, username, password, user):
+		if not self.sessionLength:
+			return
+		# fix by emmanuel
+		if username == self._emergency_user.getUserName():
+			return
+		# /fix
+		pp = string.join(self.getPhysicalPath(), '/')
+		x = XUFUserCache.getCache(pp)
+		if not x:
+			x = XUFUserCache.createCache(pp, self.sessionLength)
+		x.addToCache(username, password, user)
+
+	def cache_getUser(self, username, password, checkpassword=1):
+		if not self.sessionLength:
+			return None
+		pp = string.join(self.getPhysicalPath(), '/')
+		x = XUFUserCache.getCache(pp)
+		if not x:
+			return None
+		u = x.getUser(self, username, password, checkpassword)
+		if u is not None:
+			u = u.__of__(self)
+		return u
+
+	def cache_removeUser(self, username):
+		if not self.sessionLength:
+			return
+		pp = string.join(self.getPhysicalPath(), '/')
+		x = XUFUserCache.getCache(pp)
+		if x:
+			x.removeUser(username)
+
+	def cache_getCacheStats(self):
+		pp = string.join(self.getPhysicalPath(), '/')
+		x = XUFUserCache.getCache(pp)
+		if not x:
+			x = XUFUserCache.createCache(pp, self.sessionLength)			
+		if x:
+			return x.getCacheStats()
+
+	def cache_getCurrentUsers(self):
+		pp = string.join(self.getPhysicalPath(), '/')
+		x = XUFUserCache.getCache(pp)
+		if x:
+			return x.getCurrentUsers(self)
+
+	# negative cache functions
+	def xcache_deleteCache(self):
+		pp = string.join(self.getPhysicalPath(), '/')
+		XUFNotUserCache.deleteCache(pp)
+		
+	def xcache_addToCache(self, username):
+		if not self.notSessionLength:
+			return
+		pp = string.join(self.getPhysicalPath(), '/')
+		x = XUFNotUserCache.getCache(pp)
+		if not x:
+			x = XUFNotUserCache.createCache(pp, self.notSessionLength)
+		x.addToCache(username)
+
+	def xcache_getUser(self, username):
+		if not self.notSessionLength:
+			return None
+		pp = string.join(self.getPhysicalPath(), '/')
+		x = XUFNotUserCache.getCache(pp)
+		if not x:
+			return None
+		return x.getUser(username)
+
+	def xcache_removeUser(self, username):
+		if not self.notSessionLength:
+			return
+		pp = string.join(self.getPhysicalPath(), '/')
+		x = XUFNotUserCache.getCache(pp)
+		if x:
+			x.removeUser(username)
+
+	# Cookie Cache Functions
+	def cache_deleteCookieCache(self):
+		pp = string.join(self.getPhysicalPath(), '/')
+		XUFCookieCache.deleteCache(pp)
+
+	def cache_addToCookieCache(self, username, password, key):
+		pp = string.join(self.getPhysicalPath(), '/')
+		c = XUFCookieCache.getCache(pp)
+		if not c:
+			c = XUFCookieCache.createCache(pp, 86400)
+		c.addToCache(username, password, key)
+
+	def cache_getCookieCacheUser(self, key):
+		pp = string.join(self.getPhysicalPath(), '/')
+		c = XUFCookieCache.getCache(pp)
+		if not c:
+			return None
+		return c.getUser(key)
+
+	def cache_removeCookieCacheUser(self, key):
+		pp = string.join(self.getPhysicalPath(), '/')
+		c = XUFCookieCache.getCache(pp)
+		if c:
+			c.removeUser(key)
+		
+	def manage_editUser(self, username, REQUEST={}):
+		""" Edit a User """
+		# username=self.reqattr(REQUEST,'username')
+		password=self.reqattr(REQUEST,'password')
+		password_confirm=self.reqattr(REQUEST,'password_confirm')
+		roles=self.reqattr(REQUEST,'roles', [])
+		groups=self.reqattr(REQUEST, 'groupnames', [])
+
+		if not username:
+			return self.MessageDialog(self,REQUEST=REQUEST,
+				title  ='Illegal value', 
+				message='A username must be specified',
+				action ='manage_main')
+
+		if (password or password_confirm) and (password != password_confirm):
+			return self.MessageDialog(self,REQUEST=REQUEST,
+				title  ='Illegal value', 
+				message='Password and confirmation do not match',
+				action ='manage_main')
+		
+		self._doChangeUser(username, password, roles, domains='', groups=groups, REQUEST=REQUEST)
+		
+		return self.MessageDialog(self,REQUEST=REQUEST,
+			title = 'User Updated',
+			message= 'User %s was updated.'%(username),
+			action = 'manage_main')
+
+
+	#
+	# Membership helper
+	#
+	def goHome(self, REQUEST, RESPONSE):
+		""" Go to home directory """
+		if self.currentMembershipSource:
+			self.currentMembershipSource.goHome(REQUEST, RESPONSE)
+
+
+	# 
+	# Membership method of changing user properties
+	# 
+
+	def manage_changeProps(self, REQUEST):
+		""" Change Properties """
+		if self.currentMembershipSource:
+			return self.currentMembershipSource.changeProperties(REQUEST)
+		else:
+			
+			return self.MessageDialog(self,REQUEST,
+				title = 'This is a test',
+				message= 'This was a test',
+				action = '..')
+ 
+
+	#
+	# Membership method of adding a new user.
+	# If everything goes well the membership plugin calls manage_addUser()
+	#
+	
+	def manage_signupUser(self, REQUEST):
+		""" Signup a new user """
+		""" This is seperate so you can add users using the normal """
+		""" interface w/o going through membership policy """
+
+		username=self.reqattr(REQUEST,'username')
+		roles=self.reqattr(REQUEST,'roles')
+
+		if not username:
+			return self.MessageDialog(self,REQUEST=REQUEST,
+				title  ='Illegal value', 
+				message='A username must be specified',
+				action ='manage_main')
+
+		if (self.getUser(username) or
+			(self._emergency_user and
+			 username == self._emergency_user.getUserName())):
+			return self.MessageDialog(self,REQUEST=REQUEST,
+				title  ='Illegal value', 
+				message='A user with the specified name already exists',
+				action ='manage_main')
+
+		if self.currentMembershipSource:
+			return self.currentMembershipSource.createUser(REQUEST)
+
+	#
+	# Membership method of changing passwords
+	#
+	def manage_changePassword(self, REQUEST):
+		""" Change a password """
+		if self.currentMembershipSource:
+			return self.currentMembershipSource.changePassword(REQUEST)
+		
+	#
+	# User says they can't remember their password
+	#
+	def manage_forgotPassword(self, REQUEST):
+		""" So something about forgetting your password """
+		if self.currentMembershipSource:
+			return self.currentMembershipSource.forgotPassword(REQUEST)
+		
+	def __creatable_by_emergency_user__(self): return 1
+
+	def manage_addUser(self, REQUEST):
+		""" Add a New User """
+		username=self.reqattr(REQUEST,'username')
+		password=self.reqattr(REQUEST,'password')
+		password_confirm=self.reqattr(REQUEST,'password_confirm')
+		roles=self.reqattr(REQUEST,'roles')
+		groups=self.reqattr(REQUEST, 'groupnames', [])
+
+		if not username:
+			return self.MessageDialog(self,REQUEST=REQUEST,
+				title  ='Illegal value', 
+				message='A username must be specified',
+				action ='manage_main')
+
+		if not password or not password_confirm:
+			return self.MessageDialog(self,REQUEST=REQUEST,
+				title  ='Illegal value', 
+				message='Password and confirmation must be specified',
+				action ='manage_main')
+
+		if (self.getUser(username) or
+			(self._emergency_user and
+			 username == self._emergency_user.getUserName())):
+			return self.MessageDialog(self,REQUEST=REQUEST,
+				title  ='Illegal value', 
+				message='A user with the specified name already exists',
+				action ='manage_main')
+
+		if (password or password_confirm) and (password != password_confirm):
+			return self.MessageDialog(self,REQUEST=REQUEST,
+				title  ='Illegal value', 
+				message='Password and confirmation do not match',
+				action ='manage_main')
+
+		self._doAddUser(username, password, roles, domains='', groups=groups, REQUEST=REQUEST)
+		#
+		# Explicitly check our contents, do not just acquire postUserCreate
+		#
+		if 'postUserCreate' in self.objectIds():
+			self.postUserCreate(self, REQUEST)
+		
+		return self.MessageDialog(self,REQUEST=REQUEST,
+			title = 'User Created',
+			message= 'User %s was created.'%(username),
+			action = 'manage_main')
+
+	def _doAddUser(self, name, password, roles, domains='', groups=(), **kw):
+		""" For programatically adding simple users """
+		self.currentAuthSource.createUser(name, password, roles)
+		if self.currentPropSource:
+			# copy items not in kw from REQUEST
+			REQUEST = kw.get('REQUEST', self.REQUEST)
+			map(kw.setdefault, REQUEST.keys(), REQUEST.values())
+			self.currentPropSource.createUser(name, kw)
+		if self.currentGroupSource:
+			self.currentGroupSource.setGroupsOfUser(groups, name)
+
+	def _doChangeUser(self, name, password, roles, domains='', groups=(), **kw):
+		self.currentAuthSource.updateUser(name, password, roles)
+		if self.currentPropSource:
+			# copy items not in kw from REQUEST
+			REQUEST = kw.get('REQUEST', self.REQUEST)
+			map(kw.setdefault, REQUEST.keys(), REQUEST.values())
+			self.currentPropSource.updateUser(name, kw)
+		# We may have updated roles or passwords... flush the user...
+		self.cache_removeUser(name)
+		self.xcache_removeUser(name)
+		if self.currentGroupSource:
+			self.currentGroupSource.setGroupsOfUser(groups, name)
+		
+	def _doDelUsers(self, names):
+		self.deleteUsers(names)
+
+	def _createInitialUser(self):
+		if len(self.getUserNames()) <= 1:
+			info = readUserAccessFile('inituser')
+			if info:
+				name, password, domains, remote_user_mode = info
+				self._doAddUser(name, password, ('Manager',), domains)
+
+
+	def getUsers(self):
+		"""Return a list of user objects or [] if no users exist"""
+		data=[]
+		try:
+			items=self.listUsers()
+			for people in items:
+				user=User({'name':		people['username'],
+						   'password':	people['password'],
+						   'roles':		people['roles'], 
+						   'domains':	''},
+						  self.currentPropSource,
+						  self.cryptPassword,
+						  self.currentAuthSource,
+						  self.currentGroupSource)
+				data.append(user)
+		except:
+			import traceback
+			traceback.print_exc()
+			pass
+			
+		return data
+
+	getUsers__roles__=('Anonymous','Authenticated')
+	
+	def getUser(self, name):
+		"""Return the named user object or None if no such user exists"""
+		user = self.cache_getUser(name, '', 0)
+		if user:
+			return user
+		try:
+			items=self.listOneUser(name)
+		except:
+			zLOG.LOG("exUserFolder", zLOG.ERROR,
+                                 "error trying to list user %s" % name,
+                                 '',
+                                 sys.exc_info())
+			return None
+
+		if not items:
+			return None
+		
+		for people in items:
+			user =  User({'name':    people['username'],
+						  'password':people['password'],
+						  'roles':   people['roles'],
+						  'domains':	''},
+						 self.currentPropSource,
+						 self.cryptPassword,
+						 self.currentAuthSource,
+						 self.currentGroupSource)
+			return user
+		return None
+		
+	def manage_userActions(self, submit=None, userids=None, REQUEST={}):
+		""" Do things to users """
+		if submit==' Add ':
+			if hasattr(self.currentAuthSource,'manage_addUserForm'):
+				return self.currentAuthSource.manage_addUserForm(self, REQUEST)
+			else:
+				return self.manage_addUserForm(self, REQUEST)
+		if submit==' Delete ':
+			self.deleteUsers(userids)
+			return self.MessageDialog(self,REQUEST=REQUEST,
+				title  ='Users Deleted',
+				message='Selected Users have been deleted',
+				action =REQUEST['URL1']+'/manage_main',
+				target ='manage_main')
+
+		if REQUEST:
+			return self.manage_main(self,REQUEST)
+		return ''
+
+	def identify(self, auth):
+		# Identify the username and password.  This is where new modes should
+		# be called from, and if pluggable modes ever take shape, here ya go!
+
+		if self.cookie_mode and not auth:
+			# The identify signature does not include the request, sadly.
+			# I think that's dumb.
+			request = self.REQUEST
+			response = request.RESPONSE
+	
+			if request.has_key('__ac_name') and request.has_key('__ac_password'):
+				return request['__ac_name'], request['__ac_password']
+			elif request.has_key('__ac') and self.cookie_mode == 1:
+				return self.decodeBasicCookie(request, response)
+			elif request.has_key('__aca') and self.cookie_mode == 2:
+				return self.decodeAdvancedCookie(request, response)
+
+		if auth and lower(auth[:6]) == 'basic ':
+				return tuple(split(decodestring(split(auth)[-1]), ':', 1))
+
+		return None, None
+
+	def decodeUserCookie(self, request, response):
+		return self.identify('')
+
+	def validate(self, request, auth='', roles=_noroles):
+		"""
+		Perform identification, authentication, and authorization.
+		"""
+
+		v = request['PUBLISHED']
+		a, c, n, v = self._getobcontext(v, request)
+
+		name, password = self.identify(auth)
+		zLOG.LOG('exUserFolder', zLOG.DEBUG, 'identify returned %s, %s' % (name, password))
+
+		response = request.RESPONSE
+		if name is not None:
+			try:
+				xcached_user = self.xcache_getUser(name)
+				if xcached_user:
+					return None
+			except:
+				zLOG.LOG('exUserFolder', zLOG.ERROR,
+						 "error while looking up '%s' on the xcache" % name,
+						 '',
+						 sys.exc_info())
+
+			user = self.authenticate(name, password, request)
+			if user is None:
+				# If it's none, because there's no user by that name,
+				# don't raise a login, allow it to go higher...
+				# This kinda breaks for people putting in the wrong username
+				# when the Folder above uses a different auth method.
+				# But it doesn't lock Manager users out inside Zope.
+				# Perhaps this should be a tunable.
+
+				# modified by Emmanuel
+				try:
+					lou = self.listOneUser(name) 
+				except:
+					lou = None
+				if lou:
+					self.challenge(request, response, 'login_failed', auth)
+				return None
+			self.remember(name, password, request)
+			self.cache_addToCache(name, password, user)
+			emergency = self._emergency_user
+			if emergency and user is emergency:
+				if self._isTop():
+					return emergency.__of__(self)
+				else:
+					return None
+			if self.authorize(user, a, c, n, v, roles):
+				return user.__of__(self)
+			if self._isTop() and self.authorize(self._nobody, a, c, n, v, roles):
+				return self._nobody.__of__(self)
+			self.challenge(request, response, 'unauthorized')
+			return None
+		else:
+			if self.sessionTracking and self.currentPropSource:
+				user = self.createAnonymousUser(request, response)
+				if self.authorize(user, a, c, n, v, roles):
+					return user.__of__(self)
+			if self.authorize(self._nobody, a, c, n, v, roles):
+				if self._isTop():
+					return self._nobody.__of__(self)
+				else:
+					return None
+			else:
+				self.challenge(request, response, None, auth)
+				return None
+
+	_logfilename =  Globals.INSTANCE_HOME + '/log/scodoc_auth.log'
+	def authenticate(self, name, password, request): # debug DO NOT DISTRIBUTE !
+		f = open( self._logfilename, 'a' )
+		f.write('authenticating %s...' % name)
+		f.flush()
+		r = self._real_authenticate(name, password, request)
+		f.write('%s\n' % str(r))
+		return r
+
+	def _real_authenticate(self, name, password, request):
+		emergency = self._emergency_user
+		if emergency and name == emergency.getUserName():
+			return emergency
+		try:
+			user = self.cache_getUser(name, password)
+			if user:
+				return user
+		except SessionExpiredException:
+			if self.idleTimeout:
+				self.logout(request)
+				self.challenge(request, request.RESPONSE, 'session_expired')
+				return None
+		user = self.getUser(name)
+		if user is not None:
+			if user.authenticate(self.currentAuthSource.listOneUser,
+								 password,
+								 request,
+								 self.currentAuthSource.remoteAuthMethod):
+				return user
+		return None
+
+	def challenge(self, request, response, reason_code='unauthorized',
+				  auth=''):
+		# Give whatever mode we're in a chance to challenge the validation
+		# failure.  We do this to preserve LoginRequired behavior.  The
+		# other thing we could do is let the None propagate on up and patch
+		# the request's unauthorized method to 
+
+		if self.cookie_mode and not auth:
+			zLOG.LOG('exUserFolder', zLOG.DEBUG, 'raising LoginRequired for %s' % reason_code)
+			if reason_code == 'login_failed':
+				response.expireCookie('__ac', path='/')
+				response.expireCookie('__aca', path='/')
+			if reason_code:
+				request.set('authFailedCode', reason_code)
+			raise 'LoginRequired', self.docLogin(self, request)
+		else:
+			zLOG.LOG('exUserFolder', zLOG.DEBUG, 'not raising LoginRequired for %s' % reason_code)
+
+	def remember(self, name, password, request):
+		response = request.RESPONSE
+		if self.cookie_mode == 1:
+			self.setBasicCookie(name, password, request, response)
+		elif self.cookie_mode == 2:
+			self.setAdvancedCookie(name, password, request, response)
+
+		if self.cookie_mode:
+			try:
+				del request.form['__ac_name']
+				del request.form['__ac_password']
+			except KeyError:
+				pass
+
+	def makeRedirectPath(self):
+		REQUEST=self.REQUEST
+		if not REQUEST.has_key('destination'):
+			script=REQUEST['SCRIPT_NAME']
+			pathinfo=REQUEST['PATH_INFO']
+			redirectstring=script+pathinfo
+			if REQUEST.has_key('QUERY_STRING'):
+				querystring='?'+quote(REQUEST['QUERY_STRING'])
+				redirectstring=redirectstring+querystring
+
+			REQUEST['destination']=redirectstring
+		
+	def redirectToLogin(self, REQUEST):
+		""" Allow methods to call from Web """
+		script=''
+		pathinfo=''
+		querystring=''
+		redirectstring=''
+		authFailedCode=''
+		
+		if not REQUEST.has_key('destination'):
+			if self.currentMembershipSource:
+				redirectstring = self.currentMembershipSource.getLoginDestination(REQUEST)
+			else:
+				script=REQUEST['SCRIPT_NAME']
+				pathinfo=REQUEST['PATH_INFO']
+				redirectstring=script+pathinfo
+				if REQUEST.has_key('QUERY_STRING'):
+					querystring='?'+REQUEST['QUERY_STRING']
+					redirectstring=redirectstring+querystring
+
+			REQUEST['destination']=redirectstring
+
+		
+		if REQUEST.has_key('authFailedCode'):
+			authFailedCode='&authFailedCode='+REQUEST['authFailedCode']
+		
+			
+			
+		if self.currentMembershipSource and self.currentMembershipSource.loginPage:
+			try:
+				REQUEST.RESPONSE.redirect('%s/%s?destination=%s%s'%(self.currentMembershipSource.baseURL, self.currentMembershipSource.loginPage,REQUEST['destination'],authFailedCode))				
+				return
+			except:
+				pass
+		return self.docLogin(self,REQUEST)
+
+	def decodeBasicCookie(self, request, response):
+		c=request['__ac']
+		c=unquote(c)
+		try:
+			c=decodestring(c)
+		except:
+			response.expireCookie('__ac', path='/')
+			raise 'LoginRequired', self.docLogin(self, request)
+		
+		name,password=tuple(split(c, ':', 1))
+		return name, password
+		
+	def decodeAdvancedCookie(self, request, response):
+		c = ''
+		try:
+			c = request['__aca']
+			c = unquote(c)
+		except:
+			response.expireCookie('__aca', path='/')
+			response.expireCookie('__ac', path='/')	# Precaution
+			response.flush()
+			raise 'LoginRequired', self.docLogin(self, request)
+
+		u = self.cache_getCookieCacheUser(c)
+		if u:
+			return u
+
+		response.expireCookie('__aca', path='/')
+		response.expireCookie('__ac', path='/')	# Precaution
+		response.flush()
+		raise 'LoginRequired', self.docLogin(self, request)
+
+	def setBasicCookie(self, name, password, request, response):
+		token='%s:%s' % (name, password)
+		token=encodestring(token)
+		token=quote(token)
+		response.setCookie('__ac', token, path='/')
+		request['__ac']=token
+		
+	def setAdvancedCookie(self, name, password, request, response):
+		xufid = self._p_oid
+		hash = encodestring(sha.new('%s%s%f%f%s'%(
+			name, password, time(), whrandom.random(), str(request))).digest())
+		token=quote(hash)
+		response.setCookie('__aca', token, path='/')
+		response.flush()
+		request['__aca']=token
+		self.cache_addToCookieCache(name, password, hash)
+		
+	def setAnonCookie(self, name, request, resp):
+		token='%s:%s' % (name, '')
+		token=encodestring(token)
+		token=quote(token)
+		resp.setCookie('__ac', token, path='/')
+		request['__ac']=token
+
+	def createAnonymousUser(self, request, resp):
+		aName=createTempName()
+		bogusREQUEST={}
+		bogusREQUEST['user_realname']='Guest User'
+		self.currentPropSource.createUser(aName, bogusREQUEST)
+		ob = AnonUser(aName, [], self.currentPropSource)
+		ob = ob.__of__(self)
+		self.cache_addToCache(aName, '', ob)			
+		self.setAnonCookie(aName, request, resp)
+		return ob
+		
+	def manage_edit(self, cookie_mode, session_length, sessionTracking=None,
+					idleTimeout=0, not_session_length=0,
+			                title=None,
+					REQUEST=None):
+		"""Change properties"""
+
+		self.cookie_mode=cookie_mode
+		self.sessionLength=session_length
+		self.notSessionLength=not_session_length
+		self.sessionTracking=sessionTracking
+		self.idleTimeout=idleTimeout
+		if title:
+			self.title = title
+		
+		if REQUEST:
+			return self.MessageDialog(self,REQUEST=REQUEST,
+				title  ='exUserFolder Changed',
+				message='exUserFolder properties have been updated',
+				action =REQUEST['URL1']+'/manage_main',
+				target ='manage_main')
+
+	def logout(self, REQUEST):
+		"""Logout"""
+		try:
+			self.cache_removeUser(REQUEST['AUTHENTICATED_USER'].getUserName())
+		except:
+			pass
+		
+		REQUEST['RESPONSE'].expireCookie('__ac', path='/')
+		REQUEST.cookies['__ac']=''
+		try:
+			acc = REQUEST['__aca']
+			self.cache_removeCookieCacheUser(acc)
+			REQUEST.cookies['__aca']=''
+		except:
+			pass
+		REQUEST['RESPONSE'].expireCookie('__aca', path='/')
+
+		
+		
+		return self.docLogout(self, REQUEST)
+
+	#
+	# Methods to be supplied by Auth Source
+	#
+	def deleteUsers(self, userids):
+		self.currentAuthSource.deleteUsers(userids)
+
+		# Comment out to use Andreas' pgSchema
+		if self.currentPropSource:
+			self.currentPropSource.deleteUsers(userids)
+
+		if self.currentGroupSource:
+			self.currentGroupSource.deleteUsers(userids)
+			
+
+	def listUsers(self):
+		return self.currentAuthSource.listUsers()
+
+	def user_names(self):
+		return self.currentAuthSource.listUserNames()
+	
+	def getUserNames(self):
+		return self.currentAuthSource.listUserNames()
+
+	def listOneUser(self,username):
+		return self.currentAuthSource.listOneUser(username)
+
+	def cryptPassword(self, username, password):
+		if hasattr(aq_base(self.currentAuthSource), 'cryptPassword'):
+			return self.currentAuthSource.cryptPassword(username, password)
+
+		if hasattr(self, 'cryptoId'):
+			return self.cryptoSources[self.cryptoId].plugin(self, username, password)
+		return self.cryptoSources['Crypt'].plugin(self, username, password)
+
+	def PropertyEditor(self):
+		""" """
+		if self.REQUEST.has_key(self.REQUEST['propName']):
+			return PropertyEditor.EditMethods[self.REQUEST['propType']](self.REQUEST['propName'], self.REQUEST[self.REQUEST['propName']])
+		return PropertyEditor.EditMethods[self.REQUEST['propType']](self.REQUEST['propName'], None)
+
+	def PropertyView(self):
+		""" """
+		if self.REQUEST.has_key(self.REQUEST['propName']):
+			return PropertyEditor.ViewMethods[self.REQUEST['propType']](self.REQUEST['propName'], self.REQUEST[self.REQUEST['propName']])
+		return PropertyEditor.ViewMethods[self.REQUEST['propType']](self.REQUEST['propName'], None)
+
+	def manage_addUserProperty(self, username, propName, propValue, REQUEST):
+		""" add a new property """
+		self.currentPropSource.setUserProperty(propName, username, propValue)
+		if hasattr(self.currentAuthSource,'manage_editUserForm'):
+			return self.currentAuthSource.manage_editUserForm(self, REQUEST)
+		else:
+			return self.manage_editUserForm(self,REQUEST)
+
+	def getUserCacheStats(self):
+		""" Stats """
+		if self.sessionLength:
+			if self.cache_getCacheStats()['attempts']:
+				return self.cache_getCacheStats()
+		return None
+
+	def getUserCacheUsers(self):
+		""" Current Users """
+		if self.sessionLength:
+			return self.cache_getCurrentUsers()
+		return None
+
+	def userFolderAddGroup(self, groupname, title='', **kw):
+		"""Creates a group"""
+		if self.currentGroupSource:
+			apply(self.currentGroupSource.addGroup, (groupname, title), kw)
+	
+	def userFolderDelGroups(self, groupnames):
+		"""Deletes groups"""
+		if self.currentGroupSource:
+			for groupname in groupnames:
+				self.currentGroupSource.delGroup(groupname)
+
+	def getGroupNames(self):
+		"""Returns a list of group names"""
+		if self.currentGroupSource:
+			return self.currentGroupSource.listGroups()
+		else:
+			return []
+
+
+	def getGroupById(self, groupname, default=_marker):
+		"""Returns the given group"""
+		if self.currentGroupSource:
+			group = self.currentGroupSource.getGroup(groupname, default)
+			if group:
+				return group.__of__(self)
+			else:
+				return None
+
+	def setUsersOfGroup(self, usernames, groupname):
+		"""Sets the users of the group"""
+		if self.currentGroupSource:
+			return self.currentGroupSource.setUsersOfGroup(usernames, groupname)
+
+	def addUsersToGroup(self, usernames, groupname):
+		"""Adds users to a group"""
+		if self.currentGroupSource:
+			return self.currentGroupSource.addUsersToGroup(usernames, groupname)
+
+	def delUsersFromGroup(self, usernames, groupname):
+		"""Deletes users from a group"""
+		if self.currentGroupSource:
+			return self.currentGroupSource.delUsersFromGroup(usernames, groupname)
+
+	def setGroupsOfUser(self, groupnames, username):
+		"""Sets the groups of a user"""
+		if self.currentGroupSource:
+			return self.currentGroupSource.setGroupsOfUser(groupnames, username)
+
+	def addGroupsOfUser(self, groupnames, username):
+		"""Add groups to a user"""
+		if self.currentGroupSource:
+			return self.currentGroupSource.addGroupsToUser(groupnames, username)
+
+	def delGroupsOfUser(self, groupnames, username):
+		"""Deletes groups from a user"""
+		if self.currentGroupSource:
+			return self.currentGroupSource.delGroupsFromUser(groupnames, username)
+
+	# We lie.
+	def hasUsers(self):
+		return 1
+
+
+def doAuthSourceForm(self,authId):
+	""" la de da """
+	return exUserFolder.authSources[authId].manage_addForm
+
+def doPropSourceForm(self,propId):
+	""" la de da """
+	return exUserFolder.propSources[propId].manage_addForm
+
+def doMembershipSourceForm(self, memberId):
+	""" doot de doo """
+	return exUserFolder.membershipSources[memberId].manage_addForm
+
+def doGroupSourceForm(self,groupId):
+	""" la de da """
+	return exUserFolder.groupSources[groupId].manage_addForm
+
+def getAuthSources(self):
+	""" Hrm I need a docstring """
+	l=[]
+	for o in exUserFolder.authSources.keys():
+		l.append(
+			exUserFolder.authSources[o]
+			)
+	return l
+
+def getPropSources(self):
+	""" Hrm I need a docstring """
+	l=[]
+	for o in exUserFolder.propSources.keys():
+		l.append(
+			exUserFolder.propSources[o]
+			)
+	return l
+
+def getMembershipSources(self):
+	""" Hrm I need a docstring """
+	l=[]
+	for o in exUserFolder.membershipSources.keys():
+		l.append(
+			exUserFolder.membershipSources[o]
+			)
+	return l
+
+def getGroupSources(self):
+	""" Hrm I need a docstring """
+	l=[]
+	for o in exUserFolder.groupSources.keys():
+		l.append(
+			exUserFolder.groupSources[o]
+			)
+	return l
+
+def getCryptoSources(self):
+	""" Doc String """
+	l = []
+	for o in exUserFolder.cryptoSources.keys():
+		l.append(
+			exUserFolder.cryptoSources[o]
+			)
+	return l
+
+def MailHostIDs(self):
+    """Find SQL database connections in the current folder and above
+
+    This function return a list of ids.
+    """
+    ids={}
+    have_id=ids.has_key
+    StringType=type('')
+
+    while self is not None:
+        if hasattr(self, 'objectValues'):
+            for o in self.objectValues():
+                if (hasattr(o,'meta_type') and o.meta_type=='Mail Host'
+                    and hasattr(o,'id')):
+                    id=o.id
+                    if type(id) is not StringType: id=id()
+                    if not have_id(id):
+                        if hasattr(o,'title_and_id'): o=o.title_and_id()
+                        else: o=id
+                        ids[id]=id
+        if hasattr(self, 'aq_parent'): self=self.aq_parent
+        else: self=None
+
+    ids=map(lambda item: (item[1], item[0]), ids.items())
+    ids.sort()
+    return ids
+
+
+from types import ListType, IntType, LongType, FloatType, NoneType, DictType, StringType
+
+def getVariableType(self, o):
+
+	if type(o) == ListType:
+		return 'List'
+	if type(o) == IntType:
+		return 'Int'
+	if type(o) == LongType:
+		return 'Long'
+	if type(o) == FloatType:
+		return 'Float'
+	if type(o) == NoneType:
+		return 'None'
+	if type(o) == DictType:
+		return 'Dict'
+	if type(o) == StringType:
+		return 'String'
+	return 'Unknown or Restricted'
+
+_postUserCreate='''
+<dtml-comment>
+Replace this method with whatever you want to do
+when a user is created, you can use a Python Script,
+or External Method, or keep it as a DTML Method if you
+want to
+</dtml-comment>
+'''

Added: trunk/ZopeProducts/exUserFolder/exUserFolder.py.orig
===================================================================
--- trunk/ZopeProducts/exUserFolder/exUserFolder.py.orig	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/exUserFolder.py.orig	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,1381 @@
+#
+# Extensible User Folder
+#
+# (C) Copyright 2000,2001 The Internet (Aust) Pty Ltd
+# ACN: 082 081 472  ABN: 83 082 081 472
+# All Rights Reserved
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# Author: Andrew Milton <akm at theinternet.com.au>
+# $Id: exUserFolder.py,v 1.93 2004/11/10 14:15:33 akm Exp $
+
+##############################################################################
+#
+# Zope Public License (ZPL) Version 0.9.4
+# ---------------------------------------
+# 
+# Copyright (c) Digital Creations.  All rights reserved.
+# 
+# Redistribution and use in source and binary forms, with or
+# without modification, are permitted provided that the following
+# conditions are met:
+# 
+# 1. Redistributions in source code must retain the above
+#    copyright notice, this list of conditions, and the following
+#    disclaimer.
+# 
+# 6. Redistributions of any form whatsoever must retain the
+#    following acknowledgment:
+# 
+#      "This product includes software developed by Digital
+#      Creations for use in the Z Object Publishing Environment
+#      (http://www.zope.org/)."
+# 
+# Disclaimer
+# 
+#   THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS ``AS IS'' AND
+#   ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+#   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+#   FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT
+#   SHALL DIGITAL CREATIONS OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+#   DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+#   CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+#   PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+#   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+#   ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+#   LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+#   IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+#   THE POSSIBILITY OF SUCH DAMAGE.
+#
+##############################################################################
+
+# Portions Copyright (c) 2002 Nuxeo SARL <http://nuxeo.com>,
+#          Copyright (c) 2002 Florent Guillaume <mailto:fg at nuxeo.com>.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in
+#    the documentation and/or other materials provided with the
+#    distribution.
+# 
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+import Globals, App.Undo, socket, os, string, sha, whrandom, sys, zLOG
+
+from Globals import DTMLFile, PersistentMapping
+from string import join,strip,split,lower,upper,find
+
+from OFS.Folder import Folder
+from OFS.CopySupport import CopyContainer
+
+from base64 import decodestring, encodestring
+from urllib import quote, unquote
+
+from Acquisition import aq_base
+from AccessControl import ClassSecurityInfo
+from AccessControl.Role import RoleManager
+from AccessControl.User import BasicUser, BasicUserFolder, readUserAccessFile
+from AccessControl.PermissionRole import PermissionRole
+from AccessControl.ZopeSecurityPolicy import _noroles
+from OFS.DTMLMethod import DTMLMethod
+from time import time
+from OFS.ObjectManager import REPLACEABLE
+from Persistence import Persistent
+
+from PropertyEditor import *
+
+from User import User, AnonUser
+from UserCache.UserCache import GlobalUserCache, GlobalNegativeUserCache, GlobalAdvancedCookieCache, SessionExpiredException
+
+from LoginRequiredMessages import LoginRequiredMessages
+
+from AccessControl import Unauthorized
+
+
+
+# If there is no NUG Product just define a dummy class
+try:
+	from Products.NuxUserGroups.UserFolderWithGroups import BasicGroupFolderMixin, _marker
+except ImportError:
+	class BasicGroupFolderMixin:
+		pass
+	_marker = None
+
+# Little function to create temp usernames
+def createTempName():
+	t=time()
+	t1=time()
+	t2=time()
+	t3 = 0.0
+	t3 = (t + t1 + t2) / 3
+	un = "Anonymous %.0f"%(t3)
+	return(un)
+
+
+manage_addexUserFolderForm=DTMLFile('dtml/manage_addexUserFolder', globals(), __name__='manage_addexUserFolderForm')
+
+
+
+def manage_addexUserFolder(self, authId, propId, memberId,
+						   cookie_mode=0, session_length=0,
+						   not_session_length=0,
+						   sessionTracking=None, idleTimeout=None,
+						   REQUEST={}, groupId=None, cryptoId=None):
+	""" """
+	if hasattr(self.aq_base, 'acl_users'):
+		return Globals.MessageDialog(self,REQUEST,
+			title  ='Item Exists',
+			message='This object already contains a User Folder',
+			action ='%s/manage_main' % REQUEST['URL1'])
+	ob=exUserFolder(authId, propId, memberId, groupId, cryptoId, cookie_mode,
+					session_length, sessionTracking, idleTimeout,
+					not_session_length)
+			
+	self._setObject('acl_users', ob, None, None, 0)
+	self.__allow_groups__=self.acl_users
+	ob=getattr(self, 'acl_users')
+	ob.postInitialisation(REQUEST)
+
+	if REQUEST:
+		return self.manage_main(self, REQUEST)
+	return ''
+
+#
+# Module level caches
+#
+XUFUserCache=GlobalUserCache()
+XUFNotUserCache=GlobalNegativeUserCache()
+XUFCookieCache=GlobalAdvancedCookieCache()
+
+class exUserFolder(Folder,BasicUserFolder,BasicGroupFolderMixin,
+				   CopyContainer):
+	""" """
+
+	# HACK! We use this meta_type internally so we can be pasted into
+	# the root. We registered with 'exUserFolder' meta_type however, so
+	# our constructors work.
+	meta_type='User Folder'
+	id       ='acl_users'
+	title    ='Extensible User Folder'
+	icon     ='misc_/exUserFolder/exUserFolder.gif'
+
+	isPrincipiaFolderish=1
+	isAUserFolder=1
+	__allow_access_to_unprotected_subobjects__=1
+	authSources={}
+	propSources={}
+	cryptoSources={}
+	membershipSources={}
+	groupSources={}
+
+	manage_options=(
+		{'label':'Users',      'action':'manage_main'},
+		{'label':'Groups',	   'action':'manage_userGroups'},
+		{'label':'Parameters', 'action':'manage_editexUserFolderForm'},
+		{'label':'Authentication Source','action':'manage_editAuthSourceForm'},
+		{'label':'Properties Source','action':'manage_editPropSourceForm'},
+		{'label':'Membership Source', 'action':'manage_editMembershipSourceForm'},
+		{'label':'Groups Source','action':'manage_editGroupSourceForm'},
+		{'label':'Cache Data', 'action':'manage_showCacheData'},
+		{'label':'Security',   'action':'manage_access'},
+		{'label':'Contents',   'action':'manage_contents'},
+		{'label':'Ownership',  'action':'manage_owner'},
+		{'label':'Undo',       'action':'manage_UndoForm'},
+		)
+
+	__ac_permissions__=(
+		('View management screens', ('manage','manage_menu','manage_main',
+									 'manage_copyright', 'manage_tabs',
+									 'manage_properties', 'manage_UndoForm',
+									 'manage_edit', 'manage_contents',
+									 'manage_cutObjects','manage_copyObjects',
+									 'manage_pasteObjects',
+									 'manage_renameForm',
+									 'manage_renameObject',
+									 'manage_renameObjects', ),
+		 ('Manager',)),
+		
+		('Undo changes',            ('manage_undo_transactions',),
+		 ('Manager',)),
+		
+		('Change permissions',      ('manage_access',),
+		 ('Manager',)),
+		
+		('Manage users',            ('manage_users', 'manage_editUserForm',
+									 'manage_editUser', 'manage_addUserForm',
+									 'manage_addUser', 'manage_userActions',
+									 'userFolderAddGroup',
+									 'userFolderDelGroups',
+									 'getGroupNames',
+					                                 'getGroupById',
+								         'manage_userGroups',
+									 'manage_addGroup',
+									 'manage_showGroup',),
+		 ('Manager',)),
+		
+		('Change exUser Folders',   ('manage_edit',),
+		 ('Manager',)),
+		
+		('View',                    ('manage_changePassword',
+									 'manage_forgotPassword', 'docLogin','docLoginRedirect',
+									 'docLogout', 'logout', 'DialogHeader',
+									 'DialogFooter', 'manage_signupUser',
+									 'MessageDialog', 'redirectToLogin','manage_changeProps'),
+		 ('Anonymous', 'Authenticated', 'Manager')),
+		
+		('Manage properties',       ('manage_addProperty',
+									 'manage_editProperties',
+									 'manage_delProperties',
+									 'manage_changeProperties',
+									 'manage_propertiesForm',
+									 'manage_propertyTypeForm',
+									 'manage_changePropertyTypes',
+									 ),
+		 ('Manager',)),
+		('Access contents information', ('hasProperty', 'propertyIds',
+										 'propertyValues','propertyItems',
+										 'getProperty', 'getPropertyType',
+										 'propertyMap', 'docLogin','docLoginRedirect',
+										 'DialogHeader', 'DialogFooter',
+										 'MessageDialog', 'redirectToLogin',),
+		 ('Anonymous', 'Authenticated', 'Manager')),
+		)
+	manage_access=DTMLFile('dtml/access',globals())
+	manage_tabs=DTMLFile('common/manage_tabs',globals())
+	manage_properties=DTMLFile('dtml/properties', globals())
+	manage_main=DTMLFile('dtml/mainUser', globals())
+	manage_contents=Folder.manage_main
+	manage_showCacheData=DTMLFile('dtml/manage_showCacheData', globals())
+
+	# This is going away soon...
+	docLoginRedirect=DTMLFile('dtml/docLoginRedirect', globals())	
+
+	# Stupid crap
+	try:
+		manage_contents._setName('manage_contents')
+	except AttributeError:
+		pass
+
+
+	MessageDialog=DTMLFile('common/MessageDialog', globals())
+	MessageDialog.__replaceable__ = REPLACEABLE
+	
+	manage_addUserForm=DTMLFile('dtml/manage_addUserForm',globals())
+	manage_editUserForm=DTMLFile('dtml/manage_editUserForm',globals())
+
+	DialogHeader__roles__=()
+	DialogHeader=DTMLFile('common/DialogHeader',globals())
+	DialogFooter__roles__=()
+	DialogFooter=DTMLFile('common/DialogFooter',globals())
+
+	manage_editAuthSourceForm=DTMLFile('dtml/manage_editAuthSourceForm',globals())
+	manage_editPropSourceForm=DTMLFile('dtml/manage_editPropSourceForm',globals())
+	manage_editMembershipSourceForm=DTMLFile('dtml/manage_editMembershipSourceForm', globals())
+	manage_editGroupSourceForm=DTMLFile('dtml/manage_editGroupSourceForm',globals())
+
+	manage_addPropertyForm=DTMLFile('dtml/manage_addPropertyForm', globals())
+	manage_createPropertyForm=DTMLFile('dtml/manage_createPropertyForm', globals())
+	manage_editUserPropertyForm=DTMLFile('dtml/manage_editUserPropertyForm', globals())
+
+	manage_editexUserFolderForm=DTMLFile('dtml/manage_editexUserFolderForm', globals())
+
+	manage_userGroups=DTMLFile('dtml/mainGroup',globals())
+
+
+	# Use pages from NUG if it's there, otherwise no group support
+	try:
+		manage_addGroup = BasicGroupFolderMixin.manage_addGroup
+		manage_showGroup = BasicGroupFolderMixin.manage_showGroup
+	except:
+		manage_addGroup = None
+		manage_showGroup = None
+
+	# No more class globals
+	
+	# sessionLength=0 # Upgrading users should get no caching.
+	# notSessionLength=0 # bad cache limit
+	# cookie_mode=0
+	# sessionTracking=None # Or session tracking.
+	# idleTimeout=0
+	
+	def __init__(self, authId, propId, memberId, groupId, cryptoId,
+				 cookie_mode=0, session_length=0, sessionTracking=None,
+				 idleTimeout=0, not_session_length=0):
+		self.cookie_mode=cookie_mode
+		self.sessionLength=session_length
+		self.notSessionLength=not_session_length
+		self.sessionTracking=sessionTracking
+		self.idleTimeout=idleTimeout
+		
+		_docLogin=DTMLFile('dtml/docLogin',globals())
+		_docLogout=DTMLFile('dtml/docLogout',globals())
+
+		docLogin=DTMLMethod(__name__='docLogin')
+		docLogin.manage_edit(data=_docLogin, title='Login Page')
+		self._setObject('docLogin', docLogin, None, None, 0)
+
+		docLogout=DTMLMethod(__name__='docLogout')
+		docLogout.manage_edit(data=_docLogout, title='Logout Page')
+		self._setObject('docLogout', docLogout, None, None, 0)
+
+		postUserCreate=DTMLMethod(__name__='postUserCreate')
+		postUserCreate.manage_edit(data=_postUserCreate, title='Post User Creation methods')
+		self._setObject('postUserCreate', postUserCreate, None, None, 0)
+
+		self.manage_addAuthSource=self.authSources[authId].manage_addMethod
+		self.manage_addPropSource=self.propSources[propId].manage_addMethod
+		self.manage_addMembershipSource=self.membershipSources[memberId].manage_addMethod
+		if groupId:
+			self.manage_addGroupSource=self.groupSources[groupId].manage_addMethod
+		else:
+			self.manage_addGroupSource=None
+			self.currentGroupsSource=None
+
+		if cryptoId:
+			self.cryptoId = cryptoId
+		else:
+			self.cryptoId = 'Crypt'
+			
+	def __setstate__(self, state):
+		Persistent.__setstate__(self, state)
+		if not hasattr(self, 'currentGroupSource'):
+			self.currentGroupSource = None
+		if not hasattr(self, 'sessionLength'):
+			self.sessionLength = 0
+		if not hasattr(self, 'notSessionLength'):
+			self.notSessionLength = 0
+		if not hasattr(self, 'cookie_mode'):
+			self.cookie_mode = 0
+		if not hasattr(self, 'sessionTraining'):
+			self.sessionTracking = None
+		if not hasattr(self, 'idleTimeout'):
+			self.idleTimeout=0
+
+	def manage_beforeDelete(self, item, container):
+		zLOG.LOG("exUserFolder", zLOG.BLATHER, "Attempting to delete an exUserFolder instance")
+		if item is self:
+			try:
+				self.cache_deleteCache()
+				self.xcache_deleteCache()
+				zLOG.LOG("exUserFolder", zLOG.BLATHER, "-- Caches deleted")
+			except:
+				#pass
+				zLOG.LOG("exUserFolder", zLOG.BLATHER, "-- Cache deletion failed")
+
+			try:
+				del container.__allow_groups__
+				zLOG.LOG("exUserFolder", zLOG.BLATHER, "-- container.__allow_groups_ deleted")
+			except:
+				#pass
+				zLOG.LOG("exUserFolder", zLOG.BLATHER, "-- container.__allow_groups_ deletion failed")
+
+
+	def manage_afterAdd(self, item, container):
+		zLOG.LOG("exUserFolder", zLOG.BLATHER, "Adding an exUserFolder")
+                         
+		if item is self:
+			if hasattr(self, 'aq_base'): self=self.aq_base
+			container.__allow_groups__=self
+
+	def manage_editPropSource(self, REQUEST):
+		""" Edit Prop Source """
+		if self.currentPropSource:
+			self.currentPropSource.manage_editPropSource(REQUEST)
+		return self.manage_main(self, REQUEST)
+
+	def manage_editAuthSource(self, REQUEST):
+		""" Edit Auth Source """
+		self.currentAuthSource.manage_editAuthSource(REQUEST)
+		return self.manage_main(self, REQUEST)
+
+	def manage_editMembershipSource(self, REQUEST):
+		""" Edit Membership Source """
+		if self.currentMembershipSource:
+			return self.currentMembershipSource.manage_editMembershipSource(REQUEST)
+
+
+	def manage_editGroupSource(self, REQUEST):
+		""" Edit Group Source """
+		if self.currentGroupSource:
+			self.currentGroupSource.manage_editGroupSource(REQUEST)
+		return self.manage_main(self, REQUEST)
+
+
+	def postInitialisation(self, REQUEST):
+		self.manage_addAuthSource(self=self,REQUEST=REQUEST)
+		self.manage_addPropSource(self=self,REQUEST=REQUEST)
+		self.manage_addMembershipSource(self=self,REQUEST=REQUEST)
+		if self.manage_addGroupSource:
+			self.manage_addGroupSource(self=self,REQUEST=REQUEST)
+		else:
+			self.currentGroupSource = None
+	
+	def addAuthSource(self, REQUEST={}):
+		return self.manage_addAuthSourceForm(self, REQUEST)
+
+	def addPropSource(self, REQUEST={}):
+		return self.manage_addPropSourceForm(self, REQUEST)
+
+	def addMembershipSource(self, REQUEST={}):
+		return self.manage_editMembershipSourceForm(self, REQUEST)
+
+	def addGroupSource(self, REQUEST={}):
+		return self.manage_addGroupSourceForm(self, REQUEST)
+
+	def listUserProperties(self, username):
+		if self.currentPropSource:
+			return self.currentPropSource.listUserProperties(username=username)
+
+	def getUserProperty(self, username, key):
+		if self.currentPropSource:
+			return self.currentPropSource.getUserProperty(key=key, username=username)
+	
+	def reqattr(self, request, attr, default=None):
+		try:    return request[attr]
+		except: return default
+
+	def getAuthFailedMessage(self, code):
+		""" Return a code """
+		if LoginRequiredMessages.has_key(code):
+			return LoginRequiredMessages[code]
+		return 'Login Required'
+
+	# Called when we are deleted
+	def cache_deleteCache(self):
+		pp = string.join(self.getPhysicalPath(), '/')
+		XUFUserCache.deleteCache(pp)
+		
+	def cache_addToCache(self, username, password, user):
+		if not self.sessionLength:
+			return
+		# fix by emmanuel
+		if username == self._emergency_user.getUserName():
+			return
+		# /fix
+		pp = string.join(self.getPhysicalPath(), '/')
+		x = XUFUserCache.getCache(pp)
+		if not x:
+			x = XUFUserCache.createCache(pp, self.sessionLength)
+		x.addToCache(username, password, user)
+
+	def cache_getUser(self, username, password, checkpassword=1):
+		if not self.sessionLength:
+			return None
+		pp = string.join(self.getPhysicalPath(), '/')
+		x = XUFUserCache.getCache(pp)
+		if not x:
+			return None
+		u = x.getUser(self, username, password, checkpassword)
+		if u is not None:
+			u = u.__of__(self)
+		return u
+
+	def cache_removeUser(self, username):
+		if not self.sessionLength:
+			return
+		pp = string.join(self.getPhysicalPath(), '/')
+		x = XUFUserCache.getCache(pp)
+		if x:
+			x.removeUser(username)
+
+	def cache_getCacheStats(self):
+		pp = string.join(self.getPhysicalPath(), '/')
+		x = XUFUserCache.getCache(pp)
+		if not x:
+			x = XUFUserCache.createCache(pp, self.sessionLength)			
+		if x:
+			return x.getCacheStats()
+
+	def cache_getCurrentUsers(self):
+		pp = string.join(self.getPhysicalPath(), '/')
+		x = XUFUserCache.getCache(pp)
+		if x:
+			return x.getCurrentUsers(self)
+
+	# negative cache functions
+	def xcache_deleteCache(self):
+		pp = string.join(self.getPhysicalPath(), '/')
+		XUFNotUserCache.deleteCache(pp)
+		
+	def xcache_addToCache(self, username):
+		if not self.notSessionLength:
+			return
+		pp = string.join(self.getPhysicalPath(), '/')
+		x = XUFNotUserCache.getCache(pp)
+		if not x:
+			x = XUFNotUserCache.createCache(pp, self.notSessionLength)
+		x.addToCache(username)
+
+	def xcache_getUser(self, username):
+		if not self.notSessionLength:
+			return None
+		pp = string.join(self.getPhysicalPath(), '/')
+		x = XUFNotUserCache.getCache(pp)
+		if not x:
+			return None
+		return x.getUser(username)
+
+	def xcache_removeUser(self, username):
+		if not self.notSessionLength:
+			return
+		pp = string.join(self.getPhysicalPath(), '/')
+		x = XUFNotUserCache.getCache(pp)
+		if x:
+			x.removeUser(username)
+
+	# Cookie Cache Functions
+	def cache_deleteCookieCache(self):
+		pp = string.join(self.getPhysicalPath(), '/')
+		XUFCookieCache.deleteCache(pp)
+
+	def cache_addToCookieCache(self, username, password, key):
+		pp = string.join(self.getPhysicalPath(), '/')
+		c = XUFCookieCache.getCache(pp)
+		if not c:
+			c = XUFCookieCache.createCache(pp, 86400)
+		c.addToCache(username, password, key)
+
+	def cache_getCookieCacheUser(self, key):
+		pp = string.join(self.getPhysicalPath(), '/')
+		c = XUFCookieCache.getCache(pp)
+		if not c:
+			return None
+		return c.getUser(key)
+
+	def cache_removeCookieCacheUser(self, key):
+		pp = string.join(self.getPhysicalPath(), '/')
+		c = XUFCookieCache.getCache(pp)
+		if c:
+			c.removeUser(key)
+		
+	def manage_editUser(self, username, REQUEST={}):
+		""" Edit a User """
+		# username=self.reqattr(REQUEST,'username')
+		password=self.reqattr(REQUEST,'password')
+		password_confirm=self.reqattr(REQUEST,'password_confirm')
+		roles=self.reqattr(REQUEST,'roles', [])
+		groups=self.reqattr(REQUEST, 'groupnames', [])
+
+		if not username:
+			return self.MessageDialog(self,REQUEST=REQUEST,
+				title  ='Illegal value', 
+				message='A username must be specified',
+				action ='manage_main')
+
+		if (password or password_confirm) and (password != password_confirm):
+			return self.MessageDialog(self,REQUEST=REQUEST,
+				title  ='Illegal value', 
+				message='Password and confirmation do not match',
+				action ='manage_main')
+		
+		self._doChangeUser(username, password, roles, domains='', groups=groups, REQUEST=REQUEST)
+		
+		return self.MessageDialog(self,REQUEST=REQUEST,
+			title = 'User Updated',
+			message= 'User %s was updated.'%(username),
+			action = 'manage_main')
+
+
+	#
+	# Membership helper
+	#
+	def goHome(self, REQUEST, RESPONSE):
+		""" Go to home directory """
+		if self.currentMembershipSource:
+			self.currentMembershipSource.goHome(REQUEST, RESPONSE)
+
+
+	# 
+	# Membership method of changing user properties
+	# 
+
+	def manage_changeProps(self, REQUEST):
+		""" Change Properties """
+		if self.currentMembershipSource:
+			return self.currentMembershipSource.changeProperties(REQUEST)
+		else:
+			
+			return self.MessageDialog(self,REQUEST,
+				title = 'This is a test',
+				message= 'This was a test',
+				action = '..')
+ 
+
+	#
+	# Membership method of adding a new user.
+	# If everything goes well the membership plugin calls manage_addUser()
+	#
+	
+	def manage_signupUser(self, REQUEST):
+		""" Signup a new user """
+		""" This is seperate so you can add users using the normal """
+		""" interface w/o going through membership policy """
+
+		username=self.reqattr(REQUEST,'username')
+		roles=self.reqattr(REQUEST,'roles')
+
+		if not username:
+			return self.MessageDialog(self,REQUEST=REQUEST,
+				title  ='Illegal value', 
+				message='A username must be specified',
+				action ='manage_main')
+
+		if (self.getUser(username) or
+			(self._emergency_user and
+			 username == self._emergency_user.getUserName())):
+			return self.MessageDialog(self,REQUEST=REQUEST,
+				title  ='Illegal value', 
+				message='A user with the specified name already exists',
+				action ='manage_main')
+
+		if self.currentMembershipSource:
+			return self.currentMembershipSource.createUser(REQUEST)
+
+	#
+	# Membership method of changing passwords
+	#
+	def manage_changePassword(self, REQUEST):
+		""" Change a password """
+		if self.currentMembershipSource:
+			return self.currentMembershipSource.changePassword(REQUEST)
+		
+	#
+	# User says they can't remember their password
+	#
+	def manage_forgotPassword(self, REQUEST):
+		""" So something about forgetting your password """
+		if self.currentMembershipSource:
+			return self.currentMembershipSource.forgotPassword(REQUEST)
+		
+	def __creatable_by_emergency_user__(self): return 1
+
+	def manage_addUser(self, REQUEST):
+		""" Add a New User """
+		username=self.reqattr(REQUEST,'username')
+		password=self.reqattr(REQUEST,'password')
+		password_confirm=self.reqattr(REQUEST,'password_confirm')
+		roles=self.reqattr(REQUEST,'roles')
+		groups=self.reqattr(REQUEST, 'groupnames', [])
+
+		if not username:
+			return self.MessageDialog(self,REQUEST=REQUEST,
+				title  ='Illegal value', 
+				message='A username must be specified',
+				action ='manage_main')
+
+		if not password or not password_confirm:
+			return self.MessageDialog(self,REQUEST=REQUEST,
+				title  ='Illegal value', 
+				message='Password and confirmation must be specified',
+				action ='manage_main')
+
+		if (self.getUser(username) or
+			(self._emergency_user and
+			 username == self._emergency_user.getUserName())):
+			return self.MessageDialog(self,REQUEST=REQUEST,
+				title  ='Illegal value', 
+				message='A user with the specified name already exists',
+				action ='manage_main')
+
+		if (password or password_confirm) and (password != password_confirm):
+			return self.MessageDialog(self,REQUEST=REQUEST,
+				title  ='Illegal value', 
+				message='Password and confirmation do not match',
+				action ='manage_main')
+
+		self._doAddUser(username, password, roles, domains='', groups=groups, REQUEST=REQUEST)
+		#
+		# Explicitly check our contents, do not just acquire postUserCreate
+		#
+		if 'postUserCreate' in self.objectIds():
+			self.postUserCreate(self, REQUEST)
+		
+		return self.MessageDialog(self,REQUEST=REQUEST,
+			title = 'User Created',
+			message= 'User %s was created.'%(username),
+			action = 'manage_main')
+
+	def _doAddUser(self, name, password, roles, domains='', groups=(), **kw):
+		""" For programatically adding simple users """
+		self.currentAuthSource.createUser(name, password, roles)
+		if self.currentPropSource:
+			# copy items not in kw from REQUEST
+			REQUEST = kw.get('REQUEST', self.REQUEST)
+			map(kw.setdefault, REQUEST.keys(), REQUEST.values())
+			self.currentPropSource.createUser(name, kw)
+		if self.currentGroupSource:
+			self.currentGroupSource.setGroupsOfUser(groups, name)
+
+	def _doChangeUser(self, name, password, roles, domains='', groups=(), **kw):
+		self.currentAuthSource.updateUser(name, password, roles)
+		if self.currentPropSource:
+			# copy items not in kw from REQUEST
+			REQUEST = kw.get('REQUEST', self.REQUEST)
+			map(kw.setdefault, REQUEST.keys(), REQUEST.values())
+			self.currentPropSource.updateUser(name, kw)
+		# We may have updated roles or passwords... flush the user...
+		self.cache_removeUser(name)
+		self.xcache_removeUser(name)
+		if self.currentGroupSource:
+			self.currentGroupSource.setGroupsOfUser(groups, name)
+		
+	def _doDelUsers(self, names):
+		self.deleteUsers(names)
+
+	def _createInitialUser(self):
+		if len(self.getUserNames()) <= 1:
+			info = readUserAccessFile('inituser')
+			if info:
+				name, password, domains, remote_user_mode = info
+				self._doAddUser(name, password, ('Manager',), domains)
+
+
+	def getUsers(self):
+		"""Return a list of user objects or [] if no users exist"""
+		data=[]
+		try:
+			items=self.listUsers()
+			for people in items:
+				user=User({'name':		people['username'],
+						   'password':	people['password'],
+						   'roles':		people['roles'], 
+						   'domains':	''},
+						  self.currentPropSource,
+						  self.cryptPassword,
+						  self.currentAuthSource,
+						  self.currentGroupSource)
+				data.append(user)
+		except:
+			import traceback
+			traceback.print_exc()
+			pass
+			
+		return data
+
+	getUsers__roles__=('Anonymous','Authenticated')
+	
+	def getUser(self, name):
+		"""Return the named user object or None if no such user exists"""
+		user = self.cache_getUser(name, '', 0)
+		if user:
+			return user
+		try:
+			items=self.listOneUser(name)
+		except:
+			zLOG.LOG("exUserFolder", zLOG.ERROR,
+                                 "error trying to list user %s" % name,
+                                 '',
+                                 sys.exc_info())
+			return None
+
+		if not items:
+			return None
+		
+		for people in items:
+			user =  User({'name':    people['username'],
+						  'password':people['password'],
+						  'roles':   people['roles'],
+						  'domains':	''},
+						 self.currentPropSource,
+						 self.cryptPassword,
+						 self.currentAuthSource,
+						 self.currentGroupSource)
+			return user
+		return None
+		
+	def manage_userActions(self, submit=None, userids=None, REQUEST={}):
+		""" Do things to users """
+		if submit==' Add ':
+			if hasattr(self.currentAuthSource,'manage_addUserForm'):
+				return self.currentAuthSource.manage_addUserForm(self, REQUEST)
+			else:
+				return self.manage_addUserForm(self, REQUEST)
+		if submit==' Delete ':
+			self.deleteUsers(userids)
+			return self.MessageDialog(self,REQUEST=REQUEST,
+				title  ='Users Deleted',
+				message='Selected Users have been deleted',
+				action =REQUEST['URL1']+'/manage_main',
+				target ='manage_main')
+
+		if REQUEST:
+			return self.manage_main(self,REQUEST)
+		return ''
+
+	def identify(self, auth):
+		# Identify the username and password.  This is where new modes should
+		# be called from, and if pluggable modes ever take shape, here ya go!
+
+		if self.cookie_mode and not auth:
+			# The identify signature does not include the request, sadly.
+			# I think that's dumb.
+			request = self.REQUEST
+			response = request.RESPONSE
+	
+			if request.has_key('__ac_name') and request.has_key('__ac_password'):
+				return request['__ac_name'], request['__ac_password']
+			elif request.has_key('__ac') and self.cookie_mode == 1:
+				return self.decodeBasicCookie(request, response)
+			elif request.has_key('__aca') and self.cookie_mode == 2:
+				return self.decodeAdvancedCookie(request, response)
+
+		if auth and lower(auth[:6]) == 'basic ':
+				return tuple(split(decodestring(split(auth)[-1]), ':', 1))
+
+		return None, None
+
+	def decodeUserCookie(self, request, response):
+		return self.identify('')
+
+	def validate(self, request, auth='', roles=_noroles):
+		"""
+		Perform identification, authentication, and authorization.
+		"""
+
+		v = request['PUBLISHED']
+		a, c, n, v = self._getobcontext(v, request)
+
+		name, password = self.identify(auth)
+		zLOG.LOG('exUserFolder', zLOG.DEBUG, 'identify returned %s, %s' % (name, password))
+
+		response = request.RESPONSE
+		if name is not None:
+			try:
+				xcached_user = self.xcache_getUser(name)
+				if xcached_user:
+					return None
+			except:
+				zLOG.LOG('exUserFolder', zLOG.ERROR,
+						 "error while looking up '%s' on the xcache" % name,
+						 '',
+						 sys.exc_info())
+
+			user = self.authenticate(name, password, request)
+			if user is None:
+				# If it's none, because there's no user by that name,
+				# don't raise a login, allow it to go higher...
+				# This kinda breaks for people putting in the wrong username
+				# when the Folder above uses a different auth method.
+				# But it doesn't lock Manager users out inside Zope.
+				# Perhaps this should be a tunable.
+
+				# modified by Emmanuel
+				try:
+					lou = self.listOneUser(name) 
+				except:
+					lou = None
+				if lou:
+					self.challenge(request, response, 'login_failed', auth)
+				return None
+			self.remember(name, password, request)
+			self.cache_addToCache(name, password, user)
+			emergency = self._emergency_user
+			if emergency and user is emergency:
+				if self._isTop():
+					return emergency.__of__(self)
+				else:
+					return None
+			if self.authorize(user, a, c, n, v, roles):
+				return user.__of__(self)
+			if self._isTop() and self.authorize(self._nobody, a, c, n, v, roles):
+				return self._nobody.__of__(self)
+			self.challenge(request, response, 'unauthorized')
+			return None
+		else:
+			if self.sessionTracking and self.currentPropSource:
+				user = self.createAnonymousUser(request, response)
+				if self.authorize(user, a, c, n, v, roles):
+					return user.__of__(self)
+			if self.authorize(self._nobody, a, c, n, v, roles):
+				if self._isTop():
+					return self._nobody.__of__(self)
+				else:
+					return None
+			else:
+				self.challenge(request, response, None, auth)
+				return None
+	
+	def authenticate(self, name, password, request):
+		emergency = self._emergency_user
+		if emergency and name == emergency.getUserName():
+			return emergency
+		try:
+			user = self.cache_getUser(name, password)
+			if user:
+				return user
+		except SessionExpiredException:
+			if self.idleTimeout:
+				self.logout(request)
+				self.challenge(request, request.RESPONSE, 'session_expired')
+				return None
+		user = self.getUser(name)
+		if user is not None:
+			if user.authenticate(self.currentAuthSource.listOneUser,
+								 password,
+								 request,
+								 self.currentAuthSource.remoteAuthMethod):
+				return user
+		return None
+
+	def challenge(self, request, response, reason_code='unauthorized',
+				  auth=''):
+		# Give whatever mode we're in a chance to challenge the validation
+		# failure.  We do this to preserve LoginRequired behavior.  The
+		# other thing we could do is let the None propagate on up and patch
+		# the request's unauthorized method to 
+
+		if self.cookie_mode and not auth:
+			zLOG.LOG('exUserFolder', zLOG.DEBUG, 'raising LoginRequired for %s' % reason_code)
+			if reason_code == 'login_failed':
+				response.expireCookie('__ac', path='/')
+				response.expireCookie('__aca', path='/')
+			if reason_code:
+				request.set('authFailedCode', reason_code)
+			raise 'LoginRequired', self.docLogin(self, request)
+		else:
+			zLOG.LOG('exUserFolder', zLOG.DEBUG, 'not raising LoginRequired for %s' % reason_code)
+
+	def remember(self, name, password, request):
+		response = request.RESPONSE
+		if self.cookie_mode == 1:
+			self.setBasicCookie(name, password, request, response)
+		elif self.cookie_mode == 2:
+			self.setAdvancedCookie(name, password, request, response)
+
+		if self.cookie_mode:
+			try:
+				del request.form['__ac_name']
+				del request.form['__ac_password']
+			except KeyError:
+				pass
+
+	def makeRedirectPath(self):
+		REQUEST=self.REQUEST
+		if not REQUEST.has_key('destination'):
+			script=REQUEST['SCRIPT_NAME']
+			pathinfo=REQUEST['PATH_INFO']
+			redirectstring=script+pathinfo
+			if REQUEST.has_key('QUERY_STRING'):
+				querystring='?'+quote(REQUEST['QUERY_STRING'])
+				redirectstring=redirectstring+querystring
+
+			REQUEST['destination']=redirectstring
+		
+	def redirectToLogin(self, REQUEST):
+		""" Allow methods to call from Web """
+		script=''
+		pathinfo=''
+		querystring=''
+		redirectstring=''
+		authFailedCode=''
+		
+		if not REQUEST.has_key('destination'):
+			if self.currentMembershipSource:
+				redirectstring = self.currentMembershipSource.getLoginDestination(REQUEST)
+			else:
+				script=REQUEST['SCRIPT_NAME']
+				pathinfo=REQUEST['PATH_INFO']
+				redirectstring=script+pathinfo
+				if REQUEST.has_key('QUERY_STRING'):
+					querystring='?'+REQUEST['QUERY_STRING']
+					redirectstring=redirectstring+querystring
+
+			REQUEST['destination']=redirectstring
+
+		
+		if REQUEST.has_key('authFailedCode'):
+			authFailedCode='&authFailedCode='+REQUEST['authFailedCode']
+		
+			
+			
+		if self.currentMembershipSource and self.currentMembershipSource.loginPage:
+			try:
+				REQUEST.RESPONSE.redirect('%s/%s?destination=%s%s'%(self.currentMembershipSource.baseURL, self.currentMembershipSource.loginPage,REQUEST['destination'],authFailedCode))				
+				return
+			except:
+				pass
+		return self.docLogin(self,REQUEST)
+
+	def decodeBasicCookie(self, request, response):
+		c=request['__ac']
+		c=unquote(c)
+		try:
+			c=decodestring(c)
+		except:
+			response.expireCookie('__ac', path='/')
+			raise 'LoginRequired', self.docLogin(self, request)
+		
+		name,password=tuple(split(c, ':', 1))
+		return name, password
+		
+	def decodeAdvancedCookie(self, request, response):
+		c = ''
+		try:
+			c = request['__aca']
+			c = unquote(c)
+		except:
+			response.expireCookie('__aca', path='/')
+			response.expireCookie('__ac', path='/')	# Precaution
+			response.flush()
+			raise 'LoginRequired', self.docLogin(self, request)
+
+		u = self.cache_getCookieCacheUser(c)
+		if u:
+			return u
+
+		response.expireCookie('__aca', path='/')
+		response.expireCookie('__ac', path='/')	# Precaution
+		response.flush()
+		raise 'LoginRequired', self.docLogin(self, request)
+
+	def setBasicCookie(self, name, password, request, response):
+		token='%s:%s' % (name, password)
+		token=encodestring(token)
+		token=quote(token)
+		response.setCookie('__ac', token, path='/')
+		request['__ac']=token
+		
+	def setAdvancedCookie(self, name, password, request, response):
+		xufid = self._p_oid
+		hash = encodestring(sha.new('%s%s%f%f%s'%(
+			name, password, time(), whrandom.random(), str(request))).digest())
+		token=quote(hash)
+		response.setCookie('__aca', token, path='/')
+		response.flush()
+		request['__aca']=token
+		self.cache_addToCookieCache(name, password, hash)
+		
+	def setAnonCookie(self, name, request, resp):
+		token='%s:%s' % (name, '')
+		token=encodestring(token)
+		token=quote(token)
+		resp.setCookie('__ac', token, path='/')
+		request['__ac']=token
+
+	def createAnonymousUser(self, request, resp):
+		aName=createTempName()
+		bogusREQUEST={}
+		bogusREQUEST['user_realname']='Guest User'
+		self.currentPropSource.createUser(aName, bogusREQUEST)
+		ob = AnonUser(aName, [], self.currentPropSource)
+		ob = ob.__of__(self)
+		self.cache_addToCache(aName, '', ob)			
+		self.setAnonCookie(aName, request, resp)
+		return ob
+		
+	def manage_edit(self, cookie_mode, session_length, sessionTracking=None,
+					idleTimeout=0, not_session_length=0,
+			                title=None,
+					REQUEST=None):
+		"""Change properties"""
+
+		self.cookie_mode=cookie_mode
+		self.sessionLength=session_length
+		self.notSessionLength=not_session_length
+		self.sessionTracking=sessionTracking
+		self.idleTimeout=idleTimeout
+		if title:
+			self.title = title
+		
+		if REQUEST:
+			return self.MessageDialog(self,REQUEST=REQUEST,
+				title  ='exUserFolder Changed',
+				message='exUserFolder properties have been updated',
+				action =REQUEST['URL1']+'/manage_main',
+				target ='manage_main')
+
+	def logout(self, REQUEST):
+		"""Logout"""
+		try:
+			self.cache_removeUser(REQUEST['AUTHENTICATED_USER'].getUserName())
+		except:
+			pass
+		
+		REQUEST['RESPONSE'].expireCookie('__ac', path='/')
+		REQUEST.cookies['__ac']=''
+		try:
+			acc = REQUEST['__aca']
+			self.cache_removeCookieCacheUser(acc)
+			REQUEST.cookies['__aca']=''
+		except:
+			pass
+		REQUEST['RESPONSE'].expireCookie('__aca', path='/')
+
+		
+		
+		return self.docLogout(self, REQUEST)
+
+	#
+	# Methods to be supplied by Auth Source
+	#
+	def deleteUsers(self, userids):
+		self.currentAuthSource.deleteUsers(userids)
+
+		# Comment out to use Andreas' pgSchema
+		if self.currentPropSource:
+			self.currentPropSource.deleteUsers(userids)
+
+		if self.currentGroupSource:
+			self.currentGroupSource.deleteUsers(userids)
+			
+
+	def listUsers(self):
+		return self.currentAuthSource.listUsers()
+
+	def user_names(self):
+		return self.currentAuthSource.listUserNames()
+	
+	def getUserNames(self):
+		return self.currentAuthSource.listUserNames()
+
+	def listOneUser(self,username):
+		return self.currentAuthSource.listOneUser(username)
+
+	def cryptPassword(self, username, password):
+		if hasattr(aq_base(self.currentAuthSource), 'cryptPassword'):
+			return self.currentAuthSource.cryptPassword(username, password)
+
+		if hasattr(self, 'cryptoId'):
+			return self.cryptoSources[self.cryptoId].plugin(self, username, password)
+		return self.cryptoSources['Crypt'].plugin(self, username, password)
+
+	def PropertyEditor(self):
+		""" """
+		if self.REQUEST.has_key(self.REQUEST['propName']):
+			return PropertyEditor.EditMethods[self.REQUEST['propType']](self.REQUEST['propName'], self.REQUEST[self.REQUEST['propName']])
+		return PropertyEditor.EditMethods[self.REQUEST['propType']](self.REQUEST['propName'], None)
+
+	def PropertyView(self):
+		""" """
+		if self.REQUEST.has_key(self.REQUEST['propName']):
+			return PropertyEditor.ViewMethods[self.REQUEST['propType']](self.REQUEST['propName'], self.REQUEST[self.REQUEST['propName']])
+		return PropertyEditor.ViewMethods[self.REQUEST['propType']](self.REQUEST['propName'], None)
+
+	def manage_addUserProperty(self, username, propName, propValue, REQUEST):
+		""" add a new property """
+		self.currentPropSource.setUserProperty(propName, username, propValue)
+		if hasattr(self.currentAuthSource,'manage_editUserForm'):
+			return self.currentAuthSource.manage_editUserForm(self, REQUEST)
+		else:
+			return self.manage_editUserForm(self,REQUEST)
+
+	def getUserCacheStats(self):
+		""" Stats """
+		if self.sessionLength:
+			if self.cache_getCacheStats()['attempts']:
+				return self.cache_getCacheStats()
+		return None
+
+	def getUserCacheUsers(self):
+		""" Current Users """
+		if self.sessionLength:
+			return self.cache_getCurrentUsers()
+		return None
+
+	def userFolderAddGroup(self, groupname, title='', **kw):
+		"""Creates a group"""
+		if self.currentGroupSource:
+			apply(self.currentGroupSource.addGroup, (groupname, title), kw)
+	
+	def userFolderDelGroups(self, groupnames):
+		"""Deletes groups"""
+		if self.currentGroupSource:
+			for groupname in groupnames:
+				self.currentGroupSource.delGroup(groupname)
+
+	def getGroupNames(self):
+		"""Returns a list of group names"""
+		if self.currentGroupSource:
+			return self.currentGroupSource.listGroups()
+		else:
+			return []
+
+
+	def getGroupById(self, groupname, default=_marker):
+		"""Returns the given group"""
+		if self.currentGroupSource:
+			group = self.currentGroupSource.getGroup(groupname, default)
+			if group:
+				return group.__of__(self)
+			else:
+				return None
+
+	def setUsersOfGroup(self, usernames, groupname):
+		"""Sets the users of the group"""
+		if self.currentGroupSource:
+			return self.currentGroupSource.setUsersOfGroup(usernames, groupname)
+
+	def addUsersToGroup(self, usernames, groupname):
+		"""Adds users to a group"""
+		if self.currentGroupSource:
+			return self.currentGroupSource.addUsersToGroup(usernames, groupname)
+
+	def delUsersFromGroup(self, usernames, groupname):
+		"""Deletes users from a group"""
+		if self.currentGroupSource:
+			return self.currentGroupSource.delUsersFromGroup(usernames, groupname)
+
+	def setGroupsOfUser(self, groupnames, username):
+		"""Sets the groups of a user"""
+		if self.currentGroupSource:
+			return self.currentGroupSource.setGroupsOfUser(groupnames, username)
+
+	def addGroupsOfUser(self, groupnames, username):
+		"""Add groups to a user"""
+		if self.currentGroupSource:
+			return self.currentGroupSource.addGroupsToUser(groupnames, username)
+
+	def delGroupsOfUser(self, groupnames, username):
+		"""Deletes groups from a user"""
+		if self.currentGroupSource:
+			return self.currentGroupSource.delGroupsFromUser(groupnames, username)
+
+	# We lie.
+	def hasUsers(self):
+		return 1
+
+
+def doAuthSourceForm(self,authId):
+	""" la de da """
+	return exUserFolder.authSources[authId].manage_addForm
+
+def doPropSourceForm(self,propId):
+	""" la de da """
+	return exUserFolder.propSources[propId].manage_addForm
+
+def doMembershipSourceForm(self, memberId):
+	""" doot de doo """
+	return exUserFolder.membershipSources[memberId].manage_addForm
+
+def doGroupSourceForm(self,groupId):
+	""" la de da """
+	return exUserFolder.groupSources[groupId].manage_addForm
+
+def getAuthSources(self):
+	""" Hrm I need a docstring """
+	l=[]
+	for o in exUserFolder.authSources.keys():
+		l.append(
+			exUserFolder.authSources[o]
+			)
+	return l
+
+def getPropSources(self):
+	""" Hrm I need a docstring """
+	l=[]
+	for o in exUserFolder.propSources.keys():
+		l.append(
+			exUserFolder.propSources[o]
+			)
+	return l
+
+def getMembershipSources(self):
+	""" Hrm I need a docstring """
+	l=[]
+	for o in exUserFolder.membershipSources.keys():
+		l.append(
+			exUserFolder.membershipSources[o]
+			)
+	return l
+
+def getGroupSources(self):
+	""" Hrm I need a docstring """
+	l=[]
+	for o in exUserFolder.groupSources.keys():
+		l.append(
+			exUserFolder.groupSources[o]
+			)
+	return l
+
+def getCryptoSources(self):
+	""" Doc String """
+	l = []
+	for o in exUserFolder.cryptoSources.keys():
+		l.append(
+			exUserFolder.cryptoSources[o]
+			)
+	return l
+
+def MailHostIDs(self):
+    """Find SQL database connections in the current folder and above
+
+    This function return a list of ids.
+    """
+    ids={}
+    have_id=ids.has_key
+    StringType=type('')
+
+    while self is not None:
+        if hasattr(self, 'objectValues'):
+            for o in self.objectValues():
+                if (hasattr(o,'meta_type') and o.meta_type=='Mail Host'
+                    and hasattr(o,'id')):
+                    id=o.id
+                    if type(id) is not StringType: id=id()
+                    if not have_id(id):
+                        if hasattr(o,'title_and_id'): o=o.title_and_id()
+                        else: o=id
+                        ids[id]=id
+        if hasattr(self, 'aq_parent'): self=self.aq_parent
+        else: self=None
+
+    ids=map(lambda item: (item[1], item[0]), ids.items())
+    ids.sort()
+    return ids
+
+
+from types import ListType, IntType, LongType, FloatType, NoneType, DictType, StringType
+
+def getVariableType(self, o):
+
+	if type(o) == ListType:
+		return 'List'
+	if type(o) == IntType:
+		return 'Int'
+	if type(o) == LongType:
+		return 'Long'
+	if type(o) == FloatType:
+		return 'Float'
+	if type(o) == NoneType:
+		return 'None'
+	if type(o) == DictType:
+		return 'Dict'
+	if type(o) == StringType:
+		return 'String'
+	return 'Unknown or Restricted'
+
+_postUserCreate='''
+<dtml-comment>
+Replace this method with whatever you want to do
+when a user is created, you can use a Python Script,
+or External Method, or keep it as a DTML Method if you
+want to
+</dtml-comment>
+'''

Added: trunk/ZopeProducts/exUserFolder/exUserFolderPlugin.gif
===================================================================
(Binary files differ)


Property changes on: trunk/ZopeProducts/exUserFolder/exUserFolderPlugin.gif
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/ZopeProducts/exUserFolder/nullPlugin/__init__.py
===================================================================
--- trunk/ZopeProducts/exUserFolder/nullPlugin/__init__.py	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/nullPlugin/__init__.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,2 @@
+# $Id: __init__.py,v 1.4 2004/11/10 14:15:57 akm Exp $
+import nullPlugin

Added: trunk/ZopeProducts/exUserFolder/nullPlugin/nullPlugin.py
===================================================================
--- trunk/ZopeProducts/exUserFolder/nullPlugin/nullPlugin.py	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/nullPlugin/nullPlugin.py	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1,39 @@
+#
+# Extensible User Folder
+# 
+# Null Plugin for exUserFolder
+#
+# (C) Copyright 2000,2001 The Internet (Aust) Pty Ltd
+# ACN: 082 081 472  ABN: 83 082 081 472
+# All Rights Reserved
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# Author: Andrew Milton <akm at theinternet.com.au>
+# $Id: nullPlugin.py,v 1.5 2004/11/10 14:15:57 akm Exp $
+import string,Acquisition
+
+from Globals import HTMLFile, INSTANCE_HOME
+
+from OFS.Folder import Folder
+
+from Products.exUserFolder.exUserFolder import exUserFolder
+from Products.exUserFolder.Plugins import PluginRegister
+
+class NullPlugin(Folder):
+
+	def __init__(self):
+		pass
+
+	def postInitialisation(self, REQUEST):
+		pass

Added: trunk/ZopeProducts/exUserFolder/version.txt
===================================================================
--- trunk/ZopeProducts/exUserFolder/version.txt	                        (rev 0)
+++ trunk/ZopeProducts/exUserFolder/version.txt	2013-03-21 21:49:46 UTC (rev 1218)
@@ -0,0 +1 @@
+exUserFolder-0-50-1

Modified: trunk/config/upgrade.sh
===================================================================
--- trunk/config/upgrade.sh	2013-03-21 09:18:39 UTC (rev 1217)
+++ trunk/config/upgrade.sh	2013-03-21 21:49:46 UTC (rev 1218)
@@ -62,6 +62,12 @@
 chgrp -R www-data "${SCODOC_DIR}"/static/photos
 chmod -R g+w "${SCODOC_DIR}"/static/photos
 
+# Important to create .pyc:
+chgrp www-data "${SCODOC_DIR}"
+chmod g+w "${SCODOC_DIR}"
+chgrp -R www-data "${SCODOC_DIR}"/ZopeProducts
+chmod -R g+w "${SCODOC_DIR}"/ZopeProducts
+
 # check and upgrade reportlab
 ./install_reportlab23.sh
 
@@ -72,6 +78,26 @@
 # check and install psycopg2
 ./install_psycopg2.sh 
 
+# check symlinks to our customized Zope products
+for P in exUserFolder ZPsycopgDA
+do
+  if [ ! -h $SCODOC_DIR/../$P ]
+  then
+     dest=$SCODOC_DIR/../../Attic
+     if [ ! -e "$dest" ]
+     then
+       mkdir $dest
+     fi
+     if [ -e $SCODOC_DIR/../$P ]
+     then
+       echo "Moving old product $P to $dest"
+       mv "$SCODOC_DIR/../$P" "$dest"
+     fi
+     echo "creating symlink to product $P"
+     (cd $SCODOC_DIR/..; ln -s ScoDoc/ZopeProducts/$P)
+  fi
+done
+
 # post-upgrade scripts
 echo "Executing post-upgrade script..."
 "$SCODOC_DIR"/config/postupgrade.py


Plus d'informations sur la liste de diffusion Scodoc-devel