Compare commits

...

23 Commits

Author SHA1 Message Date
Seth Foster
853730acb9 Allow deleting org of a running workflow job (#15374)
Old RBAC system hits DOESNOTEXIST query errors
if a user deletes an org while a workflow job is active.

The error is triggered by
1. starting workflow job
2. delete the org that the workflow job is a part of
3. The workflow changes status (e.g. pending to waiting)

This error message would surface
awx.main.models.rbac.Role.DoesNotExist: Role matching
query does not exist.

The fix is wrap the query in a try catch, and skip
over some logic if the roles don't exist.

---------

Signed-off-by: Seth Foster <fosterbseth@gmail.com>
2024-07-18 09:40:58 -04:00
Don Naro
f1448fced1 update terminology (#15357)
* update terminology

Replace some instances of Tower with AWX and remove some references to
enterprise left over from the migration of RST content from the
Automation Controller docs.

* Update docs/docsite/rst/userguide/overview.rst

Co-authored-by: TVo <thavo@redhat.com>

---------

Co-authored-by: TVo <thavo@redhat.com>
2024-07-18 10:29:21 +01:00
Hao Liu
7697b6a69b Pin 3rd party action at SHA
For safety
2024-07-17 22:58:15 +02:00
Chris Meyers
22a491c32c Put DAB version in the PR title 2024-07-17 15:10:25 -04:00
Chris Meyers
cbd9dce940 Run at 6 am every day 2024-07-17 15:10:25 -04:00
Chris Meyers
a4fdcc1cca Update .github/workflows/dab-release.yml
Co-authored-by: Hao Liu <44379968+TheRealHaoLiu@users.noreply.github.com>
2024-07-17 15:10:25 -04:00
Chris Meyers
df95439008 Update .github/workflows/dab-release.yml
Co-authored-by: Hao Liu <44379968+TheRealHaoLiu@users.noreply.github.com>
2024-07-17 15:10:25 -04:00
Chris Meyers
acd834df8b Check and update django-ansible-base
* Check upstream django-ansible-base releases. If the version upstream
  does not match the version we are pinned to then submit a PR with the
  upstream version.
2024-07-17 15:10:25 -04:00
TVo
587f0ecf98 Updated the api file to reflect 2024 date (#15369) 2024-07-16 19:58:55 +00:00
Hao Liu
5a2091f7bf Build new/old UI with different nodejs version (#15368) 2024-07-16 13:18:47 -04:00
Hao Liu
fa7423819a Fix minor docker build warning (#15362)
Fix docker build warning

Fix
```
WARN: FromAsCasing: 'as' and 'FROM' keywords' casing do not match (line 8)
```
2024-07-15 13:34:46 +00:00
Alan Rominger
fde8af9f11 Fix task ending in error due to bad iterator (#15355) 2024-07-12 13:20:39 -04:00
Seth Foster
209e7e27b1 Check member of org when granting cred (#15353)
A user needs to be a member of the org
in order to use a credential in that org.

We were incorrectly checking for "change"
permission of the org, instead of "member".

Signed-off-by: Seth Foster <fosterbseth@gmail.com>
2024-07-10 21:46:26 -04:00
Hao Liu
6c7d29a982 Fix command to set db session timeout for locks (#15352)
Fix command to set db session timeout

Add quote around the value of the setting

Example failures
```
2024-07-10 13:33:29,237 ERROR    [a7e55a64e6744a0e920bb1fd78615e5f] awx.main.dispatch Worker failed to run task awx.main.tasks.system.awx_periodic_scheduler(*[], **{}
Traceback (most recent call last):
  File "/var/lib/awx/venv/awx/lib64/python3.11/site-packages/django/db/backends/utils.py", line 87, in _execute
    return self.cursor.execute(sql)
           ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/var/lib/awx/venv/awx/lib64/python3.11/site-packages/psycopg/cursor.py", line 732, in execute
    raise ex.with_traceback(None)
psycopg.errors.SyntaxError: trailing junk after numeric literal at or near "1d"
LINE 1: SET idle_in_transaction_session_timeout = 1d
                                                  ^
```
2024-07-10 11:11:12 -04:00
Alan Rominger
282ba36839 Fix EE admin not being able to PATCH/PUT object while providing organization (#15348)
* Fix bug where EE object-level admin could not set organization

* Finish polishing up test
2024-07-09 16:55:09 -04:00
Alan Rominger
b727d2c3b3 Log conflicts and created items by the periodic resource sync (#15337)
* Initial lazy logging of periodic sync results

* Add desired polish to log

* Add debug log
2024-07-09 15:13:08 -04:00
Hao Liu
7fc3d5c7c7 Update ActivityStream UI query to order by id (#15346)
Timestamp for activity stream is not indexed result in slow query. switching to ID (which effectively will is order by created time) to improve performance
2024-07-09 14:38:07 -04:00
TVo
4e055f46c4 Added note to API guide for filtering exact matches (#15332) 2024-07-09 10:55:19 -06:00
Seth Foster
f595985b7c Callback for role assignment (#15339)
Validate role assignment if org defined

Check that organization is defined on credential
before running queries.

Fixes a "None type does not have attribute id" error.

Signed-off-by: Seth Foster <fosterbseth@gmail.com>
2024-07-09 09:44:27 -04:00
Alan Rominger
ea232315bf Do not reference self.messages when it does not exist (#15331) 2024-07-03 20:07:46 +00:00
Alan Rominger
ee251812b5 Add complete test that we have analogs to old versions of roles, fix some mismatches (#15321)
* Add test that we got all permissions right for every role

* Fix missing Org execute role and missing adhoc role permission

* Add in missing Organization Approval Role as well

* Remove Role from role names
2024-07-03 15:40:55 -04:00
Alan Rominger
00ba1ea569 Suppress docker pull output in checks (#15323)
Supress docker pull output in checks
2024-07-03 15:04:59 -04:00
Alan Rominger
d91af132c1 Fix server error assigning teams EE object roles (#15320) 2024-07-03 14:07:03 -04:00
26 changed files with 340 additions and 61 deletions

View File

@@ -24,7 +24,7 @@ runs:
- name: Pre-pull latest devel image to warm cache
shell: bash
run: docker pull ghcr.io/${OWNER_LC}/awx_devel:${{ github.base_ref }}
run: docker pull -q ghcr.io/${OWNER_LC}/awx_devel:${{ github.base_ref }}
- name: Build image for current source checkout
shell: bash

48
.github/workflows/dab-release.yml vendored Normal file
View File

@@ -0,0 +1,48 @@
---
name: django-ansible-base requirements update
on:
workflow_dispatch:
schedule:
- cron: '0 6 * * *' # once an day @ 6 AM
permissions:
pull-requests: write
contents: write
jobs:
dab-pin-newest:
runs-on: ubuntu-latest
steps:
- id: dab-release
name: Get current django-ansible-base release version
uses: pozetroninc/github-action-get-latest-release@2a61c339ea7ef0a336d1daa35ef0cb1418e7676c # v0.8.0
with:
owner: ansible
repo: django-ansible-base
excludes: prerelease, draft
- name: Check out respository code
uses: actions/checkout@v4
- id: dab-pinned
name: Get current django-ansible-base pinned version
run:
echo "version=$(requirements/django-ansible-base-pinned-version.sh)" >> "$GITHUB_OUTPUT"
- name: Update django-ansible-base pinned version to upstream release
run:
requirements/django-ansible-base-pinned-version.sh -s ${{ steps.dab-release.outputs.release }}
- name: Create Pull Request
uses: peter-evans/create-pull-request@c5a7806660adbe173f04e3e038b0ccdcd758773c # v6
with:
base: devel
branch: bump-django-ansible-base
title: Bump django-ansible-base to ${{ steps.dab-release.outputs.release }}
body: |
Automated .github/workflows/dab-release.yml
django-ansible-base upstream released version == ${{ steps.dab-release.outputs.release }}
requirements_git.txt django-ansible-base pinned version == ${{ steps.dab-pinned.outputs.version }}
commit-message: |
Update django-ansible-base version to ${{ steps.dab-pinned.outputs.version }}
add-paths:
requirements/requirements_git.txt

View File

@@ -60,16 +60,26 @@ jobs:
run: |
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
- name: Setup node and npm
- name: Setup node and npm for old UI build
uses: actions/setup-node@v2
with:
node-version: '16.13.1'
node-version: '16'
if: matrix.build-targets.image-name == 'awx'
- name: Prebuild UI for awx image (to speed up build process)
- name: Prebuild old-UI for awx image (to speed up build process)
run: |
sudo apt-get install gettext
make ui-release
if: matrix.build-targets.image-name == 'awx'
- name: Setup node and npm for the new UI build
uses: actions/setup-node@v2
with:
node-version: '18'
if: matrix.build-targets.image-name == 'awx'
- name: Prebuild new UI for awx image (to speed up build process)
run: |
make ui-next
if: matrix.build-targets.image-name == 'awx'

View File

@@ -136,9 +136,9 @@ jobs:
- name: Pulling images for test deployment with awx-operator
# awx operator molecue test expect to kind load image and buildx exports image to registry and not local
run: |
docker pull ${AWX_OPERATOR_TEST_IMAGE}
docker pull ${AWX_EE_TEST_IMAGE}
docker pull ${AWX_TEST_IMAGE}:${AWX_TEST_VERSION}
docker pull -q ${AWX_OPERATOR_TEST_IMAGE}
docker pull -q ${AWX_EE_TEST_IMAGE}
docker pull -q ${AWX_TEST_IMAGE}:${AWX_TEST_VERSION}
- name: Run test deployment with awx-operator
working-directory: awx-operator

View File

@@ -34,7 +34,7 @@ jobs:
- name: Pre-pull image to warm build cache
run: |
docker pull ghcr.io/${{ github.repository_owner }}/awx_devel:${GITHUB_REF##*/} || :
docker pull -q ghcr.io/${{ github.repository_owner }}/awx_devel:${GITHUB_REF##*/} || :
- name: Build image
run: |

View File

@@ -1419,10 +1419,6 @@ class ExecutionEnvironmentAccess(BaseAccess):
else:
if self.user not in obj.organization.execution_environment_admin_role:
raise PermissionDenied
if data and 'organization' in data:
new_org = get_object_from_data('organization', Organization, data, obj=obj)
if not new_org or self.user not in new_org.execution_environment_admin_role:
return False
return self.check_related('organization', Organization, data, obj=obj, role_field='execution_environment_admin_role')
def can_delete(self, obj):
@@ -2100,15 +2096,18 @@ class WorkflowJobTemplateAccess(NotificationAttachMixin, BaseAccess):
if not self.check_related('organization', Organization, data, role_field='workflow_admin_role', mandatory=True):
if data.get('organization', None) is None:
self.messages['organization'] = [_('An organization is required to create a workflow job template for normal user')]
if self.save_messages:
self.messages['organization'] = [_('An organization is required to create a workflow job template for normal user')]
return False
if not self.check_related('inventory', Inventory, data, role_field='use_role'):
self.messages['inventory'] = [_('You do not have use_role to the inventory')]
if self.save_messages:
self.messages['inventory'] = [_('You do not have use_role to the inventory')]
return False
if not self.check_related('execution_environment', ExecutionEnvironment, data, role_field='read_role'):
self.messages['execution_environment'] = [_('You do not have read_role to the execution environment')]
if self.save_messages:
self.messages['execution_environment'] = [_('You do not have read_role to the execution environment')]
return False
return True

View File

@@ -277,7 +277,6 @@ def setup_managed_role_definitions(apps, schema_editor):
to_create = {
'object_admin': '{cls.__name__} Admin',
'org_admin': 'Organization Admin',
'org_audit': 'Organization Audit',
'org_children': 'Organization {cls.__name__} Admin',
'special': '{cls.__name__} {action}',
}
@@ -334,12 +333,19 @@ def setup_managed_role_definitions(apps, schema_editor):
for perm in special_perms:
action = perm.codename.split('_')[0]
view_perm = Permission.objects.get(content_type=ct, codename__startswith='view_')
perm_list = [perm, view_perm]
# Handle special-case where adhoc role also listed use permission
if action == 'adhoc':
for other_perm in object_perms:
if other_perm.codename == 'use_inventory':
perm_list.append(other_perm)
break
managed_role_definitions.append(
get_or_create_managed(
to_create['special'].format(cls=cls, action=action.title()),
f'Has {action} permissions to a single {cls._meta.verbose_name}',
ct,
[perm, view_perm],
perm_list,
RoleDefinition,
)
)
@@ -355,18 +361,40 @@ def setup_managed_role_definitions(apps, schema_editor):
)
)
if 'org_audit' in to_create:
audit_permissions = [perm for perm in org_perms if perm.codename.startswith('view_')]
audit_permissions.append(Permission.objects.get(codename='audit_organization'))
managed_role_definitions.append(
get_or_create_managed(
to_create['org_audit'].format(cls=Organization),
'Has permission to view all objects inside of a single organization',
org_ct,
audit_permissions,
RoleDefinition,
)
# Special "organization action" roles
audit_permissions = [perm for perm in org_perms if perm.codename.startswith('view_')]
audit_permissions.append(Permission.objects.get(codename='audit_organization'))
managed_role_definitions.append(
get_or_create_managed(
'Organization Audit',
'Has permission to view all objects inside of a single organization',
org_ct,
audit_permissions,
RoleDefinition,
)
)
org_execute_permissions = {'view_jobtemplate', 'execute_jobtemplate', 'view_workflowjobtemplate', 'execute_workflowjobtemplate', 'view_organization'}
managed_role_definitions.append(
get_or_create_managed(
'Organization Execute',
'Has permission to execute all runnable objects in the organization',
org_ct,
[perm for perm in org_perms if perm.codename in org_execute_permissions],
RoleDefinition,
)
)
org_approval_permissions = {'view_organization', 'view_workflowjobtemplate', 'approve_workflowjobtemplate'}
managed_role_definitions.append(
get_or_create_managed(
'Organization Approval',
'Has permission to approve any workflow steps within a single organization',
org_ct,
[perm for perm in org_perms if perm.codename in org_approval_permissions],
RoleDefinition,
)
)
unexpected_role_definitions = RoleDefinition.objects.filter(managed=True).exclude(pk__in=[rd.pk for rd in managed_role_definitions])
for role_definition in unexpected_role_definitions:

View File

@@ -321,13 +321,14 @@ class Credential(PasswordFieldsModel, CommonModelNameNotUnique, ResourceMixin):
raise ValueError('{} is not a dynamic input field'.format(field_name))
def validate_role_assignment(self, actor, role_definition):
if isinstance(actor, User):
if actor.is_superuser or Organization.access_qs(actor, 'change').filter(id=self.organization.id).exists():
return
if isinstance(actor, Team):
if actor.organization == self.organization:
return
raise DRFValidationError({'detail': _(f"You cannot grant credential access to a {actor._meta.object_name} not in the credentials' organization")})
if self.organization:
if isinstance(actor, User):
if actor.is_superuser or Organization.access_qs(actor, 'member').filter(id=self.organization.id).exists():
return
if isinstance(actor, Team):
if actor.organization == self.organization:
return
raise DRFValidationError({'detail': _(f"You cannot grant credential access to a {actor._meta.object_name} not in the credentials' organization")})
class CredentialType(CommonModelNameNotUnique):

View File

@@ -68,5 +68,5 @@ class ExecutionEnvironment(CommonModel):
raise ValidationError({'user': _('User must have view permission to Execution Environment organization')})
if actor._meta.model_name == 'team':
organization_cls = self._meta.get_field('organization').related_model
if self.orgaanization not in organization_cls.access_qs(actor, 'view'):
if self.organization not in organization_cls.access_qs(actor, 'view'):
raise ValidationError({'team': _('Team must have view permission to Execution Environment organization')})

View File

@@ -689,9 +689,15 @@ def sync_parents_to_new_rbac(instance, action, model, pk_set, reverse, **kwargs)
for role_id in pk_set:
if reverse:
child_role = Role.objects.get(id=role_id)
try:
child_role = Role.objects.get(id=role_id)
except Role.DoesNotExist:
continue
else:
parent_role = Role.objects.get(id=role_id)
try:
parent_role = Role.objects.get(id=role_id)
except Role.DoesNotExist:
continue
# To a fault, we want to avoid running this if triggered from implicit_parents management
# we only want to do anything if we know for sure this is a non-implicit team role

View File

@@ -980,5 +980,15 @@ def periodic_resource_sync():
if acquired is False:
logger.debug("Not running periodic_resource_sync, another task holds lock")
return
logger.debug("Running periodic resource sync")
SyncExecutor().run()
executor = SyncExecutor()
executor.run()
for key, item_list in executor.results.items():
if not item_list or key == 'noop':
continue
# Log creations and conflicts
if len(item_list) > 10 and settings.LOG_AGGREGATOR_LEVEL != 'DEBUG':
logger.info(f'Periodic resource sync {key}, first 10 items:\n{item_list[:10]}')
else:
logger.info(f'Periodic resource sync {key}:\n{item_list}')

View File

@@ -128,7 +128,7 @@ def test_assign_credential_to_user_of_another_org(setup_managed_roles, credentia
rd = RoleDefinition.objects.get(name="Credential Admin")
credential.organization = organization
credential.save(update_fields=['organization'])
assert credential.organization not in Organization.access_qs(rando, 'change')
assert credential.organization not in Organization.access_qs(rando, 'member')
url = django_reverse('roleuserassignment-list')
resp = post(url=url, data={"user": rando.id, "role_definition": rd.id, "object_id": credential.id}, user=admin_user, expect=400)
assert "You cannot grant credential access to a User not in the credentials' organization" in str(resp.data)
@@ -139,7 +139,7 @@ def test_assign_credential_to_user_of_another_org(setup_managed_roles, credentia
post(url=url, data={"user": rando.id, "role_definition": rd.id, "object_id": credential.id}, user=admin_user, expect=201)
# can assign credential to org_admin
assert credential.organization in Organization.access_qs(org_admin, 'change')
assert credential.organization in Organization.access_qs(org_admin, 'member')
post(url=url, data={"user": org_admin.id, "role_definition": rd.id, "object_id": credential.id}, user=admin_user, expect=201)

View File

@@ -1,4 +1,5 @@
from unittest import mock
import json
import pytest
@@ -6,11 +7,13 @@ from django.contrib.contenttypes.models import ContentType
from crum import impersonate
from awx.main.models.rbac import get_role_from_object_role, give_creator_permissions
from awx.main.fields import ImplicitRoleField
from awx.main.models.rbac import get_role_from_object_role, give_creator_permissions, get_role_codenames, get_role_definition
from awx.main.models import User, Organization, WorkflowJobTemplate, WorkflowJobTemplateNode, Team
from awx.api.versioning import reverse
from ansible_base.rbac.models import RoleUserAssignment, RoleDefinition
from ansible_base.rbac import permission_registry
@pytest.mark.django_db
@@ -24,6 +27,7 @@ from ansible_base.rbac.models import RoleUserAssignment, RoleDefinition
'auditor_role',
'read_role',
'execute_role',
'approval_role',
'notification_admin_role',
],
)
@@ -39,6 +43,37 @@ def test_round_trip_roles(organization, rando, role_name, setup_managed_roles):
assert old_role.id == getattr(organization, role_name).id
@pytest.mark.django_db
@pytest.mark.parametrize('model', sorted(permission_registry.all_registered_models, key=lambda cls: cls._meta.model_name))
def test_role_migration_matches(request, model, setup_managed_roles):
fixture_name = model._meta.verbose_name.replace(' ', '_')
obj = request.getfixturevalue(fixture_name)
role_ct = 0
for field in obj._meta.get_fields():
if isinstance(field, ImplicitRoleField):
if field.name == 'read_role':
continue # intentionally left as "Compat" roles
role_ct += 1
old_role = getattr(obj, field.name)
old_codenames = set(get_role_codenames(old_role))
rd = get_role_definition(old_role)
new_codenames = set(rd.permissions.values_list('codename', flat=True))
# all the old roles should map to a non-Compat role definition
if 'Compat' not in rd.name:
model_rds = RoleDefinition.objects.filter(content_type=ContentType.objects.get_for_model(obj))
rd_data = {}
for rd in model_rds:
rd_data[rd.name] = list(rd.permissions.values_list('codename', flat=True))
assert (
'Compat' not in rd.name
), f'Permissions for old vs new roles did not match.\nold {field.name}: {old_codenames}\nnew:\n{json.dumps(rd_data, indent=2)}'
assert new_codenames == set(old_codenames)
# In the old system these models did not have object-level roles, all others expect some model roles
if model._meta.model_name not in ('notificationtemplate', 'executionenvironment'):
assert role_ct > 0
@pytest.mark.django_db
def test_role_naming(setup_managed_roles):
qs = RoleDefinition.objects.filter(content_type=ContentType.objects.get(model='jobtemplate'), name__endswith='dmin')

View File

@@ -3,7 +3,7 @@ import pytest
from django.contrib.contenttypes.models import ContentType
from awx.main.access import ExecutionEnvironmentAccess
from awx.main.models import ExecutionEnvironment, Organization
from awx.main.models import ExecutionEnvironment, Organization, Team
from awx.main.models.rbac import get_role_codenames
from awx.api.versioning import reverse
@@ -77,6 +77,26 @@ def test_org_member_required_for_assignment(org_ee, ee_rd, rando, admin_user, po
assert 'User must have view permission to Execution Environment organization' in str(r.data)
@pytest.mark.django_db
def test_team_view_permission_required(org_ee, ee_rd, rando, admin_user, post):
org2 = Organization.objects.create(name='a different team')
team = Team.objects.create(name='a team', organization=org2)
team.member_role.members.add(rando)
assert org_ee not in ExecutionEnvironmentAccess(rando).get_queryset() # user can not view the EE
url = django_reverse('roleteamassignment-list')
r = post(url, {'role_definition': ee_rd.pk, 'team': team.id, 'object_id': org_ee.pk}, user=admin_user, expect=400)
assert 'Team must have view permission to Execution Environment organization' in str(r.data)
org_view_rd = RoleDefinition.objects.create_from_permissions(
name='organization viewer role', permissions=['view_organization'], content_type=ContentType.objects.get_for_model(Organization)
)
org_view_rd.give_permission(team, org_ee.organization)
assert org_ee in ExecutionEnvironmentAccess(rando).get_queryset() # user can view the EE now
# can give object roles to the team now
post(url, {'role_definition': ee_rd.pk, 'team': team.id, 'object_id': org_ee.pk}, user=admin_user, expect=201)
assert rando.has_obj_perm(org_ee, 'change')
@pytest.mark.django_db
def test_give_object_permission_to_ee(org_ee, ee_rd, org_member, check_user_capabilities):
access = ExecutionEnvironmentAccess(org_member)
@@ -85,11 +105,29 @@ def test_give_object_permission_to_ee(org_ee, ee_rd, org_member, check_user_capa
check_user_capabilities(org_member, org_ee, {'edit': False, 'delete': False, 'copy': False})
ee_rd.give_permission(org_member, org_ee)
assert access.can_change(org_ee, {'name': 'new'})
assert access.can_change(org_ee, {'name': 'new', 'organization': org_ee.organization.id})
check_user_capabilities(org_member, org_ee, {'edit': True, 'delete': True, 'copy': False})
@pytest.mark.django_db
def test_need_related_organization_access(org_ee, ee_rd, org_member):
org2 = Organization.objects.create(name='another organization')
ee_rd.give_permission(org_member, org_ee)
org2.member_role.members.add(org_member)
access = ExecutionEnvironmentAccess(org_member)
assert access.can_change(org_ee, {'name': 'new', 'organization': org_ee.organization})
assert access.can_change(org_ee, {'name': 'new', 'organization': org_ee.organization.id})
assert not access.can_change(org_ee, {'name': 'new', 'organization': org2.id})
assert not access.can_change(org_ee, {'name': 'new', 'organization': org2})
# User can make the change if they have relevant permission to the new organization
org_ee.organization.execution_environment_admin_role.members.add(org_member)
org2.execution_environment_admin_role.members.add(org_member)
assert access.can_change(org_ee, {'name': 'new', 'organization': org2.id})
assert access.can_change(org_ee, {'name': 'new', 'organization': org2})
@pytest.mark.django_db
@pytest.mark.parametrize('style', ['new', 'old'])
def test_give_org_permission_to_ee(org_ee, organization, org_member, check_user_capabilities, style, org_ee_rd):
@@ -103,5 +141,5 @@ def test_give_org_permission_to_ee(org_ee, organization, org_member, check_user_
else:
organization.execution_environment_admin_role.members.add(org_member)
assert access.can_change(org_ee, {'name': 'new'})
assert access.can_change(org_ee, {'name': 'new', 'organization': organization.id})
check_user_capabilities(org_member, org_ee, {'edit': True, 'delete': True, 'copy': True})

View File

@@ -48,3 +48,17 @@ def test_org_resource_role(ext_auth, organization, rando, org_admin):
assert access.can_attach(organization, rando, 'member_role.members') == ext_auth
organization.member_role.members.add(rando)
assert access.can_unattach(organization, rando, 'member_role.members') == ext_auth
@pytest.mark.django_db
def test_delete_org_while_workflow_active(workflow_job_template):
'''
Delete org while workflow job is active (i.e. changing status)
'''
assert workflow_job_template.organization # sanity check
wj = workflow_job_template.create_unified_job() # status should be new
workflow_job_template.organization.delete()
wj.refresh_from_db()
assert wj.status != 'pending' # sanity check
wj.status = 'pending' # status needs to change in order to trigger workflow_job_template.save()
wj.save(update_fields=['status'])

View File

@@ -17,13 +17,13 @@ def advisory_lock(*args, lock_session_timeout_milliseconds=0, **kwargs):
with connection.cursor() as cur:
idle_in_transaction_session_timeout = cur.execute('SHOW idle_in_transaction_session_timeout').fetchone()[0]
idle_session_timeout = cur.execute('SHOW idle_session_timeout').fetchone()[0]
cur.execute(f"SET idle_in_transaction_session_timeout = {lock_session_timeout_milliseconds}")
cur.execute(f"SET idle_session_timeout = {lock_session_timeout_milliseconds}")
cur.execute(f"SET idle_in_transaction_session_timeout = '{lock_session_timeout_milliseconds}'")
cur.execute(f"SET idle_session_timeout = '{lock_session_timeout_milliseconds}'")
with django_pglocks_advisory_lock(*args, **kwargs) as internal_lock:
yield internal_lock
if lock_session_timeout_milliseconds > 0:
with connection.cursor() as cur:
cur.execute(f"SET idle_in_transaction_session_timeout = {idle_in_transaction_session_timeout}")
cur.execute(f"SET idle_session_timeout = {idle_session_timeout}")
cur.execute(f"SET idle_in_transaction_session_timeout = '{idle_in_transaction_session_timeout}'")
cur.execute(f"SET idle_session_timeout = '{idle_session_timeout}'")
else:
yield True

View File

@@ -64,7 +64,7 @@
<div class="col-sm-6">
</div>
<div class="col-sm-6 footer-copyright">
Copyright &copy; 2021 <a href="http://www.redhat.com" target="_blank">Red Hat</a>, Inc. All Rights Reserved.
Copyright &copy; 2024 <a href="http://www.redhat.com" target="_blank">Red Hat</a>, Inc. All Rights Reserved.
</div>
</div>
</div>

View File

@@ -59,7 +59,7 @@ function ActivityStream() {
{
page: 1,
page_size: 20,
order_by: '-timestamp',
order_by: '-id',
},
['id', 'page', 'page_size']
);

View File

@@ -23,7 +23,6 @@ Authentication
.. index::
single: social authentication
single: authentication
single: enterprise authentication
pair: configuration; authentication
.. include:: ./configure_awx_authentication.rst

View File

@@ -18,7 +18,7 @@ For example, if you uploaded a specific logo, and added the following text:
:alt: Edit User Interface Settings form populated with custom text and logo.
The Tower login dialog would look like this:
The AWX login dialog would look like this:
.. image:: ../common/images/configure-awx-ui-angry-spud-login.png
:alt: AWX login screen with custom text and logo.

View File

@@ -8,7 +8,7 @@ Authentication Methods Using the API
pair: OAuth 2 Token; authentication
pair: SSO; authentication
This chapter describes the numerous enterprise authentication methods, the best use case for each, and examples:
This chapter describes different authentication methods, the best use case for each, and examples:
.. contents::
:local:

View File

@@ -94,7 +94,7 @@ Field lookups may also be used for more advanced queries, by appending the looku
The following field lookups are supported:
- ``exact``: Exact match (default lookup if not specified).
- ``exact``: Exact match (default lookup if not specified, refer to the following note for more information).
- ``iexact``: Case-insensitive version of exact.
- ``contains``: Field contains value.
- ``icontains``: Case-insensitive version of contains.
@@ -122,3 +122,18 @@ Filtering based on the requesting user's level of access by query string paramet
- ``role_level``: Level of role to filter on, such as ``admin_role``
.. note::
Previous releases of AWX returned queries with **__exact** results by default, but you may find that the latest versions are returning a larger subset instead. As a workaround, set the ``limit`` to ``?limit__exact`` for the default filter. For example, ``/api/v2/jobs/?limit__exact=example.domain.com`` results in:
::
{
"count": 1,
"next": null,
"previous": null,
"results": [
...
.. this note is generically written for AWX. For downstream, the change started in AAP 2.0 so we can be more specific if necessary.

View File

@@ -189,7 +189,7 @@ Authentication Enhancements
pair: features; authentication
pair: features; OAuth 2 token
AWX supports LDAP, SAML, token-based authentication. Enhanced LDAP and SAML support allows you to integrate your enterprise account information in a more flexible manner. Token-based Authentication allows for easily authentication of third-party tools and services with AWX via integrated OAuth 2 token support.
AWX supports LDAP, SAML, token-based authentication. Enhanced LDAP and SAML support allows you to integrate your account information in a more flexible manner. Token-based Authentication allows for easily authentication of third-party tools and services with AWX via integrated OAuth 2 token support.
Cluster Management
~~~~~~~~~~~~~~~~~~~~
@@ -240,9 +240,8 @@ Job Distribution
pair: features; jobs, slicing
pair: features; jobs, distribution
As automation moves enterprise-wide, the need to automate at scale grows. AWX offer the ability to take a fact gathering or
configuration job running across thousands of machines and slice it into individual job slices that can be distributed across your AWX cluster for increased reliability, faster job completion, and better cluster utilization. If you need to change a parameter across 15,000 switches at
scale, or gather information across your multi-thousand-node RHEL estate, you can now do so easily.
AWX offers the ability to take a fact gathering or configuration job running across thousands of machines and slice it into individual job slices that can be distributed across your AWX cluster for increased reliability, faster job completion, and better cluster utilization.
If you need to change a parameter across 15,000 switches at scale, or gather information across your multi-thousand-node RHEL estate, you can now do so easily.
Support for deployment in a FIPS-enabled environment

View File

@@ -21,7 +21,7 @@ DAB RBAC
single: roles
pair: DAB; RBAC
This section describes the latest changes to RBAC, involving use of the ``django-ansible-base`` (DAB) library, to enhance existing roles, provide a uniformed model that is compatible with platform (enterprise) components, and allow creation of custom roles. However, the internals of the system in the backend have changes implemented, but they are not reflected yet in the AWX UI. The change to the backend maintains a compatibility layer so the “old” roles in the API still exists temporarily, until a fully-functional compatible UI replaces the existing roles.
This section describes the latest changes to RBAC, involving use of the ``django-ansible-base`` (DAB) library, to enhance existing roles, and allow creation of custom roles. However, the internals of the system in the backend have changes implemented, but they are not reflected yet in the AWX UI. The change to the backend maintains a compatibility layer so the “old” roles in the API still exists temporarily, until a fully-functional compatible UI replaces the existing roles.
New functionality, specifically custom roles, are possible through direct API clients or the API browser, but the presentation in the AWX UI might not reflect the changes made in the API.

View File

@@ -0,0 +1,77 @@
#!/bin/bash
set +x
# CONSTANTS
export REGEX_LEFT='https://github.com/ansible/django-ansible-base@'
export REGEX_RIGHT='#egg=django-ansible-base'
# GLOBALS
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
REQ_FILE=$SCRIPT_DIR/requirements_git.txt
# Pin Function
DESIRED_VERSION=''
Pin()
{
export DESIRED_VERSION
perl -p -i -e 's/\Q$ENV{REGEX_LEFT}\E(.*?)\Q$ENV{REGEX_RIGHT}\E/$ENV{REGEX_LEFT}$ENV{DESIRED_VERSION}$ENV{REGEX_RIGHT}/g' $REQ_FILE
}
# Current Function
Current()
{
REQUIREMENTS_LINE=$(grep django-ansible-base $REQ_FILE)
echo "$REQUIREMENTS_LINE" | perl -nE 'say $1 if /\Q$ENV{REGEX_LEFT}\E(.*?)\Q$ENV{REGEX_RIGHT}\E/'
}
Help()
{
# Display Help
echo ""
echo "Help:"
echo ""
echo "Interact with django-ansible-base in $REQ_FILE."
echo "By default, output the current django-ansible-base pinned version."
echo
echo "Syntax: scriptTemplate [-s|h|v]"
echo "options:"
echo "s Set django-ansible-base version to pin to."
echo "h Print this Help."
echo "v Verbose mode."
echo
}
if [ $# -eq 0 ]; then
Current
exit
fi
while getopts ":hs:" option; do
case $option in
h) # display Help
Help
exit
;;
s)
DESIRED_VERSION=$OPTARG;;
:)
echo "Option -${OPTARG} requires an argument."
Help
exit 1
;;
\?) # Invalid option
echo "Error: Invalid option"
echo ""
Help
exit;;
esac
done
if [ -n "$DESIRED_VERSION" ]; then
Pin
Current
fi

View File

@@ -5,7 +5,7 @@
###
# Build container
FROM quay.io/centos/centos:stream9 as builder
FROM quay.io/centos/centos:stream9 AS builder
ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en