Skip to content

Commit

Permalink
Validate org-user membership from gateway (#15508)
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
fosterseth authored Sep 13, 2024
1 parent acd6b2e commit 3baea0f
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 4 deletions.
55 changes: 53 additions & 2 deletions awx/main/models/credential.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@
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')
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -324,10 +369,16 @@ def _get_dynamic_input(self, field_name):
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:
Expand Down
13 changes: 11 additions & 2 deletions awx/main/models/execution_environments.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,20 @@ class Meta:
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')})

0 comments on commit 3baea0f

Please sign in to comment.