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 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 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_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 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 set_password(userid, data):
    """
    Set new password for User account

    Args:
        userid: Path Parameter - Unique ID of User Resource (int)
        data - dictionary with Set new password for User attributes, loaded from
            Request body JSON and validated with models.set_password_body_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('Not Found', 404)

    if current_user.userid != userid and not current_user.get_admin():
        current_app.logger.warning(
            f'set_password(userid={userid}) failed. Userid={current_user.userid} not authorized'
            )
        return make_response('Unauthorized', 401)

    user.set_password(data['password'])

    return('OK', 200)
 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 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_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')
Пример #14
0
def add_user_to_group(groupid, userid):
    """
    Add User to Group

    Args:
        groupid: Path Parameter - Unique ID of Group Resource (int)
        userid: Path Parameter - Unique ID of User Resource (int)
        JWT Baerer Authorization in request.headers - admin privilege required

    Returns:
        Confirmation or Error Message
    """
    group = Group.retrieve(groupid)
    if group == None:
        current_app.logger.warning(
            f'add_user_to_group() Group with id={groupid} not found')
        return make_response('Group or User not found', 404)
    user = User.retrieve(userid)
    if user == None:
        current_app.logger.warning(
            f'add_user_to_group() User with id={userid} not found')
        return make_response('Group or User not found', 404)
    if user in group.users:
        return 'User already in the Group', 200
    else:
        group.add_member(user)
        return 'User added to the Group', 201
 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_06_create_user_with_duplicate_name(self):
     """Test User create with duplicate name"""
     with self.app.app_context():
         with self.assertRaises(Exception):
             duplicate = User(username='******',
                              firstname='John',
                              lastname='Example',
                              email='*****@*****.**',
                              phone='123-444-5555')
    def test_01_users_create(self):
        """Create test Users in database"""
        with self.app.app_context():
            johne = User(username='******',
                         firstname='John',
                         lastname='Example',
                         email='*****@*****.**',
                         phone='123-444-5555')
            self.assertIsNotNone(johne)
            self.assertGreater(johne.userid, 0)

            self.johne_userid = johne.userid

            lindas = User(username='******',
                          firstname='Linda',
                          lastname='Sample',
                          email='*****@*****.**',
                          phone='123-444-6666')
            self.assertIsNotNone(lindas)
            self.assertGreater(lindas.userid, 0)
            self.assertNotEqual(johne.userid, lindas.userid)

            lin = User(username='******',
                       firstname='Li',
                       lastname='Nerd',
                       email='*****@*****.**',
                       phone='123-444-7777')
            self.assertIsNotNone(lin)
            self.assertGreater(lin.userid, 0)

            removeme = User(username='******',
                            firstname='Remove',
                            lastname='Me',
                            email='*****@*****.**',
                            phone='123-444-8888')
            self.assertIsNotNone(removeme)
            self.assertGreater(removeme.userid, 0)
def read_admin_status(userid):
    """
    Read User account admin privilege status

    Args:
        userid: Path Parameter - Unique ID of User Resource (int)
        JWT Baerer Authorization in request.headers - admin privilege required

    Returns:
        JSON Object with admin status or Error Message
    """
    user = User.retrieve(userid)
    if not user:
        return('Not Found', 404)

    return jsonify({'isAdmin': user.get_admin()})
def retrieve_user(userid):
    """
    Retrieve User Resource Representation

    Args:
        userid: Path Parameter - Unique ID of User Resource (int)
        X-API-Key in request.headers

    Returns:
        JSON Object with User Resource Representation or Error Message
    """
    user = User.retrieve(userid)
    if user:
        return jsonify(user_schema.dump(user))
    else:
        return("Not Found", 404)
def clear_lock(userid):
    """
    Clear User lock status

    Args:
        userid: Path Parameter - Unique ID of User Resource (int)
        JWT Baerer Authorization in request.headers - admin privilege required

    Returns:
        Confirmation or Error Message
    """
    user = User.retrieve(userid)
    if not user:
        return('Not Found', 404)

    user.unlock()
    return('OK', 200)
def remoke_admin_status(userid):
    """
    Revoke admin privilege from User

    Args:
        userid: Path Parameter - Unique ID of User Resource (int)
        JWT Baerer Authorization in request.headers - admin privilege required

    Returns:
        Confirmation or Error Message
    """
    user = User.retrieve(userid)
    if not user:
        return('Not Found', 404)

    user.revoke_admin()
    return('OK', 200)
def delete_user(userid):
    """
    Delete User Resource

    Args:
        userid: Path Parameter - Unique ID of User Resource (int)
        JWT Baerer Authorization in request.headers - admin privilege required

    Returns:
        Confirmation or Error Message
    """
    user = User.retrieve(userid)
    if user:
        if userid == current_user.userid:
            current_app.logger.warning(
                f'delete_user(userid={userid}) failed. Cannot delete self'
                )
            return make_response('Unauthorized', 401)
        else:
            user.remove()
            return make_response('OK', 200)
    else:
        return make_response('Not found', 404)
Пример #23
0
def delete_user_from_group(groupid, userid):
    """
    Delete User from Group

    Args:
        groupid: Path Parameter - Unique ID of Group Resource (int)
        userid: Path Parameter - Unique ID of User Resource (int)
        JWT Baerer Authorization in request.headers - admin privilege required

    Returns:
        Confirmation or Error Message
    """
    group = Group.retrieve(groupid)
    if group == None:
        current_app.logger.warning(
            f'add_user_to_group() Group with id={groupid} not found')
        return make_response('Group or User not found', 404)
    user = User.retrieve(userid)
    if user == None:
        current_app.logger.warning(
            f'add_user_to_group() User with id={userid} not found')
        return make_response('Group or User not found', 404)
    group.remove_member(user)
    return 'User deleted from Group', 200
    def test_1_login(self):
        """Test Login operation"""
        # This test assumes that 'admin' and 'lindas' are in Users,
        # both have password 'pass', are unlocked and can login
        # 'admin' must have following values:
        # username : admin
        # firstname : Admin
        # lastname : User
        # email: [email protected]
        # phone : 123-444-5555
        # Test assumes user 'locked' is in Database with password 'pass',
        # is locked and unable to login
        # Test assumes there is no user 'admin1' in Database
        # Test assumes application configuration parameters
        # MAX_FAILED_LOGIN_ATTEMPTS and JWT_ACCESS_TOKEN_EXPIRES are set
        #
        # Test successful login operation - 200
        admin_login_data = {'username': '******', 'password': '******'}
        resp = self.client.post('/login', json=admin_login_data)
        self.assertEqual(resp.status_code, 200)
        # Assert response data is JSON
        self.assertTrue(resp.is_json)
        resp_data = resp.get_json()
        # Assert Response JSON has expected fields
        self.assertSetEqual({'jwtToken', 'userHref'}, set(resp_data.keys()))
        # Decode and check returned JWT
        with self.app.app_context():
            decoded_token = flask_jwt_extended.decode_token(
                resp_data['jwtToken'])
        # Assert JWT has 'identity', 'iat' and 'exp' fields
        self.assertIn('identity', decoded_token)
        self.assertIn('iat', decoded_token)
        self.assertIn('exp', decoded_token)
        # Assert DB object with userid==decoded_token['identity'] is in fact 'admin'
        with self.app.app_context():
            user = User.retrieve(decoded_token['identity'])
        self.assertEqual(user.username, 'admin')
        # Assert JWT expiration 'exp' is set to Application Config value
        config_jwt_exp = self.app.config['JWT_ACCESS_TOKEN_EXPIRES']
        if type(config_jwt_exp) is int:
            self.assertEqual(decoded_token['exp'] - decoded_token['iat'],
                             config_jwt_exp)
        elif type(config_jwt_exp) is datetime.timedelta:
            self.assertEqual(
                datetime.timedelta(seconds=decoded_token['exp'] -
                                   decoded_token['iat']), config_jwt_exp)
        else:
            # We have unsupported type of JWT_ACCESS_TOKEN_EXPIRES.
            # flask_jwt_extended.create_token() should fail in this case,
            # resulting in failure of first Login operation.
            # Code below should not get reached
            self.fail(
                msg=
                f'Config parameter JWT_ACCESS_TOKEN_EXPIRES has unexpected type {type(config_jwt_exp)}'
            )
        # Assert 'userHref' is 'admin' User URI
        resp1 = self.client.get(
            resp_data['userHref'],
            headers={'X-API-Key': self.app.config['API_KEY']})
        self.assertEqual(resp1.status_code, 200)
        resp1_data = resp1.get_json()
        expected_admin_data = {
            'userid': user.userid,
            'username': '******',
            'firstname': 'Admin',
            'lastname': 'User',
            'contactInfo': {
                'email': '*****@*****.**',
                'phone': '123-444-5555'
            }
        }
        self.assertDictEqual(resp1_data, expected_admin_data)

        # Test correct username and incorrect password - 401
        bad_pass_login_data = {'username': '******', 'password': '******'}
        resp = self.client.post('/login', json=bad_pass_login_data)
        self.assertEqual(resp.status_code, 401)
        # Assert no JSON in response data
        self.assertFalse(resp.is_json)
        self.assertIsNone(resp.get_json())

        # Test incorrect username - 401
        bad_username_login_data = {'username': '******', 'password': '******'}
        resp = self.client.post('/login', json=bad_username_login_data)
        self.assertEqual(resp.status_code, 401)
        # Assert no JSON in response data
        self.assertFalse(resp.is_json)
        self.assertIsNone(resp.get_json())

        # Test locked account - 401
        locked_login_data = {'username': '******', 'password': '******'}
        resp = self.client.post('/login', json=locked_login_data)
        self.assertEqual(resp.status_code, 401)
        # Assert no JSON in response data
        self.assertFalse(resp.is_json)
        self.assertIsNone(resp.get_json())

        # Test incorrect Request body data - 400
        bad_login_data = [{
            'user': '******',
            'password': '******'
        }, {
            'username': '******'
        }, {
            'username': '******',
            'password': 5
        }, {
            'username': "******",
            'password': ''
        }]
        for data in bad_login_data:
            resp = self.client.post('/login', json=data)
            self.assertEqual(resp.status_code,
                             400,
                             msg=f'bad_login_data={bad_login_data}')
            # Assert no JSON in response data
            self.assertFalse(resp.is_json,
                             msg=f'bad_login_data={bad_login_data}')
            self.assertIsNone(resp.get_json(),
                              msg=f'bad_login_data={bad_login_data}')

        # Test invalid JSON format data
        bad_json = '{"username": "******", "password": "******"'
        resp = self.client.post('/login',
                                data=bad_json,
                                headers={'Content-Type': 'application/json'})
        self.assertEqual(resp.status_code, 400)
        # Assert no JSON in response data
        self.assertFalse(resp.is_json)
        self.assertIsNone(resp.get_json())

        # Test 'text/plain' Request body format - 415
        text_data = 'random text'
        resp = self.client.post('/login', data=text_data)
        self.assertEqual(resp.status_code, 415)
        # Assert no JSON in response data
        self.assertFalse(resp.is_json)
        self.assertIsNone(resp.get_json())

        # Test User account lock due to unsuccessful logins
        # Reset LOCK_TIMEOUT to very short period
        lock_timeout_config = self.app.config['LOCK_TIMEOUT']
        temporary_lock_timeout = 1
        self.app.config['LOCK_TIMEOUT'] = datetime.timedelta(
            seconds=temporary_lock_timeout)
        # Confirm User lindas is unlocked and can log in
        lindas_login_data = {'username': '******', 'password': '******'}
        resp = self.client.post('/login', json=lindas_login_data)
        self.assertEqual(resp.status_code, 200)
        # Make MAX_FAILED_LOGIN_ATTEMPTS failed login attempts
        # User account should not get locked yet
        incorrect_data = {'username': '******', 'password': '******'}
        for i in range(self.app.config['MAX_FAILED_LOGIN_ATTEMPTS']):
            resp = self.client.post('/login', json=incorrect_data)
            self.assertEqual(resp.status_code, 401, msg=f'i={i}')
        # Make correct login to verify that account is not locked
        # and to clear failed logins counter
        resp = self.client.post('/login', json=lindas_login_data)
        self.assertEqual(resp.status_code, 200)
        # Make MAX_FAILED_LOGIN_ATTEMPTS + 1 failed login attempts
        # User account should get locked in the end
        for i in range(self.app.config['MAX_FAILED_LOGIN_ATTEMPTS'] + 1):
            resp = self.client.post('/login', json=incorrect_data)
            self.assertEqual(resp.status_code, 401, msg=f'i={i}')
        # Assert User cannot login now
        resp = self.client.post('/login', json=lindas_login_data)
        self.assertEqual(resp.status_code, 401)
        # Sleep for temporary lock timeout (1 sec)
        time.sleep(temporary_lock_timeout)
        # Assert lindas can login again now
        resp = self.client.post('/login', json=lindas_login_data)
        self.assertEqual(resp.status_code, 200)
        # Restore LOCK_TIMEOUT
        self.app.config['LOCK_TIMEOUT'] = lock_timeout_config
    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 setUpClass(cls):
        """Initialize app and create test_client"""

        if 'APPUSERS_CONFIG' not in os.environ:
            os.environ['APPUSERS_CONFIG'] = 'test_config.py'

        cls.app = create_app()
        cls.client = cls.app.test_client()

        with cls.app.app_context():
            # Clear existing data in test database
            meta = db.metadata
            for table in reversed(meta.sorted_tables):
                db.session.execute(table.delete())
            db.session.commit()
            # Create user with admin privilege
            admin_user = User(username='******',
                              firstname='Admin',
                              lastname='User',
                              email='*****@*****.**',
                              phone='123-444-5555')
            admin_user.set_password('pass')
            admin_user.grant_admin()
            # Create other Users and Groups
            johne = User(username='******',
                         firstname='John',
                         lastname='Example',
                         email='*****@*****.**',
                         phone='123-444-6666')
            lindas = User(username='******',
                          firstname='Linda',
                          lastname='Sample',
                          email='*****@*****.**',
                          phone='123-444-7777')
            lindas.set_password('pass')
            lin = User(username='******',
                       firstname='Li',
                       lastname='Nerd',
                       email='*****@*****.**',
                       phone='123-444-8888')
            locked = User(username='******',
                          firstname='Locked',
                          lastname='Account',
                          email='*****@*****.**',
                          phone='123-444-9999')
            locked.set_password('pass')
            locked.set_lock()
            devs = Group(groupname='devs', description='Developers')
            testers = Group(groupname='testers', description='Testers')
# Module level set-up for all unit tests in this file
if 'APPUSERS_CONFIG' not in os.environ:
    os.environ['APPUSERS_CONFIG'] = 'test_config.py'

app = create_app()
with app.app_context():
    # Clear existing data in test database
    meta = db.metadata
    for table in reversed(meta.sorted_tables):
        db.session.execute(table.delete())
    db.session.commit()
    # Create user with admin privilege
    admin_user = User(username='******',
                      firstname='Admin',
                      lastname='User',
                      email='*****@*****.**',
                      phone='123-444-5555')
    admin_user.set_password('pass')
    admin_user.grant_admin()

# End of module level set-up


def login(client, username, password):
    """Login helper function

    User login with application test client.

    Arguments:
        client - Flask Test Client
Пример #28
0
def load_current_user(identity):
    """Load User object for JWTManager, using Token's identity"""
    return User.retrieve(identity)
Пример #29
0
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)