From 30e2c3a8cd081a4f35e769ffc85110899607080a Mon Sep 17 00:00:00 2001 From: Seth Foster Date: Sat, 14 Sep 2024 12:01:04 -0400 Subject: [PATCH] Validate org-user membership from gateway (#15508) (#6698) Adding credential and execution environment roles validates that the user belongs to the same org as the credential or EE. In some situations, the user-org membership has not yet been synced from gateway to controller. In this case, controller will make a request to gateway to check if the user is part of the org. Signed-off-by: Seth Foster --- awx/main/models/credential/__init__.py | 55 ++++++++++++++++++++++- awx/main/models/execution_environments.py | 13 +++++- 2 files changed, 64 insertions(+), 4 deletions(-) diff --git a/awx/main/models/credential/__init__.py b/awx/main/models/credential/__init__.py index e07553e1a9..ffe0af2d79 100644 --- a/awx/main/models/credential/__init__.py +++ b/awx/main/models/credential/__init__.py @@ -49,6 +49,11 @@ from awx.main.models import Team, Organization from awx.main.utils import encrypt_field from . import injectors as builtin_injectors +# DAB +from ansible_base.resource_registry.tasks.sync import get_resource_server_client +from ansible_base.resource_registry.utils.settings import resource_server_defined + + __all__ = ['Credential', 'CredentialType', 'CredentialInputSource', 'build_safe_env'] logger = logging.getLogger('awx.main.models.credential') @@ -77,6 +82,46 @@ def build_safe_env(env): return safe_env +def check_resource_server_for_user_in_organization(user, organization, requesting_user): + if not resource_server_defined(): + return False + + if not requesting_user: + return False + + client = get_resource_server_client(settings.RESOURCE_SERVICE_PATH, jwt_user_id=str(requesting_user.resource.ansible_id), raise_if_bad_request=False) + # need to get the organization object_id in resource server, by querying with ansible_id + response = client._make_request(path=f'resources/?ansible_id={str(organization.resource.ansible_id)}', method='GET') + response_json = response.json() + if response.status_code != 200: + logger.error(f'Failed to get organization object_id in resource server: {response_json.get("detail", "")}') + return False + + if response_json.get('count', 0) == 0: + return False + org_id_in_resource_server = response_json['results'][0]['object_id'] + + client.base_url = client.base_url.replace('/api/gateway/v1/service-index/', '/api/gateway/v1/') + # find role assignments with: + # - roles Organization Member or Organization Admin + # - user ansible id + # - organization object id + + response = client._make_request( + path=f'role_user_assignments/?role_definition__name__in=Organization Member,Organization Admin&user__resource__ansible_id={str(user.resource.ansible_id)}&object_id={org_id_in_resource_server}', + method='GET', + ) + response_json = response.json() + if response.status_code != 200: + logger.error(f'Failed to get role user assignments in resource server: {response_json.get("detail", "")}') + return False + + if response_json.get('count', 0) > 0: + return True + + return False + + class Credential(PasswordFieldsModel, CommonModelNameNotUnique, ResourceMixin): """ A credential contains information about how to talk to a remote resource @@ -320,10 +365,16 @@ class Credential(PasswordFieldsModel, CommonModelNameNotUnique, ResourceMixin): else: raise ValueError('{} is not a dynamic input field'.format(field_name)) - def validate_role_assignment(self, actor, role_definition): + def validate_role_assignment(self, actor, role_definition, **kwargs): if self.organization: if isinstance(actor, User): - if actor.is_superuser or Organization.access_qs(actor, 'member').filter(id=self.organization.id).exists(): + if actor.is_superuser: + return + if Organization.access_qs(actor, 'member').filter(id=self.organization.id).exists(): + return + + requesting_user = kwargs.get('requesting_user', None) + if check_resource_server_for_user_in_organization(actor, self.organization, requesting_user): return if isinstance(actor, Team): if actor.organization == self.organization: diff --git a/awx/main/models/execution_environments.py b/awx/main/models/execution_environments.py index 321b38264b..ea74125f8f 100644 --- a/awx/main/models/execution_environments.py +++ b/awx/main/models/execution_environments.py @@ -58,11 +58,20 @@ class ExecutionEnvironment(CommonModel): def get_absolute_url(self, request=None): return reverse('api:execution_environment_detail', kwargs={'pk': self.pk}, request=request) - def validate_role_assignment(self, actor, role_definition): + def validate_role_assignment(self, actor, role_definition, **kwargs): + from awx.main.models.credential import check_resource_server_for_user_in_organization + if self.managed: raise ValidationError({'object_id': _('Can not assign object roles to managed Execution Environments')}) if self.organization_id is None: raise ValidationError({'object_id': _('Can not assign object roles to global Execution Environments')}) - if actor._meta.model_name == 'user' and (not actor.has_obj_perm(self.organization, 'view')): + if actor._meta.model_name == 'user': + if actor.has_obj_perm(self.organization, 'view'): + return + + requesting_user = kwargs.get('requesting_user', None) + if check_resource_server_for_user_in_organization(actor, self.organization, requesting_user): + return + raise ValidationError({'user': _('User must have view permission to Execution Environment organization')})