From 47a061eb390c522b201375fc393324ef2499b71e Mon Sep 17 00:00:00 2001 From: Alan Rominger Date: Wed, 24 Apr 2024 15:14:03 -0400 Subject: [PATCH 1/4] Fix and test data migration error from DAB RBAC (#15138) * Fix and test data migration error from DAB RBAC * Fix up migration test * Fix custom method bug * Fix another fat fingered bug --- awx/main/migrations/_dab_rbac.py | 13 ++++++++++++- awx/main/tests/functional/test_migrations.py | 17 +++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/awx/main/migrations/_dab_rbac.py b/awx/main/migrations/_dab_rbac.py index 6e3c04882f..f97c80e29a 100644 --- a/awx/main/migrations/_dab_rbac.py +++ b/awx/main/migrations/_dab_rbac.py @@ -140,6 +140,17 @@ def get_permissions_for_role(role_field, children_map, apps): return perm_list +def model_class(ct, apps): + """ + You can not use model methods in migrations, so this duplicates + what ContentType.model_class does, using current apps + """ + try: + return apps.get_model(ct.app_label, ct.model) + except LookupError: + return None + + def migrate_to_new_rbac(apps, schema_editor): """ This method moves the assigned permissions from the old rbac.py models @@ -197,7 +208,7 @@ def migrate_to_new_rbac(apps, schema_editor): role_definition = managed_definitions[permissions] else: action = role.role_field.rsplit('_', 1)[0] # remove the _field ending of the name - role_definition_name = f'{role.content_type.model_class().__name__} {action.title()}' + role_definition_name = f'{model_class(role.content_type, apps).__name__} {action.title()}' description = role_descriptions[role.role_field] if type(description) == dict: diff --git a/awx/main/tests/functional/test_migrations.py b/awx/main/tests/functional/test_migrations.py index ab877f603f..89697f2cc1 100644 --- a/awx/main/tests/functional/test_migrations.py +++ b/awx/main/tests/functional/test_migrations.py @@ -1,6 +1,7 @@ import pytest from django_test_migrations.plan import all_migrations, nodes_to_tuples +from django.utils.timezone import now """ Most tests that live in here can probably be deleted at some point. They are mainly @@ -68,3 +69,19 @@ class TestMigrationSmoke: bar_peers = bar.peers.all() assert len(bar_peers) == 1 assert fooaddr in bar_peers + + def test_migrate_DAB_RBAC(self, migrator): + old_state = migrator.apply_initial_migration(('main', '0190_alter_inventorysource_source_and_more')) + Organization = old_state.apps.get_model('main', 'Organization') + User = old_state.apps.get_model('auth', 'User') + + org = Organization.objects.create(name='arbitrary-org', created=now(), modified=now()) + user = User.objects.create(username='random-user') + org.read_role.members.add(user) + + new_state = migrator.apply_tested_migration( + ('main', '0192_custom_roles'), + ) + + RoleUserAssignment = new_state.apps.get_model('dab_rbac', 'RoleUserAssignment') + assert RoleUserAssignment.objects.filter(user=user.id, object_id=org.id).exists() From f5f85666c80a92e2552769476935a46bc4644ccb Mon Sep 17 00:00:00 2001 From: Michael Tipton <36353334+CastawayEGR@users.noreply.github.com> Date: Wed, 24 Apr 2024 15:44:31 -0400 Subject: [PATCH 2/4] Add ability to set SameSite policy for userLoggedIn cookie (#15100) * Add ability to set SameSite policy for userLoggedIn cookie * reformat line for linter --- awx/api/generics.py | 4 +++- awx/settings/defaults.py | 3 +++ awx/sso/views.py | 4 +++- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/awx/api/generics.py b/awx/api/generics.py index 7c7fda877e..c51470c1a4 100644 --- a/awx/api/generics.py +++ b/awx/api/generics.py @@ -95,7 +95,9 @@ class LoggedLoginView(auth_views.LoginView): ret = super(LoggedLoginView, self).post(request, *args, **kwargs) if request.user.is_authenticated: logger.info(smart_str(u"User {} logged in from {}".format(self.request.user.username, request.META.get('REMOTE_ADDR', None)))) - ret.set_cookie('userLoggedIn', 'true', secure=getattr(settings, 'SESSION_COOKIE_SECURE', False)) + ret.set_cookie( + 'userLoggedIn', 'true', secure=getattr(settings, 'SESSION_COOKIE_SECURE', False), samesite=getattr(settings, 'USER_COOKIE_SAMESITE', 'Lax') + ) ret.setdefault('X-API-Session-Cookie-Name', getattr(settings, 'SESSION_COOKIE_NAME', 'awx_sessionid')) return ret diff --git a/awx/settings/defaults.py b/awx/settings/defaults.py index 751e419730..c927086354 100644 --- a/awx/settings/defaults.py +++ b/awx/settings/defaults.py @@ -277,6 +277,9 @@ SESSION_COOKIE_SECURE = True # Note: This setting may be overridden by database settings. SESSION_COOKIE_AGE = 1800 +# Option to change userLoggedIn cookie SameSite policy. +USER_COOKIE_SAMESITE = 'Lax' + # Name of the cookie that contains the session information. # Note: Changing this value may require changes to any clients. SESSION_COOKIE_NAME = 'awx_sessionid' diff --git a/awx/sso/views.py b/awx/sso/views.py index c23ee4428a..b6fd724df7 100644 --- a/awx/sso/views.py +++ b/awx/sso/views.py @@ -38,7 +38,9 @@ class CompleteView(BaseRedirectView): response = super(CompleteView, self).dispatch(request, *args, **kwargs) if self.request.user and self.request.user.is_authenticated: logger.info(smart_str(u"User {} logged in".format(self.request.user.username))) - response.set_cookie('userLoggedIn', 'true', secure=getattr(settings, 'SESSION_COOKIE_SECURE', False)) + response.set_cookie( + 'userLoggedIn', 'true', secure=getattr(settings, 'SESSION_COOKIE_SECURE', False), samesite=getattr(settings, 'USER_COOKIE_SAMESITE', 'Lax') + ) response.setdefault('X-API-Session-Cookie-Name', getattr(settings, 'SESSION_COOKIE_NAME', 'awx_sessionid')) return response From 7dc77546f4d067e968d765d740cfabb3daa57f33 Mon Sep 17 00:00:00 2001 From: Bruno Sanchez <58506651+brsanche@users.noreply.github.com> Date: Wed, 24 Apr 2024 20:47:03 +0100 Subject: [PATCH 3/4] Adding CSRF Validation for schemas (#15027) * Adding CSRF Validation for schemas * Changing retrieve of scheme to avoid importing new library * check if CSRF_TRUSTED_ORIGINS exists before accessing it --------- Signed-off-by: Bruno Sanchez --- awx/main/conf.py | 25 +++++++++++++++++++ ...0_alter_inventorysource_source_and_more.py | 1 - 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/awx/main/conf.py b/awx/main/conf.py index b05c4e70c9..6af2d7d64c 100644 --- a/awx/main/conf.py +++ b/awx/main/conf.py @@ -2,6 +2,7 @@ import logging # Django +from django.core.checks import Error from django.utils.translation import gettext_lazy as _ # Django REST Framework @@ -954,3 +955,27 @@ def logging_validate(serializer, attrs): register_validate('logging', logging_validate) + + +def csrf_trusted_origins_validate(serializer, attrs): + if not serializer.instance or not hasattr(serializer.instance, 'CSRF_TRUSTED_ORIGINS'): + return attrs + if 'CSRF_TRUSTED_ORIGINS' not in attrs: + return attrs + errors = [] + for origin in attrs['CSRF_TRUSTED_ORIGINS']: + if "://" not in origin: + errors.append( + Error( + "As of Django 4.0, the values in the CSRF_TRUSTED_ORIGINS " + "setting must start with a scheme (usually http:// or " + "https://) but found %s. See the release notes for details." % origin, + ) + ) + if errors: + error_messages = [error.msg for error in errors] + raise serializers.ValidationError(_('\n'.join(error_messages))) + return attrs + + +register_validate('system', csrf_trusted_origins_validate) diff --git a/awx/main/migrations/0190_alter_inventorysource_source_and_more.py b/awx/main/migrations/0190_alter_inventorysource_source_and_more.py index 0c1eb703ed..47ebd79431 100644 --- a/awx/main/migrations/0190_alter_inventorysource_source_and_more.py +++ b/awx/main/migrations/0190_alter_inventorysource_source_and_more.py @@ -4,7 +4,6 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ ('main', '0189_inbound_hop_nodes'), ] From e4646ae611dec3cc5ddee3a76330f8c87d7ec569 Mon Sep 17 00:00:00 2001 From: irozet12 <119814380+irozet12@users.noreply.github.com> Date: Wed, 24 Apr 2024 22:58:09 +0300 Subject: [PATCH 4/4] Add help message for expiration tokens (#15076) (#15077) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ирина Розет --- .../MiscAuthenticationEdit/MiscAuthenticationEdit.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/awx/ui/src/screens/Setting/MiscAuthentication/MiscAuthenticationEdit/MiscAuthenticationEdit.js b/awx/ui/src/screens/Setting/MiscAuthentication/MiscAuthenticationEdit/MiscAuthenticationEdit.js index 97240efdbc..6bc5a8198e 100644 --- a/awx/ui/src/screens/Setting/MiscAuthentication/MiscAuthenticationEdit/MiscAuthenticationEdit.js +++ b/awx/ui/src/screens/Setting/MiscAuthentication/MiscAuthenticationEdit/MiscAuthenticationEdit.js @@ -78,12 +78,14 @@ function MiscAuthenticationEdit() { default: OAUTH2_PROVIDER_OPTIONS.default.ACCESS_TOKEN_EXPIRE_SECONDS, type: OAUTH2_PROVIDER_OPTIONS.child.type, label: t`Access Token Expiration`, + help_text: t`Access Token Expiration in seconds`, }, REFRESH_TOKEN_EXPIRE_SECONDS: { ...OAUTH2_PROVIDER_OPTIONS, default: OAUTH2_PROVIDER_OPTIONS.default.REFRESH_TOKEN_EXPIRE_SECONDS, type: OAUTH2_PROVIDER_OPTIONS.child.type, label: t`Refresh Token Expiration`, + help_text: t`Refresh Token Expiration in seconds`, }, AUTHORIZATION_CODE_EXPIRE_SECONDS: { ...OAUTH2_PROVIDER_OPTIONS, @@ -91,6 +93,7 @@ function MiscAuthenticationEdit() { OAUTH2_PROVIDER_OPTIONS.default.AUTHORIZATION_CODE_EXPIRE_SECONDS, type: OAUTH2_PROVIDER_OPTIONS.child.type, label: t`Authorization Code Expiration`, + help_text: t`Authorization Code Expiration in seconds`, }, };