def test_change_hash_type(app, sqlalchemy_datastore): init_app_with_options(app, sqlalchemy_datastore, **{ 'SECURITY_PASSWORD_HASH': 'plaintext', 'SECURITY_PASSWORD_SALT': None, 'SECURITY_PASSWORD_SCHEMES': ['bcrypt', 'plaintext'] }) app.config['SECURITY_PASSWORD_HASH'] = 'bcrypt' app.config['SECURITY_PASSWORD_SALT'] = 'salty' app.security = Security( app, datastore=sqlalchemy_datastore, register_blueprint=False) client = app.test_client() response = client.post( '/login', data=dict( email='*****@*****.**', password='******')) assert response.status_code == 302 response = client.get('/logout') response = client.post( '/login', data=dict( email='*****@*****.**', password='******')) assert response.status_code == 302
def test_flash_messages_off(app, sqlalchemy_datastore, get_message): init_app_with_options(app, sqlalchemy_datastore, **{ 'SECURITY_FLASH_MESSAGES': False }) client = app.test_client() response = client.get('/profile') assert get_message('LOGIN') not in response.data
def test_get_user(app, datastore): # The order of identity attributes is important for testing. # drivers like psycopg2 will abort the transaction if they throw an # error and not continue on so we want to check that case of passing in # a string for a numeric field and being able to move onto the next # column. init_app_with_options( app, datastore, **{ "SECURITY_USER_IDENTITY_ATTRIBUTES": ( "email", "security_number", "username", ) }) with app.app_context(): user_id = datastore.find_user(email="*****@*****.**").id user = datastore.get_user(user_id) assert user is not None user = datastore.get_user("*****@*****.**") assert user is not None user = datastore.get_user("matt") assert user is not None # Regression check (make sure we don't match wildcards) user = datastore.get_user("%lp.com") assert user is None # Verify that numeric non PK works user = datastore.get_user(123456) assert user is not None
def test_user_loader(app, sqlalchemy_datastore): # user_loader now tries first to match fs_uniquifier then match user.id # While we know that fs_uniquifier is a string - we don't know what user.id is # and we know that psycopg2 is really finicky about this. from flask_security.core import _user_loader init_app_with_options(app, sqlalchemy_datastore) with app.app_context(): jill = sqlalchemy_datastore.find_user(email="*****@*****.**") # normal case user = _user_loader(jill.fs_uniquifier) assert user.email == "*****@*****.**" # send in an int - make sure it is cast to string for check against # fs_uniquifier user = _user_loader(1000) assert not user # with psycopg2 this will lock up DB but since we pass exceptions we can # check this by trying some other DB operation jill = sqlalchemy_datastore.find_user(email="*****@*****.**") assert jill # This works since DBs seem to try to cast to underlying type user = _user_loader("10") jill = sqlalchemy_datastore.find_user(email="*****@*****.**") assert jill # Since this doesn't match an existing fs_uniqifier, and user.id is an int # this will make pyscopg2 angry. This test verifies that we don't in fact # try to take a string and use it to lookup an int. user = _user_loader("someunknown") jill = sqlalchemy_datastore.find_user(email="*****@*****.**") assert jill
def test_login_with_bcrypt_enabled(app, sqlalchemy_datastore): init_app_with_options(app, sqlalchemy_datastore, **{ 'SECURITY_PASSWORD_HASH': 'bcrypt', 'SECURITY_PASSWORD_SALT': 'salty' }) response = authenticate(app.test_client(), follow_redirects=True) assert b'Home Page' in response.data
def test_modify_permissions(app, datastore): ds = datastore if not hasattr(ds.role_model, "permissions"): return init_app_with_options(app, ds) with app.app_context(): perms = {"read", "write"} ds.create_role(name="test1", permissions=perms) ds.commit() t1 = ds.find_role("test1") assert perms == t1.get_permissions() orig_update_time = t1.update_datetime assert t1.update_datetime <= datetime.datetime.utcnow() t1.add_permissions("execute") ds.commit() t1 = ds.find_role("test1") assert perms.union({"execute"}) == t1.get_permissions() t1.remove_permissions("read") ds.commit() t1 = ds.find_role("test1") assert {"write", "execute"} == t1.get_permissions() assert t1.update_datetime > orig_update_time
def test_single_hash_should_have_no_salt(app, sqlalchemy_datastore): with raises(RuntimeError): init_app_with_options(app, sqlalchemy_datastore, **{ 'SECURITY_PASSWORD_HASH': 'bcrypt', 'SECURITY_PASSWORD_SALT': 'salty', 'SECURITY_PASSWORD_SINGLE_HASH': True, })
def test_missing_hash_salt_option(app, sqlalchemy_datastore): with raises(RuntimeError): init_app_with_options(app, sqlalchemy_datastore, **{ 'SECURITY_PASSWORD_HASH': 'bcrypt', 'SECURITY_PASSWORD_SALT': None, 'SECURITY_PASSWORD_SINGLE_HASH': False, })
def test_verify_password_bcrypt(app, sqlalchemy_datastore): init_app_with_options(app, sqlalchemy_datastore, **{ 'SECURITY_PASSWORD_HASH': 'bcrypt', 'SECURITY_PASSWORD_SALT': 'salty' }) with app.app_context(): assert verify_password('pass', encrypt_password('pass'))
def test_change_hash_type(app, sqlalchemy_datastore): init_app_with_options( app, sqlalchemy_datastore, **{ "SECURITY_PASSWORD_HASH": "plaintext", "SECURITY_PASSWORD_SALT": None, "SECURITY_PASSWORD_SCHEMES": ["bcrypt", "plaintext"], }) app.config["SECURITY_PASSWORD_HASH"] = "bcrypt" app.config["SECURITY_PASSWORD_SALT"] = "salty" app.security = Security(app, datastore=sqlalchemy_datastore, register_blueprint=False) client = app.test_client() response = client.post("/login", data=dict(email="*****@*****.**", password="******")) assert response.status_code == 302 response = client.get("/logout") response = client.post("/login", data=dict(email="*****@*****.**", password="******")) assert response.status_code == 302
def test_passwordless_and_two_factor_configuration_mismatch(app, sqlalchemy_datastore): with pytest.raises(ValueError): init_app_with_options( app, sqlalchemy_datastore, **{"SECURITY_TWO_FACTOR": True, "SECURITY_TWO_FACTOR_ENABLED_METHODS": []} )
def test_addition_identity_attributes(app, sqlalchemy_datastore): init_app_with_options( app, sqlalchemy_datastore, **{'SECURITY_USER_IDENTITY_ATTRIBUTES': ('email', 'username')}) client = app.test_client() response = authenticate(client, email='matt', follow_redirects=True) assert b'Hello [email protected]' in response.data
def test_change_hash_type(app, sqlalchemy_datastore): init_app_with_options( app, sqlalchemy_datastore, **{ 'SECURITY_PASSWORD_HASH': 'plaintext', 'SECURITY_PASSWORD_SALT': None, 'SECURITY_PASSWORD_SCHEMES': ['bcrypt', 'plaintext'] }) app.config['SECURITY_PASSWORD_HASH'] = 'bcrypt' app.config['SECURITY_PASSWORD_SALT'] = 'salty' app.security = Security(app, datastore=sqlalchemy_datastore, register_blueprint=False) client = app.test_client() response = client.post('/login', data=dict(email='*****@*****.**', password='******')) assert response.status_code == 302 response = client.get('/logout') response = client.post('/login', data=dict(email='*****@*****.**', password='******')) assert response.status_code == 302
def test_addition_identity_attributes(app, sqlalchemy_datastore): init_app_with_options(app, sqlalchemy_datastore, **{ 'SECURITY_USER_IDENTITY_ATTRIBUTES': ('email', 'username') }) client = app.test_client() response = authenticate(client, email='matt', follow_redirects=True) assert b'Hello [email protected]' in response.data
def test_addition_identity_attributes(app, sqlalchemy_datastore): init_app_with_options( app, sqlalchemy_datastore, **{"SECURITY_USER_IDENTITY_ATTRIBUTES": ("email", "username")}) client = app.test_client() response = authenticate(client, email="matt", follow_redirects=True) assert b"Welcome [email protected]" in response.data
def test_logout_methods(app, sqlalchemy_datastore, logout_methods): init_app_with_options(app, sqlalchemy_datastore, **{"SECURITY_LOGOUT_METHODS": logout_methods}) client = app.test_client() authenticate(client) response = client.get("/logout", follow_redirects=True) if "GET" in logout_methods: assert response.status_code == 200 authenticate(client) else: assert response.status_code == 405 # method not allowed response = client.post("/logout", follow_redirects=True) if "POST" in logout_methods: assert response.status_code == 200 else: assert response.status_code == 405 # method not allowed
def test_find_role(app, datastore): init_app_with_options(app, datastore) role = datastore.find_role('admin') assert role is not None role = datastore.find_role('bogus') assert role is None
def test_verify_password_bcrypt_single_hash(app, sqlalchemy_datastore): init_app_with_options(app, sqlalchemy_datastore, **{ 'SECURITY_PASSWORD_HASH': 'bcrypt', 'SECURITY_PASSWORD_SALT': None, 'SECURITY_PASSWORD_SINGLE_HASH': True, }) with app.app_context(): assert verify_password('pass', encrypt_password('pass'))
def test_delete_user(app, datastore): init_app_with_options(app, datastore) user = datastore.find_user(email='*****@*****.**') datastore.delete_user(user) datastore.commit() user = datastore.find_user(email='*****@*****.**') assert user is None
def test_bcrypt_speed(app, sqlalchemy_datastore): init_app_with_options( app, sqlalchemy_datastore, **{ "SECURITY_PASSWORD_HASH": "bcrypt", "SECURITY_PASSWORD_SALT": "salty", "SECURITY_PASSWORD_SINGLE_HASH": False, }) with app.app_context(): print(timeit.timeit(lambda: hash_password("pass"), number=100))
def test_login_with_bcrypt_enabled(app, sqlalchemy_datastore): init_app_with_options( app, sqlalchemy_datastore, **{ "SECURITY_PASSWORD_HASH": "bcrypt", "SECURITY_PASSWORD_SALT": "salty", "SECURITY_PASSWORD_SINGLE_HASH": False, }) response = authenticate(app.test_client(), follow_redirects=True) assert b"Home Page" in response.data
def test_delete_user(app, datastore): init_app_with_options(app, datastore) with app.app_context(): user = datastore.find_user(email="*****@*****.**") datastore.delete_user(user) datastore.commit() user = datastore.find_user(email="*****@*****.**") assert user is None
def test_verify_password_bcrypt_single_hash(app, sqlalchemy_datastore): init_app_with_options( app, sqlalchemy_datastore, **{ "SECURITY_PASSWORD_HASH": "bcrypt", "SECURITY_PASSWORD_SALT": None, "SECURITY_PASSWORD_SINGLE_HASH": True, }) with app.app_context(): assert verify_password("pass", hash_password("pass"))
def test_find_role(app, datastore): init_app_with_options(app, datastore) with app.app_context(): role = datastore.find_role("admin") assert role is not None role = datastore.find_role("bogus") assert role is None
def test_get_permissions(app, datastore): """ Verify that role.permissions = None works. """ ds = datastore if not hasattr(ds.role_model, "permissions"): return init_app_with_options(app, ds) with app.app_context(): t1 = ds.find_role("simple") assert set() == t1.get_permissions()
def test_verify_password_argon2(app, sqlalchemy_datastore): init_app_with_options(app, sqlalchemy_datastore, **{"SECURITY_PASSWORD_HASH": "argon2"}) with app.app_context(): hashed_pwd = hash_password("pass") assert verify_password("pass", hashed_pwd) assert "t=10" in hashed_pwd # Verify double hash assert verify_password("pass", argon2.hash(get_hmac("pass")))
def test_argon2_speed(app, sqlalchemy_datastore): init_app_with_options( app, sqlalchemy_datastore, **{ "SECURITY_PASSWORD_HASH": "argon2", "SECURITY_PASSWORD_HASH_PASSLIB_OPTIONS": { "argon2__rounds": 10 }, }) with app.app_context(): print("Hash time for {} iterations: {}".format( 100, timeit.timeit(lambda: hash_password("pass"), number=100)))
def test_create_user_with_roles(app, datastore): init_app_with_options(app, datastore) role = datastore.find_role('admin') datastore.commit() user = datastore.create_user(email='*****@*****.**', username='******', password='******', roles=[role]) datastore.commit() user = datastore.find_user(email='*****@*****.**') assert user.has_role('admin') is True
def test_verify_password_backward_compatibility(app, sqlalchemy_datastore): init_app_with_options( app, sqlalchemy_datastore, **{ 'SECURITY_PASSWORD_HASH': 'bcrypt', 'SECURITY_PASSWORD_SINGLE_HASH': False, 'SECURITY_PASSWORD_SCHEMES': ['bcrypt', 'plaintext'] }) with app.app_context(): # double hash assert verify_password('pass', encrypt_password('pass')) # single hash assert verify_password('pass', plaintext.hash('pass'))
def test_verify_password_backward_compatibility(app, sqlalchemy_datastore): init_app_with_options( app, sqlalchemy_datastore, **{ "SECURITY_PASSWORD_HASH": "bcrypt", "SECURITY_PASSWORD_SINGLE_HASH": False, "SECURITY_PASSWORD_SCHEMES": ["bcrypt", "plaintext"], }) with app.app_context(): # double hash assert verify_password("pass", hash_password("pass")) # single hash assert verify_password("pass", plaintext.hash("pass"))
def test_create_user_with_roles(app, datastore): init_app_with_options(app, datastore) with app.app_context(): role = datastore.find_role('admin') datastore.commit() user = datastore.create_user(email='*****@*****.**', username='******', password='******', roles=[role]) datastore.commit() user = datastore.find_user(email='*****@*****.**') assert user.has_role('admin') is True
def test_verify_password_bcrypt_rounds_too_low(app, sqlalchemy_datastore): with raises(ValueError) as exc_msg: init_app_with_options( app, sqlalchemy_datastore, **{ "SECURITY_PASSWORD_HASH": "bcrypt", "SECURITY_PASSWORD_SALT": "salty", "SECURITY_PASSWORD_HASH_OPTIONS": {"bcrypt": {"rounds": 3}}, } ) assert all(s in str(exc_msg) for s in ["rounds", "too low"])
def test_verify_password_bcrypt_rounds_too_low(app, sqlalchemy_datastore): with raises(ValueError) as exc_msg: init_app_with_options( app, sqlalchemy_datastore, **{ 'SECURITY_PASSWORD_HASH': 'bcrypt', 'SECURITY_PASSWORD_SALT': 'salty', 'SECURITY_PASSWORD_HASH_OPTIONS': { 'bcrypt': { 'rounds': 3 } } }) assert all(s in str(exc_msg) for s in ['rounds', 'too low'])
def test_get_user(app, datastore): init_app_with_options(app, datastore, **{ 'SECURITY_USER_IDENTITY_ATTRIBUTES': ('email', 'username') }) with app.app_context(): user_id = datastore.find_user(email='*****@*****.**').id user = datastore.get_user(user_id) assert user is not None user = datastore.get_user('*****@*****.**') assert user is not None user = datastore.get_user('matt') assert user is not None
def test_add_role_to_user(app, datastore): init_app_with_options(app, datastore) # Test with user object user = datastore.find_user(email='*****@*****.**') assert user.has_role('editor') is False assert datastore.add_role_to_user(user, 'editor') is True assert datastore.add_role_to_user(user, 'editor') is False assert user.has_role('editor') is True # Test with email assert datastore.add_role_to_user('*****@*****.**', 'editor') is True user = datastore.find_user(email='*****@*****.**') assert user.has_role('editor') is True # Test remove role assert datastore.remove_role_from_user(user, 'editor') is True assert datastore.remove_role_from_user(user, 'editor') is False
def test_invalid_hash_scheme(app, sqlalchemy_datastore, get_message): with pytest.raises(ValueError): init_app_with_options(app, sqlalchemy_datastore, **{ 'SECURITY_PASSWORD_HASH': 'bogus' })
def test_missing_hash_salt_option(app, sqlalchemy_datastore): with raises(RuntimeError): init_app_with_options(app, sqlalchemy_datastore, **{ 'SECURITY_PASSWORD_HASH': 'bcrypt', })