import Globals, App.Undo, socket, os, string, sha, whrandom, sys, zLOG from Globals import DTMLFile, MessageDialog from string import join,strip,split,lower,upper,find from OFS.Folder import Folder from OFS.CopySupport import CopyContainer from urllib import quote, unquote from AccessControl import ClassSecurityInfo from AccessControl.Role import RoleManager, DEFAULTMAXLISTUSERS from AccessControl.User import User, BasicUserFolder, readUserAccessFile, UserFolder, _noroles from AccessControl.PermissionRole import PermissionRole from OFS.DTMLMethod import DTMLMethod from time import time # from Acquisition import Implicit from zLOG import format_exception, LOG, ERROR, INFO import traceback class NameIsIdUser(User): def __init__(self, user): self.user = user def getId(self): return self.user.getId() def getUserName(self): return self.user.getId() def getRoles(self): return self.user.getRoles() def _shared_roles(self, object): return self.user._shared_roles(object) def _check_context(self, object): return self.user._check_context(object) class RemoteUser(User): def __init__(self, ruf, name, roles=[], domains=[]): self.ruf = ruf User.__init__(self, name, "", roles, domains) def getUserName(self): """Return the username of a user""" id = self.getId() if self.ruf.simple_usernames: return self.ruf.splitName( id )[ 1 ] else: return id def getId(self): """Get the ID of the user. The ID can be used, at least from Python, to get the user from the user's UserDatabase""" return self.name def getRoles(self): """Return the list of roles assigned to a user. This includes roles of NT domain""" roles = User.getRoles(self) if not self.ruf.domain_roles: return roles id = self.getId() dom, usr = self.ruf.splitName( id ) if usr != "*": try: dom_usr = self.ruf.getUser( self.ruf.joinName( dom, "*" ) ) if not dom_usr is None: roles = roles + dom_usr.getRoles() except: traceback.print_exc() return roles def getRolesInContext(self, object): """This override is to hack a problem where the normal user object uses the user name rather than the id to detect local roles """ return User.getRolesInContext(NameIsIdUser(self), object) def allowed(self, object, object_roles=None): """Check whether the user has access to object. The user must have one of the roles in object_roles to allow access.""" user = NameIsIdUser(user=self) return User.allowed(user, object, object_roles) def __getattr__( self, name ): if name == 'fullname' or name == 'email': return self.getId() else: raise AttributeError, name manage_addRemoteUserFolderForm=DTMLFile('dtml/manage_addRemoteUserFolder', globals()) class RemoteUserFolder(UserFolder): """RemoteUserFolder object A RemoteUserFolder is used in cases where the Zope installation is behind a webserver which already takes care of authentication. In some cases it makes sense to let the webserver handle the authentication and Zope to handle permissions for those authenticated users. This user folder enables the hand off of all authentication to the remote webserver and you to control the roles of these users once authenticated. If an object requires more than anonymous permissions then this folder will use the REMOTE_USER environment variable determine the ID of the user that was authenticated. If the ID matches that of a user object contained in the folder then this is the user object that will be used. If the ID does not match then a new user object will be created with no roles (if "auto add" option is turned on). This allows the webserver administrator to have complete control over who is allowed authenticated and the Zope administrator to control what they have access to. An example of how this might be useful is the use of IIS internal windows authentication. IIS can be set to handle authentication of users against their current windows domain login, thus not requiring any further login to the website. With RemoteUserFolder installed, any user with a domain login will be automatically be a zope authenticated user. In addition with RemoteUserFolder it is possible to set a default set of roles for any user of a particular NT domain. """ meta_type='Remote User Folder' id ='acl_users' title ='Remote User Folder' icon ='RemoteUserFolder.gif' user_at_domain_syntax = 0 _userFolderProperties = DTMLFile('dtml/userFolderProps', globals()) _editUser=DTMLFile('dtml/editUser', globals(), remote_user_mode__=1) _add_User=DTMLFile('dtml/addUser', globals(), remote_user_mode__=1) def __init__(self): #self.data=PersistentMapping() self.anon_prefix = '' self.auth_prefix = '' self.case_insensitive = 1 self.domain_roles = 1 self.user_at_domain_syntax = 0 UserFolder.__init__(self) def splitName( self, name_and_domain ): if self.user_at_domain_syntax: if name_and_domain.count( "@" ): return name_and_domain.split( "@", 1 )[ 1 : : -1 ] else: if name_and_domain.count("\\"): return name_and_domain.split( "\\", 1 ) return "", name_and_domain def joinName( self, domain, name ): if domain: if self.user_at_domain_syntax: return name + "@" + domain else: return domain + "\\" + name else: return name; def _doAddUser(self, name, password, roles, domains, **kw): """Create a new user. Override so we use RemoteUser objects""" self.data[ name ] = RemoteUser( self, name, roles, domains ) def normalizeName(self, name): """ If we are case insensitive then normalize the name to avoid duplicates """ if name and self.case_insensitive: domain, username = self.splitName( name ) if domain: return self.joinName( domain.upper(), username.lower() ) else: return name.lower() else: return name def getUsers( self ): return [ user for user in UserFolder.getUsers( self ) if self.splitName( user.name )[ 1 ] != "*" ] def validate(self, request, auth='', roles=_noroles): """ this method performs identification, authentication, and authorization 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 We allow the publishing machinery to defer to higher-level user folders or to raise an unauthorized by returning None from this method. """ v = request['PUBLISHED'] # the published object a, c, n, v = self._getobcontext(v, request) name = request.environ.get('REMOTE_USER', None) name = self.normalizeName(name) #LOG('RemoteUserFolder', INFO, 'validate %s' % str(name) ) if name is None: if self._domain_auth_mode: for user in self.getUsers(): if user.getDomains(): if self.authenticate( user.getUserName(), '', request ): if self.authorize(user, a, c, n, v, roles): return user.__of__(self) user = self.getUser(name) if name is not None and user is None and self.auto_add: #LOG('RemoteUserFolder', INFO, 'Trying to validate user %s' % str(name)) # we will add REMOTE users who we haven't seen before self._doAddUser(name, None, [], []) return self.getUser(name).__of__( self ) # user will be None if we can't find his username in this user # database. emergency = self._emergency_user if emergency and name==emergency.getUserName(): if self._isTop(): # we do not need to authorize the emergency user against # the published object. return emergency.__of__(self) else: # we're not the top-level user folder return None elif user is None: # we didn't find the username in this database # try to authorize and return the anonymous user. if self._isTop() and self.authorize(self._nobody, a, c, n, v, roles): return self._nobody.__of__(self) else: # anonymous can't authorize or we're not top-level user # folder return None else: # We found a user and the user wasn't the emergency user. # We need to authorize the user against the published object. if self.authorize(user, a, c, n, v, roles): return user.__of__(self) # That didn't work. Try to authorize the anonymous user. elif self._isTop() and self.authorize( self._nobody, a, c, n, v, roles): return self._nobody.__of__(self) else: # we can't authorize the user, and we either can't # authorize nobody against the published object or # we're not top-level #TODO if we are not using the Auth url then redirect to it url = request['URL1'] LOG('RemoteUserFolder', INFO, 'url is %s' % str(url) ) if url[0:len(self.anon_prefix)] == self.anon_prefix: new_url = self.auth_prefix + url[len(self.anon_prefix):] LOG('RemoteUserFolder', INFO, 'should be %s' % str(new_url) ) return None def manage_setUserFolderProperties(self, encrypt_passwords=0, update_passwords=0, anon_prefix='', auth_prefix='', simple_usernames=0, domain_roles=0, maxlistusers=DEFAULTMAXLISTUSERS, auto_add=0, case_insensitive=0, user_at_domain_syntax=0, REQUEST=None): """ Sets the properties of the user folder. """ self.anon_prefix = anon_prefix self.auth_prefix = auth_prefix self.simple_usernames = simple_usernames self.domain_roles = domain_roles self.auto_add = auto_add self.case_insensitive = case_insensitive self.user_at_domain_syntax = user_at_domain_syntax return BasicUserFolder.manage_setUserFolderProperties(self, encrypt_passwords, update_passwords, maxlistusers, REQUEST) def _changeUser(self,name,password,confirm,roles,domains,REQUEST=None): # Our users don't use passwords return UserFolder._changeUser(self,name,None,None,roles,domains,REQUEST) Globals.default__class_init__(RemoteUserFolder) def manage_addRemoteUserFolder(self,dtself=None,REQUEST=None,**ignored): """ """ f=RemoteUserFolder() self=self.this() try: self._setObject('acl_users', f) except: return MessageDialog( title ='Item Exists', message='This object already contains a User Folder', action ='%s/manage_main' % REQUEST['URL1']) self.__allow_groups__=f if REQUEST is not None: REQUEST['RESPONSE'].redirect(self.absolute_url()+'/manage_main')