[gen] Added the possiblity to synchronize external users from a LDAP.
This commit is contained in:
parent
6cd64fdc50
commit
865e6bf08e
|
@ -861,45 +861,6 @@ class ToolMixin(BaseMixin):
|
||||||
login = 'anon'
|
login = 'anon'
|
||||||
return login, password
|
return login, password
|
||||||
|
|
||||||
def getLdapUser(self, login, password):
|
|
||||||
'''Returns a local User instance corresponding to a LDAP user if p_login
|
|
||||||
and p_password correspong to a valid LDAP user.'''
|
|
||||||
# Check if LDAP is configured
|
|
||||||
cfg = self.getProductConfig(True).ldap
|
|
||||||
if not cfg or not cfg.enabled: return
|
|
||||||
# Get a connector to the LDAP server and connect to the LDAP server
|
|
||||||
serverUri = cfg.getServerUri()
|
|
||||||
connector = LdapConnector(serverUri, tool=self)
|
|
||||||
success, msg = connector.connect(cfg.adminLogin, cfg.adminPassword)
|
|
||||||
if not success: return
|
|
||||||
# Check if the user corresponding to p_login exists in the LDAP.
|
|
||||||
filter = connector.getFilter(cfg.getUserFilterValues(login))
|
|
||||||
params = cfg.getUserAttributes()
|
|
||||||
ldapData = connector.search(cfg.baseDn, cfg.scope, filter, params)
|
|
||||||
if not ldapData: return
|
|
||||||
# The user exists. Try to connect to the LDAP with this user in order
|
|
||||||
# to validate its password.
|
|
||||||
userConnector = LdapConnector(serverUri, tool=self)
|
|
||||||
success, msg = userConnector.connect(ldapData[0][0], password)
|
|
||||||
if not success: return
|
|
||||||
# The password is correct. We can create/update our local user
|
|
||||||
# corresponding to this LDAP user.
|
|
||||||
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', noSecurity=True, login=login,
|
|
||||||
password1=password, source='ldap', **userParams)
|
|
||||||
return user
|
|
||||||
|
|
||||||
def getUser(self, authentify=False, source='zodb'):
|
def getUser(self, authentify=False, source='zodb'):
|
||||||
'''Gets the current user. If p_authentify is True, in addition to
|
'''Gets the current user. If p_authentify is True, in addition to
|
||||||
finding the logged user and returning it (=identification), we check
|
finding the logged user and returning it (=identification), we check
|
||||||
|
@ -928,7 +889,9 @@ class ToolMixin(BaseMixin):
|
||||||
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.
|
if user and (user.source != 'zodb'): user = None # Not a local one.
|
||||||
elif source == 'ldap':
|
elif source == 'ldap':
|
||||||
user = self.getLdapUser(login, password)
|
user = None
|
||||||
|
cfg = self.getProductConfig(True).ldap
|
||||||
|
if cfg: user = cfg.getUser(self.appy(), login, password)
|
||||||
elif source == 'any':
|
elif source == 'any':
|
||||||
# Get the user object, be it really local or a copy of a LDAP user.
|
# Get the user object, be it really local or a copy of a LDAP user.
|
||||||
user = tool.search1('User', noSecurity=True, login=login)
|
user = tool.search1('User', noSecurity=True, login=login)
|
||||||
|
|
16
gen/model.py
16
gen/model.py
|
@ -244,12 +244,12 @@ setattr(Page, Page.pages.back.attribute, Page.pages.back)
|
||||||
defaultToolFields = ('title', 'mailHost', 'mailEnabled', 'mailFrom',
|
defaultToolFields = ('title', 'mailHost', 'mailEnabled', 'mailFrom',
|
||||||
'appyVersion', 'dateFormat', 'hourFormat',
|
'appyVersion', 'dateFormat', 'hourFormat',
|
||||||
'unoEnabledPython', 'openOfficePort',
|
'unoEnabledPython', 'openOfficePort',
|
||||||
'numberOfResultsPerPage', 'users', 'connectedUsers',
|
'numberOfResultsPerPage', 'users',
|
||||||
'groups', 'translations', 'loadTranslationsAtStartup',
|
'connectedUsers', 'synchronizeExternalUsers', 'groups',
|
||||||
'pages')
|
'translations', 'loadTranslationsAtStartup', 'pages')
|
||||||
|
|
||||||
class Tool(ModelClass):
|
class Tool(ModelClass):
|
||||||
# In a ModelClass we need to declare attributes in the following list.
|
# In a ModelClass we need to declare attributes in the following list
|
||||||
_appy_attributes = list(defaultToolFields)
|
_appy_attributes = list(defaultToolFields)
|
||||||
folder = True
|
folder = True
|
||||||
|
|
||||||
|
@ -269,16 +269,22 @@ class Tool(ModelClass):
|
||||||
openOfficePort = gen.Integer(default=2002, **lf)
|
openOfficePort = gen.Integer(default=2002, **lf)
|
||||||
numberOfResultsPerPage = gen.Integer(default=30, **lf)
|
numberOfResultsPerPage = gen.Integer(default=30, **lf)
|
||||||
|
|
||||||
# Ref(User) will maybe be transformed into Ref(CustomUserClass).
|
# Ref(User) will maybe be transformed into Ref(CustomUserClass)
|
||||||
userPage = gen.Page('users', show=isManager)
|
userPage = gen.Page('users', show=isManager)
|
||||||
users = gen.Ref(User, multiplicity=(0,None), add=True, link=False,
|
users = gen.Ref(User, multiplicity=(0,None), add=True, link=False,
|
||||||
back=gen.Ref(attribute='toTool', show=False), page=userPage,
|
back=gen.Ref(attribute='toTool', show=False), page=userPage,
|
||||||
queryable=True, queryFields=('title', 'login'),
|
queryable=True, queryFields=('title', 'login'),
|
||||||
show=isManager, showHeaders=True,
|
show=isManager, showHeaders=True,
|
||||||
shownInfo=('title', 'login*120px', 'roles*120px'))
|
shownInfo=('title', 'login*120px', 'roles*120px'))
|
||||||
|
|
||||||
def computeConnectedUsers(self): pass
|
def computeConnectedUsers(self): pass
|
||||||
connectedUsers = gen.Computed(method=computeConnectedUsers, page=userPage,
|
connectedUsers = gen.Computed(method=computeConnectedUsers, page=userPage,
|
||||||
plainText=False, show=isManager)
|
plainText=False, show=isManager)
|
||||||
|
def doSynchronizeExternalUsers(self): pass
|
||||||
|
def showSynchronizeUsers(self): pass
|
||||||
|
synchronizeExternalUsers = gen.Action(action=doSynchronizeExternalUsers,
|
||||||
|
show=showSynchronizeUsers, confirm=True, page=userPage)
|
||||||
|
|
||||||
groups = gen.Ref(Group, multiplicity=(0,None), add=True, link=False,
|
groups = gen.Ref(Group, multiplicity=(0,None), add=True, link=False,
|
||||||
back=gen.Ref(attribute='toTool2', show=False),
|
back=gen.Ref(attribute='toTool2', show=False),
|
||||||
page=gen.Page('groups', show=isManager), show=isManager,
|
page=gen.Page('groups', show=isManager), show=isManager,
|
||||||
|
|
|
@ -5,6 +5,7 @@ from appy.px import Px
|
||||||
from appy.gen.mail import sendMail
|
from appy.gen.mail import sendMail
|
||||||
from appy.gen.wrappers import AbstractWrapper
|
from appy.gen.wrappers import AbstractWrapper
|
||||||
from appy.shared.utils import executeCommand
|
from appy.shared.utils import executeCommand
|
||||||
|
from appy.shared.ldap_connector import LdapConnector
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
class ToolWrapper(AbstractWrapper):
|
class ToolWrapper(AbstractWrapper):
|
||||||
|
|
||||||
|
@ -712,4 +713,17 @@ class ToolWrapper(AbstractWrapper):
|
||||||
def _login(self, login):
|
def _login(self, login):
|
||||||
'''Performs a login programmatically. Used by the test system.'''
|
'''Performs a login programmatically. Used by the test system.'''
|
||||||
self.request.user = self.search1('User', noSecurity=True, login=login)
|
self.request.user = self.search1('User', noSecurity=True, login=login)
|
||||||
|
|
||||||
|
def doSynchronizeExternalUsers(self):
|
||||||
|
'''Synchronizes the local User copies with a distant LDAP user base.'''
|
||||||
|
cfg = self.o.getProductConfig(True).ldap
|
||||||
|
if not cfg: raise Exception('LDAP config not found.')
|
||||||
|
counts = cfg.synchronizeUsers(self)
|
||||||
|
msg = 'LDAP users: %d created, %d updated, %d untouched.' % counts
|
||||||
|
return True, msg
|
||||||
|
|
||||||
|
def showSynchronizeUsers(self):
|
||||||
|
'''Show this button only if a LDAP connection exists and is enabled.'''
|
||||||
|
cfg = self.o.getProductConfig(True).ldap
|
||||||
|
if cfg and cfg.enabled: return 'view'
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
import string
|
||||||
try:
|
try:
|
||||||
import ldap
|
import ldap
|
||||||
except ImportError:
|
except ImportError:
|
||||||
# For people that do not care about ldap.
|
# For people that do not care about ldap
|
||||||
ldap = None
|
ldap = None
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
@ -44,10 +45,11 @@ class LdapConfig:
|
||||||
port = self.port or 389
|
port = self.port or 389
|
||||||
return 'ldap://%s:%d' % (self.server, port)
|
return 'ldap://%s:%d' % (self.server, port)
|
||||||
|
|
||||||
def getUserFilterValues(self, login):
|
def getUserFilterValues(self, login=None):
|
||||||
'''Gets the filter values required to perform a query for finding user
|
'''Gets the filter values required to perform a query for finding user
|
||||||
corresponding to p_login in the LDAP.'''
|
corresponding to p_login in the LDAP, or all users if p_login is
|
||||||
res = [(self.loginAttribute, login)]
|
None.'''
|
||||||
|
res = login and [(self.loginAttribute, login)] or []
|
||||||
for userClass in self.userClasses:
|
for userClass in self.userClasses:
|
||||||
res.append( ('objectClass', userClass) )
|
res.append( ('objectClass', userClass) )
|
||||||
return res
|
return res
|
||||||
|
@ -68,7 +70,7 @@ class LdapConfig:
|
||||||
res = {}
|
res = {}
|
||||||
for name, appyName in self.ldapAttributes.iteritems():
|
for name, appyName in self.ldapAttributes.iteritems():
|
||||||
if not appyName: continue
|
if not appyName: continue
|
||||||
# Get the name of the attribute as known in the LDAP.
|
# Get the name of the attribute as known in the LDAP
|
||||||
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]:
|
||||||
|
@ -77,6 +79,100 @@ class LdapConfig:
|
||||||
res[appyName] = value
|
res[appyName] = value
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
def setLocalUser(self, tool, attrs, login, password=None):
|
||||||
|
'''Creates or updates the local User instance corresponding to a LDAP
|
||||||
|
user from the LDAP, having p_login. Its other attributes are in
|
||||||
|
p_attrs and, when relevant, its password is in p_password. This
|
||||||
|
method returns a 2-tuple containing:
|
||||||
|
* the local User instance;
|
||||||
|
* the status of the operation:
|
||||||
|
- "created" if the instance has been created,
|
||||||
|
- "updated" if at least one data from p_attrs is different from the
|
||||||
|
one stored on the existing User instance;
|
||||||
|
- None else.
|
||||||
|
'''
|
||||||
|
# Do we already have a local User instance for this user ?
|
||||||
|
status = None
|
||||||
|
user = tool.search1('User', noSecurity=True, login=login)
|
||||||
|
if user:
|
||||||
|
# Yes. Update it with info about him from the LDAP
|
||||||
|
for name, value in attrs.iteritems():
|
||||||
|
currentValue = getattr(user, name)
|
||||||
|
if value != currentValue:
|
||||||
|
setattr(user, name, value)
|
||||||
|
status = 'updated'
|
||||||
|
# Update user password, if given
|
||||||
|
if password: user.setPassword(password, log=False)
|
||||||
|
user.reindex()
|
||||||
|
else:
|
||||||
|
# Create the user
|
||||||
|
user = tool.create('users', noSecurity=True, login=login,
|
||||||
|
source='ldap', **attrs)
|
||||||
|
if password: user.setPassword(password, log=False)
|
||||||
|
status = 'created'
|
||||||
|
return user, status
|
||||||
|
|
||||||
|
def getUser(self, tool, login, password):
|
||||||
|
'''Returns a local User instance corresponding to a LDAP user if p_login
|
||||||
|
and p_password correspond to a valid LDAP user.'''
|
||||||
|
# Check if LDAP is enabled
|
||||||
|
if not self.enabled: return
|
||||||
|
# Get a connector to the LDAP server and connect to the LDAP server
|
||||||
|
serverUri = self.getServerUri()
|
||||||
|
connector = LdapConnector(serverUri, tool=tool)
|
||||||
|
success, msg = connector.connect(self.adminLogin, self.adminPassword)
|
||||||
|
if not success: return
|
||||||
|
# Check if the user corresponding to p_login exists in the LDAP
|
||||||
|
filter = connector.getFilter(self.getUserFilterValues(login))
|
||||||
|
params = self.getUserAttributes()
|
||||||
|
ldapData = connector.search(self.baseDn, self.scope, filter, params)
|
||||||
|
if not ldapData: return
|
||||||
|
# The user exists. Try to connect to the LDAP with this user in order
|
||||||
|
# to validate its password.
|
||||||
|
userConnector = LdapConnector(serverUri, tool=tool)
|
||||||
|
success, msg = userConnector.connect(ldapData[0][0], password)
|
||||||
|
if not success: return
|
||||||
|
# The password is correct. We can create/update our local user
|
||||||
|
# corresponding to this LDAP user.
|
||||||
|
userParams = self.getUserParams(ldapData[0][1])
|
||||||
|
user, status = self.setLocalUser(tool, userParams, login, password)
|
||||||
|
return user
|
||||||
|
|
||||||
|
def synchronizeUsers(self, tool):
|
||||||
|
'''Synchronizes the local User copies with this LDAP user base. Returns
|
||||||
|
a 2-tuple containing the number of created, updated and untouched
|
||||||
|
local copies.'''
|
||||||
|
if not self.enabled: raise Exception('LDAP config not enabled.')
|
||||||
|
# Get a connector to the LDAP server and connect to the LDAP server
|
||||||
|
serverUri = self.getServerUri()
|
||||||
|
tool.log('reading users from %s...' % serverUri)
|
||||||
|
connector = LdapConnector(serverUri, tool=tool)
|
||||||
|
success, msg = connector.connect(self.adminLogin, self.adminPassword)
|
||||||
|
if not success: raise Exception('Could not connect to %s' % serverUri)
|
||||||
|
# Query the LDAP for users. Perform several queries to avoid having
|
||||||
|
# error ldap.SIZELIMIT_EXCEEDED.
|
||||||
|
params = self.getUserAttributes()
|
||||||
|
# Count the number of created, updated and untouched users
|
||||||
|
created = updated = untouched = 0
|
||||||
|
for letter in string.ascii_lowercase:
|
||||||
|
# Get all the users whose login starts with "letter"
|
||||||
|
filter = connector.getFilter(self.getUserFilterValues('%s*'%letter))
|
||||||
|
ldapData = connector.search(self.baseDn, self.scope, filter, params)
|
||||||
|
if not ldapData: continue
|
||||||
|
for userData in ldapData:
|
||||||
|
# Get the user login
|
||||||
|
login = userData[1][self.loginAttribute][0]
|
||||||
|
# Get the other user parameters, as Appy wants it
|
||||||
|
userParams = self.getUserParams(userData[1])
|
||||||
|
# Create or update the user
|
||||||
|
user, status = self.setLocalUser(tool, userParams, login)
|
||||||
|
if status == 'created': created += 1
|
||||||
|
elif status == 'updated': updated += 1
|
||||||
|
else: untouched += 1
|
||||||
|
tool.log('users synchronization: %d local user(s) created, ' \
|
||||||
|
'%d updated and %d untouched.'% (created, updated, untouched))
|
||||||
|
return created, updated, untouched
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
class LdapConnector:
|
class LdapConnector:
|
||||||
'''This class manages the communication with a LDAP server.'''
|
'''This class manages the communication with a LDAP server.'''
|
||||||
|
|
Loading…
Reference in a new issue