diff --git a/bin/checkldap.py b/bin/checkldap.py
index 4254df1..a517a38 100644
--- a/bin/checkldap.py
+++ b/bin/checkldap.py
@@ -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()
# ------------------------------------------------------------------------------
diff --git a/gen/__init__.py b/gen/__init__.py
index 0a1780f..b519f3e 100644
--- a/gen/__init__.py
+++ b/gen/__init__.py
@@ -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
# ------------------------------------------------------------------------------
diff --git a/gen/installer.py b/gen/installer.py
index f61c02a..8737cdc 100644
--- a/gen/installer.py
+++ b/gen/installer.py
@@ -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 = ''
-# 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.
diff --git a/gen/mixins/ToolMixin.py b/gen/mixins/ToolMixin.py
index 099d0de..ef0694c 100644
--- a/gen/mixins/ToolMixin.py
+++ b/gen/mixins/ToolMixin.py
@@ -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
diff --git a/gen/templates/config.pyt b/gen/templates/config.pyt
index 795c346..950e267 100644
--- a/gen/templates/config.pyt
+++ b/gen/templates/config.pyt
@@ -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('')
diff --git a/gen/wrappers/ToolWrapper.py b/gen/wrappers/ToolWrapper.py
index 4be1719..26ad711 100644
--- a/gen/wrappers/ToolWrapper.py
+++ b/gen/wrappers/ToolWrapper.py
@@ -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):
'
| %s |
' % \
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)
diff --git a/gen/wrappers/UserWrapper.py b/gen/wrappers/UserWrapper.py
index 713b250..546e4ca 100644
--- a/gen/wrappers/UserWrapper.py
+++ b/gen/wrappers/UserWrapper.py
@@ -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
# ------------------------------------------------------------------------------
diff --git a/shared/ldap_connector.py b/shared/ldap_connector.py
index adcd218..e7c9894 100644
--- a/shared/ldap_connector.py
+++ b/shared/ldap_connector.py
@@ -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