mirror of
https://github.com/ansible/awx.git
synced 2026-03-26 21:35:01 -02:30
Compare commits
8 Commits
24.3.0
...
x-request-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
19e3cba35c | ||
|
|
e4646ae611 | ||
|
|
c11ff49a56 | ||
|
|
7dc77546f4 | ||
|
|
f5f85666c8 | ||
|
|
47a061eb39 | ||
|
|
51bcf82cf4 | ||
|
|
c760577855 |
@@ -95,7 +95,9 @@ class LoggedLoginView(auth_views.LoginView):
|
|||||||
ret = super(LoggedLoginView, self).post(request, *args, **kwargs)
|
ret = super(LoggedLoginView, self).post(request, *args, **kwargs)
|
||||||
if request.user.is_authenticated:
|
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))))
|
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'))
|
ret.setdefault('X-API-Session-Cookie-Name', getattr(settings, 'SESSION_COOKIE_NAME', 'awx_sessionid'))
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
# Django
|
# Django
|
||||||
|
from django.core.checks import Error
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
# Django REST Framework
|
# Django REST Framework
|
||||||
@@ -954,3 +955,27 @@ def logging_validate(serializer, attrs):
|
|||||||
|
|
||||||
|
|
||||||
register_validate('logging', logging_validate)
|
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)
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ class TimingMiddleware(threading.local, MiddlewareMixin):
|
|||||||
response['X-API-Profile-File'] = self.prof.stop()
|
response['X-API-Profile-File'] = self.prof.stop()
|
||||||
perf_logger.debug(
|
perf_logger.debug(
|
||||||
f'request: {request}, response_time: {response["X-API-Total-Time"]}',
|
f'request: {request}, response_time: {response["X-API-Total-Time"]}',
|
||||||
extra=dict(python_objects=dict(request=request, response=response, X_API_TOTAL_TIME=response["X-API-Total-Time"])),
|
extra=dict(python_objects=dict(request=request, response=response, X_API_TOTAL_TIME=response["X-API-Total-Time"], x_request_id=request.get('x-request-id', 'not-set'))),
|
||||||
)
|
)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ from django.db import migrations, models
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('main', '0189_inbound_hop_nodes'),
|
('main', '0189_inbound_hop_nodes'),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -140,6 +140,17 @@ def get_permissions_for_role(role_field, children_map, apps):
|
|||||||
return perm_list
|
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):
|
def migrate_to_new_rbac(apps, schema_editor):
|
||||||
"""
|
"""
|
||||||
This method moves the assigned permissions from the old rbac.py models
|
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]
|
role_definition = managed_definitions[permissions]
|
||||||
else:
|
else:
|
||||||
action = role.role_field.rsplit('_', 1)[0] # remove the _field ending of the name
|
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]
|
description = role_descriptions[role.role_field]
|
||||||
if type(description) == dict:
|
if type(description) == dict:
|
||||||
|
|||||||
@@ -40,10 +40,25 @@ def test_custom_system_roles_prohibited(admin_user, post):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_assign_managed_role(admin_user, alice, rando, inventory, post, managed_roles):
|
def test_assignment_to_invisible_user(admin_user, alice, rando, inventory, post, managed_roles):
|
||||||
|
"Alice can not see rando, and so can not give them a role assignment"
|
||||||
rd = RoleDefinition.objects.get(name='Inventory Admin')
|
rd = RoleDefinition.objects.get(name='Inventory Admin')
|
||||||
rd.give_permission(alice, inventory)
|
rd.give_permission(alice, inventory)
|
||||||
# Now that alice has full permissions to the inventory, she will give rando permission
|
url = django_reverse('roleuserassignment-list')
|
||||||
|
r = post(url=url, data={"user": rando.id, "role_definition": rd.id, "object_id": inventory.id}, user=alice, expect=400)
|
||||||
|
assert 'does not exist' in str(r.data)
|
||||||
|
assert not rando.has_obj_perm(inventory, 'change')
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_assign_managed_role(admin_user, alice, rando, inventory, post, managed_roles, organization):
|
||||||
|
rd = RoleDefinition.objects.get(name='Inventory Admin')
|
||||||
|
rd.give_permission(alice, inventory)
|
||||||
|
# When alice and rando are members of the same org, they can see each other
|
||||||
|
member_rd = RoleDefinition.objects.get(name='Organization Member')
|
||||||
|
for u in (alice, rando):
|
||||||
|
member_rd.give_permission(u, organization)
|
||||||
|
# Now that alice has full permissions to the inventory, and can see rando, she will give rando permission
|
||||||
url = django_reverse('roleuserassignment-list')
|
url = django_reverse('roleuserassignment-list')
|
||||||
post(url=url, data={"user": rando.id, "role_definition": rd.id, "object_id": inventory.id}, user=alice, expect=201)
|
post(url=url, data={"user": rando.id, "role_definition": rd.id, "object_id": inventory.id}, user=alice, expect=201)
|
||||||
assert rando.has_obj_perm(inventory, 'change') is True
|
assert rando.has_obj_perm(inventory, 'change') is True
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from django_test_migrations.plan import all_migrations, nodes_to_tuples
|
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
|
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()
|
bar_peers = bar.peers.all()
|
||||||
assert len(bar_peers) == 1
|
assert len(bar_peers) == 1
|
||||||
assert fooaddr in bar_peers
|
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()
|
||||||
|
|||||||
@@ -277,6 +277,9 @@ SESSION_COOKIE_SECURE = True
|
|||||||
# Note: This setting may be overridden by database settings.
|
# Note: This setting may be overridden by database settings.
|
||||||
SESSION_COOKIE_AGE = 1800
|
SESSION_COOKIE_AGE = 1800
|
||||||
|
|
||||||
|
# Option to change userLoggedIn cookie SameSite policy.
|
||||||
|
USER_COOKIE_SAMESITE = 'Lax'
|
||||||
|
|
||||||
# Name of the cookie that contains the session information.
|
# Name of the cookie that contains the session information.
|
||||||
# Note: Changing this value may require changes to any clients.
|
# Note: Changing this value may require changes to any clients.
|
||||||
SESSION_COOKIE_NAME = 'awx_sessionid'
|
SESSION_COOKIE_NAME = 'awx_sessionid'
|
||||||
|
|||||||
@@ -38,7 +38,9 @@ class CompleteView(BaseRedirectView):
|
|||||||
response = super(CompleteView, self).dispatch(request, *args, **kwargs)
|
response = super(CompleteView, self).dispatch(request, *args, **kwargs)
|
||||||
if self.request.user and self.request.user.is_authenticated:
|
if self.request.user and self.request.user.is_authenticated:
|
||||||
logger.info(smart_str(u"User {} logged in".format(self.request.user.username)))
|
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'))
|
response.setdefault('X-API-Session-Cookie-Name', getattr(settings, 'SESSION_COOKIE_NAME', 'awx_sessionid'))
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|||||||
@@ -78,12 +78,14 @@ function MiscAuthenticationEdit() {
|
|||||||
default: OAUTH2_PROVIDER_OPTIONS.default.ACCESS_TOKEN_EXPIRE_SECONDS,
|
default: OAUTH2_PROVIDER_OPTIONS.default.ACCESS_TOKEN_EXPIRE_SECONDS,
|
||||||
type: OAUTH2_PROVIDER_OPTIONS.child.type,
|
type: OAUTH2_PROVIDER_OPTIONS.child.type,
|
||||||
label: t`Access Token Expiration`,
|
label: t`Access Token Expiration`,
|
||||||
|
help_text: t`Access Token Expiration in seconds`,
|
||||||
},
|
},
|
||||||
REFRESH_TOKEN_EXPIRE_SECONDS: {
|
REFRESH_TOKEN_EXPIRE_SECONDS: {
|
||||||
...OAUTH2_PROVIDER_OPTIONS,
|
...OAUTH2_PROVIDER_OPTIONS,
|
||||||
default: OAUTH2_PROVIDER_OPTIONS.default.REFRESH_TOKEN_EXPIRE_SECONDS,
|
default: OAUTH2_PROVIDER_OPTIONS.default.REFRESH_TOKEN_EXPIRE_SECONDS,
|
||||||
type: OAUTH2_PROVIDER_OPTIONS.child.type,
|
type: OAUTH2_PROVIDER_OPTIONS.child.type,
|
||||||
label: t`Refresh Token Expiration`,
|
label: t`Refresh Token Expiration`,
|
||||||
|
help_text: t`Refresh Token Expiration in seconds`,
|
||||||
},
|
},
|
||||||
AUTHORIZATION_CODE_EXPIRE_SECONDS: {
|
AUTHORIZATION_CODE_EXPIRE_SECONDS: {
|
||||||
...OAUTH2_PROVIDER_OPTIONS,
|
...OAUTH2_PROVIDER_OPTIONS,
|
||||||
@@ -91,6 +93,7 @@ function MiscAuthenticationEdit() {
|
|||||||
OAUTH2_PROVIDER_OPTIONS.default.AUTHORIZATION_CODE_EXPIRE_SECONDS,
|
OAUTH2_PROVIDER_OPTIONS.default.AUTHORIZATION_CODE_EXPIRE_SECONDS,
|
||||||
type: OAUTH2_PROVIDER_OPTIONS.child.type,
|
type: OAUTH2_PROVIDER_OPTIONS.child.type,
|
||||||
label: t`Authorization Code Expiration`,
|
label: t`Authorization Code Expiration`,
|
||||||
|
help_text: t`Authorization Code Expiration in seconds`,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user