[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.'''
import sys, ldap
import sys
from appy.shared.ldap_connector import LdapConnector
# ------------------------------------------------------------------------------
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"
login is the login user DN, ie: "cn=gdy,o=geezteem"
@ -27,34 +28,21 @@ class LdapTester:
self.attrs = self.attrs.split(',')
self.tentatives = 5
self.timeout = 5
self.attrList = ['cn']
self.attributes = ['cn']
self.ssl = False
def test(self):
# Connect the the LDAP
print('Creating server object for server %s...' % self.uri)
server = ldap.initialize(self.uri)
print('Done. Login with %s...' % self.login)
server.simple_bind(self.login, self.password)
if self.ssl:
server.start_tls_s()
try:
for i in range(self.tentatives):
try:
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)))
print('Connecting to... %s' % self.uri)
connector = LdapConnector(self.uri)
success, msg = connector.connect(self.login, self.password)
if not success: return
# Perform the query.
print ('Querying %s...' % self.base)
res = connector.search(self.base, self.scope, self.filter,
self.attributes)
print('Got %d results' % len(res))
# ------------------------------------------------------------------------------
if __name__ == '__main__':
LdapTester().test()
if __name__ == '__main__': LdapTester().test()
# ------------------------------------------------------------------------------

View file

@ -412,7 +412,7 @@ class LdapConfig:
def getUserAttributes(self):
'''Gets the attributes we want to get from the LDAP for characterizing
a user.'''
res = [self.loginAttribute]
res = []
for name in self.ldapAttributes.iterkeys():
if getattr(self, name):
res.append(getattr(self, name))
@ -429,7 +429,9 @@ class LdapConfig:
ldapName = getattr(self, name)
if not ldapName: continue
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
# ------------------------------------------------------------------------------

View file

@ -2,7 +2,7 @@
Zope product.'''
# ------------------------------------------------------------------------------
import os, os.path, time
import os, os.path
import appy
import appy.version
import appy.gen as gen
@ -16,24 +16,6 @@ from appy.shared.data import languages
homePage = '<tal:h define="dummy python: request.RESPONSE.redirect(' \
'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):
'''This function is called when a session expires.'''
rq = container.REQUEST
@ -284,17 +266,6 @@ class ZopeInstaller:
else:
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):
'''Zope-level class registration.'''
for klass in self.classes:
@ -358,7 +329,6 @@ class ZopeInstaller:
self.installRoles()
self.installAppyTypes()
self.installZopeClasses()
self.enableUserTracking()
self.configureSessions()
self.installBaseObjects()
# The following line cleans and rebuilds the catalog entirely.

View file

@ -1038,17 +1038,20 @@ class ToolMixin(BaseMixin):
if not success: return
# The password is correct. We can create/update our local user
# corresponding to this LDAP user.
userParams = cfg.getUserParams(ldapData)
userParams = cfg.getUserParams(ldapData[0][1])
tool = self.appy()
user = tool.search1('User', noSecurity=True, login=login)
if user:
# Update the user with fresh info about him from the LDAP
for name, value in userParams.iteritems():
setattr(user, name, value)
# Update user password
user.setPassword(password, log=False)
user.reindex()
else:
# 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
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
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()
req = tool.request
# 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
# Now, get the User instance.
if source == 'zodb':
# Get the User object, but only if it is a true local user.
user = tool.search1('User', noSecurity=True, login=login)
if user and (user.source != 'zodb'): user = None # Not a local one.
elif source == 'ldap':
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
# Authentify the user if required.
if authentify:
@ -1131,22 +1141,35 @@ class ToolMixin(BaseMixin):
session.invalidate()
self.log('User "%s" has been logged out.' % userId)
# Remove user from variable "loggedUsers"
from appy.gen.installer import loggedUsers
if loggedUsers.has_key(userId): del loggedUsers[userId]
if self.loggedUsers.has_key(userId): del self.loggedUsers[userId]
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):
'''This method performs authentication and authorization. It is used as
a replacement for Zope's AccessControl.User.BasicUserFolder.validate,
that allows to manage cookie-based authentication.'''
v = request['PUBLISHED'] # The published object
tool = self.getParentNode().config
# v is the object (value) we're validating access to
# n is the name used to access the object
# a is the object the object was accessed through
# c is the physical container of the object
a, c, n, v = self._getobcontext(v, request)
# Identify and authentify the user
user = self.getParentNode().config.getUser(authentify=True)
user = tool.getUser(authentify=True, source='any')
if not user:
# Login and/or password incorrect. Try to authorize and return the
# anonymous user.
@ -1156,7 +1179,9 @@ class ToolMixin(BaseMixin):
return
else:
# 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()
if self.authorize(user, a, c, n, v, roles):
return user.__of__(self)
@ -1237,6 +1262,7 @@ class ToolMixin(BaseMixin):
if ',' in contentType: return ()
return [f.__dict__ for f in self.getAllAppyTypes(contentType) \
if (f.type == 'Pod') and (f.show == 'result')]
def formatDate(self, aDate, withHour=True):
'''Returns aDate formatted as specified by tool.dateFormat.
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
# elements without being statically dependent on Zope packages.
from persistent.list import PersistentList
from zExceptions import BadRequest
from ZPublisher.HTTPRequest import BaseRequest
from OFS.Image import File
from ZPublisher.HTTPRequest import FileUpload
from AccessControl import getSecurityManager
from DateTime import DateTime
from Products.ExternalMethod.ExternalMethod import ExternalMethod
from Products.Transience.Transience import TransientObjectContainer
import appy.gen
import logging
logger = logging.getLogger('<!applicationName!>')

View file

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

View file

@ -63,7 +63,7 @@ class UserWrapper(AbstractWrapper):
'''Returns p_clearPassword, encrypted.'''
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
generate one. This method returns the generated password (or simply
p_newPassword if no generation occurred).'''
@ -76,12 +76,13 @@ class UserWrapper(AbstractWrapper):
zopeUser = self.getZopeUser()
tool = self.tool.o
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
# user. So update the authentication cookie, too.
gutils.writeCookie(login, newPassword, self.request)
self.log('Password %s by "%s" for "%s".' % \
(msgPart, self.user.login, login))
if log:
self.log('Password %s by "%s" for "%s".' % \
(msgPart, self.user.login, login))
return newPassword
def checkPassword(self, clearPassword):
@ -191,10 +192,11 @@ class UserWrapper(AbstractWrapper):
# "self" must be owned by its Zope user.
if 'Owner' not in self.o.get_local_roles_for_userid(login):
self.o.manage_addLocalRoles(login, ('Owner',))
# If the user was created by an Anonymous, Anonymous can't stay Owner
# of the object.
# If the user was created by anon or system, remove this local role.
if 'anon' in self.o.__ac_local_roles__:
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)
def mayEdit(self):
@ -311,7 +313,7 @@ class UserWrapper(AbstractWrapper):
userLogins = self.getLogins()
for login, roles in localRoles.iteritems():
# Ignore logins not corresponding to this user.
if login not in logins: continue
if login not in userLogins: continue
for role in roles:
if role in allowedRoles: return True
# ------------------------------------------------------------------------------

View file

@ -26,7 +26,10 @@ class LdapConnector:
def log(self, message, type='info'):
'''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):
'''Connects to the LDAP server using p_login and p_password as