diff --git a/.editorconfig b/.editorconfig index e12dc6e50..2a5b1f870 100644 --- a/.editorconfig +++ b/.editorconfig @@ -7,8 +7,8 @@ charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true -[html] +[*.html] indent_size = 2 -[yaml] +[*.{yaml,yml}] indent_size = 2 diff --git a/authentik/blueprints/tests/__init__.py b/authentik/blueprints/tests/__init__.py index 4f62d668b..8b39ca6dd 100644 --- a/authentik/blueprints/tests/__init__.py +++ b/authentik/blueprints/tests/__init__.py @@ -1,6 +1,5 @@ """Blueprint helpers""" from functools import wraps -from pathlib import Path from typing import Callable from django.apps import apps @@ -45,13 +44,3 @@ def reconcile_app(app_name: str): return wrapper return wrapper_outer - - -def load_yaml_fixture(path: str, **kwargs) -> str: - """Load yaml fixture, optionally formatting it with kwargs""" - with open(Path(__file__).resolve().parent / Path(path), "r", encoding="utf-8") as _fixture: - fixture = _fixture.read() - try: - return fixture % kwargs - except TypeError: - return fixture diff --git a/authentik/blueprints/tests/test_v1.py b/authentik/blueprints/tests/test_v1.py index b5f6a252a..4679de909 100644 --- a/authentik/blueprints/tests/test_v1.py +++ b/authentik/blueprints/tests/test_v1.py @@ -3,12 +3,12 @@ from os import environ from django.test import TransactionTestCase -from authentik.blueprints.tests import load_yaml_fixture from authentik.blueprints.v1.exporter import FlowExporter from authentik.blueprints.v1.importer import Importer, transaction_rollback from authentik.core.models import Group from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding from authentik.lib.generators import generate_id +from authentik.lib.tests.utils import load_fixture from authentik.policies.expression.models import ExpressionPolicy from authentik.policies.models import PolicyBinding from authentik.sources.oauth.models import OAuthSource @@ -113,14 +113,14 @@ class TestBlueprintsV1(TransactionTestCase): """Test export and import it twice""" count_initial = Prompt.objects.filter(field_key="username").count() - importer = Importer(load_yaml_fixture("fixtures/static_prompt_export.yaml")) + importer = Importer(load_fixture("fixtures/static_prompt_export.yaml")) self.assertTrue(importer.validate()[0]) self.assertTrue(importer.apply()) count_before = Prompt.objects.filter(field_key="username").count() self.assertEqual(count_initial + 1, count_before) - importer = Importer(load_yaml_fixture("fixtures/static_prompt_export.yaml")) + importer = Importer(load_fixture("fixtures/static_prompt_export.yaml")) self.assertTrue(importer.apply()) self.assertEqual(Prompt.objects.filter(field_key="username").count(), count_before) @@ -130,7 +130,7 @@ class TestBlueprintsV1(TransactionTestCase): ExpressionPolicy.objects.filter(name="foo-bar-baz-qux").delete() Group.objects.filter(name="test").delete() environ["foo"] = generate_id() - importer = Importer(load_yaml_fixture("fixtures/tags.yaml"), {"bar": "baz"}) + importer = Importer(load_fixture("fixtures/tags.yaml"), {"bar": "baz"}) self.assertTrue(importer.validate()[0]) self.assertTrue(importer.apply()) policy = ExpressionPolicy.objects.filter(name="foo-bar-baz-qux").first() diff --git a/authentik/blueprints/tests/test_v1_conditions.py b/authentik/blueprints/tests/test_v1_conditions.py index 02d657303..3914e19dd 100644 --- a/authentik/blueprints/tests/test_v1_conditions.py +++ b/authentik/blueprints/tests/test_v1_conditions.py @@ -1,10 +1,10 @@ """Test blueprints v1""" from django.test import TransactionTestCase -from authentik.blueprints.tests import load_yaml_fixture from authentik.blueprints.v1.importer import Importer from authentik.flows.models import Flow from authentik.lib.generators import generate_id +from authentik.lib.tests.utils import load_fixture class TestBlueprintsV1Conditions(TransactionTestCase): @@ -14,7 +14,7 @@ class TestBlueprintsV1Conditions(TransactionTestCase): """Test conditions fulfilled""" flow_slug1 = generate_id() flow_slug2 = generate_id() - import_yaml = load_yaml_fixture( + import_yaml = load_fixture( "fixtures/conditions_fulfilled.yaml", id1=flow_slug1, id2=flow_slug2 ) @@ -31,7 +31,7 @@ class TestBlueprintsV1Conditions(TransactionTestCase): """Test conditions not fulfilled""" flow_slug1 = generate_id() flow_slug2 = generate_id() - import_yaml = load_yaml_fixture( + import_yaml = load_fixture( "fixtures/conditions_not_fulfilled.yaml", id1=flow_slug1, id2=flow_slug2 ) diff --git a/authentik/blueprints/tests/test_v1_state.py b/authentik/blueprints/tests/test_v1_state.py index 02ddfcb50..e0bf4c13a 100644 --- a/authentik/blueprints/tests/test_v1_state.py +++ b/authentik/blueprints/tests/test_v1_state.py @@ -1,10 +1,10 @@ """Test blueprints v1""" from django.test import TransactionTestCase -from authentik.blueprints.tests import load_yaml_fixture from authentik.blueprints.v1.importer import Importer from authentik.flows.models import Flow from authentik.lib.generators import generate_id +from authentik.lib.tests.utils import load_fixture class TestBlueprintsV1State(TransactionTestCase): @@ -13,7 +13,7 @@ class TestBlueprintsV1State(TransactionTestCase): def test_state_present(self): """Test state present""" flow_slug = generate_id() - import_yaml = load_yaml_fixture("fixtures/state_present.yaml", id=flow_slug) + import_yaml = load_fixture("fixtures/state_present.yaml", id=flow_slug) importer = Importer(import_yaml) self.assertTrue(importer.validate()[0]) @@ -39,7 +39,7 @@ class TestBlueprintsV1State(TransactionTestCase): def test_state_created(self): """Test state created""" flow_slug = generate_id() - import_yaml = load_yaml_fixture("fixtures/state_created.yaml", id=flow_slug) + import_yaml = load_fixture("fixtures/state_created.yaml", id=flow_slug) importer = Importer(import_yaml) self.assertTrue(importer.validate()[0]) @@ -65,7 +65,7 @@ class TestBlueprintsV1State(TransactionTestCase): def test_state_absent(self): """Test state absent""" flow_slug = generate_id() - import_yaml = load_yaml_fixture("fixtures/state_created.yaml", id=flow_slug) + import_yaml = load_fixture("fixtures/state_created.yaml", id=flow_slug) importer = Importer(import_yaml) self.assertTrue(importer.validate()[0]) @@ -74,7 +74,7 @@ class TestBlueprintsV1State(TransactionTestCase): flow: Flow = Flow.objects.filter(slug=flow_slug).first() self.assertEqual(flow.slug, flow_slug) - import_yaml = load_yaml_fixture("fixtures/state_absent.yaml", id=flow_slug) + import_yaml = load_fixture("fixtures/state_absent.yaml", id=flow_slug) importer = Importer(import_yaml) self.assertTrue(importer.validate()[0]) self.assertTrue(importer.apply()) diff --git a/authentik/lib/tests/utils.py b/authentik/lib/tests/utils.py index 35ec8391d..6e15fe61b 100644 --- a/authentik/lib/tests/utils.py +++ b/authentik/lib/tests/utils.py @@ -1,4 +1,7 @@ """Test utils""" +from inspect import currentframe +from pathlib import Path + from django.contrib.messages.middleware import MessageMiddleware from django.contrib.sessions.middleware import SessionMiddleware from django.http import HttpRequest @@ -11,6 +14,21 @@ def dummy_get_response(request: HttpRequest): # pragma: no cover return None +def load_fixture(path: str, **kwargs) -> str: + """Load fixture, optionally formatting it with kwargs""" + current = currentframe() + parent = current.f_back + calling_file_path = parent.f_globals["__file__"] + with open( + Path(calling_file_path).resolve().parent / Path(path), "r", encoding="utf-8" + ) as _fixture: + fixture = _fixture.read() + try: + return fixture % kwargs + except TypeError: + return fixture + + def get_request(*args, user=None, **kwargs): """Get a request with usable session""" request = RequestFactory().get(*args, **kwargs) diff --git a/authentik/lib/utils/http.py b/authentik/lib/utils/http.py index 94c01c9b7..d20ad29eb 100644 --- a/authentik/lib/utils/http.py +++ b/authentik/lib/utils/http.py @@ -38,13 +38,17 @@ def _get_outpost_override_ip(request: HttpRequest) -> Optional[str]: if OUTPOST_REMOTE_IP_HEADER not in request.META or OUTPOST_TOKEN_HEADER not in request.META: return None fake_ip = request.META[OUTPOST_REMOTE_IP_HEADER] - tokens = Token.filter_not_expired( - key=request.META.get(OUTPOST_TOKEN_HEADER), intent=TokenIntents.INTENT_API + token = ( + Token.filter_not_expired( + key=request.META.get(OUTPOST_TOKEN_HEADER), intent=TokenIntents.INTENT_API + ) + .select_related("user") + .first() ) - if not tokens.exists(): + if not token: LOGGER.warning("Attempted remote-ip override without token", fake_ip=fake_ip) return None - user = tokens.first().user + user = token.user if not user.group_attributes(request).get(USER_ATTRIBUTE_CAN_OVERRIDE_IP, False): LOGGER.warning( "Remote-IP override: user doesn't have permission", diff --git a/authentik/providers/saml/tests/test_api.py b/authentik/providers/saml/tests/test_api.py index af103d8a3..f0c878e5a 100644 --- a/authentik/providers/saml/tests/test_api.py +++ b/authentik/providers/saml/tests/test_api.py @@ -10,8 +10,8 @@ from authentik.core.models import Application from authentik.core.tests.utils import create_test_admin_user, create_test_flow from authentik.flows.models import FlowDesignation from authentik.lib.generators import generate_id +from authentik.lib.tests.utils import load_fixture from authentik.providers.saml.models import SAMLPropertyMapping, SAMLProvider -from authentik.providers.saml.tests.test_metadata import load_fixture class TestSAMLProviderAPI(APITestCase): diff --git a/authentik/providers/saml/tests/test_metadata.py b/authentik/providers/saml/tests/test_metadata.py index 07fc6ad0e..ffe1b2a4a 100644 --- a/authentik/providers/saml/tests/test_metadata.py +++ b/authentik/providers/saml/tests/test_metadata.py @@ -1,6 +1,4 @@ """Test Service-Provider Metadata Parser""" -from pathlib import Path - import xmlsec from defusedxml.lxml import fromstring from django.test import RequestFactory, TestCase @@ -9,6 +7,7 @@ from lxml import etree # nosec from authentik.core.models import Application from authentik.core.tests.utils import create_test_cert, create_test_flow from authentik.lib.generators import generate_id +from authentik.lib.tests.utils import load_fixture from authentik.lib.xml import lxml_from_string from authentik.providers.saml.models import SAMLBindings, SAMLPropertyMapping, SAMLProvider from authentik.providers.saml.processors.metadata import MetadataProcessor @@ -16,12 +15,6 @@ from authentik.providers.saml.processors.metadata_parser import ServiceProviderM from authentik.sources.saml.processors.constants import NS_MAP -def load_fixture(path: str, **kwargs) -> str: - """Load fixture""" - with open(Path(__file__).resolve().parent / Path(path), "r", encoding="utf-8") as _fixture: - return _fixture.read() - - class TestServiceProviderMetadataParser(TestCase): """Test ServiceProviderMetadataParser parsing and creation of SAML Provider""" diff --git a/authentik/sources/saml/tests/fixtures/response_error.xml b/authentik/sources/saml/tests/fixtures/response_error.xml new file mode 100644 index 000000000..e0bf15cf2 --- /dev/null +++ b/authentik/sources/saml/tests/fixtures/response_error.xml @@ -0,0 +1,10 @@ + + + https://accounts.google.com/o/saml2?idpid= + + + + + Invalid request, ACS Url in request http://localhost:9000/source/saml/google/acs/ doesn't match configured ACS Url https://127.0.0.1:9443/source/saml/google/acs/. + + diff --git a/authentik/sources/saml/tests/fixtures/response_success.xml b/authentik/sources/saml/tests/fixtures/response_success.xml new file mode 100644 index 000000000..979c2e2aa --- /dev/null +++ b/authentik/sources/saml/tests/fixtures/response_success.xml @@ -0,0 +1,40 @@ + + + https://accounts.google.com/o/saml2?idpid= + + + + + https://accounts.google.com/o/saml2?idpid= + + jens@goauthentik.io + + + + + + + https://accounts.google.com/o/saml2?idpid= + + + + + foo + + + bar + + + foo@bar.baz + + + + + urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified + + + + diff --git a/authentik/sources/saml/tests/test_response.py b/authentik/sources/saml/tests/test_response.py index 2ab1c6486..f66a28b23 100644 --- a/authentik/sources/saml/tests/test_response.py +++ b/authentik/sources/saml/tests/test_response.py @@ -6,64 +6,10 @@ from django.test import RequestFactory, TestCase from authentik.core.tests.utils import create_test_flow from authentik.lib.generators import generate_id -from authentik.lib.tests.utils import dummy_get_response +from authentik.lib.tests.utils import dummy_get_response, load_fixture from authentik.sources.saml.models import SAMLSource from authentik.sources.saml.processors.response import ResponseProcessor -RESPONSE_ERROR = """ - - https://accounts.google.com/o/saml2?idpid= - - - - - Invalid request, ACS Url in request http://localhost:9000/source/saml/google/acs/ doesn't match configured ACS Url https://127.0.0.1:9443/source/saml/google/acs/. - - -""" - -RESPONSE_SUCCESS = """ - - https://accounts.google.com/o/saml2?idpid= - - - - - https://accounts.google.com/o/saml2?idpid= - - jens@goauthentik.io - - - - - - - https://accounts.google.com/o/saml2?idpid= - - - - - foo - - - bar - - - foo@bar.baz - - - - - urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified - - - - -""" - class TestResponseProcessor(TestCase): """Test ResponseProcessor""" @@ -80,7 +26,12 @@ class TestResponseProcessor(TestCase): def test_status_error(self): """Test error status""" request = self.factory.post( - "/", data={"SAMLResponse": b64encode(RESPONSE_ERROR.encode()).decode()} + "/", + data={ + "SAMLResponse": b64encode( + load_fixture("fixtures/response_error.xml").encode() + ).decode() + }, ) middleware = SessionMiddleware(dummy_get_response) @@ -99,7 +50,12 @@ class TestResponseProcessor(TestCase): def test_success(self): """Test success""" request = self.factory.post( - "/", data={"SAMLResponse": b64encode(RESPONSE_SUCCESS.encode()).decode()} + "/", + data={ + "SAMLResponse": b64encode( + load_fixture("fixtures/response_success.xml").encode() + ).decode() + }, ) middleware = SessionMiddleware(dummy_get_response) diff --git a/web/src/admin/users/UserForm.ts b/web/src/admin/users/UserForm.ts index ec657cfa4..46188dff9 100644 --- a/web/src/admin/users/UserForm.ts +++ b/web/src/admin/users/UserForm.ts @@ -68,14 +68,6 @@ export class UserForm extends ModelForm { ${t`User's primary identifier. 150 characters or fewer.`}

- - - { ${t`Designates whether this user should be treated as active. Unselect this instead of deleting accounts.`}

+ + +