mirror of
https://github.com/ansible/awx.git
synced 2026-04-08 19:49:22 -02:30
Replace role system with permissions-based DB roles
Develop ability to list permissions for existing roles Create a model registry for RBAC-tracked models Write the data migration logic for creating the preloaded role definitions Write migration to migrate old Role into ObjectRole model This loops over the old Role model, knowing it is unique on object and role_field Most of the logic is concerned with identifying the needed permissions, and then corresponding role definition As needed, object roles are created and users then teams are assigned Write re-computation of cache logic for teams and then for object role permissions Migrate new RBAC internals to ansible_base Migrate tests to ansible_base Implement solution for visible_roles Expose URLs for DAB RBAC
This commit is contained in:
@@ -4,7 +4,6 @@ from prometheus_client.parser import text_string_to_metric_families
|
||||
from awx.main import models
|
||||
from awx.main.analytics.metrics import metrics
|
||||
from awx.api.versioning import reverse
|
||||
from awx.main.models.rbac import Role
|
||||
|
||||
EXPECTED_VALUES = {
|
||||
'awx_system_info': 1.0,
|
||||
@@ -66,7 +65,6 @@ def test_metrics_permissions(get, admin, org_admin, alice, bob, organization):
|
||||
organization.auditor_role.members.add(bob)
|
||||
assert get(get_metrics_view_db_only(), user=bob).status_code == 403
|
||||
|
||||
Role.singleton('system_auditor').members.add(bob)
|
||||
bob.is_system_auditor = True
|
||||
assert get(get_metrics_view_db_only(), user=bob).status_code == 200
|
||||
|
||||
|
||||
@@ -385,10 +385,9 @@ def test_list_created_org_credentials(post, get, organization, org_admin, org_me
|
||||
@pytest.mark.django_db
|
||||
def test_list_cannot_order_by_encrypted_field(post, get, organization, org_admin, credentialtype_ssh, order_by):
|
||||
for i, password in enumerate(('abc', 'def', 'xyz')):
|
||||
response = post(reverse('api:credential_list'), {'organization': organization.id, 'name': 'C%d' % i, 'password': password}, org_admin)
|
||||
post(reverse('api:credential_list'), {'organization': organization.id, 'name': 'C%d' % i, 'password': password}, org_admin, expect=400)
|
||||
|
||||
response = get(reverse('api:credential_list'), org_admin, QUERY_STRING='order_by=%s' % order_by, status=400)
|
||||
assert response.status_code == 400
|
||||
get(reverse('api:credential_list'), org_admin, QUERY_STRING='order_by=%s' % order_by, expect=400)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@@ -399,8 +398,7 @@ def test_inputs_cannot_contain_extra_fields(get, post, organization, admin, cred
|
||||
'credential_type': credentialtype_ssh.pk,
|
||||
'inputs': {'invalid_field': 'foo'},
|
||||
}
|
||||
response = post(reverse('api:credential_list'), params, admin)
|
||||
assert response.status_code == 400
|
||||
response = post(reverse('api:credential_list'), params, admin, expect=400)
|
||||
assert "'invalid_field' was unexpected" in response.data['inputs'][0]
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import pytest
|
||||
|
||||
from awx.api.versioning import reverse
|
||||
from awx.main.models import Role
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@@ -39,7 +38,7 @@ def test_indirect_access_list(get, organization, project, team_factory, user, ad
|
||||
assert len(team_admin_res['summary_fields']['direct_access']) == 1
|
||||
assert len(team_admin_res['summary_fields']['indirect_access']) == 0
|
||||
assert len(admin_res['summary_fields']['direct_access']) == 0
|
||||
assert len(admin_res['summary_fields']['indirect_access']) == 1
|
||||
assert len(admin_res['summary_fields']['indirect_access']) == 0 # decreased to 0 because system admin role no longer exists
|
||||
|
||||
project_admin_entry = project_admin_res['summary_fields']['direct_access'][0]['role']
|
||||
assert project_admin_entry['id'] == project.admin_role.id
|
||||
@@ -52,6 +51,3 @@ def test_indirect_access_list(get, organization, project, team_factory, user, ad
|
||||
assert project_admin_team_member_entry['id'] == project.admin_role.id
|
||||
assert project_admin_team_member_entry['team_id'] == project_admin_team.id
|
||||
assert project_admin_team_member_entry['team_name'] == project_admin_team.name
|
||||
|
||||
admin_entry = admin_res['summary_fields']['indirect_access'][0]['role']
|
||||
assert admin_entry['name'] == Role.singleton('system_administrator').name
|
||||
|
||||
@@ -3,17 +3,6 @@ import pytest
|
||||
from awx.api.versioning import reverse
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_admin_visible_to_orphaned_users(get, alice):
|
||||
names = set()
|
||||
|
||||
response = get(reverse('api:role_list'), user=alice)
|
||||
for item in response.data['results']:
|
||||
names.add(item['name'])
|
||||
assert 'System Auditor' in names
|
||||
assert 'System Administrator' in names
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@pytest.mark.parametrize('role,code', [('member_role', 400), ('admin_role', 400), ('inventory_admin_role', 204)])
|
||||
@pytest.mark.parametrize('reversed', [True, False])
|
||||
|
||||
@@ -32,7 +32,6 @@ from awx.main.models.organization import (
|
||||
Organization,
|
||||
Team,
|
||||
)
|
||||
from awx.main.models.rbac import Role
|
||||
from awx.main.models.notifications import NotificationTemplate, Notification
|
||||
from awx.main.models.events import (
|
||||
JobEvent,
|
||||
@@ -434,7 +433,7 @@ def admin(user):
|
||||
@pytest.fixture
|
||||
def system_auditor(user):
|
||||
u = user('an-auditor', False)
|
||||
Role.singleton('system_auditor').members.add(u)
|
||||
u.is_system_auditor = True
|
||||
return u
|
||||
|
||||
|
||||
|
||||
68
awx/main/tests/functional/dab_rbac/test_dab_rbac_api.py
Normal file
68
awx/main/tests/functional/dab_rbac/test_dab_rbac_api.py
Normal file
@@ -0,0 +1,68 @@
|
||||
import pytest
|
||||
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.urls import reverse as django_reverse
|
||||
|
||||
from awx.api.versioning import reverse
|
||||
from awx.main.models import JobTemplate, Inventory, Organization
|
||||
|
||||
from ansible_base.rbac.models import RoleDefinition
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_managed_roles_created():
|
||||
"Managed RoleDefinitions are created in post_migration signal, we expect to see them here"
|
||||
for cls in (JobTemplate, Inventory):
|
||||
ct = ContentType.objects.get_for_model(cls)
|
||||
rds = list(RoleDefinition.objects.filter(content_type=ct))
|
||||
assert len(rds) > 1
|
||||
assert f'{cls._meta.model_name}-admin' in [rd.name for rd in rds]
|
||||
for rd in rds:
|
||||
assert rd.managed is True
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_custom_read_role(admin_user, post):
|
||||
rd_url = django_reverse('roledefinition-list')
|
||||
resp = post(
|
||||
url=rd_url, data={"name": "read role made for test", "content_type": "awx.inventory", "permissions": ['view_inventory']}, user=admin_user, expect=201
|
||||
)
|
||||
rd_id = resp.data['id']
|
||||
rd = RoleDefinition.objects.get(id=rd_id)
|
||||
assert rd.content_type == ContentType.objects.get_for_model(Inventory)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_assign_managed_role(admin_user, alice, rando, inventory, post):
|
||||
rd = RoleDefinition.objects.get(name='inventory-admin')
|
||||
rd.give_permission(alice, inventory)
|
||||
# Now that alice has full permissions to the inventory, she will give rando permission
|
||||
url = django_reverse('roleuserassignment-list')
|
||||
post(url=url, data={"user": rando.id, "role_definition": rd.id, "object_id": inventory.id}, user=alice, expect=201)
|
||||
assert rando.has_obj_perm(inventory, 'change') is True
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_assign_custom_delete_role(admin_user, rando, inventory, delete, patch):
|
||||
rd, _ = RoleDefinition.objects.get_or_create(
|
||||
name='inventory-delete', permissions=['delete_inventory', 'view_inventory'], content_type=ContentType.objects.get_for_model(Inventory)
|
||||
)
|
||||
rd.give_permission(rando, inventory)
|
||||
inv_id = inventory.pk
|
||||
inv_url = reverse('api:inventory_detail', kwargs={'pk': inv_id})
|
||||
patch(url=inv_url, data={"description": "new"}, user=rando, expect=403)
|
||||
delete(url=inv_url, user=rando, expect=202)
|
||||
assert Inventory.objects.get(id=inv_id).pending_deletion
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_assign_custom_add_role(admin_user, rando, organization, post):
|
||||
rd, _ = RoleDefinition.objects.get_or_create(
|
||||
name='inventory-add', permissions=['add_inventory', 'view_organization'], content_type=ContentType.objects.get_for_model(Organization)
|
||||
)
|
||||
rd.give_permission(rando, organization)
|
||||
url = reverse('api:inventory_list')
|
||||
r = post(url=url, data={'name': 'abc', 'organization': organization.id}, user=rando, expect=201)
|
||||
inv_id = r.data['id']
|
||||
inventory = Inventory.objects.get(id=inv_id)
|
||||
assert rando.has_obj_perm(inventory, 'change')
|
||||
23
awx/main/tests/functional/dab_rbac/test_translation_layer.py
Normal file
23
awx/main/tests/functional/dab_rbac/test_translation_layer.py
Normal file
@@ -0,0 +1,23 @@
|
||||
import pytest
|
||||
|
||||
from awx.main.models.rbac import get_role_from_object_role
|
||||
|
||||
from ansible_base.rbac.models import RoleUserAssignment
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@pytest.mark.parametrize(
|
||||
'role_name',
|
||||
['execution_environment_admin_role', 'project_admin_role', 'admin_role', 'auditor_role', 'read_role', 'execute_role', 'notification_admin_role'],
|
||||
)
|
||||
def test_round_trip_roles(organization, rando, role_name):
|
||||
"""
|
||||
Make an assignment with the old-style role,
|
||||
get the equivelent new role
|
||||
get the old role again
|
||||
"""
|
||||
getattr(organization, role_name).members.add(rando)
|
||||
assignment = RoleUserAssignment.objects.get(user=rando)
|
||||
print(assignment.role_definition.name)
|
||||
old_role = get_role_from_object_role(assignment.object_role)
|
||||
assert old_role.id == getattr(organization, role_name).id
|
||||
@@ -104,11 +104,13 @@ class TestRolesAssociationEntries:
|
||||
else:
|
||||
assert len(entry_qs) == 1
|
||||
# unfortunate, the original creation does _not_ set a real is_auditor field
|
||||
assert 'is_system_auditor' not in json.loads(entry_qs[0].changes)
|
||||
assert 'is_system_auditor' not in json.loads(entry_qs[0].changes) # NOTE: if this fails, see special note
|
||||
# special note - if system auditor flag is moved to user model then we expect this assertion to be changed
|
||||
# make sure that an extra entry is not created, expectation for count would change to 1
|
||||
if value:
|
||||
auditor_changes = json.loads(entry_qs[1].changes)
|
||||
assert auditor_changes['object2'] == 'user'
|
||||
assert auditor_changes['object2_pk'] == u.pk
|
||||
entry = entry_qs[1]
|
||||
assert json.loads(entry.changes) == {'is_system_auditor': [False, True]}
|
||||
assert entry.object1 == 'user'
|
||||
|
||||
def test_user_no_op_api(self, system_auditor):
|
||||
as_ct = ActivityStream.objects.count()
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import pytest
|
||||
|
||||
# AWX context managers for testing
|
||||
from awx.main.models.rbac import batch_role_ancestor_rebuilding
|
||||
from awx.main.signals import disable_activity_stream, disable_computed_fields, update_inventory_computed_fields
|
||||
|
||||
# AWX models
|
||||
@@ -10,15 +9,6 @@ from awx.main.models import ActivityStream, Job
|
||||
from awx.main.tests.functional import immediate_on_commit
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_rbac_batch_rebuilding(rando, organization):
|
||||
with batch_role_ancestor_rebuilding():
|
||||
organization.admin_role.members.add(rando)
|
||||
inventory = organization.inventories.create(name='test-inventory')
|
||||
assert rando not in inventory.admin_role
|
||||
assert rando in inventory.admin_role
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_disable_activity_stream():
|
||||
with disable_activity_stream():
|
||||
|
||||
@@ -3,7 +3,7 @@ import pytest
|
||||
|
||||
from django.db import transaction
|
||||
from awx.api.versioning import reverse
|
||||
from awx.main.models.rbac import Role, ROLE_SINGLETON_SYSTEM_ADMINISTRATOR
|
||||
from awx.main.models.rbac import Role
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -31,8 +31,6 @@ def test_get_roles_list_user(organization, inventory, team, get, user):
|
||||
'Users can see all roles they have access to, but not all roles'
|
||||
this_user = user('user-test_get_roles_list_user')
|
||||
organization.member_role.members.add(this_user)
|
||||
custom_role = Role.objects.create(role_field='custom_role-test_get_roles_list_user')
|
||||
organization.member_role.children.add(custom_role)
|
||||
|
||||
url = reverse('api:role_list')
|
||||
response = get(url, this_user)
|
||||
@@ -46,10 +44,8 @@ def test_get_roles_list_user(organization, inventory, team, get, user):
|
||||
for r in roles['results']:
|
||||
role_hash[r['id']] = r
|
||||
|
||||
assert Role.singleton(ROLE_SINGLETON_SYSTEM_ADMINISTRATOR).id in role_hash
|
||||
assert organization.admin_role.id in role_hash
|
||||
assert organization.member_role.id in role_hash
|
||||
assert custom_role.id in role_hash
|
||||
|
||||
assert inventory.admin_role.id not in role_hash
|
||||
assert team.member_role.id not in role_hash
|
||||
@@ -57,7 +53,8 @@ def test_get_roles_list_user(organization, inventory, team, get, user):
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_roles_visibility(get, organization, project, admin, alice, bob):
|
||||
Role.singleton('system_auditor').members.add(alice)
|
||||
alice.is_system_auditor = True
|
||||
alice.save()
|
||||
assert get(reverse('api:role_list') + '?id=%d' % project.update_role.id, user=admin).data['count'] == 1
|
||||
assert get(reverse('api:role_list') + '?id=%d' % project.update_role.id, user=alice).data['count'] == 1
|
||||
assert get(reverse('api:role_list') + '?id=%d' % project.update_role.id, user=bob).data['count'] == 0
|
||||
@@ -67,7 +64,8 @@ def test_roles_visibility(get, organization, project, admin, alice, bob):
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_roles_filter_visibility(get, organization, project, admin, alice, bob):
|
||||
Role.singleton('system_auditor').members.add(alice)
|
||||
alice.is_system_auditor = True
|
||||
alice.save()
|
||||
project.update_role.members.add(admin)
|
||||
|
||||
assert get(reverse('api:user_roles_list', kwargs={'pk': admin.id}) + '?id=%d' % project.update_role.id, user=admin).data['count'] == 1
|
||||
@@ -105,15 +103,6 @@ def test_cant_delete_role(delete, admin, inventory):
|
||||
#
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_get_user_roles_list(get, admin):
|
||||
url = reverse('api:user_roles_list', kwargs={'pk': admin.id})
|
||||
response = get(url, admin)
|
||||
assert response.status_code == 200
|
||||
roles = response.data
|
||||
assert roles['count'] > 0 # 'system_administrator' role if nothing else
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_user_view_other_user_roles(organization, inventory, team, get, alice, bob):
|
||||
'Users can see roles for other users, but only the roles that that user has access to see as well'
|
||||
@@ -141,7 +130,6 @@ def test_user_view_other_user_roles(organization, inventory, team, get, alice, b
|
||||
|
||||
assert organization.admin_role.id in role_hash
|
||||
assert custom_role.id not in role_hash # doesn't show up in the user roles list, not an explicit grant
|
||||
assert Role.singleton(ROLE_SINGLETON_SYSTEM_ADMINISTRATOR).id not in role_hash
|
||||
assert inventory.admin_role.id not in role_hash
|
||||
assert team.member_role.id not in role_hash # alice can't see this
|
||||
|
||||
|
||||
@@ -1,213 +0,0 @@
|
||||
import pytest
|
||||
|
||||
from awx.main.models import (
|
||||
Role,
|
||||
Organization,
|
||||
Project,
|
||||
)
|
||||
from awx.main.fields import update_role_parentage_for_instance
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_auto_inheritance_by_children(organization, alice):
|
||||
A = Role.objects.create()
|
||||
B = Role.objects.create()
|
||||
A.members.add(alice)
|
||||
|
||||
assert alice not in organization.admin_role
|
||||
assert Organization.accessible_objects(alice, 'admin_role').count() == 0
|
||||
A.children.add(B)
|
||||
assert alice not in organization.admin_role
|
||||
assert Organization.accessible_objects(alice, 'admin_role').count() == 0
|
||||
A.children.add(organization.admin_role)
|
||||
assert alice in organization.admin_role
|
||||
assert Organization.accessible_objects(alice, 'admin_role').count() == 1
|
||||
A.children.remove(organization.admin_role)
|
||||
assert alice not in organization.admin_role
|
||||
B.children.add(organization.admin_role)
|
||||
assert alice in organization.admin_role
|
||||
B.children.remove(organization.admin_role)
|
||||
assert alice not in organization.admin_role
|
||||
assert Organization.accessible_objects(alice, 'admin_role').count() == 0
|
||||
|
||||
# We've had the case where our pre/post save init handlers in our field descriptors
|
||||
# end up creating a ton of role objects because of various not-so-obvious issues
|
||||
assert Role.objects.count() < 50
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_auto_inheritance_by_parents(organization, alice):
|
||||
A = Role.objects.create()
|
||||
B = Role.objects.create()
|
||||
A.members.add(alice)
|
||||
|
||||
assert alice not in organization.admin_role
|
||||
B.parents.add(A)
|
||||
assert alice not in organization.admin_role
|
||||
organization.admin_role.parents.add(A)
|
||||
assert alice in organization.admin_role
|
||||
organization.admin_role.parents.remove(A)
|
||||
assert alice not in organization.admin_role
|
||||
organization.admin_role.parents.add(B)
|
||||
assert alice in organization.admin_role
|
||||
organization.admin_role.parents.remove(B)
|
||||
assert alice not in organization.admin_role
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_accessible_objects(organization, alice, bob):
|
||||
A = Role.objects.create()
|
||||
A.members.add(alice)
|
||||
B = Role.objects.create()
|
||||
B.members.add(alice)
|
||||
B.members.add(bob)
|
||||
|
||||
assert Organization.accessible_objects(alice, 'admin_role').count() == 0
|
||||
assert Organization.accessible_objects(bob, 'admin_role').count() == 0
|
||||
A.children.add(organization.admin_role)
|
||||
assert Organization.accessible_objects(alice, 'admin_role').count() == 1
|
||||
assert Organization.accessible_objects(bob, 'admin_role').count() == 0
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_team_symantics(organization, team, alice):
|
||||
assert alice not in organization.auditor_role
|
||||
team.member_role.children.add(organization.auditor_role)
|
||||
assert alice not in organization.auditor_role
|
||||
team.member_role.members.add(alice)
|
||||
assert alice in organization.auditor_role
|
||||
team.member_role.members.remove(alice)
|
||||
assert alice not in organization.auditor_role
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_auto_field_adjustments(organization, inventory, team, alice):
|
||||
'Ensures the auto role reparenting is working correctly through non m2m fields'
|
||||
org2 = Organization.objects.create(name='Org 2', description='org 2')
|
||||
org2.admin_role.members.add(alice)
|
||||
assert alice not in inventory.admin_role
|
||||
inventory.organization = org2
|
||||
inventory.save()
|
||||
assert alice in inventory.admin_role
|
||||
inventory.organization = organization
|
||||
inventory.save()
|
||||
assert alice not in inventory.admin_role
|
||||
# assert False
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_implicit_deletes(alice):
|
||||
'Ensures implicit resources and roles delete themselves'
|
||||
delorg = Organization.objects.create(name='test-org')
|
||||
child = Role.objects.create()
|
||||
child.parents.add(delorg.admin_role)
|
||||
delorg.admin_role.members.add(alice)
|
||||
|
||||
admin_role_id = delorg.admin_role.id
|
||||
auditor_role_id = delorg.auditor_role.id
|
||||
|
||||
assert child.ancestors.count() > 1
|
||||
assert Role.objects.filter(id=admin_role_id).count() == 1
|
||||
assert Role.objects.filter(id=auditor_role_id).count() == 1
|
||||
n_alice_roles = alice.roles.count()
|
||||
n_system_admin_children = Role.singleton('system_administrator').children.count()
|
||||
|
||||
delorg.delete()
|
||||
|
||||
assert Role.objects.filter(id=admin_role_id).count() == 0
|
||||
assert Role.objects.filter(id=auditor_role_id).count() == 0
|
||||
assert alice.roles.count() == (n_alice_roles - 1)
|
||||
assert Role.singleton('system_administrator').children.count() == (n_system_admin_children - 1)
|
||||
assert child.ancestors.count() == 1
|
||||
assert child.ancestors.all()[0] == child
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_content_object(user):
|
||||
'Ensure our content_object stuf seems to be working'
|
||||
|
||||
org = Organization.objects.create(name='test-org')
|
||||
assert org.admin_role.content_object.id == org.id
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_hierarchy_rebuilding_multi_path():
|
||||
'Tests a subdtle cases around role hierarchy rebuilding when you have multiple paths to the same role of different length'
|
||||
|
||||
X = Role.objects.create()
|
||||
A = Role.objects.create()
|
||||
B = Role.objects.create()
|
||||
C = Role.objects.create()
|
||||
D = Role.objects.create()
|
||||
|
||||
A.children.add(B)
|
||||
A.children.add(D)
|
||||
B.children.add(C)
|
||||
C.children.add(D)
|
||||
|
||||
assert A.is_ancestor_of(D)
|
||||
assert X.is_ancestor_of(D) is False
|
||||
|
||||
X.children.add(A)
|
||||
|
||||
assert X.is_ancestor_of(D) is True
|
||||
|
||||
X.children.remove(A)
|
||||
|
||||
# This can be the stickler, the rebuilder needs to ensure that D's role
|
||||
# hierarchy is built after both A and C are updated.
|
||||
assert X.is_ancestor_of(D) is False
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_auto_parenting():
|
||||
org1 = Organization.objects.create(name='org1')
|
||||
org2 = Organization.objects.create(name='org2')
|
||||
|
||||
prj1 = Project.objects.create(name='prj1')
|
||||
prj2 = Project.objects.create(name='prj2')
|
||||
|
||||
assert org1.admin_role.is_ancestor_of(prj1.admin_role) is False
|
||||
assert org1.admin_role.is_ancestor_of(prj2.admin_role) is False
|
||||
assert org2.admin_role.is_ancestor_of(prj1.admin_role) is False
|
||||
assert org2.admin_role.is_ancestor_of(prj2.admin_role) is False
|
||||
|
||||
prj1.organization = org1
|
||||
prj1.save()
|
||||
|
||||
assert org1.admin_role.is_ancestor_of(prj1.admin_role)
|
||||
assert org1.admin_role.is_ancestor_of(prj2.admin_role) is False
|
||||
assert org2.admin_role.is_ancestor_of(prj1.admin_role) is False
|
||||
assert org2.admin_role.is_ancestor_of(prj2.admin_role) is False
|
||||
|
||||
prj2.organization = org1
|
||||
prj2.save()
|
||||
|
||||
assert org1.admin_role.is_ancestor_of(prj1.admin_role)
|
||||
assert org1.admin_role.is_ancestor_of(prj2.admin_role)
|
||||
assert org2.admin_role.is_ancestor_of(prj1.admin_role) is False
|
||||
assert org2.admin_role.is_ancestor_of(prj2.admin_role) is False
|
||||
|
||||
prj1.organization = org2
|
||||
prj1.save()
|
||||
|
||||
assert org1.admin_role.is_ancestor_of(prj1.admin_role) is False
|
||||
assert org1.admin_role.is_ancestor_of(prj2.admin_role)
|
||||
assert org2.admin_role.is_ancestor_of(prj1.admin_role)
|
||||
assert org2.admin_role.is_ancestor_of(prj2.admin_role) is False
|
||||
|
||||
prj2.organization = org2
|
||||
prj2.save()
|
||||
|
||||
assert org1.admin_role.is_ancestor_of(prj1.admin_role) is False
|
||||
assert org1.admin_role.is_ancestor_of(prj2.admin_role) is False
|
||||
assert org2.admin_role.is_ancestor_of(prj1.admin_role)
|
||||
assert org2.admin_role.is_ancestor_of(prj2.admin_role)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_update_parents_keeps_teams(team, project):
|
||||
project.update_role.parents.add(team.member_role)
|
||||
assert list(Project.accessible_objects(team.member_role, 'update_role')) == [project] # test prep sanity check
|
||||
update_role_parentage_for_instance(project)
|
||||
assert list(Project.accessible_objects(team.member_role, 'update_role')) == [project] # actual assertion
|
||||
@@ -4,7 +4,7 @@ import pytest
|
||||
from awx.api.versioning import reverse
|
||||
from awx.main.access import BaseAccess, JobTemplateAccess, ScheduleAccess
|
||||
from awx.main.models.jobs import JobTemplate
|
||||
from awx.main.models import Project, Organization, Inventory, Schedule, User
|
||||
from awx.main.models import Project, Organization, Schedule
|
||||
|
||||
|
||||
@mock.patch.object(BaseAccess, 'check_license', return_value=None)
|
||||
@@ -283,48 +283,3 @@ class TestProjectOrganization:
|
||||
assert org_admin not in jt.admin_role
|
||||
patch(url=jt.get_absolute_url(), data={'project': project.id}, user=admin_user, expect=200)
|
||||
assert org_admin in jt.admin_role
|
||||
|
||||
def test_inventory_read_transfer_direct(self, patch):
|
||||
orgs = []
|
||||
invs = []
|
||||
admins = []
|
||||
for i in range(2):
|
||||
org = Organization.objects.create(name='org{}'.format(i))
|
||||
org_admin = User.objects.create(username='user{}'.format(i))
|
||||
inv = Inventory.objects.create(organization=org, name='inv{}'.format(i))
|
||||
org.auditor_role.members.add(org_admin)
|
||||
|
||||
orgs.append(org)
|
||||
admins.append(org_admin)
|
||||
invs.append(inv)
|
||||
|
||||
jt = JobTemplate.objects.create(name='foo', inventory=invs[0])
|
||||
assert admins[0] in jt.read_role
|
||||
assert admins[1] not in jt.read_role
|
||||
|
||||
jt.inventory = invs[1]
|
||||
jt.save(update_fields=['inventory'])
|
||||
assert admins[0] not in jt.read_role
|
||||
assert admins[1] in jt.read_role
|
||||
|
||||
def test_inventory_read_transfer_indirect(self, patch):
|
||||
orgs = []
|
||||
admins = []
|
||||
for i in range(2):
|
||||
org = Organization.objects.create(name='org{}'.format(i))
|
||||
org_admin = User.objects.create(username='user{}'.format(i))
|
||||
org.auditor_role.members.add(org_admin)
|
||||
|
||||
orgs.append(org)
|
||||
admins.append(org_admin)
|
||||
|
||||
inv = Inventory.objects.create(organization=orgs[0], name='inv{}'.format(i))
|
||||
|
||||
jt = JobTemplate.objects.create(name='foo', inventory=inv)
|
||||
assert admins[0] in jt.read_role
|
||||
assert admins[1] not in jt.read_role
|
||||
|
||||
inv.organization = orgs[1]
|
||||
inv.save(update_fields=['organization'])
|
||||
assert admins[0] not in jt.read_role
|
||||
assert admins[1] in jt.read_role
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import pytest
|
||||
|
||||
from django.apps import apps
|
||||
|
||||
from awx.main.migrations import _rbac as rbac
|
||||
from awx.main.models import UnifiedJobTemplate, InventorySource, Inventory, JobTemplate, Project, Organization, User
|
||||
from awx.main.models import UnifiedJobTemplate, InventorySource, Inventory, JobTemplate, Project, Organization
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@@ -49,27 +47,3 @@ def test_implied_organization_subquery_job_template():
|
||||
assert jt.test_field is None
|
||||
else:
|
||||
assert jt.test_field == jt.project.organization_id
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_give_explicit_inventory_permission():
|
||||
dual_admin = User.objects.create(username='alice')
|
||||
inv_admin = User.objects.create(username='bob')
|
||||
inv_org = Organization.objects.create(name='inv-org')
|
||||
proj_org = Organization.objects.create(name='proj-org')
|
||||
|
||||
inv_org.admin_role.members.add(inv_admin, dual_admin)
|
||||
proj_org.admin_role.members.add(dual_admin)
|
||||
|
||||
proj = Project.objects.create(name="test-proj", organization=proj_org)
|
||||
inv = Inventory.objects.create(name='test-inv', organization=inv_org)
|
||||
|
||||
jt = JobTemplate.objects.create(name='foo', project=proj, inventory=inv)
|
||||
|
||||
assert dual_admin in jt.admin_role
|
||||
|
||||
rbac.restore_inventory_admins(apps, None)
|
||||
|
||||
assert inv_admin in jt.admin_role.members.all()
|
||||
assert dual_admin not in jt.admin_role.members.all()
|
||||
assert dual_admin in jt.admin_role
|
||||
|
||||
@@ -92,7 +92,7 @@ def test_team_accessible_by(team, user, project):
|
||||
u = user('team_member', False)
|
||||
|
||||
team.member_role.children.add(project.use_role)
|
||||
assert list(Project.accessible_objects(team.member_role, 'read_role')) == [project]
|
||||
assert list(Project.accessible_objects(team, 'read_role')) == [project]
|
||||
assert u not in project.read_role
|
||||
|
||||
team.member_role.members.add(u)
|
||||
@@ -104,7 +104,7 @@ def test_team_accessible_objects(team, user, project):
|
||||
u = user('team_member', False)
|
||||
|
||||
team.member_role.children.add(project.use_role)
|
||||
assert len(Project.accessible_objects(team.member_role, 'read_role')) == 1
|
||||
assert len(Project.accessible_objects(team, 'read_role')) == 1
|
||||
assert not Project.accessible_objects(u, 'read_role')
|
||||
|
||||
team.member_role.members.add(u)
|
||||
|
||||
@@ -4,7 +4,7 @@ from unittest import mock
|
||||
from django.test import TransactionTestCase
|
||||
|
||||
from awx.main.access import UserAccess, RoleAccess, TeamAccess
|
||||
from awx.main.models import User, Organization, Inventory, Role
|
||||
from awx.main.models import User, Organization, Inventory
|
||||
|
||||
|
||||
class TestSysAuditorTransactional(TransactionTestCase):
|
||||
@@ -18,7 +18,7 @@ class TestSysAuditorTransactional(TransactionTestCase):
|
||||
|
||||
def test_auditor_caching(self):
|
||||
rando = self.rando()
|
||||
with self.assertNumQueries(1):
|
||||
with self.assertNumQueries(2):
|
||||
v = rando.is_system_auditor
|
||||
assert not v
|
||||
with self.assertNumQueries(0):
|
||||
@@ -153,34 +153,3 @@ def test_org_admin_cannot_delete_member_attached_to_other_group(org_admin, org_m
|
||||
access = UserAccess(org_admin)
|
||||
other_org.member_role.members.add(org_member)
|
||||
assert not access.can_delete(org_member)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('reverse', (True, False))
|
||||
@pytest.mark.django_db
|
||||
def test_consistency_of_is_superuser_flag(reverse):
|
||||
users = [User.objects.create(username='rando_{}'.format(i)) for i in range(2)]
|
||||
for u in users:
|
||||
assert u.is_superuser is False
|
||||
|
||||
system_admin = Role.singleton('system_administrator')
|
||||
if reverse:
|
||||
for u in users:
|
||||
u.roles.add(system_admin)
|
||||
else:
|
||||
system_admin.members.add(*[u.id for u in users]) # like .add(42, 54)
|
||||
|
||||
for u in users:
|
||||
u.refresh_from_db()
|
||||
assert u.is_superuser is True
|
||||
|
||||
users[0].roles.clear()
|
||||
for u in users:
|
||||
u.refresh_from_db()
|
||||
assert users[0].is_superuser is False
|
||||
assert users[1].is_superuser is True
|
||||
|
||||
system_admin.members.clear()
|
||||
|
||||
for u in users:
|
||||
u.refresh_from_db()
|
||||
assert u.is_superuser is False
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.mark.django_db()
|
||||
def test_admin_not_member(team):
|
||||
"""Test to ensure we don't add admin_role as a parent to team.member_role, as
|
||||
this creates a cycle with organization administration, which we've decided
|
||||
to remove support for
|
||||
|
||||
(2016-06-16) I think this might have been resolved. I'm asserting
|
||||
this to be true in the mean time.
|
||||
"""
|
||||
|
||||
assert team.admin_role.is_ancestor_of(team.member_role) is True
|
||||
Reference in New Issue
Block a user