summaryrefslogtreecommitdiffstats
path: root/LDAPRoleExtender.py
diff options
context:
space:
mode:
Diffstat (limited to 'LDAPRoleExtender.py')
-rw-r--r--LDAPRoleExtender.py545
1 files changed, 545 insertions, 0 deletions
diff --git a/LDAPRoleExtender.py b/LDAPRoleExtender.py
new file mode 100644
index 0000000..d3da78d
--- /dev/null
+++ b/LDAPRoleExtender.py
@@ -0,0 +1,545 @@
+#####################################################################
+#
+# LDAPRoleExtender Fake user folder to change roles
+#
+# This software is governed by a license. See
+# LICENSE.txt for the terms of this license.
+#
+#####################################################################
+__version__='$Revision$'[11:-2]
+
+import os, time, copy, ldap, urllib
+from types import StringType
+from Globals import package_home, InitializeClass, MessageDialog, DTMLFile
+from Acquisition import aq_base
+from AccessControl import ClassSecurityInfo
+from AccessControl.ZopeSecurityPolicy import _noroles
+from AccessControl.Permissions import view_management_screens, manage_users
+from AccessControl.SpecialUsers import emergency_user
+from OFS.SimpleItem import SimpleItem
+from Products.LDAPUserFolder.LDAPUser import LDAPUser
+from Products.LDAPUserFolder.utils import GROUP_MEMBER_MAP
+
+
+_marker = []
+_dtmldir = os.path.join( package_home( globals() ), 'dtml' )
+addLDAPRoleExtenderForm = DTMLFile( 'addLDAPRoleExtender', _dtmldir )
+CHANGE_LUF_PERMISSION = 'Change user folder'
+
+
+class LDAPRoleExtender( SimpleItem ):
+ """
+ LDAPRoleExtender
+
+ The LDAPRoleExtender is a fake user database. It retrieves
+ user objects through a real LDAPUserFolder and changes the
+ roles on the user object according to a group base DN
+ specified by the administrator.
+ """
+ security = ClassSecurityInfo()
+
+ meta_type = 'LDAPRoleExtender'
+ id = 'acl_users'
+ isAUserFolder = 1
+
+
+ #################################################################
+ #
+ # ZMI management screens
+ #
+ #################################################################
+
+ manage_options=(
+ (
+ {'label' : 'Configure', 'action' : 'manage_main',
+ 'help' : ('LDAPRoleExtender','Configure.stx')},
+ {'label' : 'Caches', 'action' : 'manage_cache',
+ 'help' : ('LDAPRoleExtender', 'Caches.stx')},
+ {'label' : 'Log', 'action' : 'manage_log',
+ 'help' : ('LDAPRoleExtender', 'Log.stx')},
+ )
+ + SimpleItem.manage_options
+ )
+
+ security.declareProtected( view_management_screens, 'manage' )
+ security.declareProtected( view_management_screens, 'manage_main' )
+ manage = manage_main = DTMLFile( 'dtml/properties', globals() )
+ manage_main._setName( 'manage_main' )
+
+ security.declareProtected( view_management_screens, 'manage_log' )
+ manage_log = DTMLFile( 'dtml/log', globals() )
+
+ security.declareProtected( view_management_screens, 'manage_cache' )
+ manage_cache = DTMLFile( 'dtml/cache', globals() )
+
+
+ #################################################################
+ #
+ # Initialization code
+ #
+ #################################################################
+
+
+ def __setstate__( self, v ):
+ """
+ __setstate__ is called whenever the instance is loaded
+ from the ZODB, like when Zope is restarted.
+ """
+ LDAPRoleExtender.inheritedAttribute( '__setstate__' )( self, v )
+
+ self._clearCaches()
+ if not hasattr( self, 'verbose' ):
+ self.verbose = 2
+
+ if self.verbose > 2:
+ self._log( 3,'Re-initialized through __setstate__' )
+
+
+ def __init__( self, luf, groups_base, groups_scope, title='' ):
+ """ Create a new LDAPRoleExtender instance """
+ self.title = title
+ self.groups_base = groups_base
+ self.groups_scope = groups_scope
+ self.verbose = 2 # _log needs it
+ self._v_users = {}
+ self._v_cache = {}
+ self._v_log = []
+
+ if not luf.startswith( '/' ):
+ luf = '/%s' % luf
+
+ self._luf = luf
+
+
+ security.declarePrivate( '_clearCaches' )
+ def _clearCaches( self ):
+ """ Clear all logs and caches for user-related information """
+ self._v_cache = {} # Secondary cache for non-logged in users
+ self._v_users = {} # this is the cache of logged-in users
+ self._v_log = []
+
+
+ #################################################################
+ #
+ # LDAPRoleExtender administration
+ #
+ #################################################################
+
+
+ security.declarePrivate( '_log' )
+ def _log( self, level, logline ):
+ """ Logs a single line and keeps log length at max. 500 lines.
+
+ Level: 1 - Catastrophes
+ 2 - Major Events
+ 3 - Minor events
+ 4 - Login failures
+ 5 - Login Successes
+ 7 - Login success from cache
+ 9 - Debugging
+ """
+ time_str = time.strftime( '%b %d %H:%M:%S'
+ , time.localtime( time.time() )
+ )
+ logged = '(%d) %s: %s' % ( level, time_str, logline )
+
+ self._v_log.append( logged )
+
+ if len( self._v_log ) > 500:
+ self._v_log.pop( 0 )
+
+
+
+ security.declareProtected( view_management_screens, 'getLog' )
+ def getLog( self ):
+ """ return the log contents """
+ return self._v_log
+
+
+ security.declareProtected( manage_users, 'manage_reinit' )
+ def manage_reinit( self, REQUEST=None ):
+ """ re-initialize and clear out users and log """
+ self._clearCaches()
+ self.verbose > 1 and self._log( 2, 'Reinitialized' )
+
+ if REQUEST:
+ msg = 'User caches cleared'
+ return self.manage_cache( self, REQUEST, manage_tabs_message=msg )
+
+
+ security.declareProtected( CHANGE_LUF_PERMISSION, 'manage_edit' )
+ def manage_edit( self
+ , luf
+ , groups_base
+ , groups_scope
+ , verbose=2
+ , title=''
+ , REQUEST=None
+ ):
+ """ Edit the LDAPRoleExtender Object """
+ self.title = title
+ self.verbose = verbose
+ self.groups_base = groups_base
+ self.groups_scope = groups_scope
+
+ if not luf.startswith( '/' ):
+ luf = '/%s' % luf
+
+ self._luf = luf
+
+ if REQUEST:
+ msg = 'Properties changed'
+ return self.manage_main( self, REQUEST, manage_tabs_message=msg )
+
+
+ security.declarePrivate( '_cacheUser' )
+ def _cacheUser( self, name, extended_user, authenticated=0 ):
+ """ Stick something into my internal cache """
+ name = name.lower()
+
+ if ( extended_user is None or
+ name == str( emergency_user.getUserName() ).lower() ):
+ return
+
+ if authenticated:
+ self._v_users[name] = extended_user
+ else:
+ self._v_cache[name] = extended_user
+
+
+ security.declarePrivate( 'twiddleUser' )
+ def _extendRoles( self, raw_user ):
+ """ extend the user roles """
+ if raw_user is None:
+ return raw_user
+
+ raw_roles = list(raw_user.getRoles())
+ user_dn = raw_user.getUserDN()
+ luf = self.getLUF()
+ group_filter = '(|(uniquemember=%s)(member=%s))' % ( user_dn, user_dn )
+
+ add_roles = luf._searchResults( self.groups_base
+ , self.groups_scope
+ , group_filter
+ , attrs = ['dn', 'cn']
+ )
+
+ if isinstance(add_roles, StringType) or len(add_roles) == 0:
+ return raw_user
+
+ add_role_list = []
+
+ for i in range(len(add_roles)):
+ dn = add_roles[i][0]
+ try:
+ cn = add_roles[i][1]['cn'][0]
+ except KeyError: # NDS oddity
+ cn = ldap.explode_dn(dn, 1)[0]
+
+ add_role_list.append(cn)
+
+
+ final_roles = {}
+ raw_roles.extend(add_role_list)
+
+ for role in raw_roles:
+ final_roles[role] = 1
+
+ extended_user = LDAPUser( raw_user.getUserName()
+ , raw_user._getPassword()
+ , final_roles.keys()
+ , raw_user.getDomains()
+ , raw_user.getUserDN()
+ , {}
+ , []
+ )
+ extended_user._properties = raw_user._properties
+
+ return extended_user
+
+
+ security.declareProtected( manage_users, 'getLUF' )
+ def getLUF( self ):
+ """ Return my LDAP User Folder """
+ return self.unrestrictedTraverse(self._luf)
+
+
+ #################################################################
+ #
+ # User Folder interface stuff
+ #
+ #################################################################
+
+ security.declareProtected( manage_users, 'getUsers' )
+ def getUsers( self ):
+ """ Return a list of *cached* user objects """
+ names = self._v_users.keys()
+ names.sort()
+ users = []
+
+ for n in names:
+ user = self._v_users.get( n )
+ if user.notexpired(): # Show only the unexpired
+ users.append( user )
+
+ return users
+
+
+ security.declareProtected( manage_users, 'getUserNames' )
+ def getUserNames( self ):
+ """ Return a list of usernames """
+ luf = self.getLUF()
+
+ return luf.getUserNames()
+
+ user_names = getUserNames
+
+
+ security.declareProtected( manage_users, 'getUser' )
+ def getUser( self, name, pwd=None ):
+ """Return the named user object or None"""
+ name = name.lower()
+
+ if ( pwd is not None and
+ self._v_users.has_key( name ) and
+ self._v_users[name].notexpired() ):
+ self.verbose > 8 and self._log( 9
+ , '"%s" found in auth-cache' % name
+ )
+
+ return self._v_users.get( name )
+
+ if ( pwd is None and
+ self._v_cache.has_key( name ) and
+ self._v_cache[name].notexpired() ):
+ self.verbose > 8 and self._log( 9
+ , '"%s" found in anon-cache' % name
+ )
+
+ return self._v_cache.get( name )
+
+ luf = self.getLUF()
+
+ raw_user = luf.getUser( name, pwd )
+ extended_user = self._extendRoles( raw_user )
+ self._cacheUser( name, extended_user, pwd )
+
+ return extended_user
+
+
+ security.declareProtected( manage_users, 'getUserById' )
+ def getUserById( self, id, default=_marker ):
+ """ Return a user object by ID (in this case by username) """
+ try:
+ return self.getUser( id )
+
+ except:
+ if default is _marker:
+ raise
+
+ return default
+
+
+ security.declareProtected( manage_users, 'getUserByDN' )
+ def getUserByDN( self, user_dn ):
+ """ Make a user object from a DN """
+ luf = self.getLUF()
+ raw_user = luf.getUserByDN( user_dn )
+ extended_user = self._extendRoles( raw_user )
+ self._cacheUser( extended_user.getUserName(), extended_user )
+
+ return extended_user
+
+
+ # This must stay accessible to everyone
+ def validate( self, request, auth='', roles=_noroles ):
+ """ The main engine """
+ luf = self.getLUF()
+ raw_user = luf.validate( request, auth, roles )
+
+ if raw_user is not None:
+ extended_user = self.getUser( raw_user.getUserName()
+ , raw_user._getPassword()
+ )
+ extended_user = extended_user.__of__( luf )
+ else:
+ extended_user = raw_user
+
+ return extended_user
+
+
+ def manage_beforeDelete( self, item, container ):
+ """ Clean up after myself """
+ if ( hasattr( container, '__allow_groups__' ) and
+ container.__allow_groups__ == item ):
+ del container.__allow_groups__
+
+ security.declareProtected( manage_users, 'getGroups' )
+ def getGroups(self, dn='*', attr=None):
+ """ return group records i know about """
+ group_list = []
+ no_show = ( 'Anonymous', 'Authenticated', 'Shared' )
+ group_filter = '(|(uniquemember=%s)(member=%s))' % ( dn, dn )
+ luf = self.getLUF()
+
+ res = luf._searchResults( self.groups_base
+ , self.groups_scope
+ , group_filter
+ , attrs=['dn', 'cn']
+ )
+
+ if isinstance(res, StringType):
+ return group_list
+
+ if len(res) > 0:
+ for i in range( len( res ) ):
+ dn = res[i][0]
+ try:
+ cn = res[i][1]['cn'][0]
+ except KeyError: # NDS oddity
+ cn = ldap.explode_dn( dn, 1 )[0]
+
+ if attr is None:
+ group_list.append( ( cn, dn ) )
+ elif attr == 'cn':
+ group_list.append( cn )
+ elif attr == 'dn':
+ group_list.append( dn )
+
+ return group_list
+
+ security.declareProtected( manage_users, 'getGroupDetails')
+ def getGroupDetails( self, encoded_cn ):
+ """ Return all group details """
+ result = []
+ cn = urllib.unquote( encoded_cn )
+ luf = self.getLUF()
+
+ res = luf._searchResults( self.groups_base
+ , self.groups_scope
+ , 'cn=%s' % cn
+ , ['uniqueMember', 'member']
+ )
+
+ if isinstance(res, StringType):
+ result = ( ( 'Exception', res ), )
+ elif len( res ) > 0:
+ result = res[0][1].items()
+ result.sort()
+
+ return tuple(result)
+
+
+ security.declareProtected( manage_users, 'getGroupedUsers')
+ def getGroupedUsers(self, groups=None):
+ """ Retrieve all users that in the groups i know about """
+ all_dns = {}
+ users = []
+
+ if groups is None:
+ groups = self.getGroups()
+
+ for group_id, group_dn in groups:
+ group_details = self.getGroupDetails( group_id )
+
+ for attribute_name, dn_list in group_details:
+ for dn in dn_list:
+ all_dns[dn] = 1
+
+ for dn in all_dns.keys():
+ user = self.getUserByDN( dn )
+
+ if user is not None:
+ users.append( user.__of__( self ) )
+
+ return tuple( users )
+
+
+ security.declareProtected( manage_users, 'manage_editUserRoles' )
+ def manage_editUserRoles( self, user_dn, role_dns=[], REQUEST=None ):
+ """ Edit the roles (groups) of a user """
+ all_groups = self.getGroups( attr='dn' )
+ cur_groups = self.getGroups( dn=user_dn, attr='dn' )
+ operations = []
+ luf = self.getLUF()
+
+ for group in all_groups:
+ if group in cur_groups and group not in role_dns:
+ operations.append({ 'op' : ldap.MOD_DELETE
+ , 'target' : group
+ , 'type' : luf.getGroupType( group )
+ } )
+ elif group in role_dns and group not in cur_groups:
+ operations.append({ 'op' : ldap.MOD_ADD
+ , 'target' : group
+ , 'type' : luf.getGroupType( group )
+ } )
+
+ if operations:
+ connection = luf._connect()
+
+ for to_do in operations:
+ mod_list = ( ( to_do['op']
+ , GROUP_MEMBER_MAP.get( to_do['type'] )
+ , user_dn
+ ), )
+ try:
+ connection.modify_s( to_do['target'], mod_list )
+ except Exception, e:
+ msg = luf._formatException( self, e )
+
+ msg = 'Roles changed for %s' % ( user_dn )
+ else:
+ msg = 'No roles changed for %s' % ( user_dn )
+
+ user_obj = self.getUserByDN( user_dn )
+ if user_obj is not None:
+ self._expireUser( user_obj )
+
+ if REQUEST:
+ return self.manage_userrecords( self
+ , REQUEST
+ , manage_tabs_message=msg
+ , user_dn=user_dn
+ )
+
+
+ security.declareProtected( manage_users, '_expireUser' )
+ def _expireUser( self, user_obj ):
+ """ Purge user object from caches """
+ user_name = user_obj.getUserName().lower()
+ for cache_dict in ( self._v_users, self._v_cache ):
+ if cache_dict.has_key( user_name ):
+ del cache_dict[user_name]
+
+
+def manage_addLDAPRoleExtender( self
+ , luf
+ , groups_base
+ , groups_scope
+ , title=''
+ , REQUEST=None
+ ):
+ """ Called by Zope to create and install an LDAPRoleExtender """
+
+ if hasattr( aq_base( self ), 'acl_users' ) and REQUEST is not None:
+ msg = 'This object already contains a User Folder'
+
+ return MessageDialog(
+ title = 'Item Exists',
+ message = msg,
+ action = '%s/manage_main' % REQUEST['URL1'])
+
+ n = LDAPRoleExtender( luf, groups_base, groups_scope, title )
+
+ self = self.this()
+ self._setObject( 'acl_users', n )
+ self.__allow_groups__ = self.acl_users
+
+ # return to the parent object's manage_main
+ if REQUEST:
+ url = REQUEST['URL1']
+ REQUEST.RESPONSE.redirect( '%s/manage_main' % url )
+
+
+InitializeClass( LDAPRoleExtender )