[gen] More work on LDAP authentication.
This commit is contained in:
parent
79d89aca2b
commit
e51308b277
|
@ -976,29 +976,19 @@ class ToolMixin(BaseMixin):
|
||||||
# --------------------------------------------------------------------------
|
# --------------------------------------------------------------------------
|
||||||
# Authentication-related methods
|
# Authentication-related methods
|
||||||
# --------------------------------------------------------------------------
|
# --------------------------------------------------------------------------
|
||||||
def _encryptPassword(self, password):
|
def identifyUser(self, alsoSpecial=False):
|
||||||
'''Returns the encrypted version of clear p_password.'''
|
'''To identify a user means: get its login and password. There are
|
||||||
return self.acl_users._encryptPassword(password)
|
several places to look for this information: http authentication,
|
||||||
|
cookie of credentials coming from the web form.
|
||||||
|
|
||||||
def getUser(self, authentify=False):
|
If no user could be identified, and p_alsoSpecial is True, we will
|
||||||
'''Gets the current user. If p_authentify is True, in addition to
|
nevertheless identify a "special user": "system", representing the
|
||||||
finding the logged user and returning it (=identification), we check
|
system itself (running at startup or in batch mode) or "anon",
|
||||||
if found credentials are valid (=authentification).'''
|
representing an anonymous user.'''
|
||||||
# I. Identify the user (=find its login and password). If identification
|
|
||||||
# fails, if we don't need to authentify the user (p_authentify is
|
|
||||||
# False), we consider that the current user is one of those special
|
|
||||||
# users: anon (corresponds to an anonymous user) or system (the
|
|
||||||
# technical user representing the system itself, running at startup or
|
|
||||||
# in batch mode).
|
|
||||||
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
|
|
||||||
# this case, we suppose authentification has previously been done, and
|
|
||||||
# we just return the cached user.
|
|
||||||
if hasattr(req, 'user'): return req.user
|
|
||||||
login = password = None
|
login = password = None
|
||||||
isSpecial = False
|
# a. Identify the user from http basic authentication.
|
||||||
# Ia. Identify the user from http basic authentication.
|
|
||||||
if getattr(req, '_auth', None):
|
if getattr(req, '_auth', None):
|
||||||
# HTTP basic authentication credentials are present (used when
|
# HTTP basic authentication credentials are present (used when
|
||||||
# connecting to the ZMI). Decode it.
|
# connecting to the ZMI). Decode it.
|
||||||
|
@ -1009,45 +999,25 @@ class ToolMixin(BaseMixin):
|
||||||
login, password = base64.decodestring(creds).split(':', 1)
|
login, password = base64.decodestring(creds).split(':', 1)
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
pass
|
pass
|
||||||
# Ib. Identify the user from the authentication cookie.
|
# b. Identify the user from the authentication cookie.
|
||||||
if not login:
|
if not login:
|
||||||
login, password = gutils.readCookie(req)
|
login, password = gutils.readCookie(req)
|
||||||
# Ic. Identify the user from the authentication form.
|
# c. Identify the user from the authentication form.
|
||||||
if not login:
|
if not login:
|
||||||
login = req.get('__ac_name', None)
|
login = req.get('__ac_name', None)
|
||||||
password = req.get('__ac_password', None)
|
password = req.get('__ac_password', None)
|
||||||
# Stop the identification process here if we needed to authentify the
|
# Stop identification here if we don't need to return a special user
|
||||||
# user: this user does not exist.
|
if not alsoSpecial: return login, password
|
||||||
if not login and authentify: return
|
# d. All the identification methods failed. So identify the user as
|
||||||
# Id. All the identification methods failed. So identify the user as
|
|
||||||
# "anon" or "system".
|
# "anon" or "system".
|
||||||
if not login and not authentify:
|
if not login:
|
||||||
# If we have a real request object, it is the anonymous user.
|
# If we have a real request object, it is the anonymous user.
|
||||||
login = (req.__class__.__name__ == 'Object') and 'system' or 'anon'
|
login = (req.__class__.__name__ == 'Object') and 'system' or 'anon'
|
||||||
isSpecial = True
|
return login, password
|
||||||
# Now, get the User instance from a query in the catalog.
|
|
||||||
user = tool.search1('User', noSecurity=True, login=login)
|
|
||||||
# It is possible that we find no user here: it happens before users
|
|
||||||
# "anon" and "system" are created, at first startup.
|
|
||||||
if not user: return
|
|
||||||
# Authentify the user if required
|
|
||||||
if authentify and not isSpecial:
|
|
||||||
if not user.checkPassword(password):
|
|
||||||
# Disable the authentication cookie.
|
|
||||||
req.RESPONSE.expireCookie('_appy_', path='/')
|
|
||||||
return
|
|
||||||
# Create an authentication cookie for this user.
|
|
||||||
gutils.writeCookie(login, password, req)
|
|
||||||
# Cache the user and some precomputed values, for performance.
|
|
||||||
req.user = user
|
|
||||||
req.userRoles = user.getRoles()
|
|
||||||
req.userLogins = user.getLogins()
|
|
||||||
req.zopeUser = user.getZopeUser()
|
|
||||||
return user
|
|
||||||
|
|
||||||
def _ldapAuthenticate(self, login, password):
|
def getLdapUser(self, login, password):
|
||||||
'''Performs a LDAP-based authentication. Returns True if authentication
|
'''Returns a local User instance corresponding to a LDAP user if p_login
|
||||||
succeeds.'''
|
and p_password correspong to a valid LDAP user.'''
|
||||||
# Check if LDAP is configured.
|
# Check if LDAP is configured.
|
||||||
cfg = self.getProductConfig(True).ldap
|
cfg = self.getProductConfig(True).ldap
|
||||||
if not cfg: return
|
if not cfg: return
|
||||||
|
@ -1069,8 +1039,8 @@ class ToolMixin(BaseMixin):
|
||||||
# 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)
|
||||||
user = self.search1('User', noSecurity=True, login=login)
|
tool = self.appy()
|
||||||
tool = self
|
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():
|
||||||
|
@ -1081,6 +1051,47 @@ class ToolMixin(BaseMixin):
|
||||||
user = tool.create('users', login=login, source='ldap',**userParams)
|
user = tool.create('users', login=login, source='ldap',**userParams)
|
||||||
return user
|
return user
|
||||||
|
|
||||||
|
def getUser(self, authentify=False, source='zodb'):
|
||||||
|
'''Gets the current user. If p_authentify is True, in addition to
|
||||||
|
finding the logged user and returning it (=identification), we check
|
||||||
|
if found credentials are valid (=authentification).
|
||||||
|
|
||||||
|
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).'''
|
||||||
|
tool = self.appy()
|
||||||
|
req = tool.request
|
||||||
|
# Try first to return the user that can be cached on the request. In
|
||||||
|
# this case, we suppose authentication has previously been done, and we
|
||||||
|
# just return the cached user.
|
||||||
|
if hasattr(req, 'user'): return req.user
|
||||||
|
# Identify the user (=find its login and password). If we don't need
|
||||||
|
# to authentify the user, we ask to identify a user or, if impossible,
|
||||||
|
# a special user.
|
||||||
|
login, password = self.identifyUser(alsoSpecial=not authentify)
|
||||||
|
# Stop here if no user was found and authentication was required.
|
||||||
|
if authentify and not login: return
|
||||||
|
# Now, get the User instance.
|
||||||
|
if source == 'zodb':
|
||||||
|
user = tool.search1('User', noSecurity=True, login=login)
|
||||||
|
elif source == 'ldap':
|
||||||
|
user = self.getLdapUser(login, password)
|
||||||
|
if not user: return
|
||||||
|
# Authentify the user if required.
|
||||||
|
if authentify:
|
||||||
|
if not user.checkPassword(password):
|
||||||
|
# Disable the authentication cookie.
|
||||||
|
req.RESPONSE.expireCookie('_appy_', path='/')
|
||||||
|
return
|
||||||
|
# Create an authentication cookie for this user.
|
||||||
|
gutils.writeCookie(login, password, req)
|
||||||
|
# Cache the user and some precomputed values, for performance.
|
||||||
|
req.user = user
|
||||||
|
req.userRoles = user.getRoles()
|
||||||
|
req.userLogins = user.getLogins()
|
||||||
|
req.zopeUser = user.getZopeUser()
|
||||||
|
return user
|
||||||
|
|
||||||
def performLogin(self):
|
def performLogin(self):
|
||||||
'''Logs the user in.'''
|
'''Logs the user in.'''
|
||||||
rq = self.REQUEST
|
rq = self.REQUEST
|
||||||
|
@ -1092,7 +1103,8 @@ class ToolMixin(BaseMixin):
|
||||||
return self.goto(urlBack, msg)
|
return self.goto(urlBack, msg)
|
||||||
# Authenticate the user.
|
# Authenticate the user.
|
||||||
login = rq.get('__ac_name', None)
|
login = rq.get('__ac_name', None)
|
||||||
if self.getUser(authentify=True):
|
if self.getUser(authentify=True) or \
|
||||||
|
self.getUser(authentify=True, source='ldap'):
|
||||||
msg = self.translate('login_ok')
|
msg = self.translate('login_ok')
|
||||||
logMsg = 'User "%s" logged in.' % login
|
logMsg = 'User "%s" logged in.' % login
|
||||||
else:
|
else:
|
||||||
|
@ -1107,7 +1119,7 @@ class ToolMixin(BaseMixin):
|
||||||
userId = self.getUser().login
|
userId = self.getUser().login
|
||||||
# Perform the logout in acl_users
|
# Perform the logout in acl_users
|
||||||
rq.RESPONSE.expireCookie('_appy_', path='/')
|
rq.RESPONSE.expireCookie('_appy_', path='/')
|
||||||
# Invalidate session.
|
# Invalidate the user session.
|
||||||
try:
|
try:
|
||||||
sdm = self.session_data_manager
|
sdm = self.session_data_manager
|
||||||
except AttributeError, ae:
|
except AttributeError, ae:
|
||||||
|
|
|
@ -35,8 +35,9 @@ class UserWrapper(AbstractWrapper):
|
||||||
# can potentially be changed.
|
# can potentially be changed.
|
||||||
if not self.login or (login != self.login):
|
if not self.login or (login != self.login):
|
||||||
# A new p_login is requested. Check if it is valid and free.
|
# A new p_login is requested. Check if it is valid and free.
|
||||||
# Firstly, the login can't be the id of the whole site or "admin".
|
# Some logins are not allowed.
|
||||||
if login == 'admin': return self.translate('login_reserved')
|
if login in ('admin', 'anon', 'system'):
|
||||||
|
return self.translate('login_reserved')
|
||||||
# Check that no user or group already uses this login.
|
# Check that no user or group already uses this login.
|
||||||
if self.count('User', noSecurity=True, login=login) or \
|
if self.count('User', noSecurity=True, login=login) or \
|
||||||
self.count('Group', noSecurity=True, login=login):
|
self.count('Group', noSecurity=True, login=login):
|
||||||
|
@ -58,6 +59,10 @@ class UserWrapper(AbstractWrapper):
|
||||||
# also own a User instance) wants to edit information about himself.
|
# also own a User instance) wants to edit information about himself.
|
||||||
if self.user.login == self.login: return 'edit'
|
if self.user.login == self.login: return 'edit'
|
||||||
|
|
||||||
|
def encryptPassword(self, clearPassword):
|
||||||
|
'''Returns p_clearPassword, encrypted.'''
|
||||||
|
return self.o.getTool().acl_users._encryptPassword(clearPassword)
|
||||||
|
|
||||||
def setPassword(self, newPassword=None):
|
def setPassword(self, newPassword=None):
|
||||||
'''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
|
||||||
|
@ -70,7 +75,7 @@ class UserWrapper(AbstractWrapper):
|
||||||
login = self.login
|
login = self.login
|
||||||
zopeUser = self.getZopeUser()
|
zopeUser = self.getZopeUser()
|
||||||
tool = self.tool.o
|
tool = self.tool.o
|
||||||
zopeUser.__ = tool._encryptPassword(newPassword)
|
zopeUser.__ = self.encryptPassword(newPassword)
|
||||||
if self.user.login == login:
|
if 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.
|
||||||
|
@ -91,7 +96,7 @@ class UserWrapper(AbstractWrapper):
|
||||||
self.login = newLogin
|
self.login = newLogin
|
||||||
# Update the corresponding Zope-level user
|
# Update the corresponding Zope-level user
|
||||||
aclUsers = self.o.acl_users
|
aclUsers = self.o.acl_users
|
||||||
zopeUser = aclUsers.getUser(oldLogin)
|
zopeUser = aclUsers.data[oldLogin]
|
||||||
zopeUser.name = newLogin
|
zopeUser.name = newLogin
|
||||||
del aclUsers.data[oldLogin]
|
del aclUsers.data[oldLogin]
|
||||||
aclUsers.data[newLogin] = zopeUser
|
aclUsers.data[newLogin] = zopeUser
|
||||||
|
@ -150,20 +155,25 @@ class UserWrapper(AbstractWrapper):
|
||||||
self.roles = roles
|
self.roles = roles
|
||||||
|
|
||||||
def onEdit(self, created):
|
def onEdit(self, created):
|
||||||
|
'''Triggered when a User is created or updated.'''
|
||||||
|
login = self.login
|
||||||
|
# Is it a local User or a LDAP User?
|
||||||
|
isLocal = self.source == 'zodb'
|
||||||
|
# Ensure correctness of some infos about this user.
|
||||||
|
if isLocal:
|
||||||
self.updateTitle()
|
self.updateTitle()
|
||||||
self.ensureAdminIsManager()
|
self.ensureAdminIsManager()
|
||||||
aclUsers = self.o.acl_users
|
|
||||||
login = self.login
|
|
||||||
if created:
|
if created:
|
||||||
# Create the corresponding Zope user
|
# Create the corresponding Zope user.
|
||||||
aclUsers._doAddUser(login, self.password1, self.roles, ())
|
from AccessControl.User import User as ZopeUser
|
||||||
zopeUser = aclUsers.getUser(login)
|
password = self.encryptPassword(self.password1)
|
||||||
|
zopeUser = ZopeUser(login, password, self.roles, ())
|
||||||
|
# Add it in acl_users if it is a local user.
|
||||||
|
if isLocal: self.o.acl_users.data[login] = zopeUser
|
||||||
|
# Add it in self.o._zopeUser if it is a LDAP user
|
||||||
|
else: self.o._zopeUser = zopeUser
|
||||||
# Remove our own password copies
|
# Remove our own password copies
|
||||||
self.password1 = self.password2 = ''
|
self.password1 = self.password2 = ''
|
||||||
from persistent.mapping import PersistentMapping
|
|
||||||
# The following dict will store, for every group, global roles
|
|
||||||
# granted to it.
|
|
||||||
zopeUser.groups = PersistentMapping()
|
|
||||||
else:
|
else:
|
||||||
# Update the login itself if the user has changed it.
|
# Update the login itself if the user has changed it.
|
||||||
oldLogin = self.o._oldLogin
|
oldLogin = self.o._oldLogin
|
||||||
|
@ -188,16 +198,20 @@ class UserWrapper(AbstractWrapper):
|
||||||
return self._callCustom('onEdit', created)
|
return self._callCustom('onEdit', created)
|
||||||
|
|
||||||
def mayEdit(self):
|
def mayEdit(self):
|
||||||
'''No one can edit users "system" and "anon".'''
|
'''No one can edit users "system" and "anon"; no one can edit non-zodb
|
||||||
|
users.'''
|
||||||
if self.o.id in ('system', 'anon'): return
|
if self.o.id in ('system', 'anon'): return
|
||||||
|
if self.source != 'zodb': return
|
||||||
# Call custom "mayEdit" when present.
|
# Call custom "mayEdit" when present.
|
||||||
custom = self._getCustomMethod('mayEdit')
|
custom = self._getCustomMethod('mayEdit')
|
||||||
if custom: return self._callCustom('mayEdit')
|
if custom: return self._callCustom('mayEdit')
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def mayDelete(self):
|
def mayDelete(self):
|
||||||
'''No one can delete users "system", "anon" and "admin".'''
|
'''No one can delete users "system", "anon" and "admin"; no one can
|
||||||
|
delete non-zodb users.'''
|
||||||
if self.o.id in ('system', 'anon', 'admin'): return
|
if self.o.id in ('system', 'anon', 'admin'): return
|
||||||
|
if self.source != 'zodb': return
|
||||||
# Call custom "mayDelete" when present.
|
# Call custom "mayDelete" when present.
|
||||||
custom = self._getCustomMethod('mayDelete')
|
custom = self._getCustomMethod('mayDelete')
|
||||||
if custom: return self._callCustom('mayDelete')
|
if custom: return self._callCustom('mayDelete')
|
||||||
|
@ -205,11 +219,14 @@ class UserWrapper(AbstractWrapper):
|
||||||
|
|
||||||
def getZopeUser(self):
|
def getZopeUser(self):
|
||||||
'''Gets the Zope user corresponding to this user.'''
|
'''Gets the Zope user corresponding to this user.'''
|
||||||
return self.o.acl_users.getUser(self.login)
|
if self.source == 'zodb':
|
||||||
|
return self.o.acl_users.data.get(self.login, None)
|
||||||
|
return self.o._zopeUser
|
||||||
|
|
||||||
def onDelete(self):
|
def onDelete(self):
|
||||||
'''Before deleting myself, I must delete the corresponding Zope user.'''
|
'''Before deleting myself, I must delete the corresponding Zope user
|
||||||
self.o.acl_users._doDelUsers([self.login])
|
(for local users only).'''
|
||||||
|
if self.source == 'zodb': del self.o.acl_users.data[self.login]
|
||||||
self.log('User "%s" deleted.' % self.login)
|
self.log('User "%s" deleted.' % self.login)
|
||||||
# Call a custom "onDelete" if any.
|
# Call a custom "onDelete" if any.
|
||||||
return self._callCustom('onDelete')
|
return self._callCustom('onDelete')
|
||||||
|
|
Loading…
Reference in a new issue