[gen] LDAP bugfixes.
This commit is contained in:
parent
e51308b277
commit
e344ff51e2
|
@ -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()
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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!>')
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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,12 +76,13 @@ 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)
|
||||||
self.log('Password %s by "%s" for "%s".' % \
|
if log:
|
||||||
(msgPart, self.user.login, login))
|
self.log('Password %s by "%s" for "%s".' % \
|
||||||
|
(msgPart, self.user.login, login))
|
||||||
return newPassword
|
return newPassword
|
||||||
|
|
||||||
def checkPassword(self, clearPassword):
|
def checkPassword(self, clearPassword):
|
||||||
|
@ -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
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue