initial import for Open Source 🎉
This commit is contained in:
parent
1898c361f3
commit
9c0dd3b722
2048 changed files with 218743 additions and 0 deletions
55
data/users/test/test_shared.py
Normal file
55
data/users/test/test_shared.py
Normal file
|
@ -0,0 +1,55 @@
|
|||
import pytest
|
||||
|
||||
from mock import patch
|
||||
|
||||
from data.database import model
|
||||
from data.users.shared import can_create_user
|
||||
|
||||
from test.fixtures import *
|
||||
|
||||
@pytest.mark.parametrize('open_creation, invite_only, email, has_invite, can_create', [
|
||||
# Open user creation => always allowed.
|
||||
(True, False, None, False, True),
|
||||
|
||||
# Open user creation => always allowed.
|
||||
(True, False, 'foo@example.com', False, True),
|
||||
|
||||
# Invite only user creation + no invite => disallowed.
|
||||
(True, True, None, False, False),
|
||||
|
||||
# Invite only user creation + no invite => disallowed.
|
||||
(True, True, 'foo@example.com', False, False),
|
||||
|
||||
# Invite only user creation + invite => allowed.
|
||||
(True, True, 'foo@example.com', True, True),
|
||||
|
||||
# No open creation => Disallowed.
|
||||
(False, True, 'foo@example.com', False, False),
|
||||
(False, True, 'foo@example.com', True, False),
|
||||
|
||||
# Blacklisted emails => Disallowed.
|
||||
(True, False, 'foo@blacklisted.com', False, False),
|
||||
(True, False, 'foo@blacklisted.org', False, False),
|
||||
(True, False, 'foo@BlAcKlIsTeD.CoM', False, False), # Verify Capitalization
|
||||
(True, False, u'foo@mail.bLacklisted.Com', False, False), # Verify unicode
|
||||
(True, False, 'foo@blacklisted.net', False, True), # Avoid False Positives
|
||||
(True, False, 'foo@myblacklisted.com', False, True), # Avoid partial domain matches
|
||||
(True, False, 'fooATblacklisted.com', False, True), # Ignore invalid email addresses
|
||||
])
|
||||
@pytest.mark.parametrize('blacklisting_enabled', [True, False])
|
||||
def test_can_create_user(open_creation, invite_only, email, has_invite, can_create, blacklisting_enabled, app):
|
||||
|
||||
# Mock list of blacklisted domains
|
||||
blacklisted_domains = ['blacklisted.com', 'blacklisted.org']
|
||||
|
||||
if has_invite:
|
||||
inviter = model.user.get_user('devtable')
|
||||
team = model.team.get_organization_team('buynlarge', 'owners')
|
||||
model.team.add_or_invite_to_team(inviter, team, email=email)
|
||||
|
||||
with patch('features.USER_CREATION', open_creation):
|
||||
with patch('features.INVITE_ONLY_USER_CREATION', invite_only):
|
||||
with patch('features.BLACKLISTED_EMAILS', blacklisting_enabled):
|
||||
if email and any(domain in email.lower() for domain in blacklisted_domains) and not blacklisting_enabled:
|
||||
can_create = True # blacklisted domains can be used, if blacklisting is disabled
|
||||
assert can_create_user(email, blacklisted_domains) == can_create
|
332
data/users/test/test_teamsync.py
Normal file
332
data/users/test/test_teamsync.py
Normal file
|
@ -0,0 +1,332 @@
|
|||
import os
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import pytest
|
||||
|
||||
from mock import patch
|
||||
|
||||
from data import model, database
|
||||
from data.users.federated import FederatedUsers, UserInformation
|
||||
from data.users.teamsync import sync_team, sync_teams_to_groups
|
||||
from test.test_ldap import mock_ldap
|
||||
from test.test_keystone_auth import fake_keystone
|
||||
from util.names import parse_robot_username
|
||||
|
||||
from test.fixtures import *
|
||||
|
||||
_FAKE_AUTH = 'fake'
|
||||
|
||||
class FakeUsers(FederatedUsers):
|
||||
def __init__(self, group_members):
|
||||
super(FakeUsers, self).__init__(_FAKE_AUTH, False)
|
||||
self.group_tuples = [(m, None) for m in group_members]
|
||||
|
||||
def iterate_group_members(self, group_lookup_args, page_size=None, disable_pagination=False):
|
||||
return (self.group_tuples, None)
|
||||
|
||||
|
||||
@pytest.fixture(params=[True, False])
|
||||
def user_creation(request):
|
||||
with patch('features.USER_CREATION', request.param):
|
||||
yield
|
||||
|
||||
|
||||
@pytest.fixture(params=[True, False])
|
||||
def invite_only_user_creation(request):
|
||||
with patch('features.INVITE_ONLY_USER_CREATION', request.param):
|
||||
yield
|
||||
|
||||
|
||||
@pytest.fixture(params=[True, False])
|
||||
def blacklisted_emails(request):
|
||||
mock_blacklisted_domains = {'BLACKLISTED_EMAIL_DOMAINS': ['blacklisted.com', 'blacklisted.net']}
|
||||
with patch('features.BLACKLISTED_EMAILS', request.param):
|
||||
with patch.dict('data.model.config.app_config', mock_blacklisted_domains):
|
||||
yield
|
||||
|
||||
|
||||
@pytest.mark.skipif(os.environ.get('TEST_DATABASE_URI', '').find('postgres') >= 0,
|
||||
reason="Postgres fails when existing members are added under the savepoint")
|
||||
@pytest.mark.parametrize('starting_membership,group_membership,expected_membership', [
|
||||
# Empty team + single member in group => Single member in team.
|
||||
([],
|
||||
[
|
||||
UserInformation('someuser', 'someuser', 'someuser@devtable.com'),
|
||||
],
|
||||
['someuser']),
|
||||
|
||||
# Team with a Quay user + empty group => empty team.
|
||||
([('someuser', None)],
|
||||
[],
|
||||
[]),
|
||||
|
||||
# Team with an existing external user + user is in the group => no changes.
|
||||
([
|
||||
('someuser', 'someuser'),
|
||||
],
|
||||
[
|
||||
UserInformation('someuser', 'someuser', 'someuser@devtable.com'),
|
||||
],
|
||||
['someuser']),
|
||||
|
||||
# Team with an existing external user (with a different Quay username) + user is in the group.
|
||||
# => no changes
|
||||
([
|
||||
('anotherquayname', 'someuser'),
|
||||
],
|
||||
[
|
||||
UserInformation('someuser', 'someuser', 'someuser@devtable.com'),
|
||||
],
|
||||
['someuser']),
|
||||
|
||||
# Team missing a few members that are in the group => members added.
|
||||
([('someuser', 'someuser')],
|
||||
[
|
||||
UserInformation('anotheruser', 'anotheruser', 'anotheruser@devtable.com'),
|
||||
UserInformation('someuser', 'someuser', 'someuser@devtable.com'),
|
||||
UserInformation('thirduser', 'thirduser', 'thirduser@devtable.com'),
|
||||
],
|
||||
['anotheruser', 'someuser', 'thirduser']),
|
||||
|
||||
# Team has a few extra members no longer in the group => members removed.
|
||||
([
|
||||
('anotheruser', 'anotheruser'),
|
||||
('someuser', 'someuser'),
|
||||
('thirduser', 'thirduser'),
|
||||
('nontestuser', None),
|
||||
],
|
||||
[
|
||||
UserInformation('thirduser', 'thirduser', 'thirduser@devtable.com'),
|
||||
],
|
||||
['thirduser']),
|
||||
|
||||
# Team has different membership than the group => members added and removed.
|
||||
([
|
||||
('anotheruser', 'anotheruser'),
|
||||
('someuser', 'someuser'),
|
||||
('nontestuser', None),
|
||||
],
|
||||
[
|
||||
UserInformation('anotheruser', 'anotheruser', 'anotheruser@devtable.com'),
|
||||
UserInformation('missinguser', 'missinguser', 'missinguser@devtable.com'),
|
||||
],
|
||||
['anotheruser', 'missinguser']),
|
||||
|
||||
# Team has same membership but some robots => robots remain and no other changes.
|
||||
([
|
||||
('someuser', 'someuser'),
|
||||
('buynlarge+anotherbot', None),
|
||||
('buynlarge+somerobot', None),
|
||||
],
|
||||
[
|
||||
UserInformation('someuser', 'someuser', 'someuser@devtable.com'),
|
||||
],
|
||||
['someuser', 'buynlarge+somerobot', 'buynlarge+anotherbot']),
|
||||
|
||||
# Team has an extra member and some robots => member removed and robots remain.
|
||||
([
|
||||
('someuser', 'someuser'),
|
||||
('buynlarge+anotherbot', None),
|
||||
('buynlarge+somerobot', None),
|
||||
],
|
||||
[
|
||||
# No members.
|
||||
],
|
||||
['buynlarge+somerobot', 'buynlarge+anotherbot']),
|
||||
|
||||
# Team has a different member and some robots => member changed and robots remain.
|
||||
([
|
||||
('someuser', 'someuser'),
|
||||
('buynlarge+anotherbot', None),
|
||||
('buynlarge+somerobot', None),
|
||||
],
|
||||
[
|
||||
UserInformation('anotheruser', 'anotheruser', 'anotheruser@devtable.com'),
|
||||
],
|
||||
['anotheruser', 'buynlarge+somerobot', 'buynlarge+anotherbot']),
|
||||
|
||||
# Team with an existing external user (with a different Quay username) + user is in the group.
|
||||
# => no changes and robots remain.
|
||||
([
|
||||
('anotherquayname', 'someuser'),
|
||||
('buynlarge+anotherbot', None),
|
||||
],
|
||||
[
|
||||
UserInformation('someuser', 'someuser', 'someuser@devtable.com'),
|
||||
],
|
||||
['someuser', 'buynlarge+anotherbot']),
|
||||
|
||||
# Team which returns the same member twice, as pagination in some engines (like LDAP) is not
|
||||
# stable.
|
||||
([],
|
||||
[
|
||||
UserInformation('someuser', 'someuser', 'someuser@devtable.com'),
|
||||
UserInformation('anotheruser', 'anotheruser', 'anotheruser@devtable.com'),
|
||||
UserInformation('someuser', 'someuser', 'someuser@devtable.com'),
|
||||
],
|
||||
['anotheruser', 'someuser']),
|
||||
])
|
||||
def test_syncing(user_creation, invite_only_user_creation, starting_membership, group_membership,
|
||||
expected_membership, blacklisted_emails, app):
|
||||
org = model.organization.get_organization('buynlarge')
|
||||
|
||||
# Necessary for the fake auth entries to be created in FederatedLogin.
|
||||
database.LoginService.create(name=_FAKE_AUTH)
|
||||
|
||||
# Assert the team is empty, so we have a clean slate.
|
||||
sync_team_info = model.team.get_team_sync_information('buynlarge', 'synced')
|
||||
assert len(list(model.team.list_team_users(sync_team_info.team))) == 0
|
||||
|
||||
# Add the existing starting members to the team.
|
||||
for starting_member in starting_membership:
|
||||
(quay_username, fakeauth_username) = starting_member
|
||||
if '+' in quay_username:
|
||||
# Add a robot.
|
||||
(_, shortname) = parse_robot_username(quay_username)
|
||||
robot, _ = model.user.create_robot(shortname, org)
|
||||
model.team.add_user_to_team(robot, sync_team_info.team)
|
||||
else:
|
||||
email = quay_username + '@devtable.com'
|
||||
|
||||
if fakeauth_username is None:
|
||||
quay_user = model.user.create_user_noverify(quay_username, email)
|
||||
else:
|
||||
quay_user = model.user.create_federated_user(quay_username, email, _FAKE_AUTH,
|
||||
fakeauth_username, False)
|
||||
|
||||
model.team.add_user_to_team(quay_user, sync_team_info.team)
|
||||
|
||||
# Call syncing on the team.
|
||||
fake_auth = FakeUsers(group_membership)
|
||||
assert sync_team(fake_auth, sync_team_info)
|
||||
|
||||
# Ensure the last updated time and transaction_id's have changed.
|
||||
updated_sync_info = model.team.get_team_sync_information('buynlarge', 'synced')
|
||||
assert updated_sync_info.last_updated is not None
|
||||
assert updated_sync_info.transaction_id != sync_team_info.transaction_id
|
||||
|
||||
users_expected = set([name for name in expected_membership if '+' not in name])
|
||||
robots_expected = set([name for name in expected_membership if '+' in name])
|
||||
assert len(users_expected) + len(robots_expected) == len(expected_membership)
|
||||
|
||||
# Check that the team's users match those expected.
|
||||
service_user_map = model.team.get_federated_team_member_mapping(sync_team_info.team,
|
||||
_FAKE_AUTH)
|
||||
assert set(service_user_map.keys()) == users_expected
|
||||
|
||||
quay_users = model.team.list_team_users(sync_team_info.team)
|
||||
assert len(quay_users) == len(users_expected)
|
||||
|
||||
for quay_user in quay_users:
|
||||
fakeauth_record = model.user.lookup_federated_login(quay_user, _FAKE_AUTH)
|
||||
assert fakeauth_record is not None
|
||||
assert fakeauth_record.service_ident in users_expected
|
||||
assert service_user_map[fakeauth_record.service_ident] == quay_user.id
|
||||
|
||||
# Check that the team's robots match those expected.
|
||||
robots_found = set([r.username for r in model.team.list_team_robots(sync_team_info.team)])
|
||||
assert robots_expected == robots_found
|
||||
|
||||
|
||||
def test_sync_teams_to_groups(user_creation, invite_only_user_creation, blacklisted_emails, app):
|
||||
# Necessary for the fake auth entries to be created in FederatedLogin.
|
||||
database.LoginService.create(name=_FAKE_AUTH)
|
||||
|
||||
# Assert the team has not yet been updated.
|
||||
sync_team_info = model.team.get_team_sync_information('buynlarge', 'synced')
|
||||
assert sync_team_info.last_updated is None
|
||||
|
||||
# Call to sync all teams.
|
||||
fake_auth = FakeUsers([])
|
||||
sync_teams_to_groups(fake_auth, timedelta(seconds=1))
|
||||
|
||||
# Ensure the team was synced.
|
||||
updated_sync_info = model.team.get_team_sync_information('buynlarge', 'synced')
|
||||
assert updated_sync_info.last_updated is not None
|
||||
assert updated_sync_info.transaction_id != sync_team_info.transaction_id
|
||||
|
||||
# Set the stale threshold to a high amount and ensure the team is not resynced.
|
||||
current_info = model.team.get_team_sync_information('buynlarge', 'synced')
|
||||
current_info.last_updated = datetime.now() - timedelta(seconds=2)
|
||||
current_info.save()
|
||||
|
||||
sync_teams_to_groups(fake_auth, timedelta(seconds=120))
|
||||
|
||||
third_sync_info = model.team.get_team_sync_information('buynlarge', 'synced')
|
||||
assert third_sync_info.transaction_id == updated_sync_info.transaction_id
|
||||
|
||||
# Set the stale threshold to 10 seconds, and ensure the team is resynced, after making it
|
||||
# "updated" 20s ago.
|
||||
current_info = model.team.get_team_sync_information('buynlarge', 'synced')
|
||||
current_info.last_updated = datetime.now() - timedelta(seconds=20)
|
||||
current_info.save()
|
||||
|
||||
sync_teams_to_groups(fake_auth, timedelta(seconds=10))
|
||||
|
||||
fourth_sync_info = model.team.get_team_sync_information('buynlarge', 'synced')
|
||||
assert fourth_sync_info.transaction_id != updated_sync_info.transaction_id
|
||||
|
||||
|
||||
@pytest.mark.parametrize('auth_system_builder,config', [
|
||||
(mock_ldap, {'group_dn': 'cn=AwesomeFolk'}),
|
||||
(fake_keystone, {'group_id': 'somegroupid'}),
|
||||
])
|
||||
def test_teamsync_end_to_end(user_creation, invite_only_user_creation, auth_system_builder, config,
|
||||
blacklisted_emails, app):
|
||||
with auth_system_builder() as auth:
|
||||
# Create an new team to sync.
|
||||
org = model.organization.get_organization('buynlarge')
|
||||
new_synced_team = model.team.create_team('synced2', org, 'member', 'Some synced team.')
|
||||
sync_team_info = model.team.set_team_syncing(new_synced_team, auth.federated_service, config)
|
||||
|
||||
# Sync the team.
|
||||
assert sync_team(auth, sync_team_info)
|
||||
|
||||
# Ensure we now have members.
|
||||
msg = 'Auth system: %s' % auth.federated_service
|
||||
sync_team_info = model.team.get_team_sync_information('buynlarge', 'synced2')
|
||||
team_members = list(model.team.list_team_users(sync_team_info.team))
|
||||
assert len(team_members) > 1, msg
|
||||
|
||||
it, _ = auth.iterate_group_members(config)
|
||||
assert len(team_members) == len(list(it)), msg
|
||||
|
||||
sync_team_info.last_updated = datetime.now() - timedelta(hours=6)
|
||||
sync_team_info.save()
|
||||
|
||||
# Remove one of the members and force a sync again to ensure we re-link the correct users.
|
||||
first_member = team_members[0]
|
||||
model.team.remove_user_from_team('buynlarge', 'synced2', first_member.username, 'devtable')
|
||||
|
||||
team_members2 = list(model.team.list_team_users(sync_team_info.team))
|
||||
assert len(team_members2) == 1, msg
|
||||
assert sync_team(auth, sync_team_info)
|
||||
|
||||
team_members3 = list(model.team.list_team_users(sync_team_info.team))
|
||||
assert len(team_members3) > 1, msg
|
||||
assert set([m.id for m in team_members]) == set([m.id for m in team_members3])
|
||||
|
||||
|
||||
@pytest.mark.parametrize('auth_system_builder,config', [
|
||||
(mock_ldap, {'group_dn': 'cn=AwesomeFolk'}),
|
||||
(fake_keystone, {'group_id': 'somegroupid'}),
|
||||
])
|
||||
def test_teamsync_existing_email(user_creation, invite_only_user_creation, auth_system_builder,
|
||||
blacklisted_emails, config, app):
|
||||
with auth_system_builder() as auth:
|
||||
# Create an new team to sync.
|
||||
org = model.organization.get_organization('buynlarge')
|
||||
new_synced_team = model.team.create_team('synced2', org, 'member', 'Some synced team.')
|
||||
sync_team_info = model.team.set_team_syncing(new_synced_team, auth.federated_service, config)
|
||||
|
||||
# Add a new *unlinked* user with the same email address as one of the team members.
|
||||
it, _ = auth.iterate_group_members(config)
|
||||
members = list(it)
|
||||
model.user.create_user_noverify('someusername', members[0][0].email)
|
||||
|
||||
# Sync the team and ensure it doesn't fail.
|
||||
assert sync_team(auth, sync_team_info)
|
||||
|
||||
team_members = list(model.team.list_team_users(sync_team_info.team))
|
||||
assert len(team_members) > 0
|
99
data/users/test/test_users.py
Normal file
99
data/users/test/test_users.py
Normal file
|
@ -0,0 +1,99 @@
|
|||
import pytest
|
||||
|
||||
from contextlib import contextmanager
|
||||
from mock import patch
|
||||
|
||||
from data.database import model
|
||||
from data.users.federated import DISABLED_MESSAGE
|
||||
from test.test_ldap import mock_ldap
|
||||
from test.test_keystone_auth import fake_keystone
|
||||
from test.test_external_jwt_authn import fake_jwt
|
||||
|
||||
from test.fixtures import *
|
||||
|
||||
@pytest.mark.parametrize('auth_system_builder, user1, user2', [
|
||||
(mock_ldap, ('someuser', 'somepass'), ('testy', 'password')),
|
||||
(fake_keystone, ('cool.user', 'password'), ('some.neat.user', 'foobar')),
|
||||
])
|
||||
def test_auth_createuser(auth_system_builder, user1, user2, config, app):
|
||||
with auth_system_builder() as auth:
|
||||
# Login as a user and ensure a row in the database is created for them.
|
||||
user, err = auth.verify_and_link_user(*user1)
|
||||
assert err is None
|
||||
assert user
|
||||
|
||||
federated_info = model.user.lookup_federated_login(user, auth.federated_service)
|
||||
assert federated_info is not None
|
||||
|
||||
# Disable user creation.
|
||||
with patch('features.USER_CREATION', False):
|
||||
# Ensure that the existing user can login.
|
||||
user_again, err = auth.verify_and_link_user(*user1)
|
||||
assert err is None
|
||||
assert user_again.id == user.id
|
||||
|
||||
# Ensure that a new user cannot.
|
||||
new_user, err = auth.verify_and_link_user(*user2)
|
||||
assert new_user is None
|
||||
assert err == DISABLED_MESSAGE
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'email, blacklisting_enabled, can_create',
|
||||
[
|
||||
# Blacklisting Enabled, Blacklisted Domain => Blocked
|
||||
('foo@blacklisted.net', True, False),
|
||||
('foo@blacklisted.com', True, False),
|
||||
|
||||
# Blacklisting Enabled, similar to blacklisted domain => Allowed
|
||||
('foo@notblacklisted.com', True, True),
|
||||
('foo@blacklisted.org', True, True),
|
||||
|
||||
# Blacklisting *Disabled*, Blacklisted Domain => Allowed
|
||||
('foo@blacklisted.com', False, True),
|
||||
('foo@blacklisted.net', False, True),
|
||||
]
|
||||
)
|
||||
@pytest.mark.parametrize('auth_system_builder', [mock_ldap, fake_keystone, fake_jwt])
|
||||
def test_createuser_with_blacklist(auth_system_builder, email, blacklisting_enabled, can_create, config, app):
|
||||
"""Verify email blacklisting with User Creation"""
|
||||
|
||||
MOCK_CONFIG = {'BLACKLISTED_EMAIL_DOMAINS': ['blacklisted.com', 'blacklisted.net']}
|
||||
MOCK_PASSWORD = 'somepass'
|
||||
|
||||
with auth_system_builder() as auth:
|
||||
with patch('features.BLACKLISTED_EMAILS', blacklisting_enabled):
|
||||
with patch.dict('data.model.config.app_config', MOCK_CONFIG):
|
||||
with patch('features.USER_CREATION', True):
|
||||
new_user, err = auth.verify_and_link_user(email, MOCK_PASSWORD)
|
||||
if can_create:
|
||||
assert err is None
|
||||
assert new_user
|
||||
else:
|
||||
assert err
|
||||
assert new_user is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize('auth_system_builder,auth_kwargs', [
|
||||
(mock_ldap, {}),
|
||||
(fake_keystone, {'version': 3}),
|
||||
(fake_keystone, {'version': 2}),
|
||||
(fake_jwt, {}),
|
||||
])
|
||||
def test_ping(auth_system_builder, auth_kwargs, app):
|
||||
with auth_system_builder(**auth_kwargs) as auth:
|
||||
status, err = auth.ping()
|
||||
assert status
|
||||
assert err is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize('auth_system_builder,auth_kwargs', [
|
||||
(mock_ldap, {}),
|
||||
(fake_keystone, {'version': 3}),
|
||||
(fake_keystone, {'version': 2}),
|
||||
])
|
||||
def test_at_least_one_user_exists(auth_system_builder, auth_kwargs, app):
|
||||
with auth_system_builder(**auth_kwargs) as auth:
|
||||
status, err = auth.at_least_one_user_exists()
|
||||
assert status
|
||||
assert err is None
|
Reference in a new issue