exUserFolder/0040755003462500001440000000000007702273436012253 5ustar b14741usersexUserFolder/Extensions/0040755003462500001440000000000007702273432014406 5ustar b14741usersexUserFolder/Extensions/.cvsignore0100654003462500001440000000002607524231227016400 0ustar b14741users*.pyc *.pyo *~ .*.swp exUserFolder/Extensions/getOldGroups.py0100644003462500001440000000136007531440501017364 0ustar b14741users# 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 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 exUserFolder/Extensions/loadOldGroups.py0100644003462500001440000000160707531440501017530 0ustar b14741users# 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 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 exUserFolder/Extensions/usAuthSourceMethods.py0100644003462500001440000000773407402113544020740 0ustar b14741users# (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 # $Id: usAuthSourceMethods.py,v 1.3 2025/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= """ _sqlDeleteOneUser=""" DELETE FROM passwd where uid= """ _sqlInsertUser=""" INSERT INTO passwd (username, password, roles) VALUES (, , ) """ _sqlUpdateUserPassword=""" UPDATE passwd set password= WHERE username= """ _sqlUpdateUser=""" UPDATE passwd set roles= WHERE username= """ exUserFolder/.cvsignore0100654003462500001440000000002607403212057014235 0ustar b14741users*.pyc *.pyo *~ .*.swp exUserFolder/CHANGES.txt0100644003462500001440000007625107701644655014100 0ustar b14741usersChanges 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 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 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@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 for reporting this one. zodbAuthSource made more persistent 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 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 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 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.. exUserFolder/FAQ.txt0100644003462500001440000001163307452213504013414 0ustar b14741usersFrequently 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.
Name:
Password:
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. exUserFolder/LICENSE0100644003462500001440000000773707430543521013264 0ustar b14741usersXUF 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@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. exUserFolder/LoginRequiredMessages.py0100644003462500001440000000221507402113543017050 0ustar b14741users# # 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 # $Id: LoginRequiredMessages.py,v 1.2 2025/12/01 08:40:03 akm Exp $ LoginRequiredMessages={ 'session_expired':'Your Session has Expired', 'unauthorized':'Please Login', 'login_failed':'Login Failed', } exUserFolder/Plugins.py0100644003462500001440000000303107402113543014225 0ustar b14741users# # # (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 # $Id: Plugins.py,v 1.3 2025/12/01 08:40:03 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 exUserFolder/README.API0100644003462500001440000001125107401746576013546 0ustar b14741usersUser 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='Authorisation 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. exUserFolder/README.LDAPAuthSource0100644003462500001440000000152207701627740015652 0ustar b14741usersThis 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. exUserFolder/README.Membership0100644003462500001440000001205707450665745015237 0ustar b14741usersThere 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 ------------------------------------------------------------------------ ------------------------------------------------------------------------ ------------------------------------------------------------------------ ChangePasswordForm ------------------------------------------------------------------------ ------------------------------------------------------------------------ ------------------------------------------------------------------------ SignupForm ------------------------------------------------------------------------ ------------------------------------------------------------------------ ForgotMyPassword ------------------------------------------------------------------------
Username:
------------------------------------------------------------------------ ChangePropertiesForm ------------------------------------------------------------------------

Changing Properties for


Properties

Property NameValue
">
------------------------------------------------------------------------ exUserFolder/README.cmfPropSource0100644003462500001440000000215607401743341015713 0ustar b14741userson 9.22.01 by runyaga portal_memberdata exUserFolder PropertySource REQUIRES the Content Management Framework (CMF) at least v1.1 inside your CMF Site you can have any kind of User Folder doing authentication for CMF. exUserFolder works well in this role. This is a PropertySource for exUserFolder, meaning it provides properties for Users in the exUserFolder. You may know the CMF provides you with a portal_memberdata tool that stores Properties for Members (Users). this PropertySource is a thin wrapper around the portal_memberdata tool, which is the CMF's native way to assign Propeties on Member objects. Features: * you can click on a user in the exUserFolder and edit their memberdata ;) * you can click on PropertySource and you will goto the portal_memberdata propertyiesForm * use the AuthenticationSources provided by exUserFolder to easily authenticate with external sources such as: /etc/passwd, ZODB, PostgreSQL, and many more. License included in source code. Please help out and write more Authenticators, this is a simple framework that may wind its way into the ZOPE codebase ;) exUserFolder/README.httpsAuthSource0100644003462500001440000000262107637516204016275 0ustar b14741usersThis 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. exUserFolder/README.pgAuthSource-Alternate0100644003462500001440000000124507401747075017460 0ustar b14741usersThis 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). exUserFolder/README.radiusAuthSource0100644003462500001440000000062507401744332016416 0ustar b14741usersI 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@cs.rmit.edu.au> exUserFolder/README.smbAuthSource0100644003462500001440000000347707401744622015722 0ustar b14741users 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 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. exUserFolder/README.txt0100644003462500001440000001147707401746041013751 0ustar b14741usersA 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). 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. exUserFolder/README.zodbAuthSource0100644003462500001440000000062207401740517016063 0ustar b14741usersZODB 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@quad.com.ar exUserFolder/UnenlightenedZopistasGuide.txt0100644003462500001440000006032607701627740020324 0ustar b14741usersThe Unenlightened Zopistas Guide to exUserFolder. (C) 2001-2003 Andrew Milton 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. 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. exUserFolder/User.py0100644003462500001440000001723407700041053013531 0ustar b14741users# # 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 # $Id: User.py,v 1.9 2025/06/30 13:59:07 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'] self.roles =filter(None, cacheable['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 exUserFolder/__init__.py0100644003462500001440000000762407701641321014361 0ustar b14741users# # 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 # $Id: __init__.py,v 1.16 2025/07/05 21:27:45 akm Exp $ import exUserFolder import mysqlAuthSource import pgAuthSource import pgAuthSourceAlt import usAuthSource import etcAuthSource import zodbAuthSource import zodbBTreeAuthSource import radiusAuthSource import smbAuthSource import httpsAuthSource try: import nisAuthSource except: pass try: import LDAPAuthSource except: pass import nullPropSource import mysqlPropSource import pgPropSource import usPropSource import zodbPropSource import zodbBTreePropSource from GroupSource import GroupSource # CMF Prop source needs things we might not have # So we wrap the import try: import cmfPropSource except: pass # If this fails due to NUG being absent, just skip it try: import zodbGroupSource except ImportError: pass import basicMemberSource import nullMemberSource import basicMemberSource import nullGroupSource 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), ('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") exUserFolder/dummyZBabelTag.py0100644003462500001440000001122707470250075015470 0ustar b14741userstry: 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, (('Expression (Python) Syntax error:' + '
\012%s\012
\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 exUserFolder/exUser.gif0100644003462500001440000000025107401737270014205 0ustar b14741usersGIF89aã8$0H(@$0H00a (b"0y( yiy$¥¥$ ¶²¶Þæîÿÿÿ!ù,VðÉI«}†¢+BqÂ8“qÂé ‡Å<Ë"8ªpé†b*„UÅ X<@‚ è[(9 …B0 uB„çÒ0YÒ’p4…0ñA è*„\^y[1@;exUserFolder/exUserFolder.gif0100644003462500001440000000025107401747075015345 0ustar b14741usersGIF89aãPPPÀÀÀÿÿÿÿÿ€€€ÿΜΜc ¡¡ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ!ù ,Vð=@¼x±m–À& U9 Cª®) !Ëcáaì;!Ú/î@$| nH,r”BCó€Hl”9é‘ øÝ„Ó#'yÛÌdãtÄ&S ð¸Nœï³G;exUserFolder/exUserFolder.py0100644003462500001440000012464107701644305015235 0ustar b14741users# # 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 # $Id: exUserFolder.py,v 1.89 2025/07/05 21:53:09 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 , # Copyright (c) 2002 Florent Guillaume . # # 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 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 # 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): """ """ 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, 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={} 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()) 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, 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 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 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): 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) 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='/') 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='/') 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='/') # # 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='/') 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='/') 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='/') if not password or not password_confirm: return self.MessageDialog(self,REQUEST=REQUEST, title ='Illegal value', message='Password and confirmation must be specified', action='/') 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='/') 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='/') 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='/') 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""" 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. if self.listOneUser(name): 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, 'unauthorized', 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='/') request.set('authFailedCode', reason_code) raise 'LoginRequired', self.docLoginRedirect(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.docLoginRedirect(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.docLoginRedirect(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.docLoginRedirect(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): return self.currentAuthSource.cryptPassword(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) 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 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=''' 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 ''' exUserFolder/exUserFolderPlugin.gif0100644003462500001440000000062207401737270016522 0ustar b14741usersGIF89a¥0 8 000 @ @0 @00P00a00P0@@@0P@0PP0a@0aP0P@@PPPa@@aP@q@@qP@qPPqa@qaPyyyyyP@PPa@aPqPaPqPuaaaqa•}q®aP®qP®qa¾qa‰P‰a•…q®‰P®‰a¥‘i¾‰a¾‰qÞ™qΪqÞªq®ª®¾²¶Þ™Þ²¥îªîºÿªÞÚÎÖÞÖæÎÖÿúÿÿÿÿ!ù?,¯ÀŸp8¤ÁˆÃ×  Ô¼|³Ø‚µ¤ ¢Šð¥„ž°…à!ÎZìõƒ\D ÑÙ|&ød %bÙ€æZ? gy? g„z?-™šœ? 6 gz{ ?'>.˜­ ?!> >0u¾À?$µ> B<5<<Õ×ÚãD$>ÛÜCD4íHBA;exUserFolder/mysql.sql0100644003462500001440000000071307521551643014135 0ustar b14741usersDROP 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); exUserFolder/pgAndreasScheme.sql0100644003462500001440000000077307401737524016030 0ustar b14741usersCREATE 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" ); exUserFolder/pgScheme.sql0100644003462500001440000000234007401746460014521 0ustar b14741usersCREATE 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" ); exUserFolder/version.txt0100644003462500001440000000002507524231011014454 0ustar b14741usersexUserFolder-0-10-10 exUserFolder/GroupSource/0040755003462500001440000000000007702273432014524 5ustar b14741usersexUserFolder/GroupSource/.cvsignore0100644003462500001440000000002607572765321016527 0ustar b14741users*.pyc *.pyo *~ .*.swp exUserFolder/GroupSource/GroupSource.py0100644003462500001440000000141207572765321017356 0ustar b14741users# # Extensible User Folder # # Null Group Source for exUserFolder # # Author: Brent Hendricks # $Id: GroupSource.py,v 1.1 2025/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 exUserFolder/GroupSource/__init__.py0100644003462500001440000000011207572765321016634 0ustar b14741users# $Id: __init__.py,v 1.1 2025/12/02 23:20:49 bmh Exp $ import GroupSource exUserFolder/GroupSource/manage_addGroupSourceForm.dtml0100644003462500001440000000220607572765321022475 0ustar b14741users
Group Source

Add ">
exUserFolder/I18N/0040755003462500001440000000000007702273432012726 5ustar b14741usersexUserFolder/I18N/ZBabelDictionary_txt.zexp0100644003462500001440000011215307402415223017713 0ustar b14741usersZEXP$t”G((U OFS.ImageqUFileqtqNt.}q(UsizeqMB“UdataqTB“BpMwZ6r9SEJdZ7ywG7pctg==|||en|||Update||| BpMwZ6r9SEJdZ7ywG7pctg==|||es|||Update||| BpMwZ6r9SEJdZ7ywG7pctg==|||fr|||Update||| BpMwZ6r9SEJdZ7ywG7pctg==|||label|||Update||| BpMwZ6r9SEJdZ7ywG7pctg==|||de|||Aktualisieren||| 8qbEmPuQ7jRdmX+Ij847GA==|||de|||Löschen||| 8qbEmPuQ7jRdmX+Ij847GA==|||en|||Delete||| 8qbEmPuQ7jRdmX+Ij847GA==|||es|||Delete||| 8qbEmPuQ7jRdmX+Ij847GA==|||fr|||Delete||| 8qbEmPuQ7jRdmX+Ij847GA==|||label|||Delete||| 8qbEmPuQ7jRdmX+Ij847GA==|||de|||Löschen||| e9zLI2IgnZXXNVN3xP1+IQ==|||en|||Default Roles||| e9zLI2IgnZXXNVN3xP1+IQ==|||es|||Default Roles||| e9zLI2IgnZXXNVN3xP1+IQ==|||fr|||Default Roles||| e9zLI2IgnZXXNVN3xP1+IQ==|||label|||Default Roles||| e9zLI2IgnZXXNVN3xP1+IQ==|||de|||Standardrollen||| r3P0FjWxpZnyJxN9iphQTA==|||en|||Credential Cache Timeout in Seconds (0 for No Caching)||| r3P0FjWxpZnyJxN9iphQTA==|||es|||Credential Cache Timeout in Seconds (0 for No Caching)||| r3P0FjWxpZnyJxN9iphQTA==|||fr|||Credential Cache Timeout in Seconds (0 for No Caching)||| r3P0FjWxpZnyJxN9iphQTA==|||label|||Credential Cache Timeout in Seconds (0 for No Caching)||| r3P0FjWxpZnyJxN9iphQTA==|||de|||Gültigkeitsdauer der Berechtigungsnachweise in Sekunden (0 für kein Caching)||| mS1y1YIbnLcbKHp/y7fVnQ==|||en|||Misses -- not in cache||| mS1y1YIbnLcbKHp/y7fVnQ==|||es|||Misses -- not in cache||| mS1y1YIbnLcbKHp/y7fVnQ==|||fr|||Misses -- not in cache||| mS1y1YIbnLcbKHp/y7fVnQ==|||label|||Misses -- not in cache||| mS1y1YIbnLcbKHp/y7fVnQ==|||de|||Fehlschläge -- nicht im Cache||| fc4SIASWnVauLgJFy3VNNQ==|||en|||Edit||| fc4SIASWnVauLgJFy3VNNQ==|||es|||Edit||| fc4SIASWnVauLgJFy3VNNQ==|||fr|||Edit||| fc4SIASWnVauLgJFy3VNNQ==|||label|||Edit||| fc4SIASWnVauLgJFy3VNNQ==|||de|||Bearbeiten||| 1PCya/fqTgs7VV6TwVEM2g==|||en|||Log out users who expire from cache?||| 1PCya/fqTgs7VV6TwVEM2g==|||es|||Log out users who expire from cache?||| 1PCya/fqTgs7VV6TwVEM2g==|||fr|||Log out users who expire from cache?||| 1PCya/fqTgs7VV6TwVEM2g==|||label|||Log out users who expire from cache?||| 1PCya/fqTgs7VV6TwVEM2g==|||de|||Benutzer abmelden deren Berechtigungsnachweis ausgelaufen ist?||| nc51LKM2KuHxCus+/mZu1w==|||en|||Membership requires a valid property source, you cannot use this with NULL Property Source||| nc51LKM2KuHxCus+/mZu1w==|||es|||Membership requires a valid property source, you cannot use this with NULL Property Source||| nc51LKM2KuHxCus+/mZu1w==|||fr|||Membership requires a valid property source, you cannot use this with NULL Property Source||| nc51LKM2KuHxCus+/mZu1w==|||label|||Membership requires a valid property source, you cannot use this with NULL Property Source||| nc51LKM2KuHxCus+/mZu1w==|||de|||Die Mitgliedschaft benötigt eine gültige Eigenschaften-Quelle, Sie können das nicht mit der NULL-Eigenschaften-Quelle benutzen||| zX67lP7WT/Tlx3UA6Ta0Zw==|||en|||This Membership Source has no configuration Items||| zX67lP7WT/Tlx3UA6Ta0Zw==|||es|||This Membership Source has no configuration Items||| zX67lP7WT/Tlx3UA6Ta0Zw==|||fr|||This Membership Source has no configuration Items||| zX67lP7WT/Tlx3UA6Ta0Zw==|||label|||This Membership Source has no configuration Items||| zX67lP7WT/Tlx3UA6Ta0Zw==|||de|||Diese Mitgliedschaftsquelle hat keine Konfigurationselemente||| 3GR+tl5nEeFVN1IYISs5ZA==|||label|||Password||| 3GR+tl5nEeFVN1IYISs5ZA==|||en|||Password||| 3GR+tl5nEeFVN1IYISs5ZA==|||es|||Palabra de paso||| 3GR+tl5nEeFVN1IYISs5ZA==|||fr|||Mot de passe||| 3GR+tl5nEeFVN1IYISs5ZA==|||de|||Passwort||| J/4zcXe80h8DHYbeJA4qGw==|||en|||When a role is assigned to a permission, users with the given role will be able to perform tasks associated with the permission on this item. When the Acquire permission settings checkbox is selected then the containing objects's permission settings are used. Note: the acquired permission settings may be augmented by selecting Roles for a permission in addition to selecting to acquire permissions.||| J/4zcXe80h8DHYbeJA4qGw==|||es|||When a role is assigned to a permission, users with the given role will be able to perform tasks associated with the permission on this item. When the Acquire permission settings checkbox is selected then the containing objects's permission settings are used. Note: the acquired permission settings may be augmented by selecting Roles for a permission in addition to selecting to acquire permissions.||| J/4zcXe80h8DHYbeJA4qGw==|||fr|||When a role is assigned to a permission, users with the given role will be able to perform tasks associated with the permission on this item. When the Acquire permission settings checkbox is selected then the containing objects's permission settings are used. Note: the acquired permission settings may be augmented by selecting Roles for a permission in addition to selecting to acquire permissions.||| J/4zcXe80h8DHYbeJA4qGw==|||label|||When a role is assigned to a permission, users with the given role will be able to perform tasks associated with the permission on this item. When the Acquire permission settings checkbox is selected then the containing objects's permission settings are used. Note: the acquired permission settings may be augmented by selecting Roles for a permission in addition to selecting to acquire permissions.||| J/4zcXe80h8DHYbeJA4qGw==|||de|||Wenn eine Rolle einer Berechtigung zugewiesen wird, werden Benutzer mit dieser Rolle in der Lage sein, mit der Berechtigung verbundene Aufgaben auf dieses Element auszuführen. Wenn das Rechte-Einstellungen akquirieren-Kontrollkästchen ausgewählt ist, dann werden die Rechte-Einstellungen der enthaltenden Objekte benutzt. Hinweis: Die akquirierten Rechte-Einstellungen können durch auswählen von Rollen für eine Berechtigung zusätzlich zu den akquirierten Rechten erweitert werden.||| 2TEdStmzbsicRMTBXb6f9A==|||en|||This authentication source requires no user configuration items at this time.||| 2TEdStmzbsicRMTBXb6f9A==|||es|||This authentication source requires no user configuration items at this time.||| 2TEdStmzbsicRMTBXb6f9A==|||fr|||This authentication source requires no user configuration items at this time.||| 2TEdStmzbsicRMTBXb6f9A==|||label|||This authentication source requires no user configuration items at this time.||| 2TEdStmzbsicRMTBXb6f9A==|||de|||Diese Identfizierungsquelle benötigt zur Zeit keine benutzerkonfigurierten Elemente.||| /1my3iS+5w//0sBxL7H9QQ==|||en|||User Cache||| /1my3iS+5w//0sBxL7H9QQ==|||es|||User Cache||| /1my3iS+5w//0sBxL7H9QQ==|||fr|||User Cache||| /1my3iS+5w//0sBxL7H9QQ==|||label|||User Cache||| /1my3iS+5w//0sBxL7H9QQ==|||de|||Benutzer-Cache||| 2xNTAer3epVbKwFvm51qmQ==|||en|||Default Role||| 2xNTAer3epVbKwFvm51qmQ==|||es|||Default Role||| 2xNTAer3epVbKwFvm51qmQ==|||fr|||Default Role||| 2xNTAer3epVbKwFvm51qmQ==|||label|||Default Role||| 2xNTAer3epVbKwFvm51qmQ==|||de|||Standardrolle||| saxsamZBdTshOoSa11Dhuw==|||en|||Membership Source||| saxsamZBdTshOoSa11Dhuw==|||es|||Membership Source||| saxsamZBdTshOoSa11Dhuw==|||fr|||Membership Source||| saxsamZBdTshOoSa11Dhuw==|||label|||Membership Source||| saxsamZBdTshOoSa11Dhuw==|||de|||Mitgliedschaftsquelle||| nWV6UsD5W9N41/m1Kobzbw==|||en|||Copy initial 'Home Directory' files from...(empty=No Copy)||| nWV6UsD5W9N41/m1Kobzbw==|||es|||Copy initial 'Home Directory' files from...(empty=No Copy)||| nWV6UsD5W9N41/m1Kobzbw==|||fr|||Copy initial 'Home Directory' files from...(empty=No Copy)||| nWV6UsD5W9N41/m1Kobzbw==|||label|||Copy initial 'Home Directory' files from...(empty=No Copy)||| nWV6UsD5W9N41/m1Kobzbw==|||de|||Initiale 'Home-Verzeichnis'-Dateien koperien von...(leer=nicht kopieren)||| wsoW0EjsZuBLyig+qwSOwg==|||en|||Host||| wsoW0EjsZuBLyig+qwSOwg==|||es|||Host||| wsoW0EjsZuBLyig+qwSOwg==|||fr|||Host||| wsoW0EjsZuBLyig+qwSOwg==|||label|||Host||| wsoW0EjsZuBLyig+qwSOwg==|||de|||Host||| YTLSG7N1R7VQcBrlwUBYcg==|||en|||Authentication Source||| YTLSG7N1R7VQcBrlwUBYcg==|||es|||Authentication Source||| YTLSG7N1R7VQcBrlwUBYcg==|||fr|||Authentication Source||| YTLSG7N1R7VQcBrlwUBYcg==|||label|||Authentication Source||| YTLSG7N1R7VQcBrlwUBYcg==|||de|||Indentifizierungsquelle||| a4rxisCoIfIHFat2mZkCaA==|||en|||Relative Path (from base) of Login Page||| a4rxisCoIfIHFat2mZkCaA==|||es|||Relative Path (from base) of Login Page||| a4rxisCoIfIHFat2mZkCaA==|||fr|||Relative Path (from base) of Login Page||| a4rxisCoIfIHFat2mZkCaA==|||label|||Relative Path (from base) of Login Page||| a4rxisCoIfIHFat2mZkCaA==|||de|||Relativer Pfad (von Basis) der Anmeldeseite||| 1foHnHKu1JWEBZI92HiNBg==|||en|||standard basic authentication||| 1foHnHKu1JWEBZI92HiNBg==|||es|||standard basic authentication||| 1foHnHKu1JWEBZI92HiNBg==|||fr|||standard basic authentication||| 1foHnHKu1JWEBZI92HiNBg==|||label|||standard basic authentication||| 1foHnHKu1JWEBZI92HiNBg==|||de|||Standard-Basis-Identfizierung||| Ukd76JwQ+XREotM32UfhMQ==|||en|||Password Validation Features||| Ukd76JwQ+XREotM32UfhMQ==|||es|||Password Validation Features||| Ukd76JwQ+XREotM32UfhMQ==|||fr|||Password Validation Features||| Ukd76JwQ+XREotM32UfhMQ==|||label|||Password Validation Features||| Ukd76JwQ+XREotM32UfhMQ==|||de|||Passwort-Überprüfungs-Funktionen||| k8ugdFTwakqWAXK71uKkNQ==|||en|||Yes||| k8ugdFTwakqWAXK71uKkNQ==|||es|||Yes||| k8ugdFTwakqWAXK71uKkNQ==|||fr|||Yes||| k8ugdFTwakqWAXK71uKkNQ==|||label|||Yes||| k8ugdFTwakqWAXK71uKkNQ==|||de|||Ja||| NLA73LU+/LLYv/FZOQBUrg==|||en|||data.strftime('%Y-%m-%d')||| NLA73LU+/LLYv/FZOQBUrg==|||label|||fmtDate||| NLA73LU+/LLYv/FZOQBUrg==|||de|||data.strftime('%Y-%m-%d')||| Z24W1topqli6Lh2NtRDFzQ==|||de|||sichere Cookie-basierte Identifizierung||| Z24W1topqli6Lh2NtRDFzQ==|||en|||secure cookie-based authentication||| Z24W1topqli6Lh2NtRDFzQ==|||es|||secure cookie-based authentication||| Z24W1topqli6Lh2NtRDFzQ==|||fr|||secure cookie-based authentication||| Z24W1topqli6Lh2NtRDFzQ==|||label|||secure cookie-based authentication||| aM89f0IbVqv1BgMPVNwOMQ==|||en|||Property Source||| aM89f0IbVqv1BgMPVNwOMQ==|||es|||Property Source||| aM89f0IbVqv1BgMPVNwOMQ==|||fr|||Property Source||| aM89f0IbVqv1BgMPVNwOMQ==|||label|||Property Source||| aM89f0IbVqv1BgMPVNwOMQ==|||de|||Eigenschaften-Quelle||| tp35Ra6YbmsYgs3IetGWFw==|||en|||Hits||| tp35Ra6YbmsYgs3IetGWFw==|||es|||Hits||| tp35Ra6YbmsYgs3IetGWFw==|||fr|||Hits||| tp35Ra6YbmsYgs3IetGWFw==|||label|||Hits||| tp35Ra6YbmsYgs3IetGWFw==|||de|||Zugriffe||| ikzWlZm8hXVIpgr8s9s/lg==|||en|||Your Session has Expired||| ikzWlZm8hXVIpgr8s9s/lg==|||es|||Your Session has Expired||| ikzWlZm8hXVIpgr8s9s/lg==|||fr|||Your Session has Expired||| ikzWlZm8hXVIpgr8s9s/lg==|||label|||Your Session has Expired||| ikzWlZm8hXVIpgr8s9s/lg==|||de|||Ihre Sitzung ist abgelaufen||| P1C1z7oGVHeQOdRHqarzXQ==|||en|||Fixed Destination||| P1C1z7oGVHeQOdRHqarzXQ==|||es|||Fixed Destination||| P1C1z7oGVHeQOdRHqarzXQ==|||fr|||Fixed Destination||| P1C1z7oGVHeQOdRHqarzXQ==|||label|||Fixed Destination||| P1C1z7oGVHeQOdRHqarzXQ==|||de|||Festes Ziel||| pghS8gTtgCjBxYgIt0bRFQ==|||en|||Ok||| pghS8gTtgCjBxYgIt0bRFQ==|||es|||Ok||| pghS8gTtgCjBxYgIt0bRFQ==|||fr|||Ok||| pghS8gTtgCjBxYgIt0bRFQ==|||label|||Ok||| pghS8gTtgCjBxYgIt0bRFQ==|||de|||Ok||| 9YMg0Y11ejk8yUQ26ILIGA==|||en|||This property source requires no user configuration items at this time.||| 9YMg0Y11ejk8yUQ26ILIGA==|||es|||This property source requires no user configuration items at this time.||| 9YMg0Y11ejk8yUQ26ILIGA==|||fr|||This property source requires no user configuration items at this time.||| 9YMg0Y11ejk8yUQ26ILIGA==|||de|||Diese Eigenschaften-Quelle benötigt zur Zeit keine benutzerkonfigurierten Elemente.||| 9YMg0Y11ejk8yUQ26ILIGA==|||label|||This property source requires no user configuration items at this time.||| JkFiwMeP7Mo62SAAwLidWA==|||en|||Email a Hint||| JkFiwMeP7Mo62SAAwLidWA==|||es|||Email a Hint||| JkFiwMeP7Mo62SAAwLidWA==|||fr|||Email a Hint||| JkFiwMeP7Mo62SAAwLidWA==|||label|||Email a Hint||| JkFiwMeP7Mo62SAAwLidWA==|||de|||Hinweis emailen||| WIbWdqX2EG/oCNu7CzmktQ==|||en|||Minimum Length (0 if not required)||| WIbWdqX2EG/oCNu7CzmktQ==|||es|||Minimum Length (0 if not required)||| WIbWdqX2EG/oCNu7CzmktQ==|||fr|||Minimum Length (0 if not required)||| WIbWdqX2EG/oCNu7CzmktQ==|||label|||Minimum Length (0 if not required)||| WIbWdqX2EG/oCNu7CzmktQ==|||de|||Mindestlänge (0 falls nicht benötigt)||| UZtNJcj/v43h6/Yv3l3IVg==|||en|||Retries||| UZtNJcj/v43h6/Yv3l3IVg==|||es|||Retries||| UZtNJcj/v43h6/Yv3l3IVg==|||fr|||Retries||| UZtNJcj/v43h6/Yv3l3IVg==|||label|||Retries||| UZtNJcj/v43h6/Yv3l3IVg==|||de|||Wiederholungen||| x4X/aPSp4oyNEBRT3lJTmw==|||en|||Login Failed||| x4X/aPSp4oyNEBRT3lJTmw==|||es|||Conexión Dejada||| x4X/aPSp4oyNEBRT3lJTmw==|||fr|||Procédure de connexion Tombée en panne||| x4X/aPSp4oyNEBRT3lJTmw==|||label|||Login Failed||| x4X/aPSp4oyNEBRT3lJTmw==|||de|||Anmeldung fehlgeschlagen||| OMULcx9wq8Qsi6o+c5m0Ew==|||en|||Since||| OMULcx9wq8Qsi6o+c5m0Ew==|||es|||Since||| OMULcx9wq8Qsi6o+c5m0Ew==|||fr|||Since||| OMULcx9wq8Qsi6o+c5m0Ew==|||label|||Since||| OMULcx9wq8Qsi6o+c5m0Ew==|||de|||Seit||| pXVGNbz/gI4/QHUPr7tM3A==|||en|||Acquire
permission
settings||| pXVGNbz/gI4/QHUPr7tM3A==|||es|||Acquire
permission
settings||| pXVGNbz/gI4/QHUPr7tM3A==|||fr|||Acquire
permission
settings||| pXVGNbz/gI4/QHUPr7tM3A==|||label|||Acquire
permission
settings||| pXVGNbz/gI4/QHUPr7tM3A==|||de|||Rechte-
Einstellungen
akquirieren||| cZ4DcrkNkjmaH5rvFO53TA==|||en|||Forgotten Passwords||| cZ4DcrkNkjmaH5rvFO53TA==|||es|||Forgotten Passwords||| cZ4DcrkNkjmaH5rvFO53TA==|||fr|||Forgotten Passwords||| cZ4DcrkNkjmaH5rvFO53TA==|||label|||Forgotten Passwords||| cZ4DcrkNkjmaH5rvFO53TA==|||de|||Vergessene Passwörter||| 9gOdRLKUVrIPjzcxVa5Jcw==|||en|||Username||| 9gOdRLKUVrIPjzcxVa5Jcw==|||es|||Username||| 9gOdRLKUVrIPjzcxVa5Jcw==|||fr|||Username||| 9gOdRLKUVrIPjzcxVa5Jcw==|||label|||Username||| 9gOdRLKUVrIPjzcxVa5Jcw==|||de|||Benutzername||| 6MEMmTC1v6PiXIOyNUnjvQ==|||en|||ZODB Property Source||| 6MEMmTC1v6PiXIOyNUnjvQ==|||es|||ZODB Property Source||| 6MEMmTC1v6PiXIOyNUnjvQ==|||fr|||ZODB Property Source||| 6MEMmTC1v6PiXIOyNUnjvQ==|||de|||ZODB-Eigenschaften-Quelle||| 6MEMmTC1v6PiXIOyNUnjvQ==|||label|||ZODB Property Source||| MrTQJu6X5sXut5SUfvfDww==|||en|||Create 'Home Directory'||| MrTQJu6X5sXut5SUfvfDww==|||es|||Create 'Home Directory'||| MrTQJu6X5sXut5SUfvfDww==|||fr|||Create 'Home Directory'||| MrTQJu6X5sXut5SUfvfDww==|||label|||Create 'Home Directory'||| MrTQJu6X5sXut5SUfvfDww==|||de|||'Home-Verzeichnis' anlegen||| 6Ap9YUuPgAdbNd0sK3/V/w==|||en|||portal_memberdata wrapper||| 6Ap9YUuPgAdbNd0sK3/V/w==|||es|||portal_memberdata wrapper||| 6Ap9YUuPgAdbNd0sK3/V/w==|||fr|||portal_memberdata wrapper||| 6Ap9YUuPgAdbNd0sK3/V/w==|||label|||portal_memberdata wrapper||| 6Ap9YUuPgAdbNd0sK3/V/w==|||de|||portal_memberdata-Wrapper||| 9GNlB8qTMy+S+S+yGaQ7Ag==|||en|||Database Connection||| 9GNlB8qTMy+S+S+yGaQ7Ag==|||es|||Database Connection||| 9GNlB8qTMy+S+S+yGaQ7Ag==|||fr|||Database Connection||| 9GNlB8qTMy+S+S+yGaQ7Ag==|||label|||Database Connection||| 9GNlB8qTMy+S+S+yGaQ7Ag==|||de|||Datenbankverbindung||| xSgIsQa9gmZnJY+c5qnhMg==|||en|||Logged in Users||| xSgIsQa9gmZnJY+c5qnhMg==|||es|||Logged in Users||| xSgIsQa9gmZnJY+c5qnhMg==|||fr|||Logged in Users||| xSgIsQa9gmZnJY+c5qnhMg==|||label|||Logged in Users||| xSgIsQa9gmZnJY+c5qnhMg==|||de|||Angemeldete Benutzer||| rZ/eYO1rebMGsBn9FAeZFg==|||en|||The listing below shows the current security settings for this item. Permissions are rows and roles are columns. Checkboxes are used to indicate where roles are assigned permissions. You can also assign local roles to users, which give users extra roles in the context of this object and its subobjects.||| rZ/eYO1rebMGsBn9FAeZFg==|||es|||The listing below shows the current security settings for this item. Permissions are rows and roles are columns. Checkboxes are used to indicate where roles are assigned permissions. You can also assign local roles to users, which give users extra roles in the context of this object and its subobjects.||| rZ/eYO1rebMGsBn9FAeZFg==|||fr|||The listing below shows the current security settings for this item. Permissions are rows and roles are columns. Checkboxes are used to indicate where roles are assigned permissions. You can also assign local roles to users, which give users extra roles in the context of this object and its subobjects.||| rZ/eYO1rebMGsBn9FAeZFg==|||label|||The listing below shows the current security settings for this item. Permissions are rows and roles are columns. Checkboxes are used to indicate where roles are assigned permissions. You can also assign local roles to users, which give users extra roles in the context of this object and its subobjects.||| rZ/eYO1rebMGsBn9FAeZFg==|||de|||Die untere Auflistung zeigt die aktuellen Sicherheitseinstellungen für dieses Element. Rechte werden als Zeilen und Rollen als Spalten dargestellt. Kontrollkästchen zeigen, wo Rollen Rechten zugewiesen sind. Sie können Benutzern auch lokale Rollen zuweisen, die Benutzern Sonderrollen im Kontext dieses Objekts und seiner Unterobjekte einräumt.||| E7glfSMUgq8neYTyB9QY4g==|||en|||seconds idle||| E7glfSMUgq8neYTyB9QY4g==|||es|||seconds idle||| E7glfSMUgq8neYTyB9QY4g==|||fr|||seconds idle||| E7glfSMUgq8neYTyB9QY4g==|||label|||seconds idle||| E7glfSMUgq8neYTyB9QY4g==|||de|||Sekunden Leerlauf||| L64yYp1O9PxjQfF1G0BeRQ==|||en|||Security||| L64yYp1O9PxjQfF1G0BeRQ==|||es|||Security||| L64yYp1O9PxjQfF1G0BeRQ==|||fr|||Security||| L64yYp1O9PxjQfF1G0BeRQ==|||label|||Security||| L64yYp1O9PxjQfF1G0BeRQ==|||de|||Sicherheit||| z9oBnfkjzhO6SkL+EnznXg==|||en|||Add Postgresql Property Source||| z9oBnfkjzhO6SkL+EnznXg==|||es|||Add Postgresql Property Source||| z9oBnfkjzhO6SkL+EnznXg==|||fr|||Add Postgresql Property Source||| z9oBnfkjzhO6SkL+EnznXg==|||label|||Add Postgresql Property Source||| z9oBnfkjzhO6SkL+EnznXg==|||de|||Postgresql-Eigenschaften-Quelle hinzufügen||| sLftc2E8cJ19WuWFhspqeQ==|||en|||Login Required||| sLftc2E8cJ19WuWFhspqeQ==|||es|||Conexión Requerida||| sLftc2E8cJ19WuWFhspqeQ==|||fr|||Procédure de connexion Exigée||| sLftc2E8cJ19WuWFhspqeQ==|||label|||Login Required||| sLftc2E8cJ19WuWFhspqeQ==|||de|||Anmeldung notwendig||| HIr08gBogFaxqSO00lVzZg==|||en|||Mail Host||| HIr08gBogFaxqSO00lVzZg==|||es|||Mail Host||| HIr08gBogFaxqSO00lVzZg==|||fr|||Mail Host||| HIr08gBogFaxqSO00lVzZg==|||label|||Mail Host||| HIr08gBogFaxqSO00lVzZg==|||de|||Mail-Host||| TEdfN7JtoyCqS5QUiJCtng==|||en|||Site Name (used in emails)||| TEdfN7JtoyCqS5QUiJCtng==|||es|||Site Name (used in emails)||| TEdfN7JtoyCqS5QUiJCtng==|||fr|||Site Name (used in emails)||| TEdfN7JtoyCqS5QUiJCtng==|||label|||Site Name (used in emails)||| TEdfN7JtoyCqS5QUiJCtng==|||de|||Site-Name (für Emails)||| aU+jffjOSPKasE4Rcd0bUw==|||en|||Edit ExuserFolder||| aU+jffjOSPKasE4Rcd0bUw==|||es|||Edit ExuserFolder||| aU+jffjOSPKasE4Rcd0bUw==|||fr|||Edit ExuserFolder||| aU+jffjOSPKasE4Rcd0bUw==|||label|||Edit ExuserFolder||| aU+jffjOSPKasE4Rcd0bUw==|||de|||ExUserFolder bearbeiten||| tYi7nLcIpQLTnmrHOoXV8w==|||en|||Manage Access||| tYi7nLcIpQLTnmrHOoXV8w==|||es|||Manage Access||| tYi7nLcIpQLTnmrHOoXV8w==|||fr|||Manage Access||| tYi7nLcIpQLTnmrHOoXV8w==|||label|||Manage Access||| tYi7nLcIpQLTnmrHOoXV8w==|||de|||Zugriff verwalten||| tf6xk8cELelGMgLDj98oDQ==|||en|||Choose Authentication Type||| tf6xk8cELelGMgLDj98oDQ==|||es|||Choose Authentication Type||| tf6xk8cELelGMgLDj98oDQ==|||fr|||Choose Authentication Type||| tf6xk8cELelGMgLDj98oDQ==|||label|||Choose Authentication Type||| tf6xk8cELelGMgLDj98oDQ==|||de|||Indentifizierungstyp auswählen||| atTEPwOO+z3pUMdJSyjx7Q==|||en|||Reset and Email New password||| atTEPwOO+z3pUMdJSyjx7Q==|||es|||Reset and Email New password||| atTEPwOO+z3pUMdJSyjx7Q==|||fr|||Reset and Email New password||| atTEPwOO+z3pUMdJSyjx7Q==|||label|||Reset and Email New password||| atTEPwOO+z3pUMdJSyjx7Q==|||de|||Zurücksetzen und neues Passwort emailen||| 5zwH4VPnOSvp/x78aYeqEg==|||en|||Activate Session Tracking for anonymous users?||| 5zwH4VPnOSvp/x78aYeqEg==|||es|||Activate Session Tracking for anonymous users?||| 5zwH4VPnOSvp/x78aYeqEg==|||fr|||Activate Session Tracking for anonymous users?||| 5zwH4VPnOSvp/x78aYeqEg==|||label|||Activate Session Tracking for anonymous users?||| 5zwH4VPnOSvp/x78aYeqEg==|||de|||Session-Tracking für anonyme Benutzer aktivieren?||| d6ia2yvtSVSSwSG/oIDVLA==|||en|||Authentication Type||| d6ia2yvtSVSSwSG/oIDVLA==|||es|||Authentication Type||| d6ia2yvtSVSSwSG/oIDVLA==|||fr|||Authentication Type||| d6ia2yvtSVSSwSG/oIDVLA==|||label|||Authentication Type||| d6ia2yvtSVSSwSG/oIDVLA==|||de|||Indentifizierungstyp||| hbuQ0kI5y1vhAZ4BiGrvwQ==|||en|||Relative Path (from base) of Change Password Page||| hbuQ0kI5y1vhAZ4BiGrvwQ==|||es|||Relative Path (from base) of Change Password Page||| hbuQ0kI5y1vhAZ4BiGrvwQ==|||fr|||Relative Path (from base) of Change Password Page||| hbuQ0kI5y1vhAZ4BiGrvwQ==|||label|||Relative Path (from base) of Change Password Page||| hbuQ0kI5y1vhAZ4BiGrvwQ==|||de|||Relativer Pfad (von Basis) der 'Passwort ändern'-Seite||| ybVEcgiEPweHj8abUtlgaQ==|||en|||Password Policy||| ybVEcgiEPweHj8abUtlgaQ==|||es|||Password Policy||| ybVEcgiEPweHj8abUtlgaQ==|||fr|||Password Policy||| ybVEcgiEPweHj8abUtlgaQ==|||label|||Password Policy||| ybVEcgiEPweHj8abUtlgaQ==|||de|||Passwort-Methode||| R+b6v5fdknTo1r69kpw0ig==|||en|||Allow users to change passwords||| R+b6v5fdknTo1r69kpw0ig==|||es|||Allow users to change passwords||| R+b6v5fdknTo1r69kpw0ig==|||fr|||Allow users to change passwords||| R+b6v5fdknTo1r69kpw0ig==|||label|||Allow users to change passwords||| R+b6v5fdknTo1r69kpw0ig==|||de|||Benutzern Passwortänderung erlauben||| UWemqRssy7fOiXL38MoBiQ==|||en|||The following users have been defined.||| UWemqRssy7fOiXL38MoBiQ==|||es|||The following users have been defined.||| UWemqRssy7fOiXL38MoBiQ==|||fr|||The following users have been defined.||| UWemqRssy7fOiXL38MoBiQ==|||label|||The following users have been defined.||| UWemqRssy7fOiXL38MoBiQ==|||de|||Die folgenden Benutzer wurden angelegt.||| R1WfuDP/WDIVcOXod7Rn9g==|||en|||NEXT||| R1WfuDP/WDIVcOXod7Rn9g==|||es|||NEXT||| R1WfuDP/WDIVcOXod7Rn9g==|||fr|||NEXT||| R1WfuDP/WDIVcOXod7Rn9g==|||label|||NEXT||| R1WfuDP/WDIVcOXod7Rn9g==|||de|||WEITER||| z89Q3UXE/n700NRmhIKi5Q==|||de|||Geheimcode für sicheren Cookie-Modus||| z89Q3UXE/n700NRmhIKi5Q==|||en|||Secret for Secure Cookie Mode||| z89Q3UXE/n700NRmhIKi5Q==|||es|||Secret for Secure Cookie Mode||| z89Q3UXE/n700NRmhIKi5Q==|||fr|||Secret for Secure Cookie Mode||| z89Q3UXE/n700NRmhIKi5Q==|||label|||Secret for Secure Cookie Mode||| rSDpXWzZkx50wz6nTLS9nQ==|||en|||Add...||| rSDpXWzZkx50wz6nTLS9nQ==|||es|||Add...||| rSDpXWzZkx50wz6nTLS9nQ==|||fr|||Add...||| rSDpXWzZkx50wz6nTLS9nQ==|||label|||Add...||| rSDpXWzZkx50wz6nTLS9nQ==|||de|||Hinzufügen...||| Vnq/ahun1hFl8my6MlCqww==|||en|||Go to fixed destination||| Vnq/ahun1hFl8my6MlCqww==|||es|||Go to fixed destination||| Vnq/ahun1hFl8my6MlCqww==|||fr|||Go to fixed destination||| Vnq/ahun1hFl8my6MlCqww==|||label|||Go to fixed destination||| Vnq/ahun1hFl8my6MlCqww==|||de|||Zu festem Ziel gehen||| pc0+0RZgjawBfxTARupWvw==|||en|||Roles||| pc0+0RZgjawBfxTARupWvw==|||es|||Roles||| pc0+0RZgjawBfxTARupWvw==|||fr|||Roles||| pc0+0RZgjawBfxTARupWvw==|||label|||Roles||| pc0+0RZgjawBfxTARupWvw==|||de|||Rollen||| +arl/ajYEKKfEtHmG0qyXw==|||en|||Users||| +arl/ajYEKKfEtHmG0qyXw==|||es|||Users||| +arl/ajYEKKfEtHmG0qyXw==|||fr|||Users||| +arl/ajYEKKfEtHmG0qyXw==|||label|||Users||| +arl/ajYEKKfEtHmG0qyXw==|||de|||Benutzer||| oW14kEjdQJU2PS/G1k9ghg==|||en|||Relative Path to 'Home Directory' Root||| oW14kEjdQJU2PS/G1k9ghg==|||es|||Relative Path to 'Home Directory' Root||| oW14kEjdQJU2PS/G1k9ghg==|||fr|||Relative Path to 'Home Directory' Root||| oW14kEjdQJU2PS/G1k9ghg==|||label|||Relative Path to 'Home Directory' Root||| oW14kEjdQJU2PS/G1k9ghg==|||de|||Relativer Pfad zum Stammordner für 'Home-Verzeichnis'||| pHeEyg8Ld2Al5EXRNY+dxA==|||en|||Site Email (used for emails)||| pHeEyg8Ld2Al5EXRNY+dxA==|||es|||Site Email (used for emails)||| pHeEyg8Ld2Al5EXRNY+dxA==|||fr|||Site Email (used for emails)||| pHeEyg8Ld2Al5EXRNY+dxA==|||label|||Site Email (used for emails)||| pHeEyg8Ld2Al5EXRNY+dxA==|||de|||Site-Email (für Emails)||| YKr0TUtWIlLATbf5hJfpqg==|||en|||Port||| YKr0TUtWIlLATbf5hJfpqg==|||es|||Port||| YKr0TUtWIlLATbf5hJfpqg==|||fr|||Port||| YKr0TUtWIlLATbf5hJfpqg==|||label|||Port||| YKr0TUtWIlLATbf5hJfpqg==|||de|||Port||| qYp8/UVXgfHygMrj9myIUg==|||en|||Relative Path (from base) of Signup Page||| qYp8/UVXgfHygMrj9myIUg==|||es|||Relative Path (from base) of Signup Page||| qYp8/UVXgfHygMrj9myIUg==|||fr|||Relative Path (from base) of Signup Page||| qYp8/UVXgfHygMrj9myIUg==|||label|||Relative Path (from base) of Signup Page||| qYp8/UVXgfHygMrj9myIUg==|||de|||Relativer Pfad (von Basis) zur Abonnierungsseite||| Se4whzSOjUTh/toZF0Q5hw==|||label|||Name||| Se4whzSOjUTh/toZF0Q5hw==|||en|||Name||| Se4whzSOjUTh/toZF0Q5hw==|||es|||Nombre||| Se4whzSOjUTh/toZF0Q5hw==|||fr|||Nom||| Se4whzSOjUTh/toZF0Q5hw==|||de|||Name||| eXYZJ221lNp7DyPlhgrOAg==|||en|||Roles Column||| eXYZJ221lNp7DyPlhgrOAg==|||es|||Roles Column||| eXYZJ221lNp7DyPlhgrOAg==|||fr|||Roles Column||| eXYZJ221lNp7DyPlhgrOAg==|||label|||Roles Column||| eXYZJ221lNp7DyPlhgrOAg==|||de|||Rollenspalte||| M7HBFn+0LFZzjmkkF0gv4w==|||en|||Username Column||| M7HBFn+0LFZzjmkkF0gv4w==|||es|||Username Column||| M7HBFn+0LFZzjmkkF0gv4w==|||fr|||Username Column||| M7HBFn+0LFZzjmkkF0gv4w==|||label|||Username Column||| M7HBFn+0LFZzjmkkF0gv4w==|||de|||Benutzernamen-Spalte||| Hq6PF6d6PcpBjNNYYVYpPA==|||en|||Misses -- session timed out||| Hq6PF6d6PcpBjNNYYVYpPA==|||es|||Misses -- session timed out||| Hq6PF6d6PcpBjNNYYVYpPA==|||fr|||Misses -- session timed out||| Hq6PF6d6PcpBjNNYYVYpPA==|||label|||Misses -- session timed out||| Hq6PF6d6PcpBjNNYYVYpPA==|||de|||Fehlschläge -- Sitzung abgelaufen||| qjeC0z5mt+iE4XIPF58jJw==|||en|||Windows Domain||| qjeC0z5mt+iE4XIPF58jJw==|||es|||Windows Domain||| qjeC0z5mt+iE4XIPF58jJw==|||fr|||Windows Domain||| qjeC0z5mt+iE4XIPF58jJw==|||label|||Windows Domain||| qjeC0z5mt+iE4XIPF58jJw==|||de|||Windows-Domain||| u8aUed3nbPY7URIzo+U/wA==|||en|||Add User Supplied Authorisation Source||| u8aUed3nbPY7URIzo+U/wA==|||es|||Add User Supplied Authorisation Source||| u8aUed3nbPY7URIzo+U/wA==|||fr|||Add User Supplied Authorisation Source||| u8aUed3nbPY7URIzo+U/wA==|||label|||Add User Supplied Authorisation Source||| u8aUed3nbPY7URIzo+U/wA==|||de|||Vom Benutzer bereitgestellte Autorisierungsquelle hinzufügen||| yFolHMRXhA8eAy8bcz6TmA==|||en|||Timeout||| yFolHMRXhA8eAy8bcz6TmA==|||es|||Timeout||| yFolHMRXhA8eAy8bcz6TmA==|||fr|||Timeout||| yFolHMRXhA8eAy8bcz6TmA==|||label|||Timeout||| yFolHMRXhA8eAy8bcz6TmA==|||de|||Zeitüberschreitung||| TnD0VbF2BRtYuSsEyxi5iQ==|||en|||Choose Sources||| TnD0VbF2BRtYuSsEyxi5iQ==|||es|||Choose Sources||| TnD0VbF2BRtYuSsEyxi5iQ==|||fr|||Choose Sources||| TnD0VbF2BRtYuSsEyxi5iQ==|||label|||Choose Sources||| TnD0VbF2BRtYuSsEyxi5iQ==|||de|||Quellen auswählen||| ScAsWFPmC47K63ibhOd22Q==|||de|||Extensible User Folder||| ScAsWFPmC47K63ibhOd22Q==|||en|||Extensible User Folder||| ScAsWFPmC47K63ibhOd22Q==|||es|||Extensible User Folder||| ScAsWFPmC47K63ibhOd22Q==|||fr|||Extensible User Folder||| ScAsWFPmC47K63ibhOd22Q==|||label|||Extensible User Folder||| i0LgW8Z1U+TxtTcAX8s2lQ==|||en|||Postgresql Property Source||| i0LgW8Z1U+TxtTcAX8s2lQ==|||es|||Postgresql Property Source||| i0LgW8Z1U+TxtTcAX8s2lQ==|||fr|||Postgresql Property Source||| i0LgW8Z1U+TxtTcAX8s2lQ==|||label|||Postgresql Property Source||| i0LgW8Z1U+TxtTcAX8s2lQ==|||de|||Postgresql-Eigenschaften-Quelle||| ctElvhngVnB51w0J1h9pLg==|||en|||You have been logged out of the system.||| ctElvhngVnB51w0J1h9pLg==|||es|||You have been logged out of the system.||| ctElvhngVnB51w0J1h9pLg==|||fr|||You have been logged out of the system.||| ctElvhngVnB51w0J1h9pLg==|||label|||You have been logged out of the system.||| ctElvhngVnB51w0J1h9pLg==|||de|||Sie wurden vom System abgemeldet.||| gJAF4QsLR9gil2K09mrVgg==|||en|||Password File||| gJAF4QsLR9gil2K09mrVgg==|||es|||Password File||| gJAF4QsLR9gil2K09mrVgg==|||fr|||Password File||| gJAF4QsLR9gil2K09mrVgg==|||label|||Password File||| gJAF4QsLR9gil2K09mrVgg==|||de|||Passwortdatei||| wd8dp6HOMFo7YK+dVzOsHQ==|||en|||Contents||| wd8dp6HOMFo7YK+dVzOsHQ==|||es|||Contents||| wd8dp6HOMFo7YK+dVzOsHQ==|||fr|||Contents||| wd8dp6HOMFo7YK+dVzOsHQ==|||label|||Contents||| wd8dp6HOMFo7YK+dVzOsHQ==|||de|||Inhalt||| knJqtfrrLLkgjqrJrwNGvQ==|||en|||User Management||| knJqtfrrLLkgjqrJrwNGvQ==|||es|||User Management||| knJqtfrrLLkgjqrJrwNGvQ==|||fr|||User Management||| knJqtfrrLLkgjqrJrwNGvQ==|||label|||User Management||| knJqtfrrLLkgjqrJrwNGvQ==|||de|||Benutzerverwaltung||| IC8K1DsrmIw3rUi/aQnMAQ==|||en|||ZODB Auth Source||| IC8K1DsrmIw3rUi/aQnMAQ==|||es|||ZODB Auth Source||| IC8K1DsrmIw3rUi/aQnMAQ==|||fr|||ZODB Auth Source||| IC8K1DsrmIw3rUi/aQnMAQ==|||de|||ZODB-Identfizierungsquelle||| IC8K1DsrmIw3rUi/aQnMAQ==|||label|||ZODB Auth Source||| HNwHayj3CvrF/O2t+Z+hGQ==|||en|||Undo||| HNwHayj3CvrF/O2t+Z+hGQ==|||es|||Undo||| HNwHayj3CvrF/O2t+Z+hGQ==|||fr|||Undo||| HNwHayj3CvrF/O2t+Z+hGQ==|||label|||Undo||| HNwHayj3CvrF/O2t+Z+hGQ==|||de|||Rückgängig||| CnzuK2c/Zs9k2cMYOqW8Gw==|||en|||Properties Source||| CnzuK2c/Zs9k2cMYOqW8Gw==|||es|||Properties Source||| CnzuK2c/Zs9k2cMYOqW8Gw==|||fr|||Properties Source||| CnzuK2c/Zs9k2cMYOqW8Gw==|||label|||Properties Source||| CnzuK2c/Zs9k2cMYOqW8Gw==|||de|||Eigenschaften-Quelle||| MiWhCwfxWA8Q3uSrw3eebA==|||en|||Parameters||| MiWhCwfxWA8Q3uSrw3eebA==|||es|||Parameters||| MiWhCwfxWA8Q3uSrw3eebA==|||fr|||Parameters||| MiWhCwfxWA8Q3uSrw3eebA==|||label|||Parameters||| MiWhCwfxWA8Q3uSrw3eebA==|||de|||Parameter||| JUUaIY7hVHzChgKlYmAa2Q==|||en|||Go to intended destination||| JUUaIY7hVHzChgKlYmAa2Q==|||es|||Go to intended destination||| JUUaIY7hVHzChgKlYmAa2Q==|||fr|||Go to intended destination||| JUUaIY7hVHzChgKlYmAa2Q==|||label|||Go to intended destination||| JUUaIY7hVHzChgKlYmAa2Q==|||de|||Zu beabsichtigtem Ziel gehen||| HmlHrH+zqVKalybraSyMxQ==|||en|||Secret||| HmlHrH+zqVKalybraSyMxQ==|||es|||Secret||| HmlHrH+zqVKalybraSyMxQ==|||fr|||Secret||| HmlHrH+zqVKalybraSyMxQ==|||label|||Secret||| HmlHrH+zqVKalybraSyMxQ==|||de|||Geheim||| 4KoCHiHd29bYzs7HHpz1ZA==|||en|||OK||| 4KoCHiHd29bYzs7HHpz1ZA==|||es|||OK||| 4KoCHiHd29bYzs7HHpz1ZA==|||fr|||OK||| 4KoCHiHd29bYzs7HHpz1ZA==|||de|||OK||| 4KoCHiHd29bYzs7HHpz1ZA==|||label|||OK||| EhBcpjFZQStP+X8f0elkHw==|||en|||This Auth source requires no user configuration items at this time.||| EhBcpjFZQStP+X8f0elkHw==|||es|||This Auth source requires no user configuration items at this time.||| EhBcpjFZQStP+X8f0elkHw==|||fr|||This Auth source requires no user configuration items at this time.||| EhBcpjFZQStP+X8f0elkHw==|||de|||Diese Identfizierungsquelle benötigt zur Zeit keine benutzerkonfigurierten Elemente.||| EhBcpjFZQStP+X8f0elkHw==|||label|||This Auth source requires no user configuration items at this time.||| 3fgG6089cYfIYrDvwQFukA==|||en|||Cache Data||| 3fgG6089cYfIYrDvwQFukA==|||es|||Cache Data||| 3fgG6089cYfIYrDvwQFukA==|||fr|||Cache Data||| 3fgG6089cYfIYrDvwQFukA==|||label|||Cache Data||| 3fgG6089cYfIYrDvwQFukA==|||de|||Cache-Daten||| IFTRP5H7kHrkH+0FKqVnMQ==|||en|||Add Postgresql Authorisation Source||| IFTRP5H7kHrkH+0FKqVnMQ==|||es|||Add Postgresql Authorisation Source||| IFTRP5H7kHrkH+0FKqVnMQ==|||fr|||Add Postgresql Authorisation Source||| IFTRP5H7kHrkH+0FKqVnMQ==|||label|||Add Postgresql Authorisation Source||| IFTRP5H7kHrkH+0FKqVnMQ==|||de|||Postgresql-Indentifizierungsquelle hinzufügen||| eNpxVwST06vQ+083Py7xbw==|||en|||After login....||| eNpxVwST06vQ+083Py7xbw==|||es|||After login....||| eNpxVwST06vQ+083Py7xbw==|||fr|||After login....||| eNpxVwST06vQ+083Py7xbw==|||label|||After login....||| eNpxVwST06vQ+083Py7xbw==|||de|||Nach Anmeldung...||| QXQM2Nggyd/Idj+pM39/Bg==|||en|||Site Base||| QXQM2Nggyd/Idj+pM39/Bg==|||es|||Site Base||| QXQM2Nggyd/Idj+pM39/Bg==|||fr|||Site Base||| QXQM2Nggyd/Idj+pM39/Bg==|||label|||Site Base||| QXQM2Nggyd/Idj+pM39/Bg==|||de|||Site-Basis||| y572up+UfIjkUA5va1bUWQ==|||en|||Postgresql Authorisation Source||| y572up+UfIjkUA5va1bUWQ==|||es|||Postgresql Authorisation Source||| y572up+UfIjkUA5va1bUWQ==|||fr|||Postgresql Authorisation Source||| y572up+UfIjkUA5va1bUWQ==|||label|||Postgresql Authorisation Source||| y572up+UfIjkUA5va1bUWQ==|||de|||Postgresql-Autorisierungsquelle||| Bqulw22+tIat3efU2xgCBA==|||en|||File Authentication Source||| Bqulw22+tIat3efU2xgCBA==|||es|||File Authentication Source||| Bqulw22+tIat3efU2xgCBA==|||fr|||File Authentication Source||| Bqulw22+tIat3efU2xgCBA==|||label|||File Authentication Source||| Bqulw22+tIat3efU2xgCBA==|||de|||Datei-Identifizierungsquelle||| DeSv2tV+0Q0mY74vhEgWAQ==|||en|||Ownership||| DeSv2tV+0Q0mY74vhEgWAQ==|||es|||Ownership||| DeSv2tV+0Q0mY74vhEgWAQ==|||fr|||Ownership||| DeSv2tV+0Q0mY74vhEgWAQ==|||label|||Ownership||| DeSv2tV+0Q0mY74vhEgWAQ==|||de|||Besitz||| lM3fVn6wvQM8z+yhsczNVQ==|||en|||Add POP3 Authorisation Source||| lM3fVn6wvQM8z+yhsczNVQ==|||es|||Add POP3 Authorisation Source||| lM3fVn6wvQM8z+yhsczNVQ==|||fr|||Add POP3 Authorisation Source||| lM3fVn6wvQM8z+yhsczNVQ==|||label|||Add POP3 Authorisation Source||| lM3fVn6wvQM8z+yhsczNVQ==|||de|||POP3-Indentifizierungsquelle hinzufügen||| 8BIaxFFjqNL5SYGAZqpkjg==|||en|||Add SMB Authorisation Source||| 8BIaxFFjqNL5SYGAZqpkjg==|||es|||Add SMB Authorisation Source||| 8BIaxFFjqNL5SYGAZqpkjg==|||fr|||Add SMB Authorisation Source||| 8BIaxFFjqNL5SYGAZqpkjg==|||label|||Add SMB Authorisation Source||| 8BIaxFFjqNL5SYGAZqpkjg==|||de|||SMB-Indentifizierungsquelle hinzufügen||| iF+uNjJBcEKOG4+o1Wzodw==|||en|||Password Column||| iF+uNjJBcEKOG4+o1Wzodw==|||es|||Password Column||| iF+uNjJBcEKOG4+o1Wzodw==|||fr|||Password Column||| iF+uNjJBcEKOG4+o1Wzodw==|||label|||Password Column||| iF+uNjJBcEKOG4+o1Wzodw==|||de|||Passwortspalte||| j6ewWrRZNb9IM5CVeKOj5Q==|||en|||Table Name||| j6ewWrRZNb9IM5CVeKOj5Q==|||es|||Table Name||| j6ewWrRZNb9IM5CVeKOj5Q==|||fr|||Table Name||| j6ewWrRZNb9IM5CVeKOj5Q==|||label|||Table Name||| j6ewWrRZNb9IM5CVeKOj5Q==|||de|||Tabellenname||| BrRmJbvgMeNceNIo0/p6pA==|||en|||This property source uses the underlying portal_memberdata tool in the current CMF Site.
There is no configuration needed at this time.||| BrRmJbvgMeNceNIo0/p6pA==|||es|||This property source uses the underlying portal_memberdata tool in the current CMF Site.
There is no configuration needed at this time.||| BrRmJbvgMeNceNIo0/p6pA==|||fr|||This property source uses the underlying portal_memberdata tool in the current CMF Site.
There is no configuration needed at this time.||| BrRmJbvgMeNceNIo0/p6pA==|||label|||This property source uses the underlying portal_memberdata tool in the current CMF Site.
There is no configuration needed at this time.||| BrRmJbvgMeNceNIo0/p6pA==|||de|||Diese Eigenschaften-Quelle benutzt das zugrunde liegende portal_memberdata-Werkzeug der aktuellen CMF-Site.
Zur Zeit ist eine Konfiguration nötig.||| q1BJZ73MJid6nGdw5TfAkA==|||en|||Edit this Username...||| q1BJZ73MJid6nGdw5TfAkA==|||es|||Edit this Username...||| q1BJZ73MJid6nGdw5TfAkA==|||fr|||Edit this Username...||| q1BJZ73MJid6nGdw5TfAkA==|||label|||Edit this Username...||| q1BJZ73MJid6nGdw5TfAkA==|||de|||Diesen Benutzernamen bearbeiten...||| 7V3qCQlfZxuAG+406iijGQ==|||en|||Permission||| 7V3qCQlfZxuAG+406iijGQ==|||es|||Permission||| 7V3qCQlfZxuAG+406iijGQ==|||fr|||Permission||| 7V3qCQlfZxuAG+406iijGQ==|||label|||Permission||| 7V3qCQlfZxuAG+406iijGQ==|||de|||Berechtigung||| aNG7z+qMlYbaX/NDhK0oBQ==|||en|||cookie-based authentication||| aNG7z+qMlYbaX/NDhK0oBQ==|||es|||cookie-based authentication||| aNG7z+qMlYbaX/NDhK0oBQ==|||fr|||cookie-based authentication||| aNG7z+qMlYbaX/NDhK0oBQ==|||label|||cookie-based authentication||| aNG7z+qMlYbaX/NDhK0oBQ==|||de|||Cookie-basierte Identifizierung||| 7CEffCCvQ+dCvyVww8uE+Q==|||en|||Add||| 7CEffCCvQ+dCvyVww8uE+Q==|||es|||Add||| 7CEffCCvQ+dCvyVww8uE+Q==|||fr|||Add||| 7CEffCCvQ+dCvyVww8uE+Q==|||label|||Add||| 7CEffCCvQ+dCvyVww8uE+Q==|||de|||Hinzufügen||| CL//3J2C80hseSEM+YkYPQ==|||en|||Add Basic Membership Source||| CL//3J2C80hseSEM+YkYPQ==|||es|||Add Basic Membership Source||| CL//3J2C80hseSEM+YkYPQ==|||fr|||Add Basic Membership Source||| CL//3J2C80hseSEM+YkYPQ==|||label|||Add Basic Membership Source||| CL//3J2C80hseSEM+YkYPQ==|||de|||Basis-Mitgliedschaftsquelle hinzufügen||| yLv4ZAhninxtfuw7EWnQBA==|||en|||Misses -- wrong password||| yLv4ZAhninxtfuw7EWnQBA==|||es|||Misses -- wrong password||| yLv4ZAhninxtfuw7EWnQBA==|||fr|||Misses -- wrong password||| yLv4ZAhninxtfuw7EWnQBA==|||label|||Misses -- wrong password||| yLv4ZAhninxtfuw7EWnQBA==|||de|||Fehlschläge -- falsches Passwort||| 7wAj4kvp8frUxsBCrtgxxA==|||de|||exUserFolder properties have been updated||| 7wAj4kvp8frUxsBCrtgxxA==|||en|||exUserFolder properties have been updated||| 7wAj4kvp8frUxsBCrtgxxA==|||es|||exUserFolder properties have been updated||| 7wAj4kvp8frUxsBCrtgxxA==|||fr|||exUserFolder properties have been updated||| 7wAj4kvp8frUxsBCrtgxxA==|||label|||exUserFolder properties have been updated||| DdUhqXM5O+KBxFZTkAIpKg==|||en|||Total Access||| DdUhqXM5O+KBxFZTkAIpKg==|||es|||Total Access||| DdUhqXM5O+KBxFZTkAIpKg==|||fr|||Total Access||| DdUhqXM5O+KBxFZTkAIpKg==|||label|||Total Access||| DdUhqXM5O+KBxFZTkAIpKg==|||de|||Gesamtzugriffe||| FtafbJl7t50UNMXUQOeqCQ==|||en|||Go to Home Directory||| FtafbJl7t50UNMXUQOeqCQ==|||es|||Go to Home Directory||| FtafbJl7t50UNMXUQOeqCQ==|||fr|||Go to Home Directory||| FtafbJl7t50UNMXUQOeqCQ==|||label|||Go to Home Directory||| FtafbJl7t50UNMXUQOeqCQ==|||de|||Zum Home-Verzeichnis gehen||| qU_EtagSupport__etagqU ts07292627.7q U__name__q UZBabelDictionary_txtq Utitleq UZBabel Export Fileq U content_typeqU text/htmlqU preconditionqUU__ac_local_roles__q}qUadminq]qUOwnerqasu.ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿexUserFolder/LDAPAuthSource/0040755003462500001440000000000007702273433014773 5ustar b14741usersexUserFolder/LDAPAuthSource/LDAPAuthSource.py0100644003462500001440000004340107701627740020072 0ustar b14741usersimport 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 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('LDAPBindDN', '') 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='Authorisation Source' id ='LDAPAuthSource' title ='LDAP Authorisation' 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('LDAPBindDN', '') 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 getLDAPCache(self, key): return LDAPCacheFetch(LDAP_CACHE, key) def _ldap_FindConnection(self): zLOG.LOG(LOG_STRING, zLOG.DEBUG, "Entering _ldapFindConnection") connections = self.getConnections() l = None for l in connections: if ( l.port == self._port and l.host == self._host ): 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) try: result = l.ldapConn.search_s(self._basedn, self._scope, filter) except: import traceback traceback.print_exc() return None resultData = result # Should only get one result if len(resultData) != 1: 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, zLOG.PROBLEM, "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, zLOG.PROBLEM, "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, zLOG.PROBLEM, "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, zLOG.DEBUG, "testing for group membership in '%s'"%(t)) for ent in self.groupAttribute: if not ent: continue zLOG.LOG(LOG_STRING, zLOG.DEBUG, "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, zLOG.DEBUG, "Found It...") if (curtime - compare_node.lastcompare) > self.compareCacheTTL: zLOG.LOG(LOG_STRING, zLOG.DEBUG, "...but it's too old.") cache.remove(compare_node) else: zLOG.LOG(LOG_STRING, zLOG.DEBUG, "...and it's good.") return 1 try: ldc.lock.acquire() connected = self._ldap_connectToServer(ldc) if not connected: return 0 zLOG.LOG(LOG_STRING, zLOG.DEBUG, "Doing LDAP compare of %s=%s in entry %s"%(attrib, value, dn)) zLOG.LOG(LOG_STRING, zLOG.DEBUG, "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, zLOG.DEBUG, "Compare succeeded; caching result") compare_node.lastcompare=curtime cache.insert(compare_node) return 1 zLOG.LOG(LOG_STRING, zLOG.DEBUG, "Compare failed") return 0 finally: ldc.lock.release() def _ldap_connectToServer(self, l): zLOG.LOG(LOG_STRING, zLOG.DEBUG, "Entering _ldap_connectToServer") if not l.ldapConn: l.boundas=bind_none if l.bounddn: l.bounddn = None zLOG.LOG(LOG_STRING, zLOG.DEBUG, "Opening Connection to ldap server(s) '%s'"%(self._host)) zLOG.LOG(LOG_STRING, zLOG.DEBUG, "LDAP OP: init") l.init() if not l.ldapConn: zLOG.LOG(LOG_STRING, zLOG.PROBLEM, "Could not connect to LDAP server") return 0 try: l.ldapConn.set_option(ldap.OPT_DEREF, self.dereferenceAliases) except: zLOG.LOG(LOG_STRING, zLOG.PROBLEM, "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, zLOG.PROBLEM, "Setting LDAP version option failed") return 0 zLOG.LOG(LOG_STRING, zLOG.DEBUG, "Starting TLS for this connection") l.withtls = 1 try: l.ldapConn.start_tls_s() except: zLOG.LOG(LOG_STRING, zLOG.PROBLEM, "Start TLS Failed.") return 0 else: l.withtls=0 if l.boundas == bind_system: return 1 zLOG.LOG(LOG_STRING, zLOG.DEBUG, "Binding to server '%s' as %s/%s"%(self._host, self.bindDN, self.bindPassword)) zLOG.LOG(LOG_STRING, zLOG.DEBUG, "LDAP OP: simple bind") result = l.ldapConn.simple_bind(self.bindDN, self.bindPassword) if result == ldap.SERVER_DOWN: self.freeConnection(l, 1) return 0 if result != 1: self.freeConnection(l) zLOG.LOG(LOG_STRING, zLOG.PROBLEM, "Could not bind to LDAP server '%s' as %s: %s"%(self._host, self.bindDN, result)) return 0 l.bounddn = self.bindDN l.boundas = bind_system return 1 def freeConnection(self, ldc, log=0): if log: zLOG.LOG(LOG_STRING, zLOG.DEBUG, "Server is down") if ldc.ldapConn: zLOG.LOG(LOG_STRING, zLOG.DEBUG, "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 def remoteAuthMethod(self, username, password): zLOG.LOG(LOG_STRING, zLOG.DEBUG, "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, zLOG.PROBLEM, "Could not find/create LDAPConnection") return 0 zLOG.LOG(LOG_STRING, zLOG.DEBUG, "Using URL: %s"%(self.url)) searchNode = curl.searchCache.fetch(username) if searchNode and searchNode.bindpw: zLOG.LOG(LOG_STRING, zLOG.DEBUG, "Found entry in cache for '%s'..."%(username)) curtime=time.time() if curtime == searchNode.lastBind > self.searchCacheTTL: zLOG.LOG(LOG_STRING, zLOG.DEBUG, "... but entry is too old (%d seconds)"%(curtime-searchnode.lastbind)) curl.searchCache.remove(username) elif searchNode.bindpw != password: zLOG.LOG(LOG_STRING, zLOG.DEBUG, "... but entry password doesn't match") curl.searchCache.remove(username) else: zLOG.LOG(LOG_STRING, zLOG.DEBUG, "... and entry is valid") return 1 zLOG.LOG(LOG_STRING, zLOG.DEBUG, "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, zLOG.DEBUG, "Performing a search (scope = %d) with filter %s"%( self._scope, filtbuf)) zLOG.LOG(LOG_STRING, zLOG.DEBUG, "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, zLOG.PROBLEM, "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, zLOG.PROBLEM, "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, zLOG.DEBUG, "DN returned from search is %s"%(dn)) if len(password) <= 0: zLOG.LOG(LOG_STRING, zLOG.PROBLEM, "Empty Password") return 0 zLOG.LOG(LOG_STRING, zLOG.DEBUG, "Validating user '%s' via bind"%(username)) zLOG.LOG(LOG_STRING, zLOG.DEBUG, "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, zLOG.PROBLEM, "User bind as %s failed: LDAP error: %s; URI %s"%( dn, e, self.url)) return 0 zLOG.LOG(LOG_STRING, zLOG.DEBUG, "authenticate: accepting") finally: l.lock.release() zLOG.LOG(LOG_STRING, zLOG.DEBUG, "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 exUserFolder/LDAPAuthSource/LDAPCache.py0100644003462500001440000000700007701627740017006 0ustar b14741usersimport 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.nunpurges = 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 index = 0 for k,n in self.cache_nodes.items(): 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 index = index + 1 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) exUserFolder/LDAPAuthSource/LDAPConnection.py0100644003462500001440000000063707701641610020103 0ustar b14741usersimport 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 traceback.print_exc() self.ldapConn = None exUserFolder/LDAPAuthSource/README0100644003462500001440000000152207701627740015653 0ustar b14741usersThis 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. exUserFolder/LDAPAuthSource/__init__.py0100644003462500001440000000002607701627740017102 0ustar b14741usersimport LDAPAuthSource exUserFolder/LDAPAuthSource/manage_addLDAPAuthSourceForm.dtml0100644003462500001440000000643007701627740023210 0ustar b14741users
">
URL
(ldap://host:port/basedn?username_attribute?scope?filter) (use ldaps:// for secure)
BindDN
Bind Password
Certificate DB Path
Require DN
Compare DN On Server
Dereference Aliases
Group Attribute
Group Attribute is DN
Start TLS
Require Group
Require User
Manager User Name (must auth)
Default Role
Search Cache Size
Search Cache TTL
Compare Cache Size
Compare Cache TTL

Add" >
exUserFolder/LDAPAuthSource/manage_editLDAPAuthSourceForm.dtml0100644003462500001440000000662707701627740023415 0ustar b14741users
URL
(ldap://host:port/basedn?username_attribute?scope?filter) (use ldaps:// for secure)
BindDN
Bind Password
Certificate DB Path
Require DN
Compare DN On Server
Dereference Aliases
Group Attribute
Group Attribute is DN
Start TLS
Require Group
Require User
Manager User Name (must auth)
Default Role
Search Cache Size
Search Cache TTL
Compare Cache Size
Compare Cache TTL

Add" >
exUserFolder/PropertyEditor/0040755003462500001440000000000007702273433015243 5ustar b14741usersexUserFolder/PropertyEditor/.cvsignore0100654003462500001440000000002607423611546017240 0ustar b14741users*.pyc *.pyo *~ .*.swp exUserFolder/PropertyEditor/PropertyEditor.py0100644003462500001440000000710407425557352020616 0ustar b14741users# # 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 # $Id: PropertyEditor.py,v 1.3 2025/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('\n'%(name, value)) def viewStringProperty( name, value): """ """ return('
%s
\n'%(value, value)) def editIntegerProperty( name, value): """ """ return('\n'%(name, value or 0)) def viewIntegerProperty( name, value): """ """ return('
%d
\n'%(value or 0 , value or 0)) def editLongProperty( name, value): """ """ return('\n'%(name, value or 0)) def viewLongProperty( name, value): """ """ return('
%d
\n'%(value or 0, value or 0)) def editFloatProperty( name, value): """ """ return('\n'%(name, value)) def viewFloatProperty( name, value): """ """ return('
%f
\n'%(value, value)) def editListProperty( name, value): a='' if value: a = a + 'Select Items to keep
\n' a = a + '\n
' a = a + 'Add an item\n
' a = a + ''%(name) return(a) def viewListProperty( name, value): a='' if value: for i in value: a = a + ( '\n'%(i)) a = a + '%s\n
'%(i) return(a) def editDictProperty( name, value): """ """ a='' if value and value.keys(): for i in value.keys(): a = a + '%s : \n
'%(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 : \n
'%(i, name, i, value[i]) a = a + '%s\n
'%(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} exUserFolder/PropertyEditor/__init__.py0100644003462500001440000000201307402113544017336 0ustar b14741users# # 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 # $Id: __init__.py,v 1.2 2025/12/01 08:40:04 akm Exp $ import PropertyEditor exUserFolder/UserCache/0040755003462500001440000000000007702273433014112 5ustar b14741usersexUserFolder/UserCache/.cvsignore0100654003462500001440000000002607423611546016107 0ustar b14741users*.pyc *.pyo *~ .*.swp exUserFolder/UserCache/UserCache.py0100644003462500001440000002141207611542025016316 0ustar b14741users# # 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 # $Id: UserCache.py,v 1.15 2025/01/16 14:39:17 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): 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 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() exUserFolder/UserCache/__init__.py0100644003462500001440000000200607402113544016207 0ustar b14741users# # 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 # $Id: __init__.py,v 1.2 2025/12/01 08:40:04 akm Exp $ import UserCache exUserFolder/basicMemberSource/0040755003462500001440000000000007702273433015642 5ustar b14741usersexUserFolder/basicMemberSource/.cvsignore0100654003462500001440000000002607423611547017640 0ustar b14741users*.pyc *.pyo *~ .*.swp exUserFolder/basicMemberSource/PasswordForm.dtml0100644003462500001440000000167307401747075021162 0ustar b14741users
Old Password
Password
Confirm Password
Password Hint
Change Password ">
exUserFolder/basicMemberSource/SignupForm.dtml0100644003462500001440000000260607401747075020622 0ustar b14741users
Username
Password
Confirm Password
Password Hint
Real Name
Email
Signup ">
exUserFolder/basicMemberSource/__init__.py0100644003462500001440000000012007402113544017732 0ustar b14741users# $Id: __init__.py,v 1.2 2025/12/01 08:40:04 akm Exp $ import basicMemberSource exUserFolder/basicMemberSource/basicMemberSource.py0100644003462500001440000004535207701644655021623 0ustar b14741users# # 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 # $Id: basicMemberSource.py,v 1.12 2025/07/05 21:57:01 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 exUserFolder/basicMemberSource/forgotPasswordEmail.dtml0100644003462500001440000000066107401743341022513 0ustar b14741users To: <> From: <> Subject: You forgot your password for Dear , Your username is and your password is now . You should have tested this first, and now that you've tested it, you'll see you need to customise this method. exUserFolder/basicMemberSource/manage_addBasicMemberSourceForm.dtml0100644003462500001440000001302107531440501024645 0ustar b14741users Membership requires a valid property source, you cannot use this with NULL Property Source
">
Site Name (used in emails)
Site Email (used for emails)
Mail Host
Site Base ">
Relative Path (from base) of Login Page
Relative Path (from base) of Signup Page
Relative Path (from base) of Change Password Page
Password Validation Features
Minimum Length (0 if not required)
Password Policy
Forgotten Passwords
Allow users to change passwords Yes
Create 'Home Directory' Yes
Relative Path to 'Home Directory' Root
Copy initial 'Home Directory' files from...(empty=No Copy) ">
After login....
Fixed Destination
Default Roles
Add">
exUserFolder/basicMemberSource/manage_editBasicMemberSourceForm.dtml0100644003462500001440000001203307401747075025060 0ustar b14741users
Site Name (used in emails)
Site Email (used for emails)
Mail Host
Site Base
Relative Path (from base) of Login Page
Relative Path (from base) of Signup Page
Relative Path (from base) of Change Password Page
Password Validation Features
Minimum Length (if required)
Allow users to change passwords checked>Yes
Create 'Home Directory' checked>Yes
Path to 'Home Directory' Root
Copy initial 'Home Directory' files from...(empty=No Copy)
After login....
Fixed Destination
Default Roles
Update ">
exUserFolder/basicMemberSource/newPasswordEmail.dtml0100644003462500001440000000071307401743341022002 0ustar b14741users To: <> From: <> Subject: Welcome to Dear , Welcome to . Your username is and your password is . You should have tested this first, and now that you've tested it, you'll see you need to customise this method. exUserFolder/basicMemberSource/passwordHintEmail.dtml0100644003462500001440000000063407401743341022155 0ustar b14741users To: <> From: <> Subject: Hint for Dear , Your username is and your password hint was; . You should have tested this first, and now that you've tested it, you'll see you need to customise this method. exUserFolder/cmfPropSource/0040755003462500001440000000000007702273433015037 5ustar b14741usersexUserFolder/cmfPropSource/.cvsignore0100654003462500001440000000002607423611547017035 0ustar b14741users*.pyc *.pyo *~ .*.swp exUserFolder/cmfPropSource/README0100644003462500001440000000215607401743341015714 0ustar b14741userson 9.22.01 by runyaga portal_memberdata exUserFolder PropertySource REQUIRES the Content Management Framework (CMF) at least v1.1 inside your CMF Site you can have any kind of User Folder doing authentication for CMF. exUserFolder works well in this role. This is a PropertySource for exUserFolder, meaning it provides properties for Users in the exUserFolder. You may know the CMF provides you with a portal_memberdata tool that stores Properties for Members (Users). this PropertySource is a thin wrapper around the portal_memberdata tool, which is the CMF's native way to assign Propeties on Member objects. Features: * you can click on a user in the exUserFolder and edit their memberdata ;) * you can click on PropertySource and you will goto the portal_memberdata propertyiesForm * use the AuthenticationSources provided by exUserFolder to easily authenticate with external sources such as: /etc/passwd, ZODB, PostgreSQL, and many more. License included in source code. Please help out and write more Authenticators, this is a simple framework that may wind its way into the ZOPE codebase ;) exUserFolder/cmfPropSource/__init__.py0100644003462500001440000000011407402113544017132 0ustar b14741users# $Id: __init__.py,v 1.2 2025/12/01 08:40:04 akm Exp $ import cmfPropSource exUserFolder/cmfPropSource/cmfPropSource.py0100644003462500001440000001141707570446217020206 0ustar b14741users# # Extensible User Folder # # CMF memberdata_tool wrapper Property 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: alan runyan # $Id: cmfPropSource.py,v 1.5 2025/11/25 16:15:11 davidcoe Exp $ __version__ = '0.02' DEBUG = 0 from Globals import HTMLFile, MessageDialog, Acquisition from OFS.Folder import Folder from Products.exUserFolder.exUserFolder import exUserFolder from Products.exUserFolder.Plugins import PluginRegister from Products.CMFCore.utils import getToolByName manage_addPropSourceForm=HTMLFile('manage_addcmfPropSourceForm', globals()) manage_addcmfPropSourceForm=HTMLFile('manage_addcmfPropSourceForm', globals()) manage_editcmfPropSourceForm=HTMLFile('manage_editcmfPropSourceForm', globals()) def manage_addcmfPropSource(self, REQUEST): """ Add a portal_memberdata wrapper """ o = cmfPropSource() self._setObject('cmfPropSource', o, None, None, 0) o = getattr(self, 'cmfPropSource') if hasattr(o, 'postInitialisation'): o.postInitialisation(REQUEST) self.currentPropSource = o class cmfPropSource(Folder): """ Store User Data in portal_memberdata tool """ meta_type='Property Source' title='portal_memberdata Properties' icon ='misc_/CMFCore/tool.gif' manage_editForm=manage_editcmfPropSourceForm manage_tabs=Acquisition.Acquired def __init__(self): self.id='cmfPropSource' self.title='portal_memberdata wrapper' def __str__(self): return 'exUserFolder that wraps memberdata' def hasProperty(self, key): return self.getToolByName(self, 'portal_memberdata').hasProperty(key) def setProperty(self, key, value): if DEBUG: 'setProperty on propSource', key, str(value) pass def setTempProperty(self, key, value): pass def flushTempProperties(self): pass def delProperty(self, key): if DEBUG: 'delProperty on propSource', key self.delUserProperty(key=key, username=self.name) def delUserProperty(self, key, username): pass def getProperty(self, key, default=None): return getToolByName(self, 'portal_memberdata').getProperty(key, default) def listProperties(self): prop_ids = [] for t in self.getToolByName(self, 'portal_memberdata').propertyItems(): prop_ids.append(t[0]) return props def getUserProperty(self, key, username, default = None): member = getToolByName(self, 'portal_membership').getMemberById(username) return getattr(member, key, default) def setUserProperty(self, key, username, value): if DEBUG: print 'setUserProperty', username, str(key), str(value) member = getToolByName(self, 'portal_membership').getMemberById(username) setattr(member, key, value) def listUserProperties(self, username): props=[] member = getToolByName(self, 'portal_membership').getMemberById(username) for p in getToolByName(self, 'portal_memberdata').propertyItems(): if hasattr(member, p[0]): props.append(p[0]) return props def createUser(self, username, REQUEST): """ disabled because CMF plumbing does this before we are reached """ pass def deleteUsers(self, userids): """ you can delete a user and then prune the memberdata, this is not impl'd """ pass def updateUser(self, username, REQUEST): """ set member's properties from a mapping """ if DEBUG: print 'updateUser ', username, str(REQUEST.keys()) map = {} for prop in REQUEST.keys(): if prop[:5]=='user_': map[prop[5:]]=self.REQUEST[prop] getToolByName(self, 'portal_membership').getMemberById(username).setMemberProperties(map) def postInitialisation(self, REQUEST): """ currently isnt getting called.. why? """ if DEBUG: 'init and tried to override manage_editcmfPropSourceForm' manage_editcmfPropSourceForm = getattr( getToolByName(self, 'portal_memberdata') , 'manage_propertiesForm' ) cmfPropReg=PluginRegister('cmfPropSource', 'portal_memberdata wrapper', cmfPropSource, manage_addcmfPropSourceForm, manage_addcmfPropSource, manage_editcmfPropSourceForm ) exUserFolder.propSources['cmfPropSource']=cmfPropReg exUserFolder/cmfPropSource/manage_addcmfPropSourceForm.dtml0100644003462500001440000000163607401747075023324 0ustar b14741users
">

This property source uses the underlying portal_memberdata tool in the current CMF Site.
There is no configuration needed at this time.


NEXT ">
exUserFolder/cmfPropSource/manage_editcmfPropSourceForm.dtml0100644003462500001440000000107007431654371023510 0ustar b14741users

Please use the portal_memberdata tool Properties tab to configure this property Source.


OK ">
exUserFolder/cmfPropSource/version.txt0100644003462500001440000000002507401743341017253 0ustar b14741usersCMF PropSource v0.02 exUserFolder/common/0040755003462500001440000000000007702273433013540 5ustar b14741usersexUserFolder/common/DialogFooter.dtml0100644003462500001440000000004707401737276017004 0ustar b14741users exUserFolder/common/DialogHeader.dtml0100644003462500001440000000072507401747075016737 0ustar b14741users
exUserFolder/common/MessageDialog.dtml0100644003462500001440000000215107452636107017125 0ustar b14741users
TARGET=""> ">

!


Ok ">
exUserFolder/common/manage_tabs.dtml0100644003462500001440000001340007401747336016662 0ustar b14741users
   ">
 href="&dtml-action;"href="&dtml-URL1;" target="&dtml-target;">    href="&dtml-action;"href="&dtml-URL1;" target="&dtml-target;">  

&dtml-meta_type; &dtml-meta_type; Object at ()
You are currently working in version
()
exUserFolder/common/text_manage_tabs.dtml0100644003462500001440000000143707401747336017735 0ustar b14741users
href="" href="" target=""> [] 
exUserFolder/dtml/0040755003462500001440000000000007702273433013210 5ustar b14741usersexUserFolder/dtml/access.dtml0100644003462500001440000001111507401747075015333 0ustar b14741users

The listing below shows the current security settings for this item. Permissions are rows and roles are columns. Checkboxes are used to indicate where roles are assigned permissions. You can also assign local roles to users, which give users extra roles in the context of this object and its subobjects.

When a role is assigned to a permission, users with the given role will be able to perform tasks associated with the permission on this item. When the Acquire permission settings checkbox is selected then the containing objects's permission settings are used. Note: the acquired permission settings may be augmented by selecting Roles for a permission in addition to selecting to acquire permissions.

 
Permission
">
Roles
 
/>
/>
" align="left"> " align="left">

You can define new roles by entering a role name and clicking the "Add Role" button.

User defined roles
 
Delete Role" />
exUserFolder/dtml/docLogin.dtml0100644003462500001440000000220107410012431015601 0ustar b14741users

Name
Password
Ok ">

exUserFolder/dtml/docLoginRedirect.dtml0100644003462500001440000000175007701627740017315 0ustar b14741users Logging In "> &authFailedCode=&dtml-authFailedCode;"> exUserFolder/dtml/docLogout.dtml0100644003462500001440000000024307401747075016031 0ustar b14741users

You have been logged out of the system.
exUserFolder/dtml/mainGroup.dtml0100644003462500001440000000344707700041053016025 0ustar b14741users

The following groups have been defined. Click on the name of a group to edit users in that group.

 

There are no groups defined.

This user folder has no group source.

exUserFolder/dtml/mainUser.dtml0100644003462500001440000000402507700041053015640 0ustar b14741users
The following users have been defined.

Previous results
Next results

There are no users defined, or users cannot be listed with .

Add "> Delete ">

Username: Edit this Username...">
exUserFolder/dtml/manage_addPropertyForm.dtml0100644003462500001440000000142407425557352020530 0ustar b14741users
Property Name
Create Property ">
exUserFolder/dtml/manage_addUserForm.dtml0100644003462500001440000000362107531440501017604 0ustar b14741users Any fields preceded by user_ will be store as properties. e.g. user_realname -> realname You can set anything you like.
Username (min 3 chars)
Password
Confirm Password
Real Name
Office
Home Phone
Work Phone
Email
Roles
Groups
exUserFolder/dtml/manage_addexUserFolder.dtml0100644003462500001440000001072107531440501020450 0ustar b14741users
">
Authentication Type standard basic authentication
cookie-based authentication
secure cookie-based authentication
Credential Cache Timeout in Seconds (0 for No Caching)
Negative Credential Cache Timeout in Seconds (0 for No Caching)
Log out users who expire from cache?
Activate Session Tracking for anonymous users?

Add">
Authentication Source
Property Source
Membership Source
Group Source

NEXT ">
exUserFolder/dtml/manage_createPropertyForm.dtml0100644003462500001440000000172407401747075021243 0ustar b14741users

Change Property ">
When you're finished press Finish here
Finish "> exUserFolder/dtml/manage_editAuthSourceForm.dtml0100644003462500001440000000016507401745153021154 0ustar b14741users exUserFolder/dtml/manage_editGroupSourceForm.dtml0100644003462500001440000000030607531440501021335 0ustar b14741users You cannot edit this source exUserFolder/dtml/manage_editMembershipSourceForm.dtml0100644003462500001440000000032007401745153022337 0ustar b14741users You cannot edit this source exUserFolder/dtml/manage_editPropSourceForm.dtml0100644003462500001440000000030407401745153021166 0ustar b14741users You cannot edit this source exUserFolder/dtml/manage_editUserForm.dtml0100644003462500001440000000756307531440501020012 0ustar b14741users Note: passwords are stored encrypted -- we can't get them back, so you must leave the password fields blank if you do not wish to change the password
Username &dtml-username;
Password
Confirm Password
Roles
Groups

Properties

Property NameValue
">
Edit
Change ">
Add Properties ">
exUserFolder/dtml/manage_editUserPropertyForm.dtml0100644003462500001440000000231707401747075021563 0ustar b14741users

Change Property ">
When you're finished press Finish here
Finish "> exUserFolder/dtml/manage_editexUserFolderForm.dtml0100644003462500001440000000435407552627405021513 0ustar b14741users
Title
Authentication Type CHECKED> standard basic authentication
CHECKED> cookie-based authentication
CHECKED> secure cookie-based authentication
Credential Cache Timeout in Seconds (0 for No Caching)
Negative Credential Cache Timeout in Seconds (0 for No Caching)
Log out users who expire from cache? CHECKED value="1">
Activate Session Tracking for anonymous users? CHECKED>

Update">
exUserFolder/dtml/manage_showCacheData.dtml0100644003462500001440000000336407401747075020107 0ustar b14741users
Total Access:
Hits: %
Misses -- not in cache %
Misses -- wrong password %
Misses -- session timed out %
Since:
Cache is inactive.
Logged in Users
: seconds idle
exUserFolder/etcAuthSource/0040755003462500001440000000000007702273433015026 5ustar b14741usersexUserFolder/etcAuthSource/.cvsignore0100654003462500001440000000002607423611547017024 0ustar b14741users*.pyc *.pyo *~ .*.swp exUserFolder/etcAuthSource/CHANGES.txt0100644003462500001440000000011307401737277016636 0ustar b14741usersetcUserFolder Changes etcUserFolder 1.2.0 Initial release for Zope exUserFolder/etcAuthSource/README.txt0100644003462500001440000000146407401737277016535 0ustar b14741usersetcUserFolder 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). exUserFolder/etcAuthSource/__init__.py0100644003462500001440000000735207402113544017134 0ustar b14741users############################################################################## # # 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.2 2025/12/01 08:40:04 akm Exp $ __doc__="""etc User Folder Product """ import etcAuthSource exUserFolder/etcAuthSource/etcAuthSource.py0100644003462500001440000002366207402113544020155 0ustar b14741users############################################################################## # # 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 # $Id: etcAuthSource.py,v 1.13 2025/12/01 08:40:04 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='Authorisation Source' id ='etcAuthSource' title ='File System Authorisation' 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(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): 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: 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 exUserFolder/etcAuthSource/manage_addetcAuthSourceForm.dtml0100644003462500001440000000156507401747075023303 0ustar b14741users
">
Password File
Default Role

NEXT ">
exUserFolder/etcAuthSource/manage_editetcAuthSourceForm.dtml0100644003462500001440000000144207401747075023472 0ustar b14741users
Password File ">
Default Role ">

Edit ">
exUserFolder/fcrypt/0040755003462500001440000000000007702273434013560 5ustar b14741usersexUserFolder/fcrypt/.cvsignore0100654003462500001440000000002607524231114015543 0ustar b14741users*.pyc *.pyo *~ .*.swp exUserFolder/fcrypt/ChangeLog0100644003462500001440000000253707401740517015333 0ustar b14741users2001-05-05 Carey Evans * 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. 2025-03-24 Carey Evans * 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. exUserFolder/fcrypt/LICENSE0100644003462500001440000001007207401740517014557 0ustar b14741users 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 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@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@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@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.] exUserFolder/fcrypt/MANIFEST.in0100644003462500001440000000004607401740517015310 0ustar b14741usersinclude LICENSE ChangeLog MANIFEST.in exUserFolder/fcrypt/PKG-INFO0100644003462500001440000000077207401740517014655 0ustar b14741usersMetadata-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@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 exUserFolder/fcrypt/README0100644003462500001440000000171307401740517014434 0ustar b14741users 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@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 5 May 2025 exUserFolder/fcrypt/__init__.py0100644003462500001440000000001607401742517015662 0ustar b14741usersimport fcrypt exUserFolder/fcrypt/fcrypt.py0100644003462500001440000006241407401740517015442 0ustar b14741users# 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 , and amkCrypto at . Functions: crypt() -- return hashed password """ __author__ = 'Carey Evans ' __version__ = '1.2' __date__ = '6 May 2025' __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 # # 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@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@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@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@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('> 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('> 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() exUserFolder/fcrypt/setup.py0100644003462500001440000000133407401740517015265 0ustar b14741users#!/usr/bin/env python # distutils setup script for fcrypt. # # Copyright (C) 2000, 2001 Carey Evans from distutils.core import setup setup( name = 'fcrypt', version = '1.2', description = 'The Unix password crypt function.', author = 'Carey Evans', author_email = 'careye@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'] ) exUserFolder/httpsAuthSource/0040755003462500001440000000000007702273434015416 5ustar b14741usersexUserFolder/httpsAuthSource/tests/0040755003462500001440000000000007702273434016560 5ustar b14741usersexUserFolder/httpsAuthSource/tests/testSSL.py0100755003462500001440000000055407637475122020504 0ustar b14741users # # 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() exUserFolder/httpsAuthSource/__init__.py0100644003462500001440000000002707637475121017527 0ustar b14741usersimport httpsAuthSource exUserFolder/httpsAuthSource/httpsAuthSource.py0100644003462500001440000002042007645046757021144 0ustar b14741users""" 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/httpsAuthSource/httpsAuthSource.py,v 1.6 2025/04/09 16:44:31 mrenoch 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= 'Authorisation 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(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 exUserFolder/httpsAuthSource/manage_addhttpsAuthSourceForm.dtml0100644003462500001440000000300107644316220024235 0ustar b14741users
">
Service URL
User Name Parameter
Passwd Parameter
Authorization Response Pattern
Default Role

NEXT ">
exUserFolder/httpsAuthSource/manage_edithttpsAuthSourceForm.dtml0100644003462500001440000000275007637475121024454 0ustar b14741users
Service URL ">
User Name Parameter ">
Passwd Parameter ">
Authorization Response Pattern ">
Default Role ">

Edit ">
exUserFolder/mysqlAuthSource/0040755003462500001440000000000007702273434015421 5ustar b14741usersexUserFolder/mysqlAuthSource/.cvsignore0100654003462500001440000000002607521562676017425 0ustar b14741users*.pyc *.pyo *~ .*.swp exUserFolder/mysqlAuthSource/__init__.py0100644003462500001440000000012607531440501017515 0ustar b14741users# $Id: __init__.py,v 1.2 2025/08/23 14:05:53 mattbehrens Exp $ import mysqlAuthSource exUserFolder/mysqlAuthSource/manage_addmysqlAuthSourceForm.dtml0100644003462500001440000000256407521553077024267 0ustar b14741users
">
Database Connection:
Table Name:
Username Column:
Password Column:
Roles Column:

Add">
exUserFolder/mysqlAuthSource/manage_editmysqlAuthSourceForm.dtml0100644003462500001440000000266207521553077024463 0ustar b14741users
Database Connection:
Table Name: ">
Username Column: ">
Password Column: ">
Roles Column: ">

Edit ">
exUserFolder/mysqlAuthSource/mysqlAuthSource.py0100644003462500001440000002163407531440501021135 0ustar b14741users# # 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 adapted from pgPropSource by Andrew Milton # $Id: mysqlAuthSource.py,v 1.2 2025/08/23 14:05:53 mattbehrens 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='Authorisation 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(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 """ _sqlListOneUser=""" SELECT * FROM where = """ _sqlDeleteOneUser=""" DELETE FROM where = """ _sqlInsertUser=""" INSERT INTO (, , ) VALUES (, , ) """ _sqlUpdateUserPassword=""" UPDATE set = WHERE = """ _sqlUpdateUser=""" UPDATE set = WHERE = """ exUserFolder/mysqlPropSource/0040755003462500001440000000000007702273434015440 5ustar b14741usersexUserFolder/mysqlPropSource/.cvsignore0100644003462500001440000000002607524230370017425 0ustar b14741users*.pyc *.pyo *~ .*.swp exUserFolder/mysqlPropSource/__init__.py0100644003462500001440000000012607531440501017534 0ustar b14741users# $Id: __init__.py,v 1.2 2025/08/23 14:05:53 mattbehrens Exp $ import mysqlPropSource exUserFolder/mysqlPropSource/manage_addmysqlPropSourceForm.dtml0100644003462500001440000000212507521552124024306 0ustar b14741users
">
Database Connection:

Add">
exUserFolder/mysqlPropSource/manage_editmysqlPropSourceForm.dtml0100644003462500001440000000132507521552124024504 0ustar b14741users
Database Connection:

Edit ">
exUserFolder/mysqlPropSource/mysqlPropSource.py0100644003462500001440000002216707531440501021175 0ustar b14741users# # Extensible User Folder # # MySQL Property Source for exUserFolder # # (C) Copyright 2000,2001 The Internet (Aust) Pty Ltd # 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 adapted from pgPropSource by Andrew Milton # $Id: mysqlPropSource.py,v 1.2 2025/08/23 14:05:53 mattbehrens Exp $ from Globals import HTMLFile, MessageDialog, INSTANCE_HOME,Acquisition from OFS.Folder import Folder from ZODB.PersistentMapping import PersistentMapping from Products.ZSQLMethods.SQL import SQL from Products.exUserFolder.exUserFolder import exUserFolder from Products.exUserFolder.Plugins import PluginRegister from Persistence import Persistent manage_addPropSourceForm=HTMLFile('manage_addmysqlPropSourceForm', globals()) import cPickle def manage_addmysqlPropSource(self, REQUEST): """ Add a MySQL Prop Source """ connection=REQUEST['mysqlprop_connection'] o = mysqlPropSource(connection) self._setObject('mysqlPropSource', o, None, None, 0) o = getattr(self, 'mysqlPropSource') # Allow Prop Source to setup default users... if hasattr(o, 'postInitialisation'): o.postInitialisation(REQUEST) self.currentPropSource=o manage_addmysqlPropSourceForm=HTMLFile('manage_addmysqlPropSourceForm', globals()) manage_editmysqlPropSourceForm=HTMLFile('manage_editmysqlPropSourceForm', globals()) # # Very simple thing # class mysqlPropSource(Folder): """ Store User Data in a MySQL Database """ meta_type='Property Source' title='MySQL Properties' icon ='misc_/exUserFolder/exUserFolderPlugin.gif' manage_tabs=Acquisition.Acquired manage_editForm=manage_editmysqlPropSourceForm dict=PersistentMapping() def __init__(self, connection): self.id='mysqlPropSource' self.connection=connection self.addSQLQueries() def manage_editPropSource(self, REQUEST): """ Add a MySQL Prop Source """ self.connection=REQUEST['mysqlprop_connection'] self.delSQLQueries() self.addSQLQueries() # Re-add queries with new connection def hasProperty(self, key): if not self.dict.has_key(self.name): self.loadUserProperties(username=self.name) if not self.dict.has_key(self.name): return 0 return self.dict[self.name].has_key(key) def delProperty(self, key): self.delUserProperty(key=key, username=self.name) def delUserProperty(self, key, username): self.sqlDelProperty(username=username, key=key) if not self.dict.has_key(self.name): self.loadUserProperties(username=self.name) else: try: del self.dict[username][key] self._p_changed = 1 except: pass def flushTempProperties(self): if self.dict.has_key(self.name): del self.dict[self.name] self.sqlDelTempProperties(username=self.name) self.loadUserProperties(username=self.name) def setProperty(self, key, value): self.setUserProperty(key=key, value=value, username=self.name) def setTempProperty(self, key, value): self.setUserProperty(key=key, value=value, username=self.name, temp=1) def setUserProperty(self, key, username, value, temp=0): if not self.dict.has_key(username): self.loadUserProperties(username=username) if self.dict[username].has_key(key): self.sqlUpdateProperty(username=username, key=key, value=cPickle.dumps(value)) else: self.sqlInsertProperty(username=username, key=key, value=cPickle.dumps(value), temp=temp) self.dict[username][key]=value self._p_changed=1 def getUserProperty(self, key, username, default=None): # Load Properties once we start asking for them... if not self.dict.has_key(username): self.loadUserProperties(username=username) if self.dict[username].has_key(key): return self.dict[username][key] return default def getProperty(self, key, default=None): return self.getUserProperty(key=key, username=self.name, default=default) def loadProperties(self): self.loadUserProperties(username=self.name) def loadUserProperties(self, username): self.dict[username]={} for p in self.sqlLoadProperties(username=username): try: self.dict[username][sqlattr(p, 'key')]=cPickle.loads(sqlattr(p, 'value')) except: self.dict[username][sqlattr(p, 'key')]=sqlattr(p, 'value') self._p_changed=1 def listProperties(self): self.listUserProperties(username=self.name) def listUserProperties(self, username): if not self.dict.has_key(username): self.loadUserProperties(username=username) if self.dict.has_key(username): return self.dict[username].keys() return None def createUser(self, username, REQUEST): for k in REQUEST.keys(): if k[:5]=='user_': key=k[5:] value=REQUEST[k] self.sqlInsertProperty(username=username, key=key, value=value, temp=0) def deleteUsers(self, userids): for username in userids: self.sqlDeleteUser(username=username) if self.dict.has_key(username): del self.dict[username] def updateUser(self, username, REQUEST): self.loadUserProperties(username) for k in REQUEST.keys(): if k[:5]=='user_': key=k[5:] value=REQUEST[k] self.dict[username][key]=value self.sqlUpdateProperty(username=username, key=key, value=value) def delSQLQueries(self): sqllist=self.objectIds('Z SQL Method') self.manage_delObjects(ids=sqllist) def addSQLQueries(self): if not hasattr(self, 'sqlLoadProperties'): sqlLoadProperties=SQL( 'sqlLoadProperties', 'Load Properties for One User', self.connection, 'username', _sqlLoadProperties) self._setObject('sqlLoadProperties', sqlLoadProperties) if not hasattr(self, 'sqlUpdateProperty'): sqlUpdateProperty=SQL( 'sqlUpdateProperty', 'Update One Property', self.connection, 'key value username', _sqlUpdateProperty) self._setObject('sqlUpdateProperty', sqlUpdateProperty) if not hasattr(self, 'sqlInsertProperty'): sqlInsertProperty=SQL( 'sqlInsertProperty', 'Insert a New Property', self.connection, 'key value username temp:int', _sqlInsertProperty) self._setObject('sqlInsertProperty', sqlInsertProperty) if not hasattr(self, 'sqlDeleteUser'): sqlDeleteUser=SQL( 'sqlDeleteUser', 'Delete a Users properties', self.connection, 'username', _sqlDeleteUser) self._setObject('sqlDeleteUser', sqlDeleteUser) if not hasattr(self, 'sqlDelProperty'): sqlDelProperty=SQL( 'sqlDelProperty', 'Delete a Property', self.connection, 'username key', _sqlDelProperty) self._setObject('sqlDelProperty', sqlDelProperty) if not hasattr(self, 'sqlDelTempProperties'): sqlDelTempProperties=SQL( 'sqlDelTempProperties', 'Delete all temp properties for a user', self.connection, 'username', _sqlDelTempProperties) self._setObject('sqlDelTempProperties', sqlDelTempProperties) def __setstate__(self, state): Persistent.__setstate__(self, state) self.addSQLQueries() def postInitialisation(self, REQUEST): pass mysqlPropReg=PluginRegister('mysqlPropSource', 'MySQL Properties Source', mysqlPropSource, manage_addmysqlPropSourceForm, manage_addmysqlPropSource, manage_editmysqlPropSourceForm) exUserFolder.propSources['mysqlPropSource']=mysqlPropReg 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 _sqlInsertProperty=''' INSERT INTO UserProperties (prop_key, username, value, istemporary) VALUES(, , , ) ''' _sqlUpdateProperty=''' UPDATE UserProperties SET value= WHERE prop_key= AND username= ''' _sqlLoadProperties=''' SELECT username, prop_key as 'key', value, istemporary FROM UserProperties WHERE username= ''' _sqlDeleteUser=''' DELETE FROM UserProperties WHERE username= ''' _sqlDelProperty=''' DELETE FROM UserProperties WHERE username= AND prop_key= ''' _sqlDelTempProperties=''' DELETE FROM UserProperties WHERE username= AND isTemporary=1 ''' exUserFolder/nisAuthSource/0040755003462500001440000000000007702273434015045 5ustar b14741usersexUserFolder/nisAuthSource/__init__.py0100644003462500001440000000002507600623221017136 0ustar b14741usersimport nisAuthSource exUserFolder/nisAuthSource/manage_addnisAuthSourceForm.dtml0100644003462500001440000000125207614257307023330 0ustar b14741users
">
Default Role
Do not use local roles
NEXT ">
exUserFolder/nisAuthSource/manage_editnisAuthSourceForm.dtml0100644003462500001440000000115007614257307023522 0ustar b14741users
Default Role ">
Do not use local roles CHECKED>
exUserFolder/nisAuthSource/nisAuthSource.py0100644003462500001440000002045107614257310020207 0ustar b14741users# # Extensible User Folder # # NIS Authentication Source for exUserFolder # # Author: Jason Gibson # # 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='/') except nis.error: return self.MessageDialog(self,REQUEST=REQUEST, title ='NIS Error', message='NIS server unreachable', action='/') 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='Authorisation 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='/') 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='/') # 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='/') else: self.data[username].update({'roles':roles}) # Encrypt a password def cryptPassword(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='/') 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 exUserFolder/nullGroupSource/0040755003462500001440000000000007702273434015421 5ustar b14741usersexUserFolder/nullGroupSource/.cvsignore0100644003462500001440000000002607531440501017403 0ustar b14741users*.pyc *.pyo *~ .*.swp exUserFolder/nullGroupSource/__init__.py0100644003462500001440000000012607531440501017515 0ustar b14741users# $Id: __init__.py,v 1.2 2025/08/23 14:05:53 mattbehrens Exp $ import nullGroupSource exUserFolder/nullGroupSource/manage_addNullPluginSourceForm.dtml0100644003462500001440000000150707531440501024351 0ustar b14741users
"> This Group Source has no configuration Items

Add">
exUserFolder/nullGroupSource/nullGroupSource.py0100644003462500001440000000167607531440501021141 0ustar b14741users# # Extensible User Folder # # Null Group Source for exUserFolder # # Author: Brent Hendricks # $Id: nullGroupSource.py,v 1.2 2025/08/23 14:05:53 mattbehrens 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 exUserFolder/nullMemberSource/0040755003462500001440000000000007702273434015534 5ustar b14741usersexUserFolder/nullMemberSource/.cvsignore0100654003462500001440000000002607423611547017531 0ustar b14741users*.pyc *.pyo *~ .*.swp exUserFolder/nullMemberSource/__init__.py0100644003462500001440000000011707402113545017632 0ustar b14741users# $Id: __init__.py,v 1.2 2025/12/01 08:40:05 akm Exp $ import nullMemberSource exUserFolder/nullMemberSource/manage_addNullPluginSourceForm.dtml0100644003462500001440000000152107531440502024461 0ustar b14741users
"> This Membership Source has no configuration Items

Add">
exUserFolder/nullMemberSource/nullMemberSource.py0100644003462500001440000000350007402113545021355 0ustar b14741users# # 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 # $Id: nullMemberSource.py,v 1.2 2025/12/01 08:40:05 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 exUserFolder/nullPlugin/0040755003462500001440000000000007702273434014402 5ustar b14741usersexUserFolder/nullPlugin/.cvsignore0100654003462500001440000000002607423611547016377 0ustar b14741users*.pyc *.pyo *~ .*.swp exUserFolder/nullPlugin/__init__.py0100644003462500001440000000011107402113545016472 0ustar b14741users# $Id: __init__.py,v 1.2 2025/12/01 08:40:05 akm Exp $ import nullPlugin exUserFolder/nullPlugin/nullPlugin.py0100644003462500001440000000253507402113545017100 0ustar b14741users# # 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 # $Id: nullPlugin.py,v 1.3 2025/12/01 08:40:05 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 exUserFolder/nullPropSource/0040755003462500001440000000000007702273434015245 5ustar b14741usersexUserFolder/nullPropSource/.cvsignore0100654003462500001440000000002607423611547017242 0ustar b14741users*.pyc *.pyo *~ .*.swp exUserFolder/nullPropSource/__init__.py0100644003462500001440000000011507402113545017341 0ustar b14741users# $Id: __init__.py,v 1.2 2025/12/01 08:40:05 akm Exp $ import nullPropSource exUserFolder/nullPropSource/manage_addNullPluginSourceForm.dtml0100644003462500001440000000151607401747075024211 0ustar b14741users
"> This Property Source has no configuration Items

Add">
exUserFolder/nullPropSource/nullPropSource.py0100644003462500001440000000343707402113545020610 0ustar b14741users# # 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 # $Id: nullPropSource.py,v 1.2 2025/12/01 08:40:05 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 exUserFolder/pgAuthSource/0040755003462500001440000000000007702273434014662 5ustar b14741usersexUserFolder/pgAuthSource/.cvsignore0100654003462500001440000000002607423611547016657 0ustar b14741users*.pyc *.pyo *~ .*.swp exUserFolder/pgAuthSource/__init__.py0100644003462500001440000000011307402113545016754 0ustar b14741users# $Id: __init__.py,v 1.2 2025/12/01 08:40:05 akm Exp $ import pgAuthSource exUserFolder/pgAuthSource/manage_addpgAuthSourceForm.dtml0100644003462500001440000000255207401747075022766 0ustar b14741users
">
Database Connection:
Table Name:
Username Column:
Password Column:
Roles Column:

Add">
exUserFolder/pgAuthSource/manage_editpgAuthSourceForm.dtml0100644003462500001440000000265007401747075023162 0ustar b14741users
Database Connection:
Table Name: ">
Username Column: ">
Password Column: ">
Roles Column: ">

Edit ">
exUserFolder/pgAuthSource/pgAuthSource.py0100644003462500001440000002142107402113545017633 0ustar b14741users# # 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 # $Id: pgAuthSource.py,v 1.11 2025/12/01 08:40:05 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='Authorisation 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) 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(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 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 """ _sqlListOneUser=""" SELECT * FROM where = """ _sqlDeleteOneUser=""" DELETE FROM where = """ _sqlInsertUser=""" INSERT INTO (, , ) VALUES (, , ) """ _sqlUpdateUserPassword=""" UPDATE set = WHERE = """ _sqlUpdateUser=""" UPDATE set = WHERE = """ exUserFolder/pgAuthSourceAlt/0040755003462500001440000000000007702273434015323 5ustar b14741usersexUserFolder/pgAuthSourceAlt/.cvsignore0100654003462500001440000000002607423611547017320 0ustar b14741users*.pyc *.pyo *~ .*.swp exUserFolder/pgAuthSourceAlt/README.pgAuthSource-Alternate0100644003462500001440000000124507401747075022532 0ustar b14741usersThis 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). exUserFolder/pgAuthSourceAlt/__init__.py0100644003462500001440000000011307402113545017415 0ustar b14741users# $Id: __init__.py,v 1.2 2025/12/01 08:40:05 akm Exp $ import pgAuthSource exUserFolder/pgAuthSourceAlt/manage_addpgAuthSourceForm.dtml0100644003462500001440000000242507401747075023426 0ustar b14741users
">
Database Connection:
User Table Name:
Username Column:
Password Column:
Roles Table:
Role Column:

exUserFolder/pgAuthSourceAlt/manage_editpgAuthSourceForm.dtml0100644003462500001440000000254207401747075023623 0ustar b14741users
Database Connection:
User Table Name: ">
Username Column: ">
Password Column: ">
Role Table: ">
Role Column: ">

exUserFolder/pgAuthSourceAlt/pgAuthSource-Alternate.sql0100644003462500001440000000142607401747075022375 0ustar b14741usersCREATE 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"); exUserFolder/pgAuthSourceAlt/pgAuthSource.py0100644003462500001440000002471307402113545020303 0ustar b14741users# # 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 # $Id: pgAuthSource.py,v 1.2 2025/12/01 08:40:05 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='Authorisation 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(self, username, password): salt =username[:2] secret = crypt(password, salt) return secret # Alternate cryptPassword function, returns md5 hash of the password # def cryptPassword(self, username, password): # passhash = md5.new(password) # secret = passhash.hexdigest() # return secret # Alternate cryptPassword function, returns plain text of the password. # def cryptPassword(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 """ _sqlListOneUser=""" SELECT * FROM where = """ _sqlListUserRoles=""" SELECT * FROM where = """ _sqlDeleteOneUser=""" DELETE FROM where = """ _sqlDeleteUserRoles=""" DELETE FROM where = """ _sqlInsertUser=""" INSERT INTO (, ) VALUES (, ) """ _sqlInsertUserRole=""" INSERT INTO (, ) VALUES (, ) """ _sqlUpdateUserPassword=""" UPDATE set = WHERE = """ exUserFolder/pgPropSource/0040755003462500001440000000000007702273435014702 5ustar b14741usersexUserFolder/pgPropSource/.cvsignore0100654003462500001440000000002607423611547016676 0ustar b14741users*.pyc *.pyo *~ .*.swp exUserFolder/pgPropSource/__init__.py0100644003462500001440000000011307402113545016773 0ustar b14741users# $Id: __init__.py,v 1.2 2025/12/01 08:40:05 akm Exp $ import pgPropSource exUserFolder/pgPropSource/manage_addpgPropSourceForm.dtml0100644003462500001440000000212707401747075023022 0ustar b14741users
">
Database Connection:

Add">
exUserFolder/pgPropSource/manage_editpgPropSourceForm.dtml0100644003462500001440000000132707401747075023220 0ustar b14741users
Database Connection:

Edit ">
exUserFolder/pgPropSource/pgPropSource.py0100644003462500001440000002577607633265706017727 0ustar b14741users# # Extensible User Folder # # Postgres Property 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 # $Id: pgPropSource.py,v 1.23 2025/03/11 04:53:26 akm Exp $ from Globals import HTMLFile, MessageDialog, INSTANCE_HOME,Acquisition from OFS.Folder import Folder from ZODB.PersistentMapping import PersistentMapping from Products.ZSQLMethods.SQL import SQL from Products.exUserFolder.exUserFolder import exUserFolder from Products.exUserFolder.Plugins import PluginRegister from Persistence import Persistent manage_addPropSourceForm=HTMLFile('manage_addpgPropSourceForm', globals()) import cPickle def manage_addpgPropSource(self, REQUEST): """ Add a Postgres Prop Source """ connection=REQUEST['pgprop_connection'] o = pgPropSource(connection) self._setObject('pgPropSource', o, None, None, 0) o = getattr(self, 'pgPropSource') # Allow Prop Source to setup default users... if hasattr(o, 'postInitialisation'): o.postInitialisation(REQUEST) self.currentPropSource=o manage_addpgPropSourceForm=HTMLFile('manage_addpgPropSourceForm', globals()) manage_editpgPropSourceForm=HTMLFile('manage_editpgPropSourceForm', globals()) # # Very simple thing # class pgPropSource(Folder): """ Store User Data in a Postgres Database """ meta_type='Property Source' title='Postgresql Properties' icon ='misc_/exUserFolder/exUserFolderPlugin.gif' manage_tabs=Acquisition.Acquired manage_editForm=manage_editpgPropSourceForm # # Reduce this if it is too long # Increase it if it's too short # This isn't a tunable right now. # PROPERTY_CACHE_TIME=10.0 def __init__(self, connection): self.id='pgPropSource' self.connection=connection self.addSQLQueries() self._v_dict=None self.loadDict() def loadDict(self): if not hasattr(self, '_v_dict'): self._v_dict=None if not self._v_dict: self._v_dict={} def manage_editPropSource(self, REQUEST): """ Add a Postgres Prop Source """ self.connection=REQUEST['Acmisprop_connection'] self.delSQLQueries() self.addSQLQueries() # Re-add queries with new connection def hasProperty(self, key): self.loadDict() self.loadUserProperty(username=self.name, key = key) if not self._v_dict.has_key(self.name): return 0 return self._v_dict[self.name].has_key(key) def delProperty(self, key): self.delUserProperty(key=key, username=self.name) def delUserProperty(self, key, username): self.loadDict() self.sqlDelProperty(username=username, key=key) self.loadUserProperties(username=self.name) try: del self._v_dict[username][key] except: pass def flushTempProperties(self): self.loadDict() if self._v_dict.has_key(self.name): del self._v_dict[self.name] self.sqlDelTempProperties(username=self.name) self.loadUserProperties(username=self.name) def setProperty(self, key, value): self.setUserProperty(key=key, value=value, username=self.name) def setTempProperty(self, key, value): self.setUserProperty(key=key, value=value, username=self.name, temp=1) def setUserProperty(self, key, username, value, temp=0): self.loadDict() if not self._v_dict.has_key(username): self._v_dict[username]={} self.loadUserProperty(username, key) if self._v_dict[username].has_key(key): done = 0 tries = 0 while not done: try: self.sqlUpdateProperty(username=username, key=key, value=cPickle.dumps(value)) done = 1 except ConfictError: tries = tries + 1 if tries > 10: # Give up done = 1 else: self.sqlInsertProperty(username=username, key=key, value=cPickle.dumps(value), temp=temp) self._v_dict[username][key]={} self._v_dict[username][key]['data']=value self._v_dict[username][key]['lastLoad']=time.time() self._v_dict[username][key]['lastLoad']=0.0 def getUserProperty(self, key, username, default=None): # Load Properties once we start asking for them... self.loadDict() self.loadUserProperty(username=username, key=key) if self._v_dict[username].has_key(key): return self._v_dict[username][key]['data'] return default def getProperty(self, key, default=None): return self.getUserProperty(key=key, username=self.name, default=default) def loadProperties(self): self.loadUserProperties(username=self.name) def loadUserProperty(self, username, key): self.loadDict() if not self._v_dict.has_key(username): self._v_dict[username]={} now = time.time() if self._v_dict[username].has_key(key): if now - self._v_dict[username][key]['lastLoad'] < self.PROPERTY_CACHE_TIME: return for p in self.sqlLoadProperty(username=username, key=key): self._v_dict[username][key]={} self._v_dict[username][key]['data']=None self._v_dict[username][key]['lastLoad']=now try: self._v_dict[username][key]['data']=cPickle.loads(sqlattr(p, 'value')) except: self._v_dict[username][key]['data']=sqlattr(p, 'value') return # There wasn't one that existed in the db # Therefore any existing one we might have must be out of date # and should be deleted. try: del self._v_dict[username][key] except: pass def loadUserProperties(self, username): self.loadDict() now = time.time() self._v_dict[username]={} for p in self.sqlLoadProperties(username=username): key = sqlattr(p, 'key') self._v_dict[username][key]={} try: self._v_dict[username][key]['data']=cPickle.loads(sqlattr(p, 'value')) except: self._v_dict[username][key]['data']=sqlattr(p, 'value') self._v_dict[username][key]['lastLoad']=now def listProperties(self): self.listUserProperties(username=self.name) def listUserProperties(self, username): self.loadDict() self.loadUserProperties(username=username) if self._v_dict.has_key(username): return self._v_dict[username].keys() return [] def createUser(self, username, REQUEST): for k in REQUEST.keys(): if k[:5]=='user_': key=k[5:] value=REQUEST[k] self.sqlInsertProperty(username=username, key=key, value=value, temp=0) def deleteUsers(self, userids): self.loadDict() for username in userids: self.sqlDeleteUser(username=username) if self._v_dict.has_key(username): del self._v_dict[username] def updateUser(self, username, REQUEST): self.loadUserProperties(username) for k in REQUEST.keys(): if k[:5]=='user_': key=k[5:] value=REQUEST[k] self._v_dict[username][key]={} self._v_dict[username][key]['data']=value self._v_dict[username][key]['lastLoad']=time.time() self.sqlUpdateProperty(username=username, key=key, value=value) def delSQLQueries(self): sqllist=self.objectIds('Z SQL Method') self.manage_delObjects(ids=sqllist) def addSQLQueries(self): if not hasattr(self, 'sqlLoadProperties'): sqlLoadProperties=SQL( 'sqlLoadProperties', 'Load Properties for One User', self.connection, 'username', _sqlLoadProperties) self._setObject('sqlLoadProperties', sqlLoadProperties) if not hasattr(self, 'sqlLoadProperty'): sqlLoadProperty=SQL( 'sqlLoadProperty', 'Load One Property for One User', self.connection, 'username key', _sqlLoadProperty) self._setObject('sqlLoadProperty', sqlLoadProperty) if not hasattr(self, 'sqlUpdateProperty'): sqlUpdateProperty=SQL( 'sqlUpdateProperty', 'Update One Property', self.connection, 'key value username', _sqlUpdateProperty) self._setObject('sqlUpdateProperty', sqlUpdateProperty) if not hasattr(self, 'sqlInsertProperty'): sqlInsertProperty=SQL( 'sqlInsertProperty', 'Insert a New Property', self.connection, 'key value username temp:int', _sqlInsertProperty) self._setObject('sqlInsertProperty', sqlInsertProperty) if not hasattr(self, 'sqlDeleteUser'): sqlDeleteUser=SQL( 'sqlDeleteUser', 'Delete a Users properties', self.connection, 'username', _sqlDeleteUser) self._setObject('sqlDeleteUser', sqlDeleteUser) if not hasattr(self, 'sqlDelProperty'): sqlDelProperty=SQL( 'sqlDelProperty', 'Delete a Property', self.connection, 'username key', _sqlDelProperty) self._setObject('sqlDelProperty', sqlDelProperty) if not hasattr(self, 'sqlDelTempProperties'): sqlDelTempProperties=SQL( 'sqlDelTempProperties', 'Delete all temp properties for a user', self.connection, 'username', _sqlDelTempProperties) self._setObject('sqlDelTempProperties', sqlDelTempProperties) def __setstate__(self, state): Persistent.__setstate__(self, state) self.addSQLQueries() def postInitialisation(self, REQUEST): pass pgPropReg=PluginRegister('pgPropSource', 'Postgresql Properties Source', pgPropSource, manage_addpgPropSourceForm, manage_addpgPropSource, manage_editpgPropSourceForm) exUserFolder.propSources['pgPropSource']=pgPropReg 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 _sqlInsertProperty=''' INSERT INTO UserProperties (key, username, value, istemporary) VALUES(, , , ) ''' _sqlUpdateProperty=''' UPDATE UserProperties SET value= WHERE key= AND username= ''' _sqlLoadProperties=''' SELECT * FROM UserProperties WHERE username= ''' _sqlDeleteUser=''' DELETE FROM UserProperties WHERE username= ''' _sqlDelProperty=''' DELETE FROM UserProperties WHERE username= AND key= ''' _sqlDelTempProperties=''' DELETE FROM UserProperties WHERE username= AND isTemporary=1 ''' _sqlLoadProperty=''' SELECT * FROM UserProperties WHERE username= AND key= ''' exUserFolder/radiusAuthSource/0040755003462500001440000000000007702273435015544 5ustar b14741usersexUserFolder/radiusAuthSource/.cvsignore0100654003462500001440000000002607423611547017540 0ustar b14741users*.pyc *.pyo *~ .*.swp exUserFolder/radiusAuthSource/Makefile0100644003462500001440000000032207401744332017171 0ustar b14741usersDEST = /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} exUserFolder/radiusAuthSource/README.txt0100644003462500001440000000040707401744332017233 0ustar b14741usersRadius 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@cs.rmit.edu.au> exUserFolder/radiusAuthSource/ZRadius.gif0100644003462500001440000000160107401744332017602 0ustar b14741usersGIF89a÷ÿÿPPP€€€ÀÀÀÿ€@ @€€€@@ÿÿÿÿ€€€@€€ÿÿÿ!ù,^  Á :XÈð #`H1ÀC‰4 ''' __version__ = '$Revision: 1.3 $' 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) exUserFolder/radiusAuthSource/__init__.py0100644003462500001440000000017707402043475017653 0ustar b14741users__doc__ = '''$Id: __init__.py,v 1.2 2025/12/01 02:58:05 akm Exp $''' __version__ = '$Revision: 1.2 $' import radiusAuthSource exUserFolder/radiusAuthSource/index.dtml0100644003462500001440000000105507401744332017526 0ustar b14741users Test Radius Authentication

Test

username
password
exUserFolder/radiusAuthSource/manage_addradiusAuthSourceForm.dtml0100644003462500001440000000214007422675027024521 0ustar b14741users
">
Host
Port
Secret
Retries
Timeout

Add">
exUserFolder/radiusAuthSource/manage_editradiusAuthSourceForm.dtml0100644003462500001440000000202007422675027024713 0ustar b14741users
Host
Port
Secret Leave blank for existing secret
Retries
Timeout
Edit ">
exUserFolder/radiusAuthSource/radius.py0100644003462500001440000001353507402043475017405 0ustar b14741users#!/usr/bin/env python ''' $Id: radius.py,v 1.4 2025/12/01 02:58:05 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 # 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" exUserFolder/radiusAuthSource/radiusAuthSource.py0100644003462500001440000001130207446165035021403 0ustar b14741users# # 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 # $Id: radiusAuthSource.py,v 1.6 2025/03/20 19:59:25 cabrubak Exp $ # This code based on ZRadius by Stuart Bishop # Copyright 1999 Stuart Bishop zen@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='Authorisation Source' id ='radiusAuthSource' title ='RADIUS Authorisation' 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 exUserFolder/smbAuthSource/0040755003462500001440000000000007702273435015036 5ustar b14741usersexUserFolder/smbAuthSource/.cvsignore0100654003462500001440000000002607423611547017032 0ustar b14741users*.pyc *.pyo *~ .*.swp exUserFolder/smbAuthSource/__init__.py0100644003462500001440000000011407402113545017130 0ustar b14741users# $Id: __init__.py,v 1.2 2025/12/01 08:40:05 akm Exp $ import smbAuthSource exUserFolder/smbAuthSource/manage_addsmbAuthSourceForm.dtml0100644003462500001440000000162607401747075023315 0ustar b14741users
">
Host
Windows Domain
WINS Server IP Address
(optional, leave empty for broadcast)

Add">
exUserFolder/smbAuthSource/manage_editsmbAuthSourceForm.dtml0100644003462500001440000000152107401747075023504 0ustar b14741users
Host
Windows Domain
WINS Server IP Address
(optional, leave empty for broadcast)

Change">
exUserFolder/smbAuthSource/nmb.py0100644003462500001440000004161707402043475016166 0ustar b14741users# -*- mode: python; tab-width: 4 -*- # $Id: nmb.py,v 1.3 2025/12/01 02:58:05 akm Exp $ # # Copyright (C) 2001 Michael Teo # 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.3 $' # Taken from socket module reference INADDR_ANY = '' BROADCAST_ADDR = '' # 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 '' 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 = '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' } exUserFolder/smbAuthSource/pysmb-0.2.0.tar.gz0100644003462500001440000006202107401745402017742 0ustar b14741users‹+š¼;ì<ýsÚH²û³þŠÙìÞv1 v–Kò¶I–Zû€$•Ês©„4]„ÄêÃõêþ÷ëî™ÑBàÜÚɾZk³ 43Ý=ý5Ýš¯ÖárÖüîQ/Öiw[ì;ÆØñQ'÷-¯<9ìëz·¥3¦ë#ý;Ö}\²Ä‡‘0öÝÒùÄ#î—öÛ×þÿôZ‘üOßMQ¾@þã#”[o?Éÿk\©üǾ=½Õ:êì’WGùwZíÃŽ~|ò?léïXëqÈÉ_qù÷øç¨'çö?®ãÅŸõ†ë[¦kûKÓñzÍ[3hZ·¡ö­ }ºåÊØ?_ù¡ùÁú¡qìµ\ósößiwÛOöÿ5®Uàÿ“[QØ$Ex²ò¿Ú•ÚÿÀ‹‡‡€cŸýwŽ7â¿ÃnW²ÿ¯q5χ§ƒÑdÐÔzóÂ÷X?¾aí„à½Ãn¯Ó…û–ÞljgMÛ·Âf“n!fLºozËÙï1Ö0þ°ù:pÄøÓap»×ýEo‚šÙ1ôê4'fÄ&|bf­ã^Kïuôl/k½ºD õ:uèµõ^·“íå:a.Ì€â]ÂÆjM˜Ç<ŒÝˆùs¶äÁ — Dëóæt³K+b ¨ºŽ{Ýç Ó_û£7ƒóË7'ßSoõôvÒs<èŸ] h Ý:ª¾µØ“KØ¿’âãàûß‘ÿ±viýë?lµñÑáqëÉþ¿ÆõÃ÷Í8 š3Çkrï–­ÖÑÂ÷´ØÁOléÛ¼'ýEæìàα£Eôš¡ÓC»Ç”îÔo˜¢PðÖófÖHÞ±ÁçûQûFžú«uàÜ,"V=­ÑváX “»l _,ÅLJfÎÍRÓ†å/_ÁH…ްN†—æßò€MO¯šÃ+ùÌõýOñ*iõÌ%™éÙ,à¸ÂÝr€’¹¢wæÁ\¨B‡Dàtá„,ôçÑø÷(Ý:6·YÅ œ°Rgw0&ŽôšñÏ«€‡!óæ,W®ý```zѺÁÜÐ p{Œs]ÄÊÌlÆÙ‚»6ssær6‡GÒ6—æ R8¡ãݰyà/i€‹CŽ^,ÊÙ º¯x°tÂЇm7HÐ\¾ÇñçF&(Wq9¯# Ç³ÜØFÄÀxp—–cºÌ\ÁÜ,3ðaxŠAL0íHòØÏ8³8âø ÍÎÝu…ñ CM‚Ó˜û®ëß!àŒ°jf¡7@0EPÇ+Ì”-ÁhŸ²fpd>Ç™þ­ý8m’¶\ÓAÖÁºƒ­wq"AÀ‡y%4Ú†7.¨Zõ¶V:x±e°Ô‘¶ø*R"çAàéˆmn=i„@.<¡N€ ú-t¨Gÿ¨B-ƒFÙ `ç$ì5BI£­Õ\ßšŒú) ÙÛ xŸØ¡D— fC‹±3¶$O¹ò|Ÿœaå8À*ÿÀ¥Z\ˆ -bZš‘£»Z+À(ªÏ<¯úE´²Íœ´a‹2$¼¤N¬’ui•ÒÞb‡îÿû¹¥Ãÿíì0ü]>4Z¯¸”óôÃÕÀx;úmtù~´µ‘½ÛˆÈId:ô\ƒ±!h†•cÜ‹mÓûËño“i aÆ~a¢ÐÑý.,ü0š­QU$u»ž±zyWÔ·/b•pû<„.Jý“ÁîRäX”Z9?(pA6UŽN•u”åñÚj@Û'!ÂÕ”cd9øA嬪µÚǬY¹GõÝúB“>Q‚ßð‰»Iª¼ˆ=ÌE¼W•/û[ÕS÷…I¾Šä,Hh[ûÊDþ™˜ƒ l¡ï{]ÊOû]Š³Ê¯N‚¹??Ê\ê6GG˜ßÜt\‘IÏÏ®l™üåW‡†2ü3iIE% ‘̫Р_ãÈíëÜæÆçŒÅйX"MNåz`¢ᣠ˜çÛf±Õ0ÎN|ý UBH(qÐm—#¨Œ¹ÅH † +3 Ūd’€Í‹–S úo‚ Æ**Ël¸ÿ„|˜ðZz3rt€R?®Õ÷ʨ¼¨°Ÿ…¤ûîAf`dt½òVf¥ƒ*hžúó½ø`É,ï"ÙÛ€pá&ðã• ó÷€|ñf|ùöêÕÀ÷õùë~4šVr¾/‘ýÓéðÝà!©¼'™õÍ]ÇŠîKèéåèõùðtú ¤î$6Íï‡C¹±Lš ”´`ß‚gÃ7•?ê,}…’î•ï…œU#éÝQìÅalºµ}xÉkâ•O 4 „oˆ€Û 5Å00i†ð³WLUE:Í\~ãë™ ÖЃ&ˆWÖ¬Ì0Ôþ<»<å—Øÿ‘›€„cÏþ¯Þ…¢þþëèXÿ×í>Õ}•KûOwb´Ý!yÐý‘í»#Úm<ÔÆÈn‹h¶'ÂjG„=Ô~»Çnˆö[!ì‹7B´‡ÛùS- Âÿ‹¢…DZÇÿwÚ­nRÿÓѱþ¯Ýé<Õÿ}•K#P¦ÄZv£õ%k¶E¬¼ëRdõèæRŸ+ëŸL.ÏßNçØè’½ïÇýÑôÃ÷Úp3`W9WÙ\ú¶3_7Ñý8CÚàR-D`é ·iò ó[fF‡BªÎžÉ æ!%GeM‘œ»Í” 4ô®øžü„bR3CŸ x%n„%‹&q§ˆÁ¿åš-ü%ø X”€Žž¶ˆ¢U¯Ù”zÓðxÔÔ4í=ú\ ãŠØþßÚËü¥iCj¼°`òÀ!oé²ÉÅIótøz‹à,0ƒ5¸l'Џ‡öŠ 4 88…ág¾¶4±7&‚Å0¦7º6yHù Ý€•fÈæ-Ìà ¸i¯2×Ê_ÂPm‰.ÖžòÖî ¬ˆbõ¸[øÀxE"L#C%, \¢¨£$Ã…ôþŒVuXý. @Ç):„ñ ÷5¸öĵD„rñÊõM»iCfM7hm3àÎ7…Snâviƒ(±a)±"ËÅâ*©Q™$‚ŽTôÒÐ^£ÏE K1ÝòAWȸgë- ¬€¬d€àΓ†ö«Q ¤3k©Ü¸ç«÷¼|¨Ok&è»â1›­!¦‘‹SÈå[S|'E3«¾w¼_ºu†_Ïé ,èM;5µSÅ^2ÿu^W*QQ[P_F"*0ån¦ƒ{¬°ºš>8¸¸KA7Œís±b;e ª×h‰¦eaä‡;«Ôˆ,ÆeL8«Ê…úû6r\°gj*®R¶‹Ð‹c –bÉY¬ú_BÕõW´@‹!Ð,´ÁŽáž³†ì"I”Â*øMîÉè%Y6œKÀÎ!^ÁéŒb5EšóØ…yjIê&u2!¿މ¹œ™•»Ê=tê@I˜.t„>: `m{äeL˜&àd€‹Š)"pi`ë ?>1Úð!¢E3«úpƒfßBø è> Ûho=ç3PgÇʃ7ð©@gá«Tˆ É7`®šL°IhzÀè ‰Ò`/D¬o†e«`_¶¦ÚÎÑr) P¯'¤òØüâ¯PZI¢ŠVǦ>‚ÐM×-8 jÆôõŸLj4høV<­U4ée4—úU¦‚~±Á®\n†B·(4–•.XÂâa á81 õZöúI.†¡–‰0$e6Ÿ“0™/g@ˆÕBm†ôEéæ’Öj_CÌ9Fæ)é²¾Äz ‰òÌòµ~³6å)ÜŽ9ÃT yÚ¿‚·³ÑÍ’råÂwéZ°.8õ,uŒƒÝÌZ Æï/WàÓÑ®´ÔXqí…lÑD/ñ­ƒ ¿ð•Ôÿ‚®=ŽÝñ?DüíCŠÿt]?ê¶0þ?>|ªÿÿ*×Vú‚–P¯,d×[MÈ °Î÷y¯Ý~è:_ti•¯Œ6žªsŸªsŸªs·:— s±BWUåª*Ý´/ä.Göò ð|@‚Ûð%Gã/Ü]1N «Ó0GÓ²U°XÈóãB:äZË -Üüy£AD Š]œ†8qjÃQÿìllôGJE;_öÏNû“©ÏñÙ‹¤²é=ãsc¢Í*{F&`h©S|hŒ&ÆÕåxŠõj‡Ç»F‡\˜ô&€É`‚sL¡ü‚D\Þy`´%7źSÌi£Ë³q‚•’Ÿ[-ñëJüÒů ú¥Ë¶ñ`2¿œ‰‡:¦z… ¨ÙR'm³JHá¤ç§çÃÁh*ŠG„hLÚ²W¾šIÐp&šrÏG&{äðèÿX– ^º9^g›TAã{HÈBa ªƒ ÚN) -î–f(†^ Ôà<ÕÐ|!j7Odù³ì–¶Ë&Á°©êÚtÓ1À……!Y„b•»ÇΜ©&Ü[ŒÇ§çýÉĸœ¤Û„¢‘U@‰h׳‚Eñh(6Ý2ˆÿx;(¥š¿.u2`ÌåX”*Hàiy#¤•}ؤVñM¤(ŒJý¿gv[U= ,;Ez*õÇbKY|h‚äìfru@¶@\­'¶fs}äÆv±—–;ùÕ#’t¡l Öm§DR6TU‘m­t¡lìÙà|0ŽÞT ’ÉŒn,Šr ƒô<à¸áY“Še•0’üYJ¢·â½Ž¤ Xõ&HœÛ¢©!A÷!Æ÷áôb^¾4Ç´r`äÍ‚r‚át£Ì)«xT"–o¨”¬˜_Uù«ÿÚÂ0•Ç4&—§¿goÆý‹´bua†ÆÌlL^‹^Nbº¹Ì ¯¶êLomþÀÞs,Ä™Æc®æz‹ÑŸÓØ_[6pxµÊÒ|¦®r¨ªÞ¢­œ#üª±Z±Ð6ă(8=:/¥¦ynàTs37’ô&S„“aÃÆy bEzImFLê‡<   æ{¦ |¾Ú¯ ñ¼L]fÞø¾ÍÞž]W!æË„Ð2ýl úoúÃËT—KËAzQ- ÏKjÛr,TzóœŽì°yv,Ÿ*›pùîü~†C©…\ÙÄsž$¶YRÓŠ„±ð ÷¢‚Þßãl…=z»&|€ƒ¯=7Ew®¿yŠSòG\å¹Öí¡ØdF´£–çLþ´’`NþäU?›’ÊýÞÂ¥]dƒ6^5«c‹C8®Á2¡gñÔÕNî }7írûs¶-„q³‹%Ôk$<¯’Û÷¡’zZe•ëÎ3 BACШÜKÕ‚6ô•“Od‡Ø2òû]â¶Úƒ¯0“6ÚñRÓ÷U%Èi^æ”R>v—¡ïæ‹pƒ–O-¨ÙÙÓLz)ω:B¡€´YŽ"àõôdÔ£žœ€²ç™÷ʈNa BKêÔ³ä1YV½HÙX¸mŽ•žC¼ð•kà†1zéÍ™TÉÛª8*ÖP†HX å 6‹Ô'áÓÎ*­"aV¢™3%y‰&“Ld÷GDì¤\ÛX’¶È>%i“˜2Á+hÅ‚üûÁ¿·†1–ËcS]Ýμ0™ã¬<š9¾8{Š«¦8ë¯ ®—Ä¥“‹Ïp0NHb”:;ÄŠ“´Ëïžáš3îB'îá CRKޤÌò½CB¿¸•i}ªV^ýJ„‚˜:½ÕÄ@ ¿é³•ýÄs7 M?§@*uz¡)†íß>äúC!RM”¾|ÀK™3’#¢9AÒ)¹­¡DžùU˜€áWª+›‘R&êLnÔ¡Ñì:£;LÉ[`ù~cÊTfVgü“æóôUý˜#«/<¿Zc×uÄÎès‹ fJlÜ‚ÒsÊ‚tU ‘çjùérñÀvóWÈØ‹W1!›Ll^\ ›—â¼ø>؈àÕE¯tï;7é°HÎ%ÐvlÊ…ørí(t.a›‘™¨B¬€[·¸#T=êv€aÛ9³Œ=ef`eìcOµ®Q2ÂlwÐŽ Pû]¥±‡×5ö_h¦óÒ1€“†í>ê¥z‘~|nî?Ú+·thÁlˆû;3Ü8;ÍäÞ“Ê!v]R®¹|fÛµÿÜAÛžÉøI/½ôü¬:–ÍãhO¢.yR®Þx•6¢kåÞM´¨ã­XZàFE6OצÞî]—£òçsÈ1`\»%Ý6Á.íïÅKÔTí¯ZÔ;°'áþ̤°v5Yg?!ÉÈò¾Å·²öœe¤âQßÖ®³;Q7Iåg"áwJöëôŽÚêÌò#ˆBA‹Vž½È4Lˆ+<»šIWª2âð{U•‚üØ;Яåò)C´IÕŠuVù›ÝPÿ*ìo‰éwN@Õ­2‚k;¼iûwÿ—,x•ºWõºE¬€;^·àöžƒdß¿TïB†£é˜±ÍzI²  »oa¡¨\ÆñO!Q¥ ý5¤¢1ÓKB\Û:£û½@*Dµûƒ§ØvWx©Ëض€\Èöž±í '.Äú3¦Þ7^|Šðr8Ÿ"¼oá©?>’Í5 } àè5#ÕîÑŽHo{ÐD0ö„L ¤ø‡Vh¡{,ƒ ýù¿Û{ÛØH²í0ŒX¯ ŽŒ§§'©vfŸº{‡lö'9ÓKRËápw˜åÉÙ}›™q£Ø]M–¦»«ÕÕÍ>ï“àÿ²`´ã v¤ØQ/°'ˆ­ÆA`%HK1`Ùˆá$xA~F”0çãÞ[÷VUWwsšœ™Ý¾ØYvUÝsÏ=÷Üsï=‰eÄâÓsrþà(S?éepïOþ@Þ{ÕY$«Ê«A3w¬B%¡'˜xÁi¶lò¢§&Çý‡‘z©¶ªe<ßQ;@ ¶©CÌŒ¾Úqã8‹îÒUðŒ·WÃ}Z$¦ ’B¨ÒüÝÐsy äa­!§S1ñY3™Ž“q)˜J± 4žk[M·ç³R2»Û´ZÎ)$S!Hžpõdk×O8]÷?ܰ ¨Gž9Æ—å,)äÒXD S:y±þ£¤^ŽP5xšOã”®Ðw:6!ÿD(*Zk8}â >©Â,…Ò8¹±å86SîˆÖB%)«¨»Ö",æ z·’w$kIç`üj ¯Fø­Rk©|‹¨|fàd+r뜴 >Ø€!÷»-·Ÿ"74â˜ášÌç;ª~!DÌ £»w"¥qxbp_˜2“Š?,¤¨=ÝÆë#¾ "›âŽA8LJÃð—i‹ŽÊe¦c5"É4ä1¿¶ÇÁÖyâ[kk°íÄÎF?±àñŒÕJõó ,n|ÔðY„hšH./7Ì2§×7sä\”Û)ƒ|¥Ä‘ åU¤ò¤P-•žeõ¹ó^ÐüÄF_*Ï­ÍK"2‡Jfw‹©©w)8ðˆü¹p R¸†Ð’†{/³hQ~uâøóÐ~% kŽ|- F ¦°=P02‡©rÂ5áIÅÒ«Dƒ^ZÕI§b&ƒ¸Co}YØ àïôz¦æÊ ðVësK|.˜Ÿ³©°Z‘P®!Û>ã>°ÑfÍxÀ;Ýyº®éW|¬Ç=O:öµâ†®_o^ò!sáÆÂ ›°Á;Î>à·ÄÌ“vÍcÔ¡íüÅüÔú†Vû”²–+ܦöi\àÇ«-ª[£±õÎ(•pÆÖÍ:8ÜßÔ•³ô½! ÜugLŠ #)‡Ö:°Ù¯ Œ¢®¬©ÓÙ.O‡êp³œHraGq5Œn^'UC½åù‘rÆÙÇÉ þ2Å!8¥ 4làaI 7N_ÌYÜ= «·è‡7ÿÒÚðèHeq‰§A+¸Í7[ ®ëqߪµ§í-–àÄON2‘ã<4!þÁ&-±×ðXOâ‰X°ƒÖXY˜šrES 0'š.oDGר1­y:W¬&¦¼d¬Q­½†ÄA¼[2ˆgº£³V;ƒ®Ý±$ã“ ÅŸØM:¤‘a½s®’¬T(ÆNšjé{±ŠÌç¸Æø5×Ú/Ž#9İ€(zÜAÛ{»Õê0"B`£,µÛóŽì£Ö™õÜqºv OYdŽ‘gæ„ÀS ó Ø_KŒCZSàÁS¹×ÂòÒt/ò_ý¸t¬ÉÈ Ñqús'r™‘Ý?–h¹Hñ»·dëà+ÃežÅãìü 3Y âêô©^—ÇO{ÆyFeÄ™kˆ=¨zÄÏÅáyœGÍ!Q: iùæ AÑ×BÁ×oO~åf ’¤Ây6™pqcfKSÙðúAï›ÚûÝ~ÕL¥tÛK2%ªGؼ*±¢§¼~dȉ™m ПŒ$âã™%Ô¤Ž7„Ž.›UÆç/U…cså³Éyéú1†´”»L&º#.±’É]ÑzÕsš¨ ãÈûÛ©”i*r7 y}R˜uH#–œ°µÐó:Jñ®‘¡`!Rx°$©Õ&EômMB„ þÐ2ˆÂƒA³éÖÉä&ƒzh&cQd:Õj77êÿƒý¿PlÏkkÃ*'Å$ Iÿ/ÅÂû,ç¬ÊµA¤¥/¹ÿmü1ðµ´1ÑøW(þo¾\™ÿM¤Ðøï{^êmŒŒÿ^)„â¿—–Ë3ÿß7’ªÎË~UôíX,/ 9òÝÇçËÕÅS»·X?õg>Ú¾˜)<ÿAÒô]tä7Å6FÎÿb94ÿ+Åü,þó$áÒ_ a6Õ¿D)4ÿQ­Çuüé¶1*þG}¾ò_¹¼\™Íÿ›H‹n§á¼ÌôÛ­ÅB®°ø6Üëƒc´(«å|µ°Ì®Sè%´Ö9Âë µ¿&)„JQ“bêñ ðINîØm&g]x~¹äkìö›½ ×FP fÆ.#3?…?e,âýn~©Z,è=2ž 8c 3KÕRÁ„"È\¢æœ®eAæÖZ.ÅáQÜ|åöòëë5­¶Ó;v ûƒçÿÒ%ÿœ`ÊmŒàÿ¥|¡¢â?•— ÌÿK3þiå{ºÙÄ·ºƒ£–[·n-,.¾(Õý¿/WZt:·ÖVðq-µòÎÂ;¨† F•¿»¨³CÃ1°µ°œ8vþ´¾ma¨Œô¬zºzkÃëàò²€Žžn‘kxZ½Õ‡}ê"¶rë}«LØwà¥ë{ wïVî-nAU°{}nõ¨£m7 ìIÏiâo·Õ÷ªñކ±Xßí·œ5Ž2ð@÷»²ÈŸR+‹Ø#ba¼„¦oç)Ý²ŽŽë^ËëÁ›&¤zý–…pˆ €q*ž+•B?Ûâ¹Ù¤ …¸æé;AQÈåŽdïŠÜW ²rR\{DÎT}¨±5`D- ¸¶b ´É%æÖüZY´×¬ô)*üLÉèu߃æE ¼äQ®éW¡ºp­Uk'Z+uA ¤÷/37ö·öô–Øí=z½u(¶vUºAÔ_±}Orró²ˆ}%üüÀ­?·P^ ÁShW:(t9 ÀQdxƒ ì†Éé]g[è½_.nyälÙ̉XÕr…Â1g¥æ*FJq|¯uêp“Lò(­ù‰ džP3Oêxa"%ëuúZj½1´~숨Ãj¶¼c·S“ž“3YŽÈÑGWØìf(£"·oQaŠº;葲„ &BÑwÈß®O~¡9#y"á†P³73§ìMš‚»°F9ô;úÂë5¿Ýô(XŸjÍdþ=pÐB\NÑhŽ·ç¹<Ç\Wf2€kPsÍ&¬,zD–G=«ÞrìÞ*ÔŒƒÝ‚ÈÙpÇ=9ic¿Énƒ¸èQomÅ]ÛFg.(Ée[¡GˆI‘¯,ºF ?—_D–¶¶²È\ýu/F³tãI“ÿ$sžz#ä¿J±´Lòìþ———J(ÿ–fçÿ7’^Yþ“TCÒ_1þäÖ5_x;¥?êÙMŠ€Ðàf„ä£D@òù/Äü·ŽlϨJëñ£\Çà¦\È£¶S+ ‚ýµ•£5]ÖŠ5Kó‘½ÔÁY #?Ñ*Œ< ÔœI©¤¹ƒžãH M~é²19 à–½…ű8_l´†A&”¢^6¥'Î4ŽÃ€Qä•r"0uÉ7rvvkMsÒ0vC}Kùˆ“A6ØÞB8|3\Ä)ÉTg¤O¡C 1n`Í3;V~¾*°A¬)6-(íLð–¤8`Öaçƒöª=o”£<Æˉ‘ù¢í3$nBõÓ6¨°‰ƒw_FýÐ}ñê,@VhaL;5•êB˜ïž{Môsd®Ö™Â/…OJ ƒÞ¶{.öÑ™¶q õVçJY6–_$E )º°¦B‰ð_ì/§îõØ•äl©þLã¡‚N!מðF°!XEÂtX^½ã¨ê‘Í’Ô£VB½–Ï¡ÁæxƒA7ñFý>µ[ÑOÞÈÁ:˜È•ìh#N£>2ûÇãs´aŠÁ‹êo~ó›p"`}'ƒ6gµŽ0qîr@D3Q¿©_2ˆÉÕp$9Èh<™¾~ífŒêu‹´±Û… 5㞢¸pÜY½Eq´nì&8y»µvHC3_PþŠÛ>¶ü^Š©«Œ„Û蟬–îY'V¶Z^²Ž`ìôVó$hõÖáî´@ Z^›ÞŽôðd0/ï4f;Ò7'™û¿ˆˆ2•6FìÿŠùR9tþ¿TY^žíÿn"Mcÿ¡š·ú&€Ž`5OÚ»tPçßðV0±ù ç³DŸcv›ÑûøÐ˜6}ÆŠ¥E] ‰fÃD2%• ñ’6ï†.ŒF–1R‚ÏäM¹Ýq ήñPÖt"ˆû”±WaãJÃâ{•+.¾PÁõ,¾q\¸7[|gIO‘õßÜõO¥Q÷ÿÅâRdý¯Ìîÿo$Miý7©æ °þ^Ûú‚±þßgë¿lJgSM¹þ°vdt€À§ žÏOLÊ ©r£êx…o9=ï¹]Q?ËP9=* fѪŠ!wå†9¢0ÕƒQi&k^Å¡»ró m©à‚c̲;ì3L ŽˆlwõA ò“¶„Å»rÃªŠ‘mÏ$ÏYºÆ–ÿ´ –©µ1êþ©–ÿ*K³óŸ›IÓ‘ÿ4ªyûe?!ç¼.¹/ܼùÖuÝ@!ñQ°†#G\6¬/£¼ ]¡ôõ¾×‹RQ ŠH„"m†o2‚¨¡1 ½€ÕŒ5°9ÉA@Uи†)ô`ßî4ì^ƒ¯M2…ÒrvèM.6è¨].@ÐÐx}9Ž]Ö0c—aäŠaF¨”Á)C‹vw-Š˜‰¤óPH-öcx,Æ/IÈÑÑ5dd<–Td©C!‘ Ç"]»‡â<ü—ÐG¤IJ½ÉÎÆk:—ÕXs¶§ë‹ZÑ¿‹3yæ ÿD¨¶:U4œbrŒm‡ÀߘW Ž™O4nåÐ3$æˆÎÇ4–Ôÿñ¢^"Q_%òe2¬šŽó+„q”=|býl«ÿþµ›ðù¹T@ øgûï[¹\ÎzG&S ¶9ý0›¯`S¡S›Ø¨Ub„€¦ýù }ðyMṛ蕥M©Ü£GŽ7Ò '!£T•½qÂP’cSYX_*|Js5*{õˆ¡×+ôÆÆù5í„%y}aóã÷ºúÚ«·1bÿW.‹!ûïåR~vþ#išû?jh( ‘ ùÅ|ÕÁ+åjáÞÛµ :›¯y7"nS(3Muo(*ÍL溡ê¾]ßé.ÜcDâA7YzØBRÑ4V“¾æIITƒ9kzwavŸí …Á™T ëd%ܱ *ù'ñ¦áçXùMÞ6#Ξ’Ó¯Þd«ãµIžÞÇks’1Mh\xhÁàk&ÓA¯‡ûv¥ ¾Þ9“G/¨«»ˆ&À n´­ç&)¯Ç. \aŸ‘„Ý1<ù‡NV˜i6™“'ZÎ+æF„†ñTAøœ¡BmXÅ̱©Gµc¡6@EɌժ³ÇôoŒ›&a¢‰U3(I½ÓÒ‡üfìõ~ßiwû>[ØÈ6:÷[h½…ãú"Eg‚>IxÜ45Ô½®;î©&T‡ .–Œ¢·ûîépKÔ.ps«øfÈÚobÒäÿ`Ô¦ÜÆHýßåbØÿG%¿4“ÿo"}´õáÝ{vz.7÷¯ÿ0>‡þ}‡þ|‡~ý¤ò[¿öýüÃßøÞßýî¯ï—ÿìý¿ôë‹¿ôË_«Ï-¯\–./›s—ýrî²Y‚?—ø ^6/›ç—Í‹ËæååùÜåyéò¼yy~~y~qy~yy1wyQº¼h^^œ_^\\^@¬è’¾ýèþèýØýøÿøOüÄO|ík_ûÉŸüɯýë?õS?õÓ?ýÓ?ó3?óÎ;ïܺuëöíÛï¾ûî7¾ñ¹9hˆö|nî–Js¥æ\é|®—øk6çšçsÍ‹¹æ%fwq1w](ÍaÇÎPl*…Z <^b ˜š¥Òy©tQ*]bƒðÔl–šç¥æE©y‰íÃÇóféü¼t~Q:¿Dp ïE³tq^º¸(]@Ÿ@(Ý,!‚.—sX‚ ð@ˆT¬«†º ð%vŸ!7›„é9lžÎÏ›ç„ø9„>^œ7/.h ëd‡‘¸ a™C/€ˆ ¥9û@_РÍa°%¨ú‚Æp;„Ï.hHç°Ð,<]\ÐR¡Ç|¿ DZA ã4þsˆ D”¾ r˜CÜ`— ² ¢Ž9D6 u_±Ì!æð3<_íÀp.¡‹Ðò’ú%ë%Ö%æú~ðÝïþµï|ç;‚bé ØËËwþÍ¿ý‡ææþðÜ<Μs?_¾òõ‡¿ùÝÿêOÿÐWÖþ­ßúÍ_ý¯ÿƒ?vûÃÏßýcüýÿèÇÿÈ¿÷»ðW?ú÷òý_úú½ü»¿ý+_=èõhÿ÷?ú•;Ÿ}­ñË?ò³ÿÝŸÿª{ÿ¿qÿfzçÏ¿ó§ÿÛßù_¾wrñ«KþÒÅoÿãÌÏÿÚrûûŸÿÏÿùßxü×÷6×¾ý­¯þ'{ãÁ_ü°òýÿáÎ?þ« þ¿ßOýñþÿVgõïþUï×¾ÿw>þãÿè[_ýýßý•çÿý ÿYöÿú§¿÷gäÿþ‘•Ï óâÏý©ÊÚŸXù¬ÞøþXø_ËŸÿ ç?þËÿÇ]úƒŸøë¿÷¾û¯þÒþFµò//þÂGÿýüïþÅïµ~û7Ÿ}ïúøï•÷~ï‡_ýÇOúÏÿZîÅÿùáÃ?øÕü‹?úg½y÷_ýÎ_ùOë߸÷ƒÓÿýw¿ý~lmíŸü›ð‹ÿà×îOýÉÿòŸþ™ÿñŸ}úëÿÛ_i|0÷þkbX³4Õ¤­ÿþkòÿP®, ÿÏËåJ¡R!ÿË…Ùúé•Ïÿü!þ4‡oþ_¬ÿÿfý?øìÿÁô©•ìïÁÌ;‰cqÞs3Ž 0Dz¤uÐykí€^=pØL6ÎêŸ6Å|¦‰ $8k‹wAÀ YÜÒ¢Ô%™ LåTù!¼˜ˆ 2²1¿G—ìòýø`« <ŒN²yÉ¢¶/€ƒ‡ëû›j¶>Æ£ñ¸·¿µsˆ!ojol~²µ¡¿ØÚÛ¸ºi»<ȘB×÷këû·>!`èyc÷ÑÞþæÁÁæõjgwÿÑú¶z|¸õàÁæŽzÜß\°»³ý™zq¸ùhow}?xó`ksãpW{sðÙd› ÏmÛî¸Ý‡‘?ôu`̪¤~‰ÖvkxÄü^x½S³Émò_3’o(Q(j·š°Ž$º:à–7¿¹±nøSä‡tLOêP½Pª{hìîms…áÙí: ê7·:²ÒÃýÇ;Q¢îÁ¤#8¹æ/ˆgƒOQ1G¸u~ûŽMù?ºèN£‘ç…ˆýÿRe&ÿßHš†ü¥š·Z œqsöÕš.å'h³øî·Æk 3NÔ ¿úصSnÒð¤vøe\tÉS–qä2•›k†·+¢±~¯~2ÚZ?d3ír1ëH˜¨6ÉøÊmAqaÐF_«>†"˜ ’ äÔ€éàaokR@¸ÔÔ€8qaïÖ™.55 PƒÎë´Î&C–› ¨Ôçað”I!Q§Š:ÖŸ ­Q[»˜°qÿ̇MŠ.5 òO¼^¢M**™Óµ'}*Q œQÍ"àURŸ¸ÑÙ&s–^w ïÿ®C$yÿW(•…ÿ·ò2QãTòåÙùï¤éìÿtaô•¿·˜çˆ”åjþm0ú’™^׎OoZ™w™Ñ¢^Õ¦ ƒ} 7àjŸ±y“4¾>ƒ.ì‘f^•`ÃU‹7âÚjªo.ßÞúŒÁ…Wq6üvûtkH¤Áü±Ó¡Pp/– ˜ô`ú´eúeÒÕD§1Ý&ð‚ Ó&ÍYìΙ°þÖtL&‘9TÓµ ï6/ý*¬Zé´î0B™öèðn3lX›%1Ù"ä’lybÍà#aeT 4(šbVÄp§1 ÊaÂkþ1R7 ˆ3ºaÖMìÐ r©$]Œë®`hφJ‹¿„ò»1lŽÄX×îŸd`HRý$£÷âé(‰t>wuŇ!N¢¤6˜Sî}pÇ¥]Ùñ®ÁiÈÌ:Ud)já‚| ÛH'lòñ‰g6ÂydןÏã^W¦@ÑeÐ×DïA«V>vŒœsXH[èÕ5Uý Q '9ÙNJm3’C´5W‚¢lå‚zt~ß´Zg ¿8€¥ŸöNDIº)*A‘q^Ö[e³(¡À3'Efs²{šÕ¯Ûi¸JëF|Fc`Ú¼áɈ’pûIV—¨uX8hjß~®-Tvï˜Ôbƒ¨«Ê^“ÌUôÁ0¹­7‚”|‚&%%RͲ>·4e§q)ë O+Y"Y©Qˆ{“(‰ý·)"Â5MJ;ì8(L9¢ tèûÖÕ†ê;€–S¤Ù9iæ…½!tT÷ºg¿W¯)2ÂD?» ÞÓPØ’5q“Úhb!¦l0‘IHˆÈ9Ê®¿épæ¬û kE‘×™¯…ä&¢7:ÉnvÏ ù b ‚Bèdˆâ¾‡Ñ°Ã™ vH4{ï:äùW#×L`'Ný9ž÷šÇHZQ¾ä4¦zp“$móp±ö¥åi>Å`+Ü9¦;#Œ‡;üÔAuPÙŸ.ü„grŸ“Ëèà$ùZqF0›j¹{ÔØ —ì[—£SgB„>ˆdÿ؃ÿfBošŒ1+Þâëµ''¼ž ÓYÁrïøB· AÀö@ ýȱ}qÜ~>9ŽU öƒ^3 •÷ ÚÍÓÄ<ñíEcìerD£›^«!ÖÜŽó¢6n÷Yº –QY¡¬Å@3­˜*bUe“t}UäÆÔ¦Xßk¤ß×q?uU‡{ÆõTýìYNײJV±PÍ—ª¥‚uðÑ!MÏ.©´Ä÷?8žÝ³ëj#ñþ§P(–øþ§²\(”*ø](./Uf÷?7‘n[ ï-иjuÏú'^ç}ÁhÞV­2~NÝL}»gtÉs7ðìW¶ ËÕürµr×¼äI݆r¸W!æae6²TÆzÄ÷0Ö!d\‰¿”Yƒ’ܘE±‹[X-÷µ+¨^’{}¯Ù; ¨Á6± +mû ®Ÿž'c)䌸Ít^vù4x^›î €Mözv§ÜªÛBEË9%qï Hñhô¯Ÿ8°x¶\Ú¢PHm¨²a·ÉÜÐî¹¾±™‡ê@W _ÈÁ½çôÚ.{à‚oÇËQP%ú“&Ç׎YR5Ùôº…CE ·‹%…ôÙ{u6<6Þ°Úœ?Ï›!¹,lܾ¸Ei¸¾Ôã‚wPS³çÀ>v´¾c§ßÁE`J°Eg•zQ`¡Ãƒ‘u;‘žš^¼\_©D8÷É×·úUAª·l·Í×1ä ¼çÉ-ªú¥pˆ?˜'Š#®Š"À‚ÍfÇ{ÑrÇt!ÏI9l24ƒo=YF⪗ t¹tÏv4èË{&ymÁÃYD¯ê€^ÜIyƒ,ê§NϧÐáR&èBïP=©m÷ž£40ê'<::¢¸U]–-ÃõÄ£ƒ`(‰ÈÞPIÊd÷? Ú; ‰H‘”´ŠðÌ jK¹mºÓó€|ü3üŸ‡~Áæ•En>@äGô‚èÐñd NRT;tþˆ×øûxþÄ÷’òÓ{)œÇ ÓÁµ<°ñr£wÖí{ißz°y Ýá«0&WW.°j×1§º–SCbíÉ,4‹"Dv<úniEŒ.Âõ{gU= n.Ûp»°©ö%üP:Å–ñÖ½!™Š‹aÍ,x¦ m|rPÛßüd /Fñ áÝ}çÔõÉÇrÒwÓˆ >ó·Ä½ Þ}§4Ãc,0í6N¥ ô÷Âc4€•Õ6±&…8ñåÁæö¦þ¥”ÙÞ=Ý.‡²óÛ%õvã®óÛå ‚‡›«š y-ûúƒÚþú§üÞVï?ÝßPÔ‡†úp¸¿¾s°¾qÈô’Y¬Ä}*Ò·RQ}ÃKÚú΃or¡† @ðÁ A|ijíl"–6vwv`4èór€¬Ív·Öj–äýÿÁæáã½ æå’Y³¨VËP¡[#&ÿÍ}¤eIÿéõjZ½£ °‰˜Noï´/Lëiø¼ƒf‘pÒø7x»¾ƒ–þ9L4w‰¥øº1:S—ijñº¡J駸<ê0ûA왂±y*0)DlQÝ×hOž ~‹ÙÁMŽ2OiÇׂqP¹qŒÂï³ÖY v“ŒWb^ÄzrÅ4æ÷Àc> Sˆ|"ÒfªŽ|Ãù°ùÍM†µù¼³»Ã%Ëüi}c¨šJépˆ×AC‚™hÙµoEý›j9r*ÕpšV‚[ ^Sx³În-V¹¹ª8±äõIz÷€wô ÿnîï?Ø=È;ýŒ¨hÞJ?î <$NRÓY*ï´¢U£UpX ­úƒýO®X})ZýC»× INkàáþƒ«5ÐlF¸"FW ¨]ÌY{-ÇöY PäÅ=-ª÷hm& ÈRT •b˜ôCÍÑN•$šm“DT†¨ûÖ–Šõ"k×.ªÜ0T­ævÜ~­–y­9o‘Uæ¥ l6èæÉÕ¤þþ Š`ø'üIZԮʊ ”‰1fÍFШ5k–!cáÄ2ˆQFš'‹@X«¡À-Ð-–^‰Õg©ŽVÓÖuw¬4㘿ô{ Ú,¬ÞÒ K B†[kid ‰4ÒÿXTçÃE þÈpz 0؆ðŸ6ÿ‘vMó–2Bšb"n¤.þ¼%õã£$Dõ")àßðG[|´ã>¶ÅÇvÜG |—?#•K±ù; u†` _á ª“Eýg’݇<ògˆÉ-™©›F©öèRíh){t);ZJ™°%T¸6[TjÉ­ªAÁ«ÌÎFͤ k6dã´~ÖÒw4F=šØ$Uû £6aÅ5IM¼s2jfX“ÔÂ{-£eG5I=rfÔBMR•ÚÔu–L“Ô¥v‚F]Â0i’Šxïh’a`Y”XSÀŒÒÊ@(±p”9Œ¹ÜºÕ€«FÀªpݘ²‘I±*Î#'tx’ïi‘ZSòËmk_GùRaaF.5î_cnR×<@åm:œ¸Ú‡âPzØGu”–‹‹.j¥øÂŒ Uˆ³Bõ9Ìô±F.ïu­ðUqP– ç<]×>«Š©C°/QÀ)ÀŸÀåD <¶I3²ºA«M·ÓȈ¢ ’æÒ¡œ0}¬5k¡`¶e¶'~=©ºÏŒ\‘u÷j«1ñgƈ£ nkm> ^]Ñ\½nÛ/kýžÝñÛ@l,¤ôìmÚ"ˆ,N§^{îœ!n„|æ×éj”k±Ý!ÆYƒj ˆ¨ ¸mêb2³*WLî¢>N+:Ó5üåDt³ nuФFL"жQÕ‘So7hsXûÄšx¢þ7[ö±_‹ô±ïBþ.þo€ÿkãÿ^Ô½‰ìƒÆ~ȤWÊþýû÷ÞX(ú!ÝOsÓOª¥Ò³aÿ³†×¦«V©ÌBTôžU ó²0˜Ãaì›°Q³¥RUµõL¼ ¿c«ÏbÐFT!G¹ýû^Ÿ¾Ô;@ˆð€ŸéwM¡úÅj’â¡!ª¢ÏøC~ÅßøÑGë–º‰Ô‡2!: 'ÕÂ= ¡¤ðÄ­B¯DKÖŠ&yþä–! 7Ç9Ä[s>ŸØ~‚§¬Zíš»ØP¶¼9¼JÙUZ°*øŸê¤>à6 öTF‰„Ba§˜¯ó´|…Œñ纫:lw eΈªÏ‰Fƒt(´ š±‰0b„ÔË¡†è•H•æ%¤?5jÃË9ÕZ Ýq`øÑEo¯¯eC.R SŒèŠ¡³= O0ƒ·ù›~ú²Ù„:ÜôÓ|Ð\=ó|\n€ad²óÚâ'9‡uÑ*f%î}úÁ4ÍaWiÞeÒ §óbÆvH['†[šc9‡žGÍlý?9O'ïmX 3`ˆOko÷£ýõGV!—‡/ø¯øhkc÷`÷ÃC™éÀ*_·×w­ïhÙ·rÅoæóE|‘ý¢¡>A¡-´¸ 0ô‘‹ 6Y§±Xt•Ÿ£Öè?D!ÚÃcn €¶ #ˆŽB% (¸Ä_^:ß%åËøâb¤a›`óÝ"Þ½ü°Î šb‰¯©U>ÄaËþC»ŸE;¤ÁI¤™µÖV­»ÃÁˆòÉp)Ñ£ñ¤z÷ÙÐ*£ø•¤ ÆÉÛ"ÞÆ¡ä¼•±n‰Ë˜œ–h>geà3ù¯Zß`UB|AÚ"ßhdoY߀‚1iecéÔ2FãÂÐÖoÇQË7ö\éø]•CD®›˜A¼ÌßÕ˜… %")á²öµ<òŽ)Þ#„´_ßÄå¿xC1øé²††¯Eˆ }˜:Õ ´¯ìuIÕõc¨ú4!M—³©ÒðÂ_GÒi£]¯ët¤-”hQ·†¢ÏÌVØ–x¸ê¼S—Ñæ|“ê3îáÃí‡ÛÛÛщgb*|®€É'qÒ'‹k]UfƒÓ4å\ö蜰S_ÉÕÀ\Û4U[`‘Öçbµü,û$?|lbWùäfžü³Ñ^›ï'°¼ Õ9¦U>OÅ é%ã­ ¯"N¯I8šä°½MÄW¾#6 Å»âW‚,!¸ÑµÂ;5>—¦;|ºàDÕô‚ÌäÒW_Ûõu8b¸ÎÌŽôrbXœE>œÝ!ž§P9ÚUÊËPt¤>‡ŸkÀ3ÄΜfX‘öÑ<éããŠà+=ëgO‹ö™ž5±Al ƒl|Å{ÃP#jÊ)€îhïXK%³?æ¿f̈³¿aë’RNJ0ܜ޸ÍÇ"ê\WÈ¡+_,Ïcè×|9²·Ó èðëŵ^Îk˜€-+ŠÄS‚ìwÜV•(¨8#¡×@BÅ/ u¼lÙjº_Jër}¹lÓ‡•¾' ‚ÈÑ8 ù/å_–šM¼ÙaŠÛƒVßíÂZðñ}ë¨åÕŸ×àt ®Æ–„#=ËŠÚ\‘F ³ÒH>Æ(nã!¹0¦Þ›Où`]ÐÁ‹•ð$iO*åa*éaº1iO@’ø²‡ Sú0½šä'ñ̾˜ú´ y)®Ê£Å5ÑÍ€ fœMî&9!sbÞÑG‰ýÏùRŸ)ä^;¦®6„(Â)™ÁÈñ~ìcÿ&Y&7«#v¼t§†DuøGJìè ™c7¾ˆº>ÌI@×ä¤êÚ®ßÔð}‘/Ñ +׬ç2½»ß¥W^ꜗ]¨P룾Xëà—a§±I™ˆjQWŸ½“XË è®3ðöŸÈ)»mÝåOè^Þ$Œ+ÙI/“É@þ |±"k­­Y…,Ýx-7³ÖÊ <%Pe5ZŠoÈ(ÿ’õ¹|,p•EÎRjfÇ«³ ë,QþŠQg‘ë,q–¸ueË”¿lÔYâ:Ë"˸u–ž©˜¿dÔYæ:+ªÙñê,?SÃüE£Î ×¹¤Ð3^•gj(¿Qç×¹¬†1±N½T<™HLÓÒÀ?±ý¥¥ÈT²?b¯¬ téd×ëðÝåvûÈNûºÀAD;ÌP(ôB·¸¹³±ÿÙÞ„’¯=Ãd€ï‚EÓzÙd ÝB9lŒ$³?©ÊÚD”¡$”UÚŽïYȵ`¡][ PLSi^bÐÄ“êò³l6'‘I?}Y>‚ËO_VJO_ ð;ñwþUÒØvrËÕÉk½©„‡<¡¥b!»ir4N(½L#põò0Ø Å [ÉP¤ŽÆŠzÞ ¶ŠcrŸ$¼†>#”_&˜­ÏðÉ$½b‚ƒUSdôÞu+˜&·dð‹nÏë{u¯%u”µF:α×w…¡|g˜C)€@ºyÒ&TÈl ‹C™ÐÄÐ$æj ÄdŒ±¢>sb´Hڪׅ¶¤4îH³f2å¡-ܼUÔOZ`,Ÿ©ܱ”$é.‹qTÏžŸ33¤ÉQ䛫×\Ó È¦#ãE‡åÚ>’ïÙe™ò•‡;´:mºè€ÜB–怎íq¢:~ß_Dwæ^0=´zÀu0D«“`…Ø W=×0¹;}¹¼]¿è©‡5a[Ôw51t²ÓO!iFÔº/N‰§[{ï¬Ó\I©v"Æ¥¬„Hû&RË~útokoóéS¶,¢Ù›ÆEùO{ÛÎÉÓüýBéþ§ßzН üÉÉËé®Ï@å§N5‰¯Q,™Uë‰e®}©Ž94E²k?èŒÑÚ‡[èÒo»†¡ªênòIÐ&¬}¦T‡Vö0ÞWø1høI±º4⎩m¿„õá˜báB¡ M.ÄAĬ¬ÊÅÛ‘†Èäu¸Ç¸®¦è ¦'âÜX7sWå Èy²£ïd HC®dÃMì©+ø}w¼›Y³‘µëc^.*ÇJéô4ï,UµA7 H“mdß¡Ãi´ÝN¦L#ËÙ°8t?L™8oVÉ#"e]Uïô·‘í¾ú¯]Ͼ‰Þ`[á@°™•Ú…Iß —Èq¢œ͈L‚HðîÞ†!a{½ô"/qé!¢ð+¬¹šÝ¨•7º.jKqQ¬Årq•ë/ÿ-,ɵ}áŸ|.‹±þ¯RO“î/ äNPÏ—ù‚AWá~ë—^ŸUþÑï9~p”g…s§mšf˜ßB:‹¡1&‚:¤C¹×$fi ï£MÓÔÏÌÓfæi7cž6‰ÁÖU¬Tf8Ž©Šd«I ãŠØ\ešÆcof§kÈ–ÈÂéUøD8äátã|$â+ ©iZŒ&œ¦ÃxÂéFŒ‰âÒhÉpzëiȹ$ÆGµ2ÔÝìõMnƦ‰ÂY\R›!~˜¢Û¨:®Ù•‡á»ÁѹÒI²û¢zurë‚~ÿùBÛ.½3j»4¤5Y«¾7FC”¹\)_Ö.„‡Z½ÈXŽó±Þ+Óâθ¹û í6¾T¿ i×~ë;ÂÁ•¯‰6Èšê­Páàè¾N6 @M±tmÒAMS§ãz|ÖH”,Â2Àèú"q-é*‰oä|æ8®ñ“Y“-b|ÊŠ°³¦‡ÙìlúG§?ãø­ûœ–Ó¿n 9 Ã+Ìûöäkìm‹ÅXljp¸¸Ù®/åoñlŸ-ßW™¿oõú-æð[±€·Ÿa„em}6Ý&–ÖÞÚé&œ¿Ó­ç¨pÚ|óZ q9Õq^ÔÞÊÙ·¿¹³þhLÑÖfMAWÛÔ…[‰9cå{‰­ÙLV#pC³Øšþ4Þgu¦7pò¦RņèO5Yé­Î©ÝB‹ÈA‡4ÒózeEÈA‘SÑoQÓ`¤wý{I«A1-3K²zžÕ¶;g@„5RÍLÈ´NŠQÀZ:®jfIÁ9$¶3̼­šÏ™å—¡ü#à ÀP'ýž×žå谮煼 vcy UP9óó= þÎõYv£AÍÜ…¼–Ý霺=¯ƒö5¡\ÆhP|òP†¢–‰Ú¡<Æ€ÀŒ }®èŸ{îi¸8"z½qŠé$ƒD„.ÚÓ´oÕ½: ë"zÇñÔq °n[(×]Ê%ôó㨠{ŠúߨF =0jÍ–[ïã€õ¡åðx•°ßÛ8ˆwd‰»yIÚRß‹7n†YØ·iºì¢¦ ú9“þ%ØõmtÂÜGÕ8a d®L×±É[Ÿ§í>'‹¿è$Pc3,e¤ê=Àyß ÙA«0?: bis÷CËëX]-gcÐî†rêôÌYµ¹ê!bnÃk·‘y²µºwÜq¿åL€%FoßéÓ”Þb†pIG.É¡ÏúägeN«ëôÚ.­¡©¿¬Oê xrtâÞÅŒ “ Ú?B±E1¹ ïŽ{Js·ÃÌôn‰çWÏóÚ8‹…2çÌË:f1 må{DAáÌÄ¡·l{.d‹Pž{ï^è zõp5÷t¬P[òó"â¡§î¶Î¬AÇ>‚2è¾zÐ¥ÈÖJMgI ®¦0jŠD¾Û ÓG{ߌcr'9wD厶ìFÙÎÃýÌvîÑêÕpméyÌY@Ÿ1L­˜§êŸw¼è‹š"v÷²}å‡0ÄÀ‹E­‚:Ï·P$®ýXæWÜO0á0šˆÄç±E—´†ÛÔYTŽeZ¦pá&ïJTå1víndȧìq¶iZ!HKˆFŠì÷‘ø‘ÓIÐ" oÐ IËE‡ ¡.ˆöv‡œº¸€Ç¬arV´´µ,”­L { *é?Gr‹YɉÐ>ܸï¢ìS€);”iI[g—p^Ö§„eäÜ,™È­Ë"ü«w¯­B>¿\É[s04ËKeã/M´B©oJ•åB!_^Ê£ì¸TÊÏYùkƒHK¿o÷,k®íÂþÓñ†æõý-M·ßYø½Å#·³²¾Õ=ëŸxÔmká½ZªâÕûVß>Zxá6ú'U«ŒŸ!Ó»[ªE´3jràèùÂbþÞb¾„²=ì$ÊeK ýYï¦nC1Ôª`ÿð™,•±¹õÛiY‡q¥ÍPèƒ#÷¸éyý0÷5(ImY ¼âÊå ;Ž1ôÒ“ð1àFÀìÚvýÄEÿk†`Ã._û 9jó½fÿ…Ý#+¨êÔ~b¥mÁA€X!rf”7œ—]Ü/aG Ÿf'¨w>o;}¯Û§¿¸ •Ea¦Ï[öQ*õÉæ>ºáB¯ù\!WL§R6÷kwk{ë‡ñ‡›û;¤º†œ¢‹öéÌÓw²dž<}ñ4÷ìN¶ºø^æÉŸX„_™ÅÜ{Ùt65¢‚ޭزïAá[ÙT Ï”»vÏwX/‚bAóÉVꉇ0×¶ûu‘—²ºM«qÇ×Î÷¼A×ÏdUuW¬ /^Dm b-µß%./JPDñ”èÊ¥µîÂj~¦w'#zÈÛßôcü$ë‰G~úüg¨`får9ëÉ n–Ö>x¶Ò9ÂÝÙZu‘¼¯,Ò.U¯H@®(±: ÓjÑëÛ媡F:VLòO¼"ˆg.E)9ÂuD.0Hòrhx8Ü~¸ê^\Õ0ó@!ì½VB¹Ž.|ß:êyv£Žž›ñLŽ ÓÚÁt’Ÿ#W9#µlÉenk/¨Eŵö˜Ó‰oX)Ö&Jœx~¤G ‚E‘%?ÐöK72 ‚~¸î"­Æ56ëÕ“-çø§ë×è 0&î¸åÇb|Âî²÷Îj½A‡oŒ+·ÓôغKW[ŠkTó¨ê×HïÔíAI¬àIþYÁ‡b.âv£KÌ)gž¢;/5÷8„Bç%Ô1o¥«01?ð<ÚHfàCá–@7¾ð34iSr:0i}c—ü‚B*yô>µ{ ÓªõÀÁí¯Kbå ÎB’zã¢%…íˆÅÕýpˆÜ_É+ßy‹%!÷ 0—t[EùþÈöÅe¨¿<ñ-ÑB"ŒHóM\¿Ç ¼ÛZ|/c­"¬¿›9X´jÒ?F&öÒŠ³ÂÚ•Kã <æÒCî²ÄÁL*®¶fˆ(㫘dâQ'ÓÂh0 9_£ã4ôVЉ5„éÕµôUeçµCïôiZ¦&ÍâYÈhX´,áÅQŒýœLC§LH¹š§1µy’c ƒáýŠ»™‹v>GfÔ1„.“úãU:Äš„èˆìšŠÈ =É«|§Ÿc6eÖÇÜ1‰j–ÌW$—DÙ±m¨j¼aÞKcIbÔ_îb).~VÍè䑱$:Y£Ä¡Ô—@qBÐ`ÿÔÀçŠB储ÿN°Æ šXÖ€¡’¸v}Òà’ˆ¾ÁeÚÀ‚hqÈr­V<€¼! k5Ñ,×+”ïÄðÕ*‘[ñ0ˆEÈX(V5ÎLÔÓQl84†P §‰ég ˜CG›ÃÖË‘3­“múJ|3–oMÀU'ÁÀÈŽÇ.e|Ý=ŒrQÕðF Õ‘ÖKbÉ/ÔªêÅ GqsLbnQ^…Ö€O’ÚÂ׸í©|– Iñ$ò… ðL¿‰s£ÏùÌxlWLO·[£­-ëæÑ—ŽÓ?r=ôœØ^°ãôïoíˆÃ¯ÛG8oÙ½cÌÎ'O9þ“ñÏü|8}R¨â¶­×¹_ÝBBzb¥Å~7miæÎÈVŸÏ[§ÄY¹âÈ’þœ$ý…^ ïÔö£”ìÄ” 8¼Üý˜rM9à 5u†€¸Ìœ†Eƒ ¢­XÐC#q:´¸ò‘Ùæ-qØOrï¼ñ z ùdK|"­ þÚ‰~ÍF]%k«¦X6ÄØ;æ’wâÞžãO) 7$¦¬µb'«†žq–±Š' â$@¨ È×Uù^¨‰íð_«c@•'ÑÄœæÁ0f³”6ŽøÈZwsDcÙ f£i)(pŽçLGgľ4ñÛfOÂü¤­c @íxtNçXói­«Cx_DZ…ÆAY[d(媶7ø¤•GZx¯ÐøX Çœ( kš²Š–áEbÛÑS©ÎX§R!Øã–Ý >c!Ž„ð”6.M¾Óà *ž®áÑ‰Û Iöâ8r•źG÷3Z‘’cÈN“@fÒ÷Ø`™Å†øpråih.“'Y7œpÊF €´Vå] bß„¨…©Ñ2âßLZÅáÁõ Å ªdŒ”*©´^µÒqƒ“ãp@ÌRâãÞ˜]$!Ã!R„eˆêã³!L*dânT,‰cmhK 2tÁ€)ª–%ø®´•›ì¸5àÂØ¨zÂ…VÓÅÐ*áÚ”›Ö CJ‘-¹´DèM[I8K̾0ÜÏϼß”õR¼eC¸º&æ î¥rÙ¦nw8êSÃãÝ–Ê¢® V×øÖžÊ˜¸âY»µªãŽ×kë}ˆ”¸Ê9U³c GDhkóë¦19Y}ÒÓûö$D‚{¨Ø‡hÁ¾d+’ $®FFFë̲2æÜñG§« öˆï¹QR¦¡"ÉÒ„*ñx:A ŠÃìdB¦!vbGqpD$ÙÙq…³xä¤8(¢²R"¯*.i]6† '…Ž)5aÂK÷@> ªHš­±¼ur„§$èÂÂSxÆá¡òH‰Iä.5aKr5)F™Ãèu1bVvÈÕ›&{©ZžDKGé%ášìÊâœj/Nš W ¦Ö¸1" ÆŒb ØÈ‡¡r¨[çÆ1’ ¦É î*y“r1Ä+ö¾V#èÐ3(Ð^Mi°w>¾Ò’ÙS­âÚþ±8¬Šg'æ9‚ÁƒDq½Éd]£Éáj“5ù±sväÙ½Y§ôú-| QÏt°ßФô¿ƒkk#Yÿ»°TÉ—Qÿ»\.äKËÅ é—K3ýï›HSÑÿn Hÿ»è¬ür5_¨– ÓÖÿn ¬kÛm»‡^šÉ¨„ŽZQرAÞE-oZ<Ô!{ël¦í=Óöži{_¿¶·Pò~5õn¾$ź"×UÈ´„À-Ã8ö{ðJÈ„áÜÅøÜ)Éà?9"LCèÒñ84aÉ ÉâE:‹vf¾ ·Gá°¬S»5À!Wh·(ìB[…í¬o·…ä¨Á}5:— „ÿṊWPË^)´©Ÿ™ ÙBFµX«z…´ª×Wk®¡î¶¡ |0)³P+ûÔ~¶ç­…Œ¶­'Ò]‚J¾„ãF¿ó4#a Ñó>ÂJÎú°I(eP욎E_Ãåžc¹çnËÓËšåÔ×pÙ6–m;Çöð²êk¸ì–õô”Òw cX¾áñi%­%WÔ+Áðžc(ì— Ø@œ¬îhPàH÷½>š§"F5¥– ª.vyYæñÈÆD¢±àäfMç¬h-X®[Èa™¢–¥Õð8µ¶Èœ¬wLì?¶‡°æwõ0a€•Ÿ1ñÑÇù xXã$‘.¬"ò5J¢£!º÷r ;³Ž¹ ë˜_8½3q>Ïa…¥û6¢qS­í&j@¾ó“ EÓð»vYÆØ 2蘊'žO°¶ýXÞomGO!°o²kÅÊ#±‹‡Õà‹yÆ"˸0‹@©LùždŽ‡Ù¾®C @jÎW ¤„(ËFî Âºô†bì·8•ıUä)\—†êá§êCÇYif…Á€a‡¨.&:j’cÍïL€á‡æjiyr¢õê°L't¸'È|Ô%…ˆ;ÕÂ3£ 9™H£Ö¢>c²rÒdã-‚dÜ»2ø± ÞC¡d ÍRZØ EîKpª¤µº‚¬“›éLŒ`G.&‹f¸ê 1î þ,¥æB‘¸Œi“¨Êw5žG5ÐR)ÏäªQ&9‘îŸ}ô¼}¨ÿ!0èÁÒFçAV:bð‰å&ø`˜GITóú ü4d©+èÂÇŒ•^°±.‡ÒV ƒÓ6VGPÔòœk «Ë ‹|±mL‘J>¡­6·àet[廕å¥áÍ-'´vÄ­ÙRB—Jq°j£"ƒ˜eHŸ20a‡hX `ØiÄ khæ½úŸ4u¢Uˆé=¼c)ŽÂî6ª8zƒz£1\|¥Oè%õ_xB†t…Zäm[ 9…®ÑA›¨Þ1j™UÃB£Ä™ õ„IeÕC‘NäÖñ µY£VŒ|IȆܦ³´—2 ål,3éÅ;i”2d¸Û´ÔÍbâ­¢ÖRè&1.¤ä8šºßsß"ù’?¸Ð+èÀ²Z›õX¹ ô,†„)Á#ð°ŽÚé`(Óèùq M@–švBžQ 1ÀŒTFxª<Í#ú ú$Ú\Ñ“p7£ ZGaœI ˜†QËú âO‹ å¬¢V >DédLuטVУ$äGßd1\4Ä-yç•p¦ÏF+1Þùqö “o&ÆVźªÕ˜úfçâ›õÂvihù`·ÛÒ´!_Q«c”¾Ãë¾Ã}•Ä÷ÿÎË~Ï^¼®6¬rÒý?¦¼¼ÿ_*—èþ?_š³*מ¾ä÷ÿúøƒ@|-40þøòù"úÿ+‹ùÙøßD ÿ¾çõ§ÝŒêR9iü+ÿ|±T.,ãü/JË3ýŸ›HUúªèÛ-ز½,äH¡²ááBX]<µ{‹õÓ·{‘›¥¡)2ÿ®ç»xu2½6FÎÔù3楴\žÍÿ›HÝž‡ \þ¢F³¹þåIáù¿Ùé£þÆTÛ5ÿËìÿY“ÿÊK¥ÂlþßDZÜ9,×öPÝPÝäzÎñb!WX|äu¬õÁ±#R(VË÷ª Ó‚ïbjñS·?óW+y¯rÅrw¯VîÑærvß:pº@p¨®\,UóEYîÁ—„ êó?–¦ÐÆÈù_Yë¾T©àú¿ùgóÿ&ÒþæG›¶Ë?œúáÔû„4{ñFÁ²ݶuÐÆ‹dŒË–óñç§nïØíä:N? %w¨ì\ý L=˜¨ÿX¸wo¾ôíþÀ¯~eƒc0Á|ÉêâÕ¯ì;ǨE|&<¹cjº¿ Åg«ô¨™+à ͅƒ½Ýó´ ÅÂûäáÇ›ŸÕ¶w7Ö·kÖ7níl>åwOE«>ëÀé?=`‹0ÿé~£÷tc¿Ápöüg?œºµImåBË’úo­6ðO5Ï©€M¾îášzÒçÿP¦þŠmL>ÿ—‹ËÅÙü¿‰”4ÿ:½#kÛyáúVæ~໨jLüÂ’õïZg8íïM}ÚE6¼>.Êù«Ïøm»Ó¶;Ÿz½ç¬¨?›ÿ*…æœhöÊmŒšÿ0ÝÃó¿0[ÿo&éó?vvù}§4»>yùàé';›‡÷&šL¯»ç³„)<ÿc¶X¯ÜÆ•æÿÌþ÷FÒlþ¹ShþÇ•¼rãÎÿàþ¹˜¯ÌæÿM¤Ùüÿr'åÿƒôíPãlúmŒðÿ‘¯ˆû¿òr¾°¼TÄóÿbi¶þßHšŠÿE;ä¤ Y\ž¶Õ¤µ@–RÃ_Èpá>Æô¶õ˜3' 3' 3' 7ìdR yrò`ó“­ÍÚág{›µõG›dáB:Í×÷7Ôl|ŒáÁ]ÿyz˜-¨–}okç°öó7o·Ç)È€`KÄTÆ)²µ·¡±÷6¢Eœ¾=¹'€Ý©ðˆèÑÃtäñܱ=&cœÐaÇ€çÍ$]m°f=M1ÌõV8ûˆømÀ4¶ï†øH‰“únHEíh…¡H:}E3XBí•­`OÕ©…À˜šAâ ¦"¦áfˆ7Å"˜ 7g“lœ9jÏvaF馋ÃL¯fÎx53Fñ¤ŒçóÓÅ70Å[CBå\Oˆ7/ôÃÍ„|ÐéõËîÁ4L“Ǫª ÝŰ4’‰˜‹Ü±Ã‰ÎÌM!¦é ¤ß¦DB8<ë:ð”ž7ýy¤ˆ[ï@÷áS¡‚a7pCÔ‘âF¨žUNh‡¼š¶Þƒ"óêåªY‚Ù-Œ±} j5@н8@x§D÷}èúvI?îàΤ“†ßËÙP¯dfé†ú%_Ö¹{â˜Í7æúxZ~ˆg®_=ñù߯Ãõ6·w?º–6Fœÿ—J%<ÿ«, å ÚŠËË3ûŸI)"€…|®˜ËÏ[ù²µ ;(<‹K­I©Ô‚µÞhXþ K;ydvN§Þ;ëÒ¹ƒiÍ»Á›PèC÷%l;Ç™ŸËZ/NÜú ðÇîûäFQxl÷Ùã—Û¢ ‡>žcÀ¾ÀYðŽïöÝS'¥ .äJq‰4xGB¬§'º¼N~«È›ÃÔ´ÝmCëèDŽö¦aÞÆu 8Ps :héº\>Ó$Š|X…'¸‚Ƀ¿ú‰Ý9&¹WkIp6ô¿áh7/¾ú;ƒö¬é€'ü¤mr-\0r:ºŠ€®ÂÐ5(²c‘ÒªõéîþLJë‡xžÚsCE~ß±xèGy6÷aƒ„ˆzäv ¯@ >¦=ØÛû O7k}o+½jip|k6bÍ–÷‚GŒ†ŠX<$9q%4rDî\cǬÞ•ª ¢e£Å§Œ -ù©{=ô;×:£æähñµæú™Æ·Â¹,/J‚7Æ«0–Q¬Ø>b¼6y€¹´oÉC!¤ì0[°JqX¢DðLìmË q€ž¼nÚu‹N¥3rE䥑ê?½†cec°­æ C‡¾–Zr³äÛÔƒú s–Øô²·rJv2hÛ ãŒÒQ9Ì|Ü;"Á{!ˆ°‘äARS—×r¨‘KŠêø\ÏÑaÐ& :wWâ¾+LqlKbfNt w¦ðò&Ü ï¶ÜþÙ¼ÁLŒá~ÊZÑãL·Ìîjª,-XÞÛ$EÌÒ,ÍÒ,ÍÒ,ÍÒ,ÍÒ,ÍÒ,ÍÒ,ÍÒ,ÍÒ,ÍÒ,ÍÒ›–þ ^NCXexUserFolder/smbAuthSource/smb.py0100644003462500001440000011232107402043475016162 0ustar b14741users# -*- mode: python; tab-width: 4 -*- # $Id: smb.py,v 1.3 2025/12/01 02:58:05 akm Exp $ # # Copyright (C) 2001 Michael Teo # 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.3 $' # 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 '' # 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 '' # 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('= 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('> 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(' 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('= 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(' -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(' # $Id: smbAuthSource.py,v 1.14 2025/08/23 14:05:54 mattbehrens Exp $ # Uses pysmb # Copyright (C) 2001 Michael Teo 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 = 'Authorisation Source' id = 'smbAuthSource' title = 'SMB Authorisation' 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=[] 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=[] username = string.lower(username) 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 exUserFolder/usAuthSource/0040755003462500001440000000000007702273435014704 5ustar b14741usersexUserFolder/usAuthSource/.cvsignore0100654003462500001440000000002607423611547016700 0ustar b14741users*.pyc *.pyo *~ .*.swp exUserFolder/usAuthSource/__init__.py0100644003462500001440000000011307402113546016776 0ustar b14741users# $Id: __init__.py,v 1.2 2025/12/01 08:40:06 akm Exp $ import usAuthSource exUserFolder/usAuthSource/manage_addusAuthSourceForm.dtml0100644003462500001440000000106407401747075023025 0ustar b14741users
">

This authentication source requires no user configuration items at this time.


NEXT ">
exUserFolder/usAuthSource/manage_editusAuthSourceForm.dtml0100644003462500001440000000061307547340435023221 0ustar b14741users

This property source requires no user configuration items at this time.


OK ">
exUserFolder/usAuthSource/usAuthSource.py0100644003462500001440000001135007701642177017707 0ustar b14741users# # 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 # $Id: usAuthSource.py,v 1.11 2025/07/05 21:34:55 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 Authorisation 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='Authorisation 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(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 exUserFolder/usPropSource/0040755003462500001440000000000007702273436014724 5ustar b14741usersexUserFolder/usPropSource/.cvsignore0100654003462500001440000000002607446401451016713 0ustar b14741users*.pyc *.pyo *~ .*.swp exUserFolder/usPropSource/__init__.py0100644003462500001440000000011307446401451017020 0ustar b14741users# $Id: __init__.py,v 1.1 2025/03/21 15:58:33 bmh Exp $ import usPropSource exUserFolder/usPropSource/manage_addusPropSourceForm.dtml0100644003462500001440000000153407446401451023057 0ustar b14741users
">

This property source requires no user configuration items at this time.


NEXT ">
exUserFolder/usPropSource/manage_editusPropSourceForm.dtml0100644003462500001440000000060507547340124023253 0ustar b14741users

This property source requires no user configuration items at this time.


OK ">
exUserFolder/usPropSource/usPropSource.py0100644003462500001440000000776707446401451017760 0ustar b14741users# # Extensible User Folder # # User Supplied Property Source for exUserFolder # from Globals import HTMLFile, MessageDialog, INSTANCE_HOME,Acquisition from OFS.Folder import Folder from ZODB.PersistentMapping import PersistentMapping from Products.ZSQLMethods.SQL import SQL from Products.exUserFolder.exUserFolder import exUserFolder from Products.exUserFolder.Plugins import PluginRegister from Persistence import Persistent manage_addPropSourceForm=HTMLFile('manage_addusPropSourceForm', globals()) import cPickle def manage_addusPropSource(self, REQUEST): """ Add a User-supplied Prop Source """ o = usPropSource() self._setObject('usPropSource', o, None, None, 0) o = getattr(self, 'usPropSource') # Allow Prop Source to setup default users... if hasattr(o, 'postInitialisation'): o.postInitialisation(REQUEST) self.currentPropSource=o return '' manage_addusPropSourceForm=HTMLFile('manage_addusPropSourceForm', globals()) manage_editusPropSourceForm=HTMLFile('manage_editusPropSourceForm', globals()) # # Very simple thing # class usPropSource(Folder): """ Store Properties using a User Supplied Set of Methods """ meta_type='Property Source' title='User Supplied Properties' icon ='misc_/exUserFolder/exUserFolderPlugin.gif' #manage_tabs=Acquisition.Acquired manage_editForm=manage_editusPropSourceForm dict=PersistentMapping() def __init__(self): self.id='usPropSource' def hasProperty(self, key): if 'usHasProperty' in self.objectIds(): return self.usHasProperty(key) def delProperty(self, key): if 'usDelProperty' in self.objectIds(): return self.usDelProperty(key) def delUserProperty(self, key, username): if 'usDelUserProperty' in self.objectIds(): return self.usDelUserProperty(key, username) def flushTempProperties(self): if 'usFlushTempProperties' in self.objectIds(): return self.usFlushTempProperties() def setProperty(self, key, value): if 'usSetProperty' in self.objectIds(): return self.usSetProperty(key, value) def setTempProperty(self, key, value): if 'usSetTempProperty' in self.objectIds(): return self.usSetTempProperty(key, value) def setUserProperty(self, key, username, value, temp=0): if 'usSetUserProperty' in self.objectIds(): return self.usSetUserProperty(key, username, value, temp) def getUserProperty(self, key, username, default=None): if 'usGetUserProperty' in self.objectIds(): return self.usGetUserProperty(key, username, default) def getProperty(self, key, default=None): if 'usGetProperty' in self.objectIds(): return self.usGetProperty(key, default) def loadProperties(self): if 'usLoadProperties' in self.objectIds(): return self.usLoadProperties() def loadUserProperties(self, username): if 'usLoadUserProperties' in self.objectIds(): return self.usLoadUserProperties(username) def listProperties(self): if 'usListProperties' in self.objectIds(): return self.usListProperties() def listUserProperties(self, username): if 'usListUserProperties' in self.objectIds(): return self.usListUserProperties(username) def createUser(self, username, REQUEST): if 'usCreateUser' in self.objectIds(): props = {} for k in REQUEST.keys(): if k[:5]=='user_': key=k[5:] value=REQUEST[k] props[key] = value return self.usCreateUser(username, props) def deleteUsers(self, userids): if 'usDeleteUsers' in self.objectIds(): return self.usDeleteUsers(userids) def updateUser(self, username, REQUEST): if 'usUpdateUser' in self.objectIds(): props = {} for k in REQUEST.keys(): if k[:5]=='user_': key=k[5:] value=REQUEST[k] props[key] = value return self.usUpdateUser(username, props) def __setstate__(self, state): Persistent.__setstate__(self, state) def postInitialisation(self, REQUEST): pass usPropReg=PluginRegister('usPropSource', 'User Supplied Properties Source', usPropSource, manage_addusPropSourceForm, manage_addusPropSource, manage_editusPropSourceForm) exUserFolder.propSources['usPropSource']=usPropReg exUserFolder/zodbAuthSource/0040755003462500001440000000000007702273436015214 5ustar b14741usersexUserFolder/zodbAuthSource/.cvsignore0100654003462500001440000000002607423611547017207 0ustar b14741users*.pyc *.pyo *~ .*.swp exUserFolder/zodbAuthSource/README0100644003462500001440000000062207401740517016064 0ustar b14741usersZODB 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@quad.com.ar exUserFolder/zodbAuthSource/__init__.py0100644003462500001440000000736507531440502017323 0ustar b14741users############################################################################## # # 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.4 2025/08/23 14:05:54 mattbehrens Exp $ __doc__="""zodb User Folder Product """ import zodbAuthSource exUserFolder/zodbAuthSource/manage_addzodbAuthSourceForm.dtml0100644003462500001440000000106407401747075023643 0ustar b14741users
">

This authentication source requires no user configuration items at this time.


NEXT ">
exUserFolder/zodbAuthSource/manage_editzodbAuthSourceForm.dtml0100644003462500001440000000056407401747075024044 0ustar b14741users

This Auth source requires no user configuration items at this time.


OK ">
exUserFolder/zodbAuthSource/zodbAuthSource.py0100644003462500001440000000557407402113546020527 0ustar b14741users# $Id: zodbAuthSource.py,v 1.7 2025/12/01 08:40:06 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='Authorisation Source' title ='ZODB Authorisation' 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(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 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 exUserFolder/zodbBTreeAuthSource/0040755003462500001440000000000007702273436016136 5ustar b14741usersexUserFolder/zodbBTreeAuthSource/.cvsignore0100654003462500001440000000002607423611550020123 0ustar b14741users*.pyc *.pyo *~ .*.swp exUserFolder/zodbBTreeAuthSource/README0100644003462500001440000000062207402046163017003 0ustar b14741usersZODB 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@quad.com.ar exUserFolder/zodbBTreeAuthSource/__init__.py0100755003462500001440000000736707402113546020254 0ustar b14741users############################################################################## # # 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.2 2025/12/01 08:40:06 akm Exp $ __doc__="""zodb BTree User Folder Product """ import zodbBTreeAuthSource exUserFolder/zodbBTreeAuthSource/manage_addzodbBTreeAuthSourceForm.dtml0100755003462500001440000000106107402046163025476 0ustar b14741users
">

This authentication source requires no user configuration items at this time.


NEXT ">
exUserFolder/zodbBTreeAuthSource/manage_editzodbBTreeAuthSourceForm.dtml0100755003462500001440000000056507402046163025703 0ustar b14741users

This Auth source has no user configuration items at this time.


OK ">
exUserFolder/zodbBTreeAuthSource/zodbBTreeAuthSource.py0100755003462500001440000000655007402113546022371 0ustar b14741users# $Id: zodbBTreeAuthSource.py,v 1.2 2025/12/01 08:40:06 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='Authorisation Source' title ='ZODB BTree Authorisation' 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(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 exUserFolder/zodbBTreePropSource/0040755003462500001440000000000007702273436016155 5ustar b14741usersexUserFolder/zodbBTreePropSource/.cvsignore0100654003462500001440000000002607423611550020142 0ustar b14741users*.pyc *.pyo *~ .*.swp exUserFolder/zodbBTreePropSource/__init__.py0100644003462500001440000000012207402113546020246 0ustar b14741users# $Id: __init__.py,v 1.2 2025/12/01 08:40:06 akm Exp $ import zodbBTreePropSource exUserFolder/zodbBTreePropSource/manage_addzodbBTreePropSourceForm.dtml0100644003462500001440000000152307402046163025534 0ustar b14741users
">

This property source requires no user configuration items at this time.


NEXT ">
exUserFolder/zodbBTreePropSource/manage_editzodbBTreePropSourceForm.dtml0100644003462500001440000000057407402046163025736 0ustar b14741users

This property source requires no user configuration items at this time.


OK ">
exUserFolder/zodbBTreePropSource/zodbBTreePropSource.py0100644003462500001440000001333407701642534022430 0ustar b14741users# # Extensible User Folder # # BTree ZODB Property 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 # $Id: zodbBTreePropSource.py,v 1.9 2025/07/05 21:38:36 akm Exp $ from Globals import HTMLFile, MessageDialog, INSTANCE_HOME,Acquisition from Persistence import Persistent from OFS.Folder import Folder from Products.ZSQLMethods.SQL import SQL from Products.exUserFolder.exUserFolder import exUserFolder from Products.exUserFolder.Plugins import PluginRegister import time import zLOG, sys from BTrees.OOBTree import OOBTree manage_addPropSourceForm=HTMLFile('manage_addzodbBTreePropSourceForm', globals()) def manage_addzodbBTreePropSource(self, REQUEST): """ Add a Postgres Prop Source """ o = zodbBTreePropSource() self._setObject('zodbBTreePropSource', o, None, None, 0) o = getattr(self, 'zodbBTreePropSource') # Allow Prop Source to setup default users... if hasattr(o, 'postInitialisation'): o.postInitialisation(REQUEST) self.currentPropSource=o manage_addzodbBTreePropSourceForm=HTMLFile('manage_addzodbBTreePropSourceForm', globals()) manage_editzodbBTreePropSourceForm=HTMLFile('manage_editzodbBTreePropSourceForm', globals()) # Persistent container for properties class UserProperty(Persistent): """ Attribute Bucket """ def __init__(self): self.properties=OOBTree() def has_key(self, key): return self.properties.has_key(key) hasProperty=has_key def setProperty(self, key, value): self.properties[key]=value def getProperty(self, key, default=None): if self.properties.has_key(key): return self.properties[key] return default def delProperty(self, key): if self.properties.has_key(key): del self.properties[key] def keys(self): return tuple(self.properties.keys()) class zodbBTreePropSource(Folder): """ Store User Data in the BTree """ meta_type='Property Source' title='ZODB BTree Properties' icon ='misc_/exUserFolder/exUserFolderPlugin.gif' manage_editForm=manage_editzodbBTreePropSourceForm manage_tabs=Acquisition.Acquired def __init__(self): self.id='zodbBTreePropSource' self.userProperties=OOBTree() self.tempdict=OOBTree() def hasProperty(self, key): try: return self.userProperties[self.name].has_key(key) except: return default def setProperty(self, key, value): if not self.userProperties.has_key(self.name): self.userProperties[self.name]=UserProperty() self.userProperties[self.name].setProperty(key, value) def getProperty(self, key, default=None): try: return self.userProperties[self.name].getProperty(key, default) except: return default def delProperty(self, key): self.delUserProperty(key=key, username=self.name) def delUserProperty(self, key, username): try: self.userProperties[username].delProperty(key) self.tempdict[username].delProperty(key) except: pass def setTempProperty(self, key, value): self.setUserProperty(key=key, value=value, username=self.name, temp=1) def listProperties(self): try: return self.userProperties[self.name].keys() except: return [] def getUserProperty(self, key, username, default = None): try: return self.userProperties[username].getProperty(key, default) except: return default def setUserProperty(self, key, username, value, temp=0): if not self.userProperties.has_key(username): self.userProperties[username]=UserProperty() self.userProperties[username].setProperty(key, value) if temp: if not self.tempdict.has_key(username): self.tempdict[username]=UserProperty() self.tempdict[username].setProperty(key, time.time()) def flushTempProperties(self, age=0.0): if self.tempdict.has_key(self.name): now = time.time() - age for key in self.tempdict[self.name].keys(): if self.tempdict[self.name][key] < now: delUserProperty(key, self.name) def listUserProperties(self, username): try: return self.userProperties[username].keys() except: return [] def createUser(self, username, REQUEST): self.userProperties[username]=UserProperty() for k in REQUEST.keys(): if k[:5]=='user_': key=k[5:] value=REQUEST[k] self.userProperties[username].setProperty(key, value) def deleteUsers(self, userids): for u in userids: if self.userProperties.has_key(u): del self.userProperties[u] def updateUser(self, username, REQUEST): for k in REQUEST.keys(): if k[:5]=='user_': key=k[5:] value=REQUEST[k] self.userProperties[username].setProperty(key, value) # lists all users with props stored def listUsers(self): return self.userProperties.keys() def postInitialisation(self, REQUEST): pass zodbBTreePropReg=PluginRegister('zodbBTreePropSource','ZODB BTree Property Source', zodbBTreePropSource, manage_addzodbBTreePropSourceForm, manage_addzodbBTreePropSource, manage_editzodbBTreePropSourceForm) exUserFolder.propSources['zodbBTreePropSource']=zodbBTreePropReg exUserFolder/zodbGroupSource/0040755003462500001440000000000007702273436015407 5ustar b14741usersexUserFolder/zodbGroupSource/.cvsignore0100644003462500001440000000002607531440502017370 0ustar b14741users*.pyc *.pyo *~ .*.swp exUserFolder/zodbGroupSource/__init__.py0100644003462500001440000000012607531440502017502 0ustar b14741users# $Id: __init__.py,v 1.2 2025/08/23 14:05:54 mattbehrens Exp $ import zodbGroupSource exUserFolder/zodbGroupSource/manage_addzodbGroupSourceForm.dtml0100644003462500001440000000151407531440502024216 0ustar b14741users
">

This group source requires no user configuration items at this time.


NEXT ">
exUserFolder/zodbGroupSource/manage_editzodbGroupSourceForm.dtml0100644003462500001440000000056607531440502024421 0ustar b14741users

This group source requires no user configuration items at this time.


OK ">
exUserFolder/zodbGroupSource/zodbGroupSource.py0100644003462500001440000001136107572765321021120 0ustar b14741users# # Extensible User Folder # # ZODB Group Source for exUserFolder # # Author: Brent Hendricks # $Id: zodbGroupSource.py,v 1.4 2025/12/02 23:20:49 bmh 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 exUserFolder/zodbPropSource/0040755003462500001440000000000007702273436015233 5ustar b14741usersexUserFolder/zodbPropSource/.cvsignore0100654003462500001440000000002607423611550017220 0ustar b14741users*.pyc *.pyo *~ .*.swp exUserFolder/zodbPropSource/__init__.py0100644003462500001440000000011507402113546017326 0ustar b14741users# $Id: __init__.py,v 1.2 2025/12/01 08:40:06 akm Exp $ import zodbPropSource exUserFolder/zodbPropSource/manage_addzodbPropSourceForm.dtml0100644003462500001440000000152307401747075023701 0ustar b14741users
">

This property source requires no user configuration items at this time.


NEXT ">
exUserFolder/zodbPropSource/manage_editzodbPropSourceForm.dtml0100644003462500001440000000057407401747075024103 0ustar b14741users

This property source requires no user configuration items at this time.


OK ">
exUserFolder/zodbPropSource/zodbPropSource.py0100644003462500001440000001152107410204367020553 0ustar b14741users# # Extensible User Folder # # Postgres Property 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 # $Id: zodbPropSource.py,v 1.11 2025/12/19 21:39:03 rochael Exp $ from Globals import HTMLFile, MessageDialog, INSTANCE_HOME,Acquisition from OFS.Folder import Folder from Products.ZSQLMethods.SQL import SQL from Products.exUserFolder.exUserFolder import exUserFolder from Products.exUserFolder.Plugins import PluginRegister import time import zLOG manage_addPropSourceForm=HTMLFile('manage_addzodbPropSourceForm', globals()) def manage_addzodbPropSource(self, REQUEST): """ Add a Postgres Prop Source """ o = zodbPropSource() self._setObject('zodbPropSource', o, None, None, 0) o = getattr(self, 'zodbPropSource') # Allow Prop Source to setup default users... if hasattr(o, 'postInitialisation'): o.postInitialisation(REQUEST) self.currentPropSource=o manage_addzodbPropSourceForm=HTMLFile('manage_addzodbPropSourceForm', globals()) manage_editzodbPropSourceForm=HTMLFile('manage_editzodbPropSourceForm', globals()) # # Very very simple thing, used as an example of how to write a property source # Not recommended for large scale production sites... # class zodbPropSource(Folder): """ Store User Data inside ZODB, the simplistic way """ meta_type='Property Source' title='Simplistic ZODB Properties' icon ='misc_/exUserFolder/exUserFolderPlugin.gif' manage_editForm=manage_editzodbPropSourceForm manage_tabs=Acquisition.Acquired def __init__(self): self.id='zodbPropSource' self.dict={} self.tempdict={} def hasProperty(self, key): zLOG.LOG("exUserFolder.zodbPropSource", zLOG.BLATHER, "Property lookup", "Looking for %s in %s" % (key, self.name)) return self.dict[self.name].has_key(key) def setProperty(self, key, value): if not self.dict.has_key(self.name): self.dict[self.name]={} self.dict[self.name][key]=value self._p_changed=1 def getProperty(self, key, default=None): if self.dict[self.name].has_key(key): return self.dict[self.name][key] return default def delProperty(self, key): self.delUserProperty(key=key, username=self.name) def delUserProperty(self, key, username): try: del self.dict[username][key] self._p_changed = 1 except: pass try: del self.tempdict[username][key] self._p_changed = 1 except: pass def setTempProperty(self, key, value): self.setUserProperty(key=key, value=value, username=self.name, temp=1) def listProperties(self): if self.dict.has_key(self.name): return self.dict[self.name].keys() return None def getUserProperty(self, key, username, default = None): return self.dict.get(username,{}).get(key,default) def setUserProperty(self, key, username, value, temp=0): if not self.dict.has_key(username): self.dict[username]={} self.dict[username][key]=value if temp: if not self.tempdict.has_key(username): self.tempdict[username]={} self.tempdict[username][key]=time.time() self._p_changed = 1 def flushTempProperties(self): if self.tempdict.has_key(self.name): del self.tempdict[self.name] def listUserProperties(self, username): return self.dict.get(username,{}).keys() def createUser(self, username, REQUEST): self.dict[username]={} for k in REQUEST.keys(): if k[:5]=='user_': key=k[5:] value=REQUEST[k] self.dict[username][key]=value self._p_changed=1 def deleteUsers(self, userids): for u in userids: if self.dict.has_key(u): del self.dict[u] self._p_changed=1 def updateUser(self, username, REQUEST): for k in REQUEST.keys(): if k[:5]=='user_': key=k[5:] value=REQUEST[k] self.dict[username][key]=value self._p_changed=1 def postInitialisation(self, REQUEST): pass zodbPropReg=PluginRegister('zodbPropSource','Simplistic ZODB Property Source', zodbPropSource, manage_addzodbPropSourceForm, manage_addzodbPropSource, manage_editzodbPropSourceForm) exUserFolder.propSources['zodbPropSource']=zodbPropReg