[gen] LDAP bugfixes.

This commit is contained in:
Gaetan Delannay 2013-09-09 23:14:50 +02:00
parent e51308b277
commit e344ff51e2
8 changed files with 66 additions and 81 deletions

View file

@ -1,9 +1,10 @@
'''This script allows to check a LDAP connection.''' '''This script allows to check a LDAP connection.'''
import sys, ldap import sys
from appy.shared.ldap_connector import LdapConnector
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
class LdapTester: class LdapTester:
'''Usage: python checkldap.py ldapUri login password base attrs filter '''Usage: python checkldap.py ldapUri login password base attrs filter scope
ldapUri is, for example, "ldap://127.0.0.1:389" ldapUri is, for example, "ldap://127.0.0.1:389"
login is the login user DN, ie: "cn=gdy,o=geezteem" login is the login user DN, ie: "cn=gdy,o=geezteem"
@ -27,34 +28,21 @@ class LdapTester:
self.attrs = self.attrs.split(',') self.attrs = self.attrs.split(',')
self.tentatives = 5 self.tentatives = 5
self.timeout = 5 self.timeout = 5
self.attrList = ['cn'] self.attributes = ['cn']
self.ssl = False self.ssl = False
def test(self): def test(self):
# Connect the the LDAP # Connect the the LDAP
print('Creating server object for server %s...' % self.uri) print('Connecting to... %s' % self.uri)
server = ldap.initialize(self.uri) connector = LdapConnector(self.uri)
print('Done. Login with %s...' % self.login) success, msg = connector.connect(self.login, self.password)
server.simple_bind(self.login, self.password) if not success: return
if self.ssl: # Perform the query.
server.start_tls_s() print ('Querying %s...' % self.base)
try: res = connector.search(self.base, self.scope, self.filter,
for i in range(self.tentatives): self.attributes)
try: print('Got %d results' % len(res))
print('Done. Performing a simple query on %s...'% self.base)
res = server.search_st(self.base,
getattr(ldap, 'SCOPE_%s' % self.scope),
filterstr=self.filter,
attrlist=self.attrs, timeout=5)
print('Got %d entries' % len(res))
break
except ldap.TIMEOUT:
print('Got timeout.')
except ldap.LDAPError, le:
print('%s %s' % (le.__class__.__name__, str(le)))
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
if __name__ == '__main__': if __name__ == '__main__': LdapTester().test()
LdapTester().test()
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------

View file

@ -412,7 +412,7 @@ class LdapConfig:
def getUserAttributes(self): def getUserAttributes(self):
'''Gets the attributes we want to get from the LDAP for characterizing '''Gets the attributes we want to get from the LDAP for characterizing
a user.''' a user.'''
res = [self.loginAttribute] res = []
for name in self.ldapAttributes.iterkeys(): for name in self.ldapAttributes.iterkeys():
if getattr(self, name): if getattr(self, name):
res.append(getattr(self, name)) res.append(getattr(self, name))
@ -429,7 +429,9 @@ class LdapConfig:
ldapName = getattr(self, name) ldapName = getattr(self, name)
if not ldapName: continue if not ldapName: continue
if ldapData.has_key(ldapName) and ldapData[ldapName]: if ldapData.has_key(ldapName) and ldapData[ldapName]:
res[appyName] = ldapData[ldapName] value = ldapData[ldapName]
if isinstance(value, list): value = value[0]
res[appyName] = value
return res return res
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------

View file

@ -2,7 +2,7 @@
Zope product.''' Zope product.'''
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
import os, os.path, time import os, os.path
import appy import appy
import appy.version import appy.version
import appy.gen as gen import appy.gen as gen
@ -16,24 +16,6 @@ from appy.shared.data import languages
homePage = '<tal:h define="dummy python: request.RESPONSE.redirect(' \ homePage = '<tal:h define="dummy python: request.RESPONSE.redirect(' \
'context.config.getHomePage())"/>' 'context.config.getHomePage())"/>'
# Stuff for tracking user activity ---------------------------------------------
loggedUsers = {}
originalTraverse = None
doNotTrack = ('.jpg','.gif','.png','.js','.css')
def traverseWrapper(self, path, response=None, validated_hook=None):
'''This function is called every time a users gets a URL, this is used for
tracking user activity. self is a BaseRequest'''
res = originalTraverse(self, path, response, validated_hook)
if os.path.splitext(path)[-1].lower() not in doNotTrack:
# Do nothing when the user gets non-pages.
userId, dummy = gutils.readCookie(self)
if userId:
loggedUsers[userId] = time.time()
# "Touch" the SESSION object. Else, expiration won't occur.
session = self.SESSION
return res
def onDelSession(sessionObject, container): def onDelSession(sessionObject, container):
'''This function is called when a session expires.''' '''This function is called when a session expires.'''
rq = container.REQUEST rq = container.REQUEST
@ -284,17 +266,6 @@ class ZopeInstaller:
else: else:
sessionData.setDelNotificationTarget(None) sessionData.setDelNotificationTarget(None)
def enableUserTracking(self):
'''Enables the machinery allowing to know who is currently logged in.
Information about logged users will be stored in RAM, in the variable
named loggedUsers defined above.'''
global originalTraverse
if not originalTraverse:
# User tracking is not enabled yet. Do it now.
BaseRequest = self.config.BaseRequest
originalTraverse = BaseRequest.traverse
BaseRequest.traverse = traverseWrapper
def installZopeClasses(self): def installZopeClasses(self):
'''Zope-level class registration.''' '''Zope-level class registration.'''
for klass in self.classes: for klass in self.classes:
@ -358,7 +329,6 @@ class ZopeInstaller:
self.installRoles() self.installRoles()
self.installAppyTypes() self.installAppyTypes()
self.installZopeClasses() self.installZopeClasses()
self.enableUserTracking()
self.configureSessions() self.configureSessions()
self.installBaseObjects() self.installBaseObjects()
# The following line cleans and rebuilds the catalog entirely. # The following line cleans and rebuilds the catalog entirely.

View file

@ -1038,17 +1038,20 @@ class ToolMixin(BaseMixin):
if not success: return if not success: return
# The password is correct. We can create/update our local user # The password is correct. We can create/update our local user
# corresponding to this LDAP user. # corresponding to this LDAP user.
userParams = cfg.getUserParams(ldapData) userParams = cfg.getUserParams(ldapData[0][1])
tool = self.appy() tool = self.appy()
user = tool.search1('User', noSecurity=True, login=login) user = tool.search1('User', noSecurity=True, login=login)
if user: if user:
# Update the user with fresh info about him from the LDAP # Update the user with fresh info about him from the LDAP
for name, value in userParams.iteritems(): for name, value in userParams.iteritems():
setattr(user, name, value) setattr(user, name, value)
# Update user password
user.setPassword(password, log=False)
user.reindex() user.reindex()
else: else:
# Create the user # Create the user
user = tool.create('users', login=login, source='ldap',**userParams) user = tool.create('users', noSecurity=True, login=login,
password1=password, source='ldap', **userParams)
return user return user
def getUser(self, authentify=False, source='zodb'): def getUser(self, authentify=False, source='zodb'):
@ -1058,7 +1061,9 @@ class ToolMixin(BaseMixin):
If p_authentify is True and p_source is "zodb", authentication is If p_authentify is True and p_source is "zodb", authentication is
performed locally. Else (p_source is "ldap"), authentication is performed locally. Else (p_source is "ldap"), authentication is
performed on a LDAP (if a LDAP configuration is found).''' performed on a LDAP (if a LDAP configuration is found). If p_source
is "any", authentication is performed on the local User object, be it
really local or a copy of a LDAP user.'''
tool = self.appy() tool = self.appy()
req = tool.request req = tool.request
# Try first to return the user that can be cached on the request. In # Try first to return the user that can be cached on the request. In
@ -1073,9 +1078,14 @@ class ToolMixin(BaseMixin):
if authentify and not login: return if authentify and not login: return
# Now, get the User instance. # Now, get the User instance.
if source == 'zodb': if source == 'zodb':
# Get the User object, but only if it is a true local user.
user = tool.search1('User', noSecurity=True, login=login) user = tool.search1('User', noSecurity=True, login=login)
if user and (user.source != 'zodb'): user = None # Not a local one.
elif source == 'ldap': elif source == 'ldap':
user = self.getLdapUser(login, password) user = self.getLdapUser(login, password)
elif source == 'any':
# Get the user object, be it really local or a copy of a LDAP user.
user = tool.search1('User', noSecurity=True, login=login)
if not user: return if not user: return
# Authentify the user if required. # Authentify the user if required.
if authentify: if authentify:
@ -1131,22 +1141,35 @@ class ToolMixin(BaseMixin):
session.invalidate() session.invalidate()
self.log('User "%s" has been logged out.' % userId) self.log('User "%s" has been logged out.' % userId)
# Remove user from variable "loggedUsers" # Remove user from variable "loggedUsers"
from appy.gen.installer import loggedUsers if self.loggedUsers.has_key(userId): del self.loggedUsers[userId]
if loggedUsers.has_key(userId): del loggedUsers[userId]
return self.goto(self.getApp().absolute_url()) return self.goto(self.getApp().absolute_url())
# This dict stores, for every logged user, the date/time of its last access
loggedUsers = {}
forgetAccessExtensions = ('.jpg', '.gif', '.png', '.js', '.css')
def rememberAccess(self, id, user):
'''Every time there is a hit on the server, this method is called in
order to update global dict loggedUsers (see above).'''
if not id: return
if os.path.splitext(id)[-1].lower() in self.forgetAccessExtensions:
return
self.loggedUsers[user.login] = time.time()
# "Touch" the SESSION object. Else, expiration won't occur.
session = self.REQUEST.SESSION
def validate(self, request, auth='', roles=_noroles): def validate(self, request, auth='', roles=_noroles):
'''This method performs authentication and authorization. It is used as '''This method performs authentication and authorization. It is used as
a replacement for Zope's AccessControl.User.BasicUserFolder.validate, a replacement for Zope's AccessControl.User.BasicUserFolder.validate,
that allows to manage cookie-based authentication.''' that allows to manage cookie-based authentication.'''
v = request['PUBLISHED'] # The published object v = request['PUBLISHED'] # The published object
tool = self.getParentNode().config
# v is the object (value) we're validating access to # v is the object (value) we're validating access to
# n is the name used to access the object # n is the name used to access the object
# a is the object the object was accessed through # a is the object the object was accessed through
# c is the physical container of the object # c is the physical container of the object
a, c, n, v = self._getobcontext(v, request) a, c, n, v = self._getobcontext(v, request)
# Identify and authentify the user # Identify and authentify the user
user = self.getParentNode().config.getUser(authentify=True) user = tool.getUser(authentify=True, source='any')
if not user: if not user:
# Login and/or password incorrect. Try to authorize and return the # Login and/or password incorrect. Try to authorize and return the
# anonymous user. # anonymous user.
@ -1156,7 +1179,9 @@ class ToolMixin(BaseMixin):
return return
else: else:
# We found a user and his password was correct. Try to authorize him # We found a user and his password was correct. Try to authorize him
# against the published object. # against the published object. By the way, remember its last access
# to this system.
tool.rememberAccess(a.getId(), user)
user = user.getZopeUser() user = user.getZopeUser()
if self.authorize(user, a, c, n, v, roles): if self.authorize(user, a, c, n, v, roles):
return user.__of__(self) return user.__of__(self)
@ -1237,6 +1262,7 @@ class ToolMixin(BaseMixin):
if ',' in contentType: return () if ',' in contentType: return ()
return [f.__dict__ for f in self.getAllAppyTypes(contentType) \ return [f.__dict__ for f in self.getAllAppyTypes(contentType) \
if (f.type == 'Pod') and (f.show == 'result')] if (f.type == 'Pod') and (f.show == 'result')]
def formatDate(self, aDate, withHour=True): def formatDate(self, aDate, withHour=True):
'''Returns aDate formatted as specified by tool.dateFormat. '''Returns aDate formatted as specified by tool.dateFormat.
If p_withHour is True, hour is appended, with a format specified If p_withHour is True, hour is appended, with a format specified

View file

@ -8,14 +8,9 @@ import wrappers
# The following imports are here for allowing mixin classes to access those # The following imports are here for allowing mixin classes to access those
# elements without being statically dependent on Zope packages. # elements without being statically dependent on Zope packages.
from persistent.list import PersistentList from persistent.list import PersistentList
from zExceptions import BadRequest
from ZPublisher.HTTPRequest import BaseRequest
from OFS.Image import File from OFS.Image import File
from ZPublisher.HTTPRequest import FileUpload from ZPublisher.HTTPRequest import FileUpload
from AccessControl import getSecurityManager
from DateTime import DateTime from DateTime import DateTime
from Products.ExternalMethod.ExternalMethod import ExternalMethod
from Products.Transience.Transience import TransientObjectContainer
import appy.gen import appy.gen
import logging import logging
logger = logging.getLogger('<!applicationName!>') logger = logging.getLogger('<!applicationName!>')

View file

@ -4,7 +4,6 @@ import appy
from appy.gen.mail import sendMail from appy.gen.mail import sendMail
from appy.shared.utils import executeCommand from appy.shared.utils import executeCommand
from appy.gen.wrappers import AbstractWrapper from appy.gen.wrappers import AbstractWrapper
from appy.gen.installer import loggedUsers
from appy.px import Px from appy.px import Px
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
@ -668,7 +667,7 @@ class ToolWrapper(AbstractWrapper):
'<tr><th></th><th>%s</th></tr>' % \ '<tr><th></th><th>%s</th></tr>' % \
self.translate('last_user_access') self.translate('last_user_access')
rows = [] rows = []
for userId, lastAccess in loggedUsers.items(): for userId, lastAccess in self.o.loggedUsers.items():
user = self.search1('User', noSecurity=True, login=userId) user = self.search1('User', noSecurity=True, login=userId)
if not user: continue # Could have been deleted in the meanwhile if not user: continue # Could have been deleted in the meanwhile
fmt = '%s (%s)' % (self.dateFormat, self.hourFormat) fmt = '%s (%s)' % (self.dateFormat, self.hourFormat)

View file

@ -63,7 +63,7 @@ class UserWrapper(AbstractWrapper):
'''Returns p_clearPassword, encrypted.''' '''Returns p_clearPassword, encrypted.'''
return self.o.getTool().acl_users._encryptPassword(clearPassword) return self.o.getTool().acl_users._encryptPassword(clearPassword)
def setPassword(self, newPassword=None): def setPassword(self, newPassword=None, log=True):
'''Sets a p_newPassword for self. If p_newPassword is not given, we '''Sets a p_newPassword for self. If p_newPassword is not given, we
generate one. This method returns the generated password (or simply generate one. This method returns the generated password (or simply
p_newPassword if no generation occurred).''' p_newPassword if no generation occurred).'''
@ -76,10 +76,11 @@ class UserWrapper(AbstractWrapper):
zopeUser = self.getZopeUser() zopeUser = self.getZopeUser()
tool = self.tool.o tool = self.tool.o
zopeUser.__ = self.encryptPassword(newPassword) zopeUser.__ = self.encryptPassword(newPassword)
if self.user.login == login: if self.user and (self.user.login == login):
# The user for which we change the password is the currently logged # The user for which we change the password is the currently logged
# user. So update the authentication cookie, too. # user. So update the authentication cookie, too.
gutils.writeCookie(login, newPassword, self.request) gutils.writeCookie(login, newPassword, self.request)
if log:
self.log('Password %s by "%s" for "%s".' % \ self.log('Password %s by "%s" for "%s".' % \
(msgPart, self.user.login, login)) (msgPart, self.user.login, login))
return newPassword return newPassword
@ -191,10 +192,11 @@ class UserWrapper(AbstractWrapper):
# "self" must be owned by its Zope user. # "self" must be owned by its Zope user.
if 'Owner' not in self.o.get_local_roles_for_userid(login): if 'Owner' not in self.o.get_local_roles_for_userid(login):
self.o.manage_addLocalRoles(login, ('Owner',)) self.o.manage_addLocalRoles(login, ('Owner',))
# If the user was created by an Anonymous, Anonymous can't stay Owner # If the user was created by anon or system, remove this local role.
# of the object.
if 'anon' in self.o.__ac_local_roles__: if 'anon' in self.o.__ac_local_roles__:
del self.o.__ac_local_roles__['anon'] del self.o.__ac_local_roles__['anon']
if 'system' in self.o.__ac_local_roles__:
del self.o.__ac_local_roles__['system']
return self._callCustom('onEdit', created) return self._callCustom('onEdit', created)
def mayEdit(self): def mayEdit(self):
@ -311,7 +313,7 @@ class UserWrapper(AbstractWrapper):
userLogins = self.getLogins() userLogins = self.getLogins()
for login, roles in localRoles.iteritems(): for login, roles in localRoles.iteritems():
# Ignore logins not corresponding to this user. # Ignore logins not corresponding to this user.
if login not in logins: continue if login not in userLogins: continue
for role in roles: for role in roles:
if role in allowedRoles: return True if role in allowedRoles: return True
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------

View file

@ -26,7 +26,10 @@ class LdapConnector:
def log(self, message, type='info'): def log(self, message, type='info'):
'''Logs via a Appy tool if available.''' '''Logs via a Appy tool if available.'''
if self.tool: self.tool.log(message, type=type) if self.tool:
self.tool.log(message, type=type)
else:
print(message)
def connect(self, login, password): def connect(self, login, password):
'''Connects to the LDAP server using p_login and p_password as '''Connects to the LDAP server using p_login and p_password as