def test_08_remove_user(self): """Test User.remove() method""" with self.app.app_context(): filters = {'username': '******'} users = User.get_list(filters) self.assertEqual(len(users), 1) users[0].remove() users = User.get_list(filters) self.assertEqual(len(users), 0)
def test_04_user_get_list_by_firstname_lastname(self): """Test User.get_list() method with firstname and lastname""" with self.app.app_context(): filters = {'firstname': 'Linda', 'lastname': 'Sample'} users = User.get_list(filters) self.assertEqual(len(users), 1) self.assertEqual(users[0].username, 'lindas')
def test_03_user_get_list_by_username(self): """Test User.get_list() method with username=johne""" with self.app.app_context(): filters = {'username': '******'} users = User.get_list(filters) self.assertEqual(len(users), 1) self.assertEqual(users[0].username, 'johne')
def test_02_user_get_list_empty_filters(self): """Test User.get_list() method with empty filters dictionary""" with self.app.app_context(): filters = {} users = User.get_list(filters) self.assertEqual(len(users), 4) self.assertIsInstance(users[0], User)
def create_user(data): """ Create User Resource Args: data - dictionary with all User Resource attributes, loaded from Request body JSON and validated with models.user_schema JWT Baerer Authorization in request.headers - admin privilege required Returns: Confirmation or Error Message 'Location' Response Header """ if User.get_list({'username': data['username']}): current_app.logger.warning( f'create_user() failed. Username={data["username"]} already exists' ) return make_response('Bad request', 400) # Ignore 'userid' if present in request data # if 'userid' in data: # del(data['userid']) # new_user = User(**data) response = make_response('Created', 201) response.headers['Location'] = url_for( 'users.retrieve_user', userid=new_user.userid, _external=True ) return response
def list_users(): """ List and filter Users Collection Args: request.args - Query String parameters: filtering, sorting and pagination X-API-Key in request.headers Returns: JSON array of User Resource Representations """ try: filters = users_filters_schema.load(request.args) except ValidationError as e: current_app.logger.warning( f'list_group() Query String validation failed.\nValidationError: {e}' ) return make_response('Bad request', 400) filtered_list = User.get_list(filters) if 'return_fields' in filters: return_fields = filters['return_fields'].split(',') + ['href'] users = UserListSchema(many=True, only=return_fields).dump(filtered_list) else: users = user_list_schema.dump(filtered_list) return jsonify(users)
def update_user(userid, data): """ Update User Resource Representation Args: userid: Path Parameter - Unique ID of User Resource (int) data - dictionary with partial User Resource attributes, loaded from Request body JSON and validated with models.user_schema JWT Baerer Authorization in request.headers - account owner or admin privilege required Returns: Confirmation or Error Message """ user = User.retrieve(userid) if not user: return make_response('Not found', 404) if 'username' in data and User.get_list({'username': data['username']}): current_app.logger.warning( f'create_user() failed. Username={data["username"]} already exists' ) return make_response('Bad request', 400) if current_user.userid != userid and not current_user.get_admin(): current_app.logger.warning( f'update_user(userid={userid}) failed. Userid={current_user.userid} not authorized' ) return make_response('Unauthorized', 401) user.update(**data) return make_response('OK', 200)
def test_12_user_get_list_pagination(self): """Test User.get_list() pagination filters""" with self.app.app_context(): # Database should contain johne, lindas and lin filters = {'sortBy': '-lastname', 'offset': 1, 'limit': 2} users = User.get_list(filters) self.assertEqual(len(users), 2) self.assertEqual(users[0].username, 'lin')
def test_09_set_password(self): """Test User.set_password() method""" with self.app.app_context(): filters = {'username': '******'} users = User.get_list(filters) self.assertEqual(len(users), 1) users[0].set_password('pass') user = User.retrieve(users[0].userid) self.assertEqual(user.password, 'pass')
def test_05_user_retrieve(self): """Test User.retrieve() method""" with self.app.app_context(): filters = {'username': '******'} users = User.get_list(filters) self.assertEqual(len(users), 1) user = User.retrieve(users[0].userid) self.assertIsNotNone(user) self.assertIsInstance(user, User) self.assertEqual(users[0].username, user.username)
def test_11_admin(self): """Test User admin methods""" with self.app.app_context(): filters = {'username': '******'} users = User.get_list(filters) self.assertEqual(len(users), 1) users[0].grant_admin() user = User.retrieve(users[0].userid) self.assertTrue(user.get_admin()) user.revoke_admin() user = User.retrieve(users[0].userid) self.assertFalse(user.get_admin())
def test_07_update_user(self): """Test User.retrieve() method""" with self.app.app_context(): filters = {'username': '******'} users = User.get_list(filters) self.assertEqual(len(users), 1) user = User.retrieve(users[0].userid) user.update(lastname='example') user = User.retrieve(users[0].userid) self.assertEqual(users[0].username, user.username) self.assertEqual(user.lastname, 'example') user.update(lastname='Example')
def test_10_lock(self): """Test User lock methods""" with self.app.app_context(): filters = {'username': '******'} users = User.get_list(filters) self.assertEqual(len(users), 1) users[0].set_lock() user = User.retrieve(users[0].userid) self.assertTrue(user.get_lock()) user.unlock() user = User.retrieve(users[0].userid) self.assertFalse(user.get_lock()) self.assertEqual(user.failed_logins, 0) self.assertIsNone(user.last_failed_login)
def test_3_retrieve_user(self): """Test Retrieve User Resource Representation operation""" # This test assumes 'admin' is in Database with password 'pass', # is unlocked and can log in, has admin privilege # Test expects User 'johne' in Database with following values: # username : johne # firstname : John # lastname : Example # email: [email protected] # phone: 123-444-6666 # # Test correct response - 200 # Get 'johne' userid directly from Database with self.app.app_context(): johne_userid = User.get_list({'username': '******'})[0].userid # Retrieve User Representation of 'johne' resp = self.client.get( f'/users/{johne_userid}', headers={'X-API-Key': self.app.config['API_KEY']}) self.assertEqual(resp.status_code, 200) # Assert response data is JSON self.assertTrue(resp.is_json) user_data = resp.get_json() # Assert response data is exactly what's expected johne = { 'userid': johne_userid, 'username': '******', 'firstname': 'John', 'lastname': 'Example', 'contactInfo': { 'email': '*****@*****.**', 'phone': '123-444-6666' } } self.assertDictEqual(johne, user_data) # Test Unauthorized response - 401 jwt_token = self.login('admin', 'pass') bad_headers = [ # Missing Authorization header {}, # Invalid X-API-Key { 'X-API-Key': self.app.config['API_KEY'] + 'incorrect' }, # valid JWT Bearer token instead of X-API-Key { 'Authorization': f'Bearer {jwt_token}' } ] # Test bad authorization headers for headers in bad_headers: resp = self.client.get(f'/users/{johne_userid}', headers=headers) # Assert Status Code is 401 self.assertEqual(resp.status_code, 401, msg=f'headers={headers}') # Assert no JSON in response data self.assertFalse(resp.is_json, msg=f'headers={headers}') self.assertIsNone(resp.get_json(), msg=f'headers={headers}') # Test response Not Found - 404 # Find largest userid in Database and add 1 with self.app.app_context(): non_existent_userid = User.get_list({'sortBy': '-userid' })[0].userid + 1 resp = self.client.get( f'/users/{non_existent_userid}', headers={'X-API-Key': self.app.config['API_KEY']}) self.assertEqual(resp.status_code, 404) # Assert no JSON in response data self.assertFalse(resp.is_json) self.assertIsNone(resp.get_json())
def login(data): """ Login operation Args: data - dictionary with all Login operation attributes, loaded from Request body JSON and validated with models.login_body_schema Returns: JSON with JWT Token and User link or Error Message. Token contains 'identity' field set to userid of authenticated User. Token expires after JWT_ACCESS_TOKEN_EXPIRES. """ user_list = User.get_list({'username': data['username']}) if len(user_list) == 0: current_app.logger.warning( f'authenticate_user() failed. No such user: {data["username"]}' ) return make_response('Unathorized', 401) else: user = user_list[0] # check if User account is locked, lift lock if lock interval passed if user.last_failed_login and datetime.now() > user.last_failed_login + current_app.config['LOCK_TIMEOUT']: user.unlock() current_app.logger.info( f'authenticate_user() - userid={user.userid} unlocked due to lock timeout' ) if user.get_lock(): current_app.logger.warning( f'authenticate_user() failed. Userid={user.userid} is locked' ) return make_response('Unathorized', 401) if safe_str_cmp(user.password.encode('utf-8'), data['password'].encode('utf-8')): # clear lock info on successful login user.unlock() access_token = create_access_token(identity=user.userid) response = {'jwtToken': access_token, 'userHref': url_for( 'users.retrieve_user', userid=user.userid, _external=True )} current_app.logger.info( f'authenticate_user() successful. {user.username} logged in' ) return(jsonify(response), 200) else: current_app.logger.warning( f'authenticate_user() failed. Incorrect password for userid={user.userid}' ) # update lock status user.failed_logins = user.failed_logins + 1 user.last_failed_login = datetime.now() # consider datetime.utcnow() if user.failed_logins > current_app.config['MAX_FAILED_LOGIN_ATTEMPTS']: user.set_lock() current_app.logger.warning( f'Too many failed logins for userid={user.userid}, account locked' ) user.update() return make_response('Unauthorized', 401)