summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSeth Foster <fosterseth@users.noreply.github.com>2024-09-13 23:56:43 +0200
committerGitHub <noreply@github.com>2024-09-13 23:56:43 +0200
commit3baea0f206fa0901c1811408a39533f941816c0a (patch)
treecf8a0081a2f308647f5c6b636ff60dc4dee5697b
parentFix instance UI URL generated by API (#15517) (diff)
downloadawx-3baea0f206fa0901c1811408a39533f941816c0a.tar.xz
awx-3baea0f206fa0901c1811408a39533f941816c0a.zip
Validate org-user membership from gateway (#15508)
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 <fosterbseth@gmail.com>
-rw-r--r--awx/main/models/credential.py55
-rw-r--r--awx/main/models/execution_environments.py13
2 files changed, 64 insertions, 4 deletions
diff --git a/awx/main/models/credential.py b/awx/main/models/credential.py
index 2f4a56a890..53d817e346 100644
--- a/awx/main/models/credential.py
+++ b/awx/main/models/credential.py
@@ -53,6 +53,11 @@ from awx.main.models import Team, Organization
from awx.main.utils import encrypt_field
from awx_plugins.credentials 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')
@@ -81,6 +86,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
@@ -324,10 +369,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')})