def _authenticate_newstyle(self, environ, username, identity): """Authenticate against a new-style auth backend.""" user = User(username) if not self.backend.authenticate_user(user, identity, ['syncNode']): user = None else: if self.config.get('auth.check_node'): if user.get('syncNode') != environ.get('HTTP_HOST'): user = None return user
def _tests(self, mgr): # Clean it up first if needed. user1_del = User() user1_del['username'] = '******' user2_del = User() user2_del['username'] = '******' mgr.delete_user(user1_del) mgr.delete_user(user2_del) # Create some initial users. user1 = mgr.create_user('user1', u'password1', '*****@*****.**') user1_id = user1['userid'] user2 = mgr.create_user('user2', u'pásswørd1', '*****@*****.**') user2_id = user2['userid'] user3 = User() user3['username'] = '******' # Make sure it can't create an existing user. self.assertEquals(mgr.create_user('user1', 'password1', '*****@*****.**'), False) # Check it can successfully authenticate existing users. credentials1 = {"username": "******", "password": "******"} credentials2 = {"username": "******", "password": u"pásswørd1"} self.assertEquals(mgr.authenticate_user(user1, credentials1), user1_id) self.assertEquals(mgr.authenticate_user(user2, credentials2), user2_id) # Check that using a raw password works, but gives DeprecationWarning. with warnings.catch_warnings(record=True) as w: warnings.simplefilter("default") self.assertEquals(mgr.authenticate_user(user1, "password1"), user1_id) self.assertEquals(len(w), 1) self.assertTrue("dict of credentials" in str(w[0].message)) # Make sure that empty credentials doesn't do bad things. self.assertEquals(mgr.authenticate_user(user2, {}), None) # Start with an unpopulated User object. user1_new = User() self.assertEquals(user1_new.get('username', None), None) self.assertEquals(user1_new.get('mail', None), None) # We should be able to populate it by authenticating. self.assertEquals(mgr.authenticate_user(user1_new, credentials1), user1_id) self.assertEquals(user1_new['username'], 'user1') self.assertEquals(user1_new['userid'], user1_id) self.assertEquals(user1_new.get('mail', None), None) # If the User object is already populated, then it will only # authenticate correctly using that user's credentials. user2_new = User() user2_new["username"] = "******" self.assertEquals(mgr.authenticate_user(user2_new, credentials1), None) self.assertEquals(user2_new['username'], 'user2') self.assertEquals(mgr.authenticate_user(user2_new, credentials2), user2_id) self.assertEquals(user2_new['username'], 'user2') # We can load additional fields explicitly. user1_new = mgr.get_user_info(user1_new, ['mail', 'syncNode']) self.assertEquals(user1_new.get('mail', None), '*****@*****.**') self.assertEquals(user1_new.get('primaryNode', ''), '') # We can't update a field using invalid credentials. badcreds = {"username": "******", "password": "******"} self.assertFalse(mgr.update_field(user1_new, badcreds, 'mail', '*****@*****.**')) self.assertEquals(user1_new.get('mail', None), '*****@*****.**') # We can't update a field using someone else's credentials. self.assertFalse(mgr.update_field(user1_new, credentials2, 'mail', '*****@*****.**')) self.assertEquals(user1_new.get('mail', None), '*****@*****.**') # But it'll work using the proper credentials. self.assertTrue(mgr.update_field(user1_new, credentials1, 'mail', '*****@*****.**')) self.assertEquals(user1_new.get('mail', None), '*****@*****.**') # And it'll work using the admin interface. self.assertTrue(mgr.admin_update_field(user1_new, 'mail', '*****@*****.**')) self.assertEquals(user1_new.get('mail', None), '*****@*****.**') # Updating a nonexistent user should fail. self.assertFalse(mgr.admin_update_field(user3, 'mail', 'foo')) # Did those changes persist? user1_new = User() self.assertEquals(user1_new.get('mail', None), None) user1_new['username'] = '******' user1_new = mgr.get_user_info(user1_new, ['mail', 'syncNode']) self.assertEquals(user1_new.get('mail', None), '*****@*****.**') # Changing password requires proper credentials. self.assertFalse(mgr.update_password(user1_new, badcreds, 'password2')) self.assertFalse(mgr.update_password(user1_new, credentials2, 'password2')) self.assertTrue(mgr.update_password(user1_new, credentials1, 'password2')) # The password change affects future authentications. self.assertEquals(mgr.authenticate_user(user1, credentials1), None) credentials1["password"] = "******" self.assertEquals(mgr.authenticate_user(user1, credentials1), user1_id) # Use the admin interface to change password without credentials. self.assertTrue(mgr.admin_update_password(user1_new, 'password3')) self.assertEquals(mgr.authenticate_user(user1, credentials1), None) credentials1["password"] = "******" self.assertEquals(mgr.authenticate_user(user1, credentials1), user1_id) # Users can be deleted, but only once. self.assertTrue(mgr.delete_user(user1)) self.assertFalse(mgr.delete_user(user1)) # Users can't log in after deletion, other users aren't affected. self.assertEquals(mgr.authenticate_user(user1, credentials1), None) self.assertEquals(mgr.authenticate_user(user2, credentials2), user2_id)
def authenticate_user(self, request, config, username=None): """Authenticates a user and returns his id. "request" is the request received. The function makes sure that the user name found in the headers is compatible with the username if provided. It returns the user id from the database, if the password is the right one. """ environ = request.environ if 'REMOTE_USER' in environ: # already authenticated return environ['REMOTE_USER'] auth = environ.get('HTTP_AUTHORIZATION') if auth is not None: # for now, only supporting basic authentication # let's decipher the base64 encoded value if not auth.startswith('Basic '): raise HTTPUnauthorized('Invalid token') auth = auth[len('Basic '):].strip() try: # Split in such a way as to preserve # passwords that contain ':'. user_name, password = base64.decodestring(auth).split(':', 1) except (binascii.Error, ValueError): raise HTTPUnauthorized('Invalid token') # let's reject the call if the url is not owned by the user if (username is not None and user_name != username): log_cef('Username Does Not Match URL', 7, environ, config, user_name, AUTH_FAILURE) raise HTTPUnauthorized() # if this is an email, hash it. Save the original for logging and # debugging. remote_user_original = user_name try: user_name = extract_username(user_name) except UnicodeError: raise HTTPBadRequest('Invalid characters specified in ' + 'username', {}, 'Username must be BIDI ' + 'compliant UTF-8') # let's try an authentication # the authenticate_user API takes a unicode UTF-8 for the password try: password = password.decode('utf8') except UnicodeDecodeError: raise HTTPUnauthorized() #first we need to figure out if this is old-style or new-style auth if hasattr(self.backend, 'generate_reset_code'): # XXX to be removed once we get the proper fix see bug #662859 if (hasattr(self.backend, 'check_node') and self.backend.check_node): user_id = self.backend.authenticate_user(user_name, password, environ.get('HTTP_HOST')) else: user_id = self.backend.authenticate_user(user_name, password) request.user = User(user_name, user_id) else: user = User(user_name) credentials = {"username": user_name, "password": password} user_id = self.backend.authenticate_user(user, credentials, ['syncNode']) if not user_id: user_id = None user = None else: if (self.config.get('auth.check_node') and user.get('syncNode') != environ.get('HTTP_HOST')): user_id = None user = None request.user = user if user_id is None: err_user = user_name if remote_user_original is not None and \ user_name != remote_user_original: err_user += ' (%s)' % (remote_user_original) log_cef('User Authentication Failed', 5, environ, config, err_user, AUTH_FAILURE) raise HTTPUnauthorized() # we're all clear ! setting up REMOTE_USER request.remote_user = environ['REMOTE_USER'] = user_name # we also want to keep the password in clear text to reuse it # and remove it from the environ request.user_password = password request._authorization = environ['HTTP_AUTHORIZATION'] del environ['HTTP_AUTHORIZATION'] return user_id
def authenticate_user(self, request, config, username=None): """Authenticates a user and returns his id. "request" is the request received. The function makes sure that the user name found in the headers is compatible with the username if provided. It returns the user id from the database, if the password is the right one. """ environ = request.environ # If something further up the stack has already dealt with auth, # then things are highly unlikely to work properly. if 'REMOTE_USER' in environ: msg = 'The webserver appears to have handled authentication '\ 'internally, which is not compatible with this product.' raise HTTPServerError(msg) auth = environ.get('HTTP_AUTHORIZATION') if auth is not None: # for now, only supporting basic authentication # let's decipher the base64 encoded value if not auth.startswith('Basic '): self._log_cef('Authorization header contains unknown protocol', 7, environ) raise HTTPUnauthorized('Invalid token') auth = auth[len('Basic '):].strip() try: # Split in such a way as to preserve # passwords that contain ':'. user_name, password = base64.decodestring(auth).split(':', 1) except (binascii.Error, ValueError): self._log_cef('Authorization header is badly encoded', 7, environ) raise HTTPUnauthorized('Invalid token') # let's reject the call if the url is not owned by the user if (username is not None and user_name != username): self._log_cef('Username Does Not Match URL', 7, environ, config, user_name, AUTH_FAILURE) raise HTTPUnauthorized() # if this is an email, hash it. Save the original for logging and # debugging. remote_user_original = user_name try: user_name = extract_username(user_name) except UnicodeError: self._log_cef('Username contains invalid characters', 7, environ) raise HTTPBadRequest( 'Invalid characters specified in ' + 'username', {}, 'Username must be BIDI ' + 'compliant UTF-8') # let's try an authentication # the authenticate_user API takes a unicode UTF-8 for the password try: password = password.decode('utf8') except UnicodeDecodeError: self._log_cef('Password is not utf-8 encoded', 7, environ) raise HTTPUnauthorized() #first we need to figure out if this is old-style or new-style auth if hasattr(self.backend, 'generate_reset_code'): # XXX to be removed once we get the proper fix see bug #662859 if (hasattr(self.backend, 'check_node') and self.backend.check_node): user_id = self.backend.authenticate_user( user_name, password, environ.get('HTTP_HOST')) else: user_id = self.backend.authenticate_user( user_name, password) request.user = User(user_name, user_id) else: user = User(user_name) credentials = {"username": user_name, "password": password} attrs = [] check_node = self.config.get('auth.check_node') if check_node: attrs.append('syncNode') user_id = self.backend.authenticate_user( user, credentials, attrs) if not user_id: user_id = None user = None else: if (check_node and user.get('syncNode') != environ.get('HTTP_HOST')): self._log_cef('User authenticated to wrong node', 7, environ) user_id = None user = None request.user = user if user_id is None: err_user = user_name if remote_user_original is not None and \ user_name != remote_user_original: err_user += ' (%s)' % (remote_user_original) self._log_cef('User Authentication Failed', 5, environ, config, err_user, AUTH_FAILURE) raise HTTPUnauthorized() # we're all clear ! setting up REMOTE_USER request.remote_user = environ['REMOTE_USER'] = user_name # we also want to keep the password in clear text to reuse it # and remove it from the environ request.user_password = password request._authorization = environ['HTTP_AUTHORIZATION'] del environ['HTTP_AUTHORIZATION'] return user_id