diff --git a/authentik/flows/tests/test_inspector.py b/authentik/flows/tests/test_inspector.py index 2a01ea370c..81a04a797b 100644 --- a/authentik/flows/tests/test_inspector.py +++ b/authentik/flows/tests/test_inspector.py @@ -46,6 +46,7 @@ class TestFlowInspector(APITestCase): res.content, { "allow_show_password": False, + "captcha_stage": None, "component": "ak-stage-identification", "flow_info": { "background": flow.background_url, diff --git a/authentik/providers/scim/clients/groups.py b/authentik/providers/scim/clients/groups.py index 44b3405dff..8221d7d131 100644 --- a/authentik/providers/scim/clients/groups.py +++ b/authentik/providers/scim/clients/groups.py @@ -197,6 +197,8 @@ class SCIMGroupClient(SCIMClient[Group, SCIMProviderGroup, SCIMGroupSchema]): chunk_size = self._config.bulk.maxOperations if chunk_size < 1: chunk_size = len(ops) + if len(ops) < 1: + return for chunk in batched(ops, chunk_size): req = PatchRequest(Operations=list(chunk)) self._request( @@ -237,13 +239,16 @@ class SCIMGroupClient(SCIMClient[Group, SCIMProviderGroup, SCIMGroupSchema]): users_to_add = [] users_to_remove = [] # Check users currently in group and if they shouldn't be in the group and remove them - for user in current_group.members: + for user in current_group.members or []: if user.value not in users_should: users_to_remove.append(user.value) # Check users that should be in the group and add them for user in users_should: if len([x for x in current_group.members if x.value == user]) < 1: users_to_add.append(user) + # Only send request if we need to make changes + if len(users_to_add) < 1 and len(users_to_remove) < 1: + return return self._patch_chunked( scim_group.scim_id, *[ diff --git a/authentik/stages/authenticator_validate/challenge.py b/authentik/stages/authenticator_validate/challenge.py index c11439684f..1f9a656a38 100644 --- a/authentik/stages/authenticator_validate/challenge.py +++ b/authentik/stages/authenticator_validate/challenge.py @@ -8,7 +8,7 @@ from django.http.response import Http404 from django.shortcuts import get_object_or_404 from django.utils.translation import gettext as __ from django.utils.translation import gettext_lazy as _ -from rest_framework.fields import CharField +from rest_framework.fields import CharField, DateTimeField from rest_framework.serializers import ValidationError from structlog.stdlib import get_logger from webauthn import options_to_json @@ -45,6 +45,7 @@ class DeviceChallenge(PassiveSerializer): device_class = CharField() device_uid = CharField() challenge = JSONDictField() + last_used = DateTimeField(allow_null=True) def get_challenge_for_device( diff --git a/authentik/stages/authenticator_validate/stage.py b/authentik/stages/authenticator_validate/stage.py index 96ae7e6215..bde76d37db 100644 --- a/authentik/stages/authenticator_validate/stage.py +++ b/authentik/stages/authenticator_validate/stage.py @@ -217,6 +217,7 @@ class AuthenticatorValidateStageView(ChallengeStageView): "device_class": device_class, "device_uid": device.pk, "challenge": get_challenge_for_device(self.request, stage, device), + "last_used": device.last_used, } ) challenge.is_valid() @@ -237,6 +238,7 @@ class AuthenticatorValidateStageView(ChallengeStageView): self.request, self.executor.current_stage, ), + "last_used": None, } ) challenge.is_valid() diff --git a/authentik/stages/authenticator_validate/tests/test_sms.py b/authentik/stages/authenticator_validate/tests/test_sms.py index 5cce796207..850854a892 100644 --- a/authentik/stages/authenticator_validate/tests/test_sms.py +++ b/authentik/stages/authenticator_validate/tests/test_sms.py @@ -107,6 +107,7 @@ class AuthenticatorValidateStageSMSTests(FlowTestCase): "device_class": "sms", "device_uid": str(device.pk), "challenge": {}, + "last_used": None, }, }, ) diff --git a/authentik/stages/authenticator_validate/tests/test_stage.py b/authentik/stages/authenticator_validate/tests/test_stage.py index 98fe5d2fe4..82a2f51322 100644 --- a/authentik/stages/authenticator_validate/tests/test_stage.py +++ b/authentik/stages/authenticator_validate/tests/test_stage.py @@ -169,6 +169,7 @@ class AuthenticatorValidateStageTests(FlowTestCase): "device_class": "baz", "device_uid": "quox", "challenge": {}, + "last_used": None, } }, ) @@ -188,6 +189,7 @@ class AuthenticatorValidateStageTests(FlowTestCase): "device_class": "static", "device_uid": "1", "challenge": {}, + "last_used": None, }, }, ) diff --git a/authentik/stages/authenticator_validate/tests/test_webauthn.py b/authentik/stages/authenticator_validate/tests/test_webauthn.py index e4247b221a..05fe216081 100644 --- a/authentik/stages/authenticator_validate/tests/test_webauthn.py +++ b/authentik/stages/authenticator_validate/tests/test_webauthn.py @@ -274,6 +274,7 @@ class AuthenticatorValidateStageWebAuthnTests(FlowTestCase): "device_class": device.__class__.__name__.lower().replace("device", ""), "device_uid": device.pk, "challenge": {}, + "last_used": None, } ] session[SESSION_KEY_PLAN] = plan @@ -352,6 +353,7 @@ class AuthenticatorValidateStageWebAuthnTests(FlowTestCase): "device_class": device.__class__.__name__.lower().replace("device", ""), "device_uid": device.pk, "challenge": {}, + "last_used": None, } ] session[SESSION_KEY_PLAN] = plan @@ -432,6 +434,7 @@ class AuthenticatorValidateStageWebAuthnTests(FlowTestCase): "device_class": device.__class__.__name__.lower().replace("device", ""), "device_uid": device.pk, "challenge": {}, + "last_used": None, } ] session[SESSION_KEY_PLAN] = plan diff --git a/authentik/stages/captcha/stage.py b/authentik/stages/captcha/stage.py index 3967e6d3d3..73bcff5dec 100644 --- a/authentik/stages/captcha/stage.py +++ b/authentik/stages/captcha/stage.py @@ -1,10 +1,11 @@ """authentik captcha stage""" from django.http.response import HttpResponse -from django.utils.translation import gettext_lazy as _ +from django.utils.translation import gettext as _ from requests import RequestException from rest_framework.fields import CharField from rest_framework.serializers import ValidationError +from structlog.stdlib import get_logger from authentik.flows.challenge import ( Challenge, @@ -16,6 +17,7 @@ from authentik.lib.utils.http import get_http_session from authentik.root.middleware import ClientIPMiddleware from authentik.stages.captcha.models import CaptchaStage +LOGGER = get_logger() PLAN_CONTEXT_CAPTCHA = "captcha" @@ -27,6 +29,56 @@ class CaptchaChallenge(WithUserInfoChallenge): component = CharField(default="ak-stage-captcha") +def verify_captcha_token(stage: CaptchaStage, token: str, remote_ip: str): + """Validate captcha token""" + try: + response = get_http_session().post( + stage.api_url, + headers={ + "Content-type": "application/x-www-form-urlencoded", + }, + data={ + "secret": stage.private_key, + "response": token, + "remoteip": remote_ip, + }, + ) + response.raise_for_status() + data = response.json() + if stage.error_on_invalid_score: + if not data.get("success", False): + error_codes = data.get("error-codes", ["unknown-error"]) + LOGGER.warning("Failed to verify captcha token", error_codes=error_codes) + + # These cases can usually be fixed by simply requesting a new token and retrying. + # [reCAPTCHA](https://developers.google.com/recaptcha/docs/verify#error_code_reference) + # [hCaptcha](https://docs.hcaptcha.com/#siteverify-error-codes-table) + # [Turnstile](https://developers.cloudflare.com/turnstile/get-started/server-side-validation/#error-codes) + retriable_error_codes = [ + "missing-input-response", + "invalid-input-response", + "timeout-or-duplicate", + "expired-input-response", + "already-seen-response", + ] + + if set(error_codes).issubset(set(retriable_error_codes)): + error_message = _("Invalid captcha response. Retrying may solve this issue.") + else: + error_message = _("Invalid captcha response") + raise ValidationError(error_message) + if "score" in data: + score = float(data.get("score")) + if stage.score_max_threshold > -1 and score > stage.score_max_threshold: + raise ValidationError(_("Invalid captcha response")) + if stage.score_min_threshold > -1 and score < stage.score_min_threshold: + raise ValidationError(_("Invalid captcha response")) + except (RequestException, TypeError) as exc: + raise ValidationError(_("Failed to validate token")) from exc + + return data + + class CaptchaChallengeResponse(ChallengeResponse): """Validate captcha token""" @@ -36,38 +88,9 @@ class CaptchaChallengeResponse(ChallengeResponse): def validate_token(self, token: str) -> str: """Validate captcha token""" stage: CaptchaStage = self.stage.executor.current_stage - try: - response = get_http_session().post( - stage.api_url, - headers={ - "Content-type": "application/x-www-form-urlencoded", - }, - data={ - "secret": stage.private_key, - "response": token, - "remoteip": ClientIPMiddleware.get_client_ip(self.stage.request), - }, - ) - response.raise_for_status() - data = response.json() - if stage.error_on_invalid_score: - if not data.get("success", False): - raise ValidationError( - _( - "Failed to validate token: {error}".format( - error=data.get("error-codes", _("Unknown error")) - ) - ) - ) - if "score" in data: - score = float(data.get("score")) - if stage.score_max_threshold > -1 and score > stage.score_max_threshold: - raise ValidationError(_("Invalid captcha response")) - if stage.score_min_threshold > -1 and score < stage.score_min_threshold: - raise ValidationError(_("Invalid captcha response")) - except (RequestException, TypeError) as exc: - raise ValidationError(_("Failed to validate token")) from exc - return data + client_ip = ClientIPMiddleware.get_client_ip(self.stage.request) + + return verify_captcha_token(stage, token, client_ip) class CaptchaStageView(ChallengeStageView): diff --git a/authentik/stages/identification/api.py b/authentik/stages/identification/api.py index 9ad97320e8..c8de2d7436 100644 --- a/authentik/stages/identification/api.py +++ b/authentik/stages/identification/api.py @@ -27,6 +27,7 @@ class IdentificationStageSerializer(StageSerializer): fields = StageSerializer.Meta.fields + [ "user_fields", "password_stage", + "captcha_stage", "case_insensitive_matching", "show_matched_user", "enrollment_flow", @@ -46,6 +47,7 @@ class IdentificationStageViewSet(UsedByMixin, ModelViewSet): filterset_fields = [ "name", "password_stage", + "captcha_stage", "case_insensitive_matching", "show_matched_user", "enrollment_flow", diff --git a/authentik/stages/identification/migrations/0015_identificationstage_captcha_stage.py b/authentik/stages/identification/migrations/0015_identificationstage_captcha_stage.py new file mode 100644 index 0000000000..734dc7631c --- /dev/null +++ b/authentik/stages/identification/migrations/0015_identificationstage_captcha_stage.py @@ -0,0 +1,26 @@ +# Generated by Django 5.0.8 on 2024-08-29 11:31 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("authentik_stages_captcha", "0003_captchastage_error_on_invalid_score_and_more"), + ("authentik_stages_identification", "0014_identificationstage_pretend"), + ] + + operations = [ + migrations.AddField( + model_name="identificationstage", + name="captcha_stage", + field=models.ForeignKey( + default=None, + help_text="When set, adds functionality exactly like a Captcha stage, but baked into the Identification stage.", + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="authentik_stages_captcha.captchastage", + ), + ), + ] diff --git a/authentik/stages/identification/models.py b/authentik/stages/identification/models.py index 27cfcb92f1..ed6728c932 100644 --- a/authentik/stages/identification/models.py +++ b/authentik/stages/identification/models.py @@ -8,6 +8,7 @@ from rest_framework.serializers import BaseSerializer from authentik.core.models import Source from authentik.flows.models import Flow, Stage +from authentik.stages.captcha.models import CaptchaStage from authentik.stages.password.models import PasswordStage @@ -43,6 +44,19 @@ class IdentificationStage(Stage): ), ) + captcha_stage = models.ForeignKey( + CaptchaStage, + null=True, + default=None, + on_delete=models.SET_NULL, + help_text=_( + ( + "When set, adds functionality exactly like a Captcha stage, but baked into the " + "Identification stage." + ), + ), + ) + case_insensitive_matching = models.BooleanField( default=True, help_text=_("When enabled, user fields are matched regardless of their casing."), diff --git a/authentik/stages/identification/stage.py b/authentik/stages/identification/stage.py index dffd119da9..1d2dfe8cab 100644 --- a/authentik/stages/identification/stage.py +++ b/authentik/stages/identification/stage.py @@ -29,6 +29,7 @@ from authentik.flows.views.executor import SESSION_KEY_APPLICATION_PRE, SESSION_ from authentik.lib.utils.reflection import all_subclasses from authentik.lib.utils.urls import reverse_with_qs from authentik.root.middleware import ClientIPMiddleware +from authentik.stages.captcha.stage import CaptchaChallenge, verify_captcha_token from authentik.stages.identification.models import IdentificationStage from authentik.stages.identification.signals import identification_failed from authentik.stages.password.stage import authenticate @@ -75,6 +76,7 @@ class IdentificationChallenge(Challenge): allow_show_password = BooleanField(default=False) application_pre = CharField(required=False) flow_designation = ChoiceField(FlowDesignation.choices) + captcha_stage = CaptchaChallenge(required=False) enroll_url = CharField(required=False) recovery_url = CharField(required=False) @@ -91,14 +93,16 @@ class IdentificationChallengeResponse(ChallengeResponse): uid_field = CharField() password = CharField(required=False, allow_blank=True, allow_null=True) + captcha_token = CharField(required=False, allow_blank=True, allow_null=True) component = CharField(default="ak-stage-identification") pre_user: User | None = None def validate(self, attrs: dict[str, Any]) -> dict[str, Any]: - """Validate that user exists, and optionally their password""" + """Validate that user exists, and optionally their password and captcha token""" uid_field = attrs["uid_field"] current_stage: IdentificationStage = self.stage.executor.current_stage + client_ip = ClientIPMiddleware.get_client_ip(self.stage.request) pre_user = self.stage.get_user(uid_field) if not pre_user: @@ -113,7 +117,7 @@ class IdentificationChallengeResponse(ChallengeResponse): self.stage.logger.info( "invalid_login", identifier=uid_field, - client_ip=ClientIPMiddleware.get_client_ip(self.stage.request), + client_ip=client_ip, action="invalid_identifier", context={ "stage": sanitize_item(self.stage), @@ -136,6 +140,15 @@ class IdentificationChallengeResponse(ChallengeResponse): return attrs raise ValidationError("Failed to authenticate.") self.pre_user = pre_user + + # Captcha check + if captcha_stage := current_stage.captcha_stage: + captcha_token = attrs.get("captcha_token", None) + if not captcha_token: + self.stage.logger.warning("Token not set for captcha attempt") + verify_captcha_token(captcha_stage, captcha_token, client_ip) + + # Password check if not current_stage.password_stage: # No password stage select, don't validate the password return attrs @@ -206,6 +219,14 @@ class IdentificationStageView(ChallengeStageView): "primary_action": self.get_primary_action(), "user_fields": current_stage.user_fields, "password_fields": bool(current_stage.password_stage), + "captcha_stage": ( + { + "js_url": current_stage.captcha_stage.js_url, + "site_key": current_stage.captcha_stage.public_key, + } + if current_stage.captcha_stage + else None + ), "allow_show_password": bool(current_stage.password_stage) and current_stage.password_stage.allow_show_password, "show_source_labels": current_stage.show_source_labels, diff --git a/authentik/stages/identification/tests.py b/authentik/stages/identification/tests.py index 57ffed1283..c39434e24a 100644 --- a/authentik/stages/identification/tests.py +++ b/authentik/stages/identification/tests.py @@ -1,6 +1,7 @@ """identification tests""" from django.urls import reverse +from requests_mock import Mocker from rest_framework.exceptions import ValidationError from authentik.core.tests.utils import create_test_admin_user, create_test_flow @@ -8,6 +9,8 @@ from authentik.flows.models import FlowDesignation, FlowStageBinding from authentik.flows.tests import FlowTestCase from authentik.lib.generators import generate_id from authentik.sources.oauth.models import OAuthSource +from authentik.stages.captcha.models import CaptchaStage +from authentik.stages.captcha.tests import RECAPTCHA_PRIVATE_KEY, RECAPTCHA_PUBLIC_KEY from authentik.stages.identification.api import IdentificationStageSerializer from authentik.stages.identification.models import IdentificationStage, UserFields from authentik.stages.password import BACKEND_INBUILT @@ -133,6 +136,135 @@ class TestIdentificationStage(FlowTestCase): user_fields=["email"], ) + @Mocker() + def test_valid_with_captcha(self, mock: Mocker): + """Test with valid email and captcha token in single step""" + mock.post( + "https://www.recaptcha.net/recaptcha/api/siteverify", + json={ + "success": True, + "score": 0.5, + }, + ) + + captcha_stage = CaptchaStage.objects.create( + name="captcha", + public_key=RECAPTCHA_PUBLIC_KEY, + private_key=RECAPTCHA_PRIVATE_KEY, + ) + self.stage.captcha_stage = captcha_stage + self.stage.save() + + form_data = {"uid_field": self.user.email, "captcha_token": "PASSED"} + url = reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}) + response = self.client.post(url, form_data) + self.assertEqual(response.status_code, 200) + self.assertStageRedirects(response, reverse("authentik_core:root-redirect")) + + @Mocker() + def test_invalid_with_captcha(self, mock: Mocker): + """Test with valid email and invalid captcha token in single step""" + mock.post( + "https://www.recaptcha.net/recaptcha/api/siteverify", + json={ + "success": False, + "score": 0.5, + }, + ) + + captcha_stage = CaptchaStage.objects.create( + name="captcha", + public_key=RECAPTCHA_PUBLIC_KEY, + private_key=RECAPTCHA_PRIVATE_KEY, + ) + + self.stage.captcha_stage = captcha_stage + self.stage.save() + + form_data = { + "uid_field": self.user.email, + "captcha_token": "FAILED", + } + url = reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}) + response = self.client.post(url, form_data) + self.assertStageResponse( + response, + self.flow, + component="ak-stage-identification", + password_fields=False, + primary_action="Log in", + response_errors={ + "non_field_errors": [{"code": "invalid", "string": "Invalid captcha response"}] + }, + sources=[ + { + "challenge": { + "component": "xak-flow-redirect", + "to": "/source/oauth/login/test/", + }, + "icon_url": "/static/authentik/sources/default.svg", + "name": "test", + } + ], + show_source_labels=False, + user_fields=["email"], + ) + + @Mocker() + def test_invalid_with_captcha_retriable(self, mock: Mocker): + """Test with valid email and invalid captcha token in single step""" + mock.post( + "https://www.recaptcha.net/recaptcha/api/siteverify", + json={ + "success": False, + "score": 0.5, + "error-codes": ["timeout-or-duplicate"], + }, + ) + + captcha_stage = CaptchaStage.objects.create( + name="captcha", + public_key=RECAPTCHA_PUBLIC_KEY, + private_key=RECAPTCHA_PRIVATE_KEY, + ) + + self.stage.captcha_stage = captcha_stage + self.stage.save() + + form_data = { + "uid_field": self.user.email, + "captcha_token": "FAILED", + } + url = reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}) + response = self.client.post(url, form_data) + self.assertStageResponse( + response, + self.flow, + component="ak-stage-identification", + password_fields=False, + primary_action="Log in", + response_errors={ + "non_field_errors": [ + { + "code": "invalid", + "string": "Invalid captcha response. Retrying may solve this issue.", + } + ] + }, + sources=[ + { + "challenge": { + "component": "xak-flow-redirect", + "to": "/source/oauth/login/test/", + }, + "icon_url": "/static/authentik/sources/default.svg", + "name": "test", + } + ], + show_source_labels=False, + user_fields=["email"], + ) + def test_invalid_with_username(self): """Test invalid with username (user exists but stage only allows email)""" form_data = {"uid_field": self.user.username} diff --git a/blueprints/schema.json b/blueprints/schema.json index 9b3b91eb74..0604c8d76d 100644 --- a/blueprints/schema.json +++ b/blueprints/schema.json @@ -10679,6 +10679,11 @@ "title": "Password stage", "description": "When set, shows a password field, instead of showing the password field as separate step." }, + "captcha_stage": { + "type": "integer", + "title": "Captcha stage", + "description": "When set, adds functionality exactly like a Captcha stage, but baked into the Identification stage." + }, "case_insensitive_matching": { "type": "boolean", "title": "Case insensitive matching", diff --git a/blueprints/system/sources-kerberos.yaml b/blueprints/system/sources-kerberos.yaml index d97e8eda53..8664183b7e 100644 --- a/blueprints/system/sources-kerberos.yaml +++ b/blueprints/system/sources-kerberos.yaml @@ -38,7 +38,7 @@ entries: name: "authentik default Kerberos User Mapping: Ignore system principals" expression: | localpart, realm = principal.rsplit("@", 1) - denied_prefixes = ["kadmin/", "krbtgt/", "K/M", "WELLKNOWN/"] + denied_prefixes = ["kadmin/", "krbtgt/", "K/M", "WELLKNOWN/", "kiprop/", "changepw/"] for prefix in denied_prefixes: if localpart.lower().startswith(prefix.lower()): raise SkipObject diff --git a/go.mod b/go.mod index 804d9d0dc5..3b36858dd0 100644 --- a/go.mod +++ b/go.mod @@ -29,7 +29,7 @@ require ( github.com/spf13/cobra v1.8.1 github.com/stretchr/testify v1.9.0 github.com/wwt/guac v1.3.2 - goauthentik.io/api/v3 v3.2024083.11 + goauthentik.io/api/v3 v3.2024083.14 golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab golang.org/x/oauth2 v0.23.0 golang.org/x/sync v0.8.0 diff --git a/go.sum b/go.sum index 7c008d78ad..fb4733c1dd 100644 --- a/go.sum +++ b/go.sum @@ -299,8 +299,8 @@ go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -goauthentik.io/api/v3 v3.2024083.11 h1:kF5WAnS0dB2cq9Uldqel8e8PDepJg/824JC3YFsQVHU= -goauthentik.io/api/v3 v3.2024083.11/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw= +goauthentik.io/api/v3 v3.2024083.14 h1:8iLXkNpVS275S4DLMBr6WIeaMkkaIJbzlNRLCFe+k3A= +goauthentik.io/api/v3 v3.2024083.14/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= diff --git a/internal/outpost/proxyv2/application/endpoint.go b/internal/outpost/proxyv2/application/endpoint.go index 9a91459182..c9cc50d40c 100644 --- a/internal/outpost/proxyv2/application/endpoint.go +++ b/internal/outpost/proxyv2/application/endpoint.go @@ -82,6 +82,9 @@ func GetOIDCEndpoint(p api.ProxyOutpostConfig, authentikHost string, embedded bo if embedded { ep.Issuer = updateURL(ep.Issuer, newHost.Scheme, newHost.Host) ep.JwksUri = updateURL(jwksUri, newHost.Scheme, newHost.Host) + } else { + // Fixes: https://github.com/goauthentik/authentik/issues/9622 / ep.Issuer must be the HostBrowser URL + ep.Issuer = updateURL(ep.Issuer, newBrowserHost.Scheme, newBrowserHost.Host) } return ep } diff --git a/internal/outpost/proxyv2/application/endpoint_test.go b/internal/outpost/proxyv2/application/endpoint_test.go index d3d0f74262..bd2be424d6 100644 --- a/internal/outpost/proxyv2/application/endpoint_test.go +++ b/internal/outpost/proxyv2/application/endpoint_test.go @@ -55,7 +55,7 @@ func TestEndpointAuthentikHostBrowser(t *testing.T) { assert.Equal(t, "https://browser.test.goauthentik.io/application/o/authorize/", ep.AuthURL) assert.Equal(t, "https://browser.test.goauthentik.io/application/o/test-app/end-session/", ep.EndSessionEndpoint) assert.Equal(t, "https://test.goauthentik.io/application/o/token/", ep.TokenURL) - assert.Equal(t, "https://test.goauthentik.io/application/o/test-app/", ep.Issuer) + assert.Equal(t, "https://browser.test.goauthentik.io/application/o/test-app/", ep.Issuer) assert.Equal(t, "https://test.goauthentik.io/application/o/test-app/jwks/", ep.JwksUri) assert.Equal(t, "https://test.goauthentik.io/application/o/introspect/", ep.TokenIntrospection) } diff --git a/lifecycle/ak b/lifecycle/ak index 2033951557..2d52023bb9 100755 --- a/lifecycle/ak +++ b/lifecycle/ak @@ -54,7 +54,9 @@ function cleanup { } function prepare_debug { - apt-get install -y --no-install-recommends krb5-kdc krb5-user krb5-admin-server + export DEBIAN_FRONTEND=noninteractive + apt-get update + apt-get install -y --no-install-recommends krb5-kdc krb5-user krb5-admin-server libkrb5-dev gcc VIRTUAL_ENV=/ak-root/venv poetry install --no-ansi --no-interaction touch /unittest.xml chown authentik:authentik /unittest.xml diff --git a/locale/en/LC_MESSAGES/django.po b/locale/en/LC_MESSAGES/django.po index 9477afafd1..9e941ce67e 100644 --- a/locale/en/LC_MESSAGES/django.po +++ b/locale/en/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-10-23 16:39+0000\n" +"POT-Creation-Date: 2024-10-28 00:09+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -2614,12 +2614,7 @@ msgid "Captcha Stages" msgstr "" #: authentik/stages/captcha/stage.py -msgid "Unknown error" -msgstr "" - -#: authentik/stages/captcha/stage.py -#, python-brace-format -msgid "Failed to validate token: {error}" +msgid "Invalid captcha response. Retrying may solve this issue." msgstr "" #: authentik/stages/captcha/stage.py diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index 243070be90..7662522a26 100644 --- a/locale/fr/LC_MESSAGES/django.po +++ b/locale/fr/LC_MESSAGES/django.po @@ -19,7 +19,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-10-18 00:09+0000\n" +"POT-Creation-Date: 2024-10-23 16:39+0000\n" "PO-Revision-Date: 2022-09-26 16:47+0000\n" "Last-Translator: Marc Schmitt, 2024\n" "Language-Team: French (https://app.transifex.com/authentik/teams/119923/fr/)\n" @@ -587,6 +587,30 @@ msgstr "Limite maximum de connection atteinte." msgid "(You are already connected in another tab/window)" msgstr "(Vous êtes déjà connecté dans un autre onglet/une autre fenêtre)" +#: authentik/enterprise/stages/authenticator_endpoint_gdtc/models.py +msgid "Endpoint Authenticator Google Device Trust Connector Stage" +msgstr "" +"Étape d'authentificateur d'appareil du connecteur de confiance des appareils" +" Google" + +#: authentik/enterprise/stages/authenticator_endpoint_gdtc/models.py +msgid "Endpoint Authenticator Google Device Trust Connector Stages" +msgstr "" +"Étapes d'authentificateur d'appareil du connecteur de confiance des " +"appareils Google" + +#: authentik/enterprise/stages/authenticator_endpoint_gdtc/models.py +msgid "Endpoint Device" +msgstr "Appareil point de terminaison" + +#: authentik/enterprise/stages/authenticator_endpoint_gdtc/models.py +msgid "Endpoint Devices" +msgstr "Appareils point de terminaison" + +#: authentik/enterprise/stages/authenticator_endpoint_gdtc/stage.py +msgid "Verifying your browser..." +msgstr "Vérification de votre navigateur..." + #: authentik/enterprise/stages/source/models.py msgid "" "Amount of time a user can take to return from the source to continue the " @@ -2029,6 +2053,125 @@ msgstr "" msgid "Used recovery-link to authenticate." msgstr "Utiliser un lien de récupération pour se connecter." +#: authentik/sources/kerberos/models.py +msgid "Kerberos realm" +msgstr "Realm Kerberos" + +#: authentik/sources/kerberos/models.py +msgid "Custom krb5.conf to use. Uses the system one by default" +msgstr "" +"krb5.conf personnalisé à utiliser. Utilise celui du système par défault" + +#: authentik/sources/kerberos/models.py +msgid "Sync users from Kerberos into authentik" +msgstr "Synchroniser les utilisateurs Kerberos dans authentik" + +#: authentik/sources/kerberos/models.py +msgid "When a user changes their password, sync it back to Kerberos" +msgstr "" +"Lorsqu'un utilisateur change son mot de passe, le synchroniser à nouveau " +"vers Kerberos." + +#: authentik/sources/kerberos/models.py +msgid "Principal to authenticate to kadmin for sync." +msgstr "Principal pour s'authentifier à kadmin pour la synchronisation." + +#: authentik/sources/kerberos/models.py +msgid "Password to authenticate to kadmin for sync" +msgstr "Mot de passe pour s'authentifier à kadmin pour la synchronisation." + +#: authentik/sources/kerberos/models.py +msgid "" +"Keytab to authenticate to kadmin for sync. Must be base64-encoded or in the " +"form TYPE:residual" +msgstr "" +"Keytab pour s'authentifier à kadmin pour la synchronisation. Doit être " +"encodé en base64 ou de la forme TYPE:residual" + +#: authentik/sources/kerberos/models.py +msgid "" +"Credentials cache to authenticate to kadmin for sync. Must be in the form " +"TYPE:residual" +msgstr "" +"Credentials cache pour s'authentifier à kadmin pour la synchronisation. Doit" +" être de la forme TYPE:residual" + +#: authentik/sources/kerberos/models.py +msgid "" +"Force the use of a specific server name for SPNEGO. Must be in the form " +"HTTP@hostname" +msgstr "" +"Force l'utilisation d'un nom de serveur spécifique pour SPNEGO. Doit être de" +" la forme HTTP@hostname" + +#: authentik/sources/kerberos/models.py +msgid "SPNEGO keytab base64-encoded or path to keytab in the form FILE:path" +msgstr "" +"Keytab SPNEGO encodée en base64 ou chemin vers la keytab de la forme " +"FILE:path" + +#: authentik/sources/kerberos/models.py +msgid "Credential cache to use for SPNEGO in form type:residual" +msgstr "Credentials cache pour SPNEGO de la forme TYPE:residual" + +#: authentik/sources/kerberos/models.py +msgid "" +"If enabled, the authentik-stored password will be updated upon login with " +"the Kerberos password backend" +msgstr "" +"Si activé, le mot de passe stocké par authentik sera mis à jour à la " +"connexion avec le backend de mot de passe Kerberos" + +#: authentik/sources/kerberos/models.py +msgid "Kerberos Source" +msgstr "Source Kerberos" + +#: authentik/sources/kerberos/models.py +msgid "Kerberos Sources" +msgstr "Sources Kerberos" + +#: authentik/sources/kerberos/models.py +msgid "Kerberos Source Property Mapping" +msgstr "Mappage de propriété source Kerberos" + +#: authentik/sources/kerberos/models.py +msgid "Kerberos Source Property Mappings" +msgstr "Mappages de propriété source Kerberos" + +#: authentik/sources/kerberos/models.py +msgid "User Kerberos Source Connection" +msgstr "Connexion de l'utilisateur à la source Kerberos" + +#: authentik/sources/kerberos/models.py +msgid "User Kerberos Source Connections" +msgstr "Connexions de l'utilisateur à la source Kerberos" + +#: authentik/sources/kerberos/models.py +msgid "Group Kerberos Source Connection" +msgstr "Connexion du groupe à la source Kerberos" + +#: authentik/sources/kerberos/models.py +msgid "Group Kerberos Source Connections" +msgstr "Connexions du groupe à la source Kerberos" + +#: authentik/sources/kerberos/views.py +msgid "SPNEGO authentication required" +msgstr "Authentification SPNEGO requise" + +#: authentik/sources/kerberos/views.py +msgid "" +"\n" +" Make sure you have valid tickets (obtainable via kinit)\n" +" and configured the browser correctly.\n" +" Please contact your administrator.\n" +" " +msgstr "" +"\n" +" Vérifiez que vous avez des tickets valides (qu'on peut obtenir via kinit)\n" +" et que le navigateur est configuré correctement.\n" +" Veuillez contacter votre administrateur.\n" +" " + #: authentik/sources/ldap/api.py msgid "Only a single LDAP Source with password synchronization is allowed" msgstr "" @@ -3121,6 +3264,10 @@ msgstr "Base de données utilisateurs + mots de passes applicatifs" msgid "User database + LDAP password" msgstr "Base de données utilisateurs + mot de passe LDAP" +#: authentik/stages/password/models.py +msgid "User database + Kerberos password" +msgstr "Base de données utilisateurs + mot de passe Kerberos" + #: authentik/stages/password/models.py msgid "Selection of backends to test the password against." msgstr "Sélection de backends pour tester le mot de passe." diff --git a/locale/it/LC_MESSAGES/django.po b/locale/it/LC_MESSAGES/django.po index 7d0ad0097d..000198dbae 100644 --- a/locale/it/LC_MESSAGES/django.po +++ b/locale/it/LC_MESSAGES/django.po @@ -11,15 +11,17 @@ # Marco Vitale, 2024 # Kowalski Dragon (kowalski7cc) , 2024 # albanobattistella , 2024 +# Nicola Mersi, 2024 +# tom max, 2024 # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-10-18 00:09+0000\n" +"POT-Creation-Date: 2024-10-28 00:09+0000\n" "PO-Revision-Date: 2022-09-26 16:47+0000\n" -"Last-Translator: albanobattistella , 2024\n" +"Last-Translator: tom max, 2024\n" "Language-Team: Italian (https://app.transifex.com/authentik/teams/119923/it/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -583,6 +585,28 @@ msgstr "Limite massimo di connessioni raggiunto." msgid "(You are already connected in another tab/window)" msgstr "(Sei già connesso in un'altra scheda/finestra)" +#: authentik/enterprise/stages/authenticator_endpoint_gdtc/models.py +msgid "Endpoint Authenticator Google Device Trust Connector Stage" +msgstr "" +"Fase di autenticazione per la verifica dispositivo Google tramite endpoint" + +#: authentik/enterprise/stages/authenticator_endpoint_gdtc/models.py +msgid "Endpoint Authenticator Google Device Trust Connector Stages" +msgstr "" +"Fasi di autenticazione per la verifica dispositivo Google tramite endpoint" + +#: authentik/enterprise/stages/authenticator_endpoint_gdtc/models.py +msgid "Endpoint Device" +msgstr "Dispositivo di Accesso" + +#: authentik/enterprise/stages/authenticator_endpoint_gdtc/models.py +msgid "Endpoint Devices" +msgstr "Dispositivi di Accesso" + +#: authentik/enterprise/stages/authenticator_endpoint_gdtc/stage.py +msgid "Verifying your browser..." +msgstr "Verifica del tuo browser..." + #: authentik/enterprise/stages/source/models.py msgid "" "Amount of time a user can take to return from the source to continue the " @@ -2017,6 +2041,124 @@ msgstr "" msgid "Used recovery-link to authenticate." msgstr "Utilizzato il link di recupero per autenticarsi." +#: authentik/sources/kerberos/models.py +msgid "Kerberos realm" +msgstr "Dominio Kerberos" + +#: authentik/sources/kerberos/models.py +msgid "Custom krb5.conf to use. Uses the system one by default" +msgstr "" +"krb5.conf personalizzato da usare. Usa la configurazione di sistema per " +"default" + +#: authentik/sources/kerberos/models.py +msgid "Sync users from Kerberos into authentik" +msgstr "Sincronizza utenti da Kerberos a authentik" + +#: authentik/sources/kerberos/models.py +msgid "When a user changes their password, sync it back to Kerberos" +msgstr "Quando un utente cambia la sua password, sincronizzala in Kerberos" + +#: authentik/sources/kerberos/models.py +msgid "Principal to authenticate to kadmin for sync." +msgstr "Entità da autenticare su kadmin per la sincronizzazione." + +#: authentik/sources/kerberos/models.py +msgid "Password to authenticate to kadmin for sync" +msgstr "Password per autenticarsi in kadmin per sincronizzare" + +#: authentik/sources/kerberos/models.py +msgid "" +"Keytab to authenticate to kadmin for sync. Must be base64-encoded or in the " +"form TYPE:residual" +msgstr "" +"Keytab per autenticarsi su kadmin per la sincronizzazione. Deve essere con " +"codifica base64 o nel formato TYPE:residual" + +#: authentik/sources/kerberos/models.py +msgid "" +"Credentials cache to authenticate to kadmin for sync. Must be in the form " +"TYPE:residual" +msgstr "" +"Credenziali memorizzate nella cache per autenticarsi su kadmin per la " +"sincronizzazione. Devono essere nel formato TYPE:residual" + +#: authentik/sources/kerberos/models.py +msgid "" +"Force the use of a specific server name for SPNEGO. Must be in the form " +"HTTP@hostname" +msgstr "" +"Forza l'uso di un nome server specifico per SPNEGO. Deve essere nel formato " +"HTTP@nomehost" + +#: authentik/sources/kerberos/models.py +msgid "SPNEGO keytab base64-encoded or path to keytab in the form FILE:path" +msgstr "" +"keytab SPNEGO con codifica base64 o percorso del keytab nel formato " +"FILE:percorso" + +#: authentik/sources/kerberos/models.py +msgid "Credential cache to use for SPNEGO in form type:residual" +msgstr "" +"Cache delle credenziali da utilizzare per SPNEGO nella forma type:residual" + +#: authentik/sources/kerberos/models.py +msgid "" +"If enabled, the authentik-stored password will be updated upon login with " +"the Kerberos password backend" +msgstr "" +"Se abilitato, la password memorizzata in authentik verrà aggiornata al login" +" nel backend Kerberos" + +#: authentik/sources/kerberos/models.py +msgid "Kerberos Source" +msgstr "Sorgente Kerberos" + +#: authentik/sources/kerberos/models.py +msgid "Kerberos Sources" +msgstr "Sorgenti Kerberos" + +#: authentik/sources/kerberos/models.py +msgid "Kerberos Source Property Mapping" +msgstr "Mappa delle proprietà della sorgente kerberos" + +#: authentik/sources/kerberos/models.py +msgid "Kerberos Source Property Mappings" +msgstr "Mappe delle proprietà della sorgente kerberos" + +#: authentik/sources/kerberos/models.py +msgid "User Kerberos Source Connection" +msgstr "Connessione sorgente dell'utente kerberos" + +#: authentik/sources/kerberos/models.py +msgid "User Kerberos Source Connections" +msgstr " Connessioni alle sorgente dell'utente kerberos" + +#: authentik/sources/kerberos/models.py +msgid "Group Kerberos Source Connection" +msgstr " Connessione sorgente del gruppo kerberos" + +#: authentik/sources/kerberos/models.py +msgid "Group Kerberos Source Connections" +msgstr "Connessioni alle sorgenti del gruppo kerberos" + +#: authentik/sources/kerberos/views.py +msgid "SPNEGO authentication required" +msgstr "autenticazione SPNEGO necessaria" + +#: authentik/sources/kerberos/views.py +msgid "" +"\n" +" Make sure you have valid tickets (obtainable via kinit)\n" +" and configured the browser correctly.\n" +" Please contact your administrator.\n" +" " +msgstr "" +"\n" +"Assicurati di avere un ticket valido (ottenibile tramite kinit)\n" +" e di aver configurato correttamente il browser. \n" +"Contatta il tuo amministratore." + #: authentik/sources/ldap/api.py msgid "Only a single LDAP Source with password synchronization is allowed" msgstr "" @@ -2735,13 +2877,10 @@ msgid "Captcha Stages" msgstr "Fasi Captcha" #: authentik/stages/captcha/stage.py -msgid "Unknown error" -msgstr "Errore sconosciuto" - -#: authentik/stages/captcha/stage.py -#, python-brace-format -msgid "Failed to validate token: {error}" -msgstr "Impossibile convalidare il token: {error}" +msgid "Invalid captcha response. Retrying may solve this issue." +msgstr "" +"Risposta captcha non valida. Un nuovo tentativo potrebbe risolvere il " +"problema." #: authentik/stages/captcha/stage.py msgid "Invalid captcha response" @@ -3114,6 +3253,10 @@ msgstr "Database utente + password app" msgid "User database + LDAP password" msgstr "Database utenti + password LDAP" +#: authentik/stages/password/models.py +msgid "User database + Kerberos password" +msgstr "Database utenti + password Kerberos" + #: authentik/stages/password/models.py msgid "Selection of backends to test the password against." msgstr "Selezione di backend su cui testare la password." diff --git a/locale/zh-Hans/LC_MESSAGES/django.mo b/locale/zh-Hans/LC_MESSAGES/django.mo index 3377f5d239..05a66744b3 100644 Binary files a/locale/zh-Hans/LC_MESSAGES/django.mo and b/locale/zh-Hans/LC_MESSAGES/django.mo differ diff --git a/locale/zh-Hans/LC_MESSAGES/django.po b/locale/zh-Hans/LC_MESSAGES/django.po index d2b79c466b..947dab8ebd 100644 --- a/locale/zh-Hans/LC_MESSAGES/django.po +++ b/locale/zh-Hans/LC_MESSAGES/django.po @@ -15,7 +15,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-10-18 00:09+0000\n" +"POT-Creation-Date: 2024-10-28 00:09+0000\n" "PO-Revision-Date: 2022-09-26 16:47+0000\n" "Last-Translator: deluxghost, 2024\n" "Language-Team: Chinese Simplified (https://app.transifex.com/authentik/teams/119923/zh-Hans/)\n" @@ -540,6 +540,26 @@ msgstr "已达到最大连接数。" msgid "(You are already connected in another tab/window)" msgstr "(您已经在另一个标签页/窗口连接了)" +#: authentik/enterprise/stages/authenticator_endpoint_gdtc/models.py +msgid "Endpoint Authenticator Google Device Trust Connector Stage" +msgstr "端点身份验证器 Google 设备信任连接器阶段" + +#: authentik/enterprise/stages/authenticator_endpoint_gdtc/models.py +msgid "Endpoint Authenticator Google Device Trust Connector Stages" +msgstr "端点身份验证器 Google 设备信任连接器阶段" + +#: authentik/enterprise/stages/authenticator_endpoint_gdtc/models.py +msgid "Endpoint Device" +msgstr "端点设备" + +#: authentik/enterprise/stages/authenticator_endpoint_gdtc/models.py +msgid "Endpoint Devices" +msgstr "端点设备" + +#: authentik/enterprise/stages/authenticator_endpoint_gdtc/stage.py +msgid "Verifying your browser..." +msgstr "正在验证您的浏览器…" + #: authentik/enterprise/stages/source/models.py msgid "" "Amount of time a user can take to return from the source to continue the " @@ -1848,6 +1868,112 @@ msgstr "创建一个密钥,可用于恢复对 authentik 的访问权限。" msgid "Used recovery-link to authenticate." msgstr "已使用恢复链接进行身份验证。" +#: authentik/sources/kerberos/models.py +msgid "Kerberos realm" +msgstr "Kerberos 领域" + +#: authentik/sources/kerberos/models.py +msgid "Custom krb5.conf to use. Uses the system one by default" +msgstr "要使用的自定义 krb5.conf。默认使用系统自带" + +#: authentik/sources/kerberos/models.py +msgid "Sync users from Kerberos into authentik" +msgstr "从 Kerberos 同步用户到 authentik" + +#: authentik/sources/kerberos/models.py +msgid "When a user changes their password, sync it back to Kerberos" +msgstr "当用户修改密码时,将其同步回 Kerberos" + +#: authentik/sources/kerberos/models.py +msgid "Principal to authenticate to kadmin for sync." +msgstr "向 kadmin 进行身份验证以进行同步的主体。" + +#: authentik/sources/kerberos/models.py +msgid "Password to authenticate to kadmin for sync" +msgstr "向 kadmin 进行身份验证以进行同步的密码" + +#: authentik/sources/kerberos/models.py +msgid "" +"Keytab to authenticate to kadmin for sync. Must be base64-encoded or in the " +"form TYPE:residual" +msgstr "向 kadmin 进行身份验证以进行同步的 Keytab。必须以 Base64 编码,或者形式为 TYPE:residual" + +#: authentik/sources/kerberos/models.py +msgid "" +"Credentials cache to authenticate to kadmin for sync. Must be in the form " +"TYPE:residual" +msgstr "向 kadmin 进行身份验证以进行同步的凭据缓存。形式必须为 TYPE:residual" + +#: authentik/sources/kerberos/models.py +msgid "" +"Force the use of a specific server name for SPNEGO. Must be in the form " +"HTTP@hostname" +msgstr "强制为 SPNEGO 使用特定服务器名称。形式必须为 HTTP@主机名" + +#: authentik/sources/kerberos/models.py +msgid "SPNEGO keytab base64-encoded or path to keytab in the form FILE:path" +msgstr "以 Base64 编码的 SPNEGO Keytab 或 FILE:path 形式的 Keytab 路径" + +#: authentik/sources/kerberos/models.py +msgid "Credential cache to use for SPNEGO in form type:residual" +msgstr "SPNEGO 使用的凭据缓存,形式为 type:residual" + +#: authentik/sources/kerberos/models.py +msgid "" +"If enabled, the authentik-stored password will be updated upon login with " +"the Kerberos password backend" +msgstr "启用时,authentik 存储的密码将会在使用 Kerberos 密码后端登录时更新" + +#: authentik/sources/kerberos/models.py +msgid "Kerberos Source" +msgstr "Kerberos 源" + +#: authentik/sources/kerberos/models.py +msgid "Kerberos Sources" +msgstr "Kerberos 源" + +#: authentik/sources/kerberos/models.py +msgid "Kerberos Source Property Mapping" +msgstr "Kerberos 源属性映射" + +#: authentik/sources/kerberos/models.py +msgid "Kerberos Source Property Mappings" +msgstr "Kerberos 源属性映射" + +#: authentik/sources/kerberos/models.py +msgid "User Kerberos Source Connection" +msgstr "用户 Kerberos 源连接" + +#: authentik/sources/kerberos/models.py +msgid "User Kerberos Source Connections" +msgstr "用户 Kerberos 源连接" + +#: authentik/sources/kerberos/models.py +msgid "Group Kerberos Source Connection" +msgstr "组 Kerberos 源连接" + +#: authentik/sources/kerberos/models.py +msgid "Group Kerberos Source Connections" +msgstr "组 Kerberos 源连接" + +#: authentik/sources/kerberos/views.py +msgid "SPNEGO authentication required" +msgstr "需要 SPNEGO 身份验证" + +#: authentik/sources/kerberos/views.py +msgid "" +"\n" +" Make sure you have valid tickets (obtainable via kinit)\n" +" and configured the browser correctly.\n" +" Please contact your administrator.\n" +" " +msgstr "" +"\n" +" 请确认您拥有有效票据(通过 kinit 获得)\n" +" 并且已正确配置浏览器。\n" +" 请联系您的管理员。\n" +" " + #: authentik/sources/ldap/api.py msgid "Only a single LDAP Source with password synchronization is allowed" msgstr "仅允许使用密码同步的单个 LDAP 源" @@ -2523,13 +2649,8 @@ msgid "Captcha Stages" msgstr "验证码阶段" #: authentik/stages/captcha/stage.py -msgid "Unknown error" -msgstr "未知错误" - -#: authentik/stages/captcha/stage.py -#, python-brace-format -msgid "Failed to validate token: {error}" -msgstr "验证令牌失败:{error}" +msgid "Invalid captcha response. Retrying may solve this issue." +msgstr "无效的验证码响应。重试可能会解决此问题。" #: authentik/stages/captcha/stage.py msgid "Invalid captcha response" @@ -2876,6 +2997,10 @@ msgstr "用户数据库 + 应用程序密码" msgid "User database + LDAP password" msgstr "用户数据库 + LDAP 密码" +#: authentik/stages/password/models.py +msgid "User database + Kerberos password" +msgstr "用户数据库 + Kerberos 密码" + #: authentik/stages/password/models.py msgid "Selection of backends to test the password against." msgstr "选择用于测试密码的后端。" diff --git a/locale/zh_CN/LC_MESSAGES/django.po b/locale/zh_CN/LC_MESSAGES/django.po index f3a6d452c4..edd40cbf85 100644 --- a/locale/zh_CN/LC_MESSAGES/django.po +++ b/locale/zh_CN/LC_MESSAGES/django.po @@ -14,7 +14,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-10-18 00:09+0000\n" +"POT-Creation-Date: 2024-10-28 00:09+0000\n" "PO-Revision-Date: 2022-09-26 16:47+0000\n" "Last-Translator: deluxghost, 2024\n" "Language-Team: Chinese (China) (https://app.transifex.com/authentik/teams/119923/zh_CN/)\n" @@ -539,6 +539,26 @@ msgstr "已达到最大连接数。" msgid "(You are already connected in another tab/window)" msgstr "(您已经在另一个标签页/窗口连接了)" +#: authentik/enterprise/stages/authenticator_endpoint_gdtc/models.py +msgid "Endpoint Authenticator Google Device Trust Connector Stage" +msgstr "端点身份验证器 Google 设备信任连接器阶段" + +#: authentik/enterprise/stages/authenticator_endpoint_gdtc/models.py +msgid "Endpoint Authenticator Google Device Trust Connector Stages" +msgstr "端点身份验证器 Google 设备信任连接器阶段" + +#: authentik/enterprise/stages/authenticator_endpoint_gdtc/models.py +msgid "Endpoint Device" +msgstr "端点设备" + +#: authentik/enterprise/stages/authenticator_endpoint_gdtc/models.py +msgid "Endpoint Devices" +msgstr "端点设备" + +#: authentik/enterprise/stages/authenticator_endpoint_gdtc/stage.py +msgid "Verifying your browser..." +msgstr "正在验证您的浏览器…" + #: authentik/enterprise/stages/source/models.py msgid "" "Amount of time a user can take to return from the source to continue the " @@ -1847,6 +1867,112 @@ msgstr "创建一个密钥,可用于恢复对 authentik 的访问权限。" msgid "Used recovery-link to authenticate." msgstr "已使用恢复链接进行身份验证。" +#: authentik/sources/kerberos/models.py +msgid "Kerberos realm" +msgstr "Kerberos 领域" + +#: authentik/sources/kerberos/models.py +msgid "Custom krb5.conf to use. Uses the system one by default" +msgstr "要使用的自定义 krb5.conf。默认使用系统自带" + +#: authentik/sources/kerberos/models.py +msgid "Sync users from Kerberos into authentik" +msgstr "从 Kerberos 同步用户到 authentik" + +#: authentik/sources/kerberos/models.py +msgid "When a user changes their password, sync it back to Kerberos" +msgstr "当用户修改密码时,将其同步回 Kerberos" + +#: authentik/sources/kerberos/models.py +msgid "Principal to authenticate to kadmin for sync." +msgstr "向 kadmin 进行身份验证以进行同步的主体。" + +#: authentik/sources/kerberos/models.py +msgid "Password to authenticate to kadmin for sync" +msgstr "向 kadmin 进行身份验证以进行同步的密码" + +#: authentik/sources/kerberos/models.py +msgid "" +"Keytab to authenticate to kadmin for sync. Must be base64-encoded or in the " +"form TYPE:residual" +msgstr "向 kadmin 进行身份验证以进行同步的 Keytab。必须以 Base64 编码,或者形式为 TYPE:residual" + +#: authentik/sources/kerberos/models.py +msgid "" +"Credentials cache to authenticate to kadmin for sync. Must be in the form " +"TYPE:residual" +msgstr "向 kadmin 进行身份验证以进行同步的凭据缓存。形式必须为 TYPE:residual" + +#: authentik/sources/kerberos/models.py +msgid "" +"Force the use of a specific server name for SPNEGO. Must be in the form " +"HTTP@hostname" +msgstr "强制为 SPNEGO 使用特定服务器名称。形式必须为 HTTP@主机名" + +#: authentik/sources/kerberos/models.py +msgid "SPNEGO keytab base64-encoded or path to keytab in the form FILE:path" +msgstr "以 Base64 编码的 SPNEGO Keytab 或 FILE:path 形式的 Keytab 路径" + +#: authentik/sources/kerberos/models.py +msgid "Credential cache to use for SPNEGO in form type:residual" +msgstr "SPNEGO 使用的凭据缓存,形式为 type:residual" + +#: authentik/sources/kerberos/models.py +msgid "" +"If enabled, the authentik-stored password will be updated upon login with " +"the Kerberos password backend" +msgstr "启用时,authentik 存储的密码将会在使用 Kerberos 密码后端登录时更新" + +#: authentik/sources/kerberos/models.py +msgid "Kerberos Source" +msgstr "Kerberos 源" + +#: authentik/sources/kerberos/models.py +msgid "Kerberos Sources" +msgstr "Kerberos 源" + +#: authentik/sources/kerberos/models.py +msgid "Kerberos Source Property Mapping" +msgstr "Kerberos 源属性映射" + +#: authentik/sources/kerberos/models.py +msgid "Kerberos Source Property Mappings" +msgstr "Kerberos 源属性映射" + +#: authentik/sources/kerberos/models.py +msgid "User Kerberos Source Connection" +msgstr "用户 Kerberos 源连接" + +#: authentik/sources/kerberos/models.py +msgid "User Kerberos Source Connections" +msgstr "用户 Kerberos 源连接" + +#: authentik/sources/kerberos/models.py +msgid "Group Kerberos Source Connection" +msgstr "组 Kerberos 源连接" + +#: authentik/sources/kerberos/models.py +msgid "Group Kerberos Source Connections" +msgstr "组 Kerberos 源连接" + +#: authentik/sources/kerberos/views.py +msgid "SPNEGO authentication required" +msgstr "需要 SPNEGO 身份验证" + +#: authentik/sources/kerberos/views.py +msgid "" +"\n" +" Make sure you have valid tickets (obtainable via kinit)\n" +" and configured the browser correctly.\n" +" Please contact your administrator.\n" +" " +msgstr "" +"\n" +" 请确认您拥有有效票据(通过 kinit 获得)\n" +" 并且已正确配置浏览器。\n" +" 请联系您的管理员。\n" +" " + #: authentik/sources/ldap/api.py msgid "Only a single LDAP Source with password synchronization is allowed" msgstr "仅允许使用密码同步的单个 LDAP 源" @@ -2522,13 +2648,8 @@ msgid "Captcha Stages" msgstr "验证码阶段" #: authentik/stages/captcha/stage.py -msgid "Unknown error" -msgstr "未知错误" - -#: authentik/stages/captcha/stage.py -#, python-brace-format -msgid "Failed to validate token: {error}" -msgstr "验证令牌失败:{error}" +msgid "Invalid captcha response. Retrying may solve this issue." +msgstr "无效的验证码响应。重试可能会解决此问题。" #: authentik/stages/captcha/stage.py msgid "Invalid captcha response" @@ -2875,6 +2996,10 @@ msgstr "用户数据库 + 应用程序密码" msgid "User database + LDAP password" msgstr "用户数据库 + LDAP 密码" +#: authentik/stages/password/models.py +msgid "User database + Kerberos password" +msgstr "用户数据库 + Kerberos 密码" + #: authentik/stages/password/models.py msgid "Selection of backends to test the password against." msgstr "选择用于测试密码的后端。" diff --git a/poetry.lock b/poetry.lock index 477f4034d8..9fde46832f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1849,35 +1849,36 @@ grpc = ["grpcio (>=1.44.0,<2.0.0.dev0)"] [[package]] name = "gssapi" -version = "1.8.3" +version = "1.9.0" description = "Python GSSAPI Wrapper" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "gssapi-1.8.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4e4a83e9b275fe69b5d40be6d5479889866b80333a12c51a9243f2712d4f0554"}, - {file = "gssapi-1.8.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8d57d67547e18f4e44a688bfb20abbf176d1b8df547da2b31c3f2df03cfdc269"}, - {file = "gssapi-1.8.3-cp310-cp310-win32.whl", hash = "sha256:3a3f63105f39c4af29ffc8f7b6542053d87fe9d63010c689dd9a9f5571facb8e"}, - {file = "gssapi-1.8.3-cp310-cp310-win_amd64.whl", hash = "sha256:b031c0f186ab4275186da385b2c7470dd47c9b27522cb3b753757c9ac4bebf11"}, - {file = "gssapi-1.8.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b03d6b30f1fcd66d9a688b45a97e302e4dd3f1386d5c333442731aec73cdb409"}, - {file = "gssapi-1.8.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ca6ceb17fc15eda2a69f2e8c6cf10d11e2edb32832255e5d4c65b21b6db4680a"}, - {file = "gssapi-1.8.3-cp311-cp311-win32.whl", hash = "sha256:edc8ef3a9e397dbe18bb6016f8e2209969677b534316d20bb139da2865a38efe"}, - {file = "gssapi-1.8.3-cp311-cp311-win_amd64.whl", hash = "sha256:8fdb1ff130cee49bc865ec1624dee8cf445cd6c6e93b04bffef2c6f363a60cb9"}, - {file = "gssapi-1.8.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:19c373b3ba63ce19cd3163aa1495635e3d01b0de6cc4ff1126095eded1df6e01"}, - {file = "gssapi-1.8.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:37f1a8046d695f2c9b8d640a6e385780d3945c0741571ed6fee6f94c31e431dc"}, - {file = "gssapi-1.8.3-cp312-cp312-win32.whl", hash = "sha256:338db18612e3e6ed64e92b6d849242a535fdc98b365f21122992fb8cae737617"}, - {file = "gssapi-1.8.3-cp312-cp312-win_amd64.whl", hash = "sha256:5731c5b40ecc3116cfe7fb7e1d1e128583ec8b3df1e68bf8cd12073160793acd"}, - {file = "gssapi-1.8.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e556878da197ad115a566d36e46a8082d0079731d9c24d1ace795132d725ff2a"}, - {file = "gssapi-1.8.3-cp37-cp37m-win32.whl", hash = "sha256:e2bb081f2db2111377effe7d40ba23f9a87359b9d2f4881552b731e9da88b36b"}, - {file = "gssapi-1.8.3-cp37-cp37m-win_amd64.whl", hash = "sha256:4d9ed83f2064cda60aad90e6840ae282096801b2c814b8cbd390bf0df4635aab"}, - {file = "gssapi-1.8.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7d91fe6e2a5c89b32102ea8e374b8ae13b9031d43d7b55f3abc1f194ddce820d"}, - {file = "gssapi-1.8.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d5b28237afc0668046934792756dd4b6b7e957b0d95a608d02f296734a2819ad"}, - {file = "gssapi-1.8.3-cp38-cp38-win32.whl", hash = "sha256:791e44f7bea602b8e3da1ec56fbdb383b8ee3326fdeb736f904c2aa9af13a67d"}, - {file = "gssapi-1.8.3-cp38-cp38-win_amd64.whl", hash = "sha256:5b4bf84d0a6d7779a4bf11dacfd3db57ae02dd53562e2aeadac4219a68eaee07"}, - {file = "gssapi-1.8.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e40efc88ccefefd6142f8c47b8af498731938958b808bad49990442a91f45160"}, - {file = "gssapi-1.8.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ee74b9211c977b9181ff4652d886d7712c9a221560752a35393b58e5ea07887a"}, - {file = "gssapi-1.8.3-cp39-cp39-win32.whl", hash = "sha256:465c6788f2ac6ef7c738394ba8fde1ede6004e5721766f386add63891d8c90af"}, - {file = "gssapi-1.8.3-cp39-cp39-win_amd64.whl", hash = "sha256:8fb8ee70458f47b51ed881a6881f30b187c987c02af16cc0fff0079255d4d465"}, - {file = "gssapi-1.8.3.tar.gz", hash = "sha256:aa3c8d0b1526f52559552bb2c9d2d6be013d76a8e5db00b39a1db5727e93b0b0"}, + {file = "gssapi-1.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:261e00ac426d840055ddb2199f4989db7e3ce70fa18b1538f53e392b4823e8f1"}, + {file = "gssapi-1.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:14a1ae12fdf1e4c8889206195ba1843de09fe82587fa113112887cd5894587c6"}, + {file = "gssapi-1.9.0-cp310-cp310-win32.whl", hash = "sha256:2a9c745255e3a810c3e8072e267b7b302de0705f8e9a0f2c5abc92fe12b9475e"}, + {file = "gssapi-1.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:dfc1b4c0bfe9f539537601c9f187edc320daf488f694e50d02d0c1eb37416962"}, + {file = "gssapi-1.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:67d9be5e34403e47fb5749d5a1ad4e5a85b568e6a9add1695edb4a5b879f7560"}, + {file = "gssapi-1.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:11e9b92cef11da547fc8c210fa720528fd854038504103c1b15ae2a89dce5fcd"}, + {file = "gssapi-1.9.0-cp311-cp311-win32.whl", hash = "sha256:6c5f8a549abd187687440ec0b72e5b679d043d620442b3637d31aa2766b27cbe"}, + {file = "gssapi-1.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:59e1a1a9a6c5dc430dc6edfcf497f5ca00cf417015f781c9fac2e85652cd738f"}, + {file = "gssapi-1.9.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b66a98827fbd2864bf8993677a039d7ba4a127ca0d2d9ed73e0ef4f1baa7fd7f"}, + {file = "gssapi-1.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2bddd1cc0c9859c5e0fd96d4d88eb67bd498fdbba45b14cdccfe10bfd329479f"}, + {file = "gssapi-1.9.0-cp312-cp312-win32.whl", hash = "sha256:10134db0cf01bd7d162acb445762dbcc58b5c772a613e17c46cf8ad956c4dfec"}, + {file = "gssapi-1.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:e28c7d45da68b7e36ed3fb3326744bfe39649f16e8eecd7b003b082206039c76"}, + {file = "gssapi-1.9.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:cea344246935b5337e6f8a69bb6cc45619ab3a8d74a29fcb0a39fd1e5843c89c"}, + {file = "gssapi-1.9.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1a5786bd9fcf435bd0c87dc95ae99ad68cefcc2bcc80c71fef4cb0ccdfb40f1e"}, + {file = "gssapi-1.9.0-cp313-cp313-win32.whl", hash = "sha256:c99959a9dd62358e370482f1691e936cb09adf9a69e3e10d4f6a097240e9fd28"}, + {file = "gssapi-1.9.0-cp313-cp313-win_amd64.whl", hash = "sha256:a2e43f50450e81fe855888c53df70cdd385ada979db79463b38031710a12acd9"}, + {file = "gssapi-1.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c0e378d62b2fc352ca0046030cda5911d808a965200f612fdd1d74501b83e98f"}, + {file = "gssapi-1.9.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b74031c70864d04864b7406c818f41be0c1637906fb9654b06823bcc79f151dc"}, + {file = "gssapi-1.9.0-cp38-cp38-win32.whl", hash = "sha256:f2f3a46784d8127cc7ef10d3367dedcbe82899ea296710378ccc9b7cefe96f4c"}, + {file = "gssapi-1.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:a81f30cde21031e7b1f8194a3eea7285e39e551265e7744edafd06eadc1c95bc"}, + {file = "gssapi-1.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cbc93fdadd5aab9bae594538b2128044b8c5cdd1424fe015a465d8a8a587411a"}, + {file = "gssapi-1.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5b2a3c0a9beb895942d4b8e31f515e52c17026e55aeaa81ee0df9bbfdac76098"}, + {file = "gssapi-1.9.0-cp39-cp39-win32.whl", hash = "sha256:060b58b455d29ab8aca74770e667dca746264bee660ac5b6a7a17476edc2c0b8"}, + {file = "gssapi-1.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:11c9fe066edb0fa0785697eb0cecf2719c7ad1d9f2bf27be57b647a617bcfaa5"}, + {file = "gssapi-1.9.0.tar.gz", hash = "sha256:f468fac8f3f5fca8f4d1ca19e3cd4d2e10bd91074e7285464b22715d13548afe"}, ] [package.dependencies] @@ -3895,13 +3896,13 @@ pytest = ">=4.0.0" [[package]] name = "pytest-randomly" -version = "3.15.0" +version = "3.16.0" description = "Pytest plugin to randomly order tests and control random.seed." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "pytest_randomly-3.15.0-py3-none-any.whl", hash = "sha256:0516f4344b29f4e9cdae8bce31c4aeebf59d0b9ef05927c33354ff3859eeeca6"}, - {file = "pytest_randomly-3.15.0.tar.gz", hash = "sha256:b908529648667ba5e54723088edd6f82252f540cc340d748d1fa985539687047"}, + {file = "pytest_randomly-3.16.0-py3-none-any.whl", hash = "sha256:8633d332635a1a0983d3bba19342196807f6afb17c3eef78e02c2f85dade45d6"}, + {file = "pytest_randomly-3.16.0.tar.gz", hash = "sha256:11bf4d23a26484de7860d82f726c0629837cf4064b79157bd18ec9d41d7feb26"}, ] [package.dependencies] @@ -4292,29 +4293,29 @@ pyasn1 = ">=0.1.3" [[package]] name = "ruff" -version = "0.7.0" +version = "0.7.1" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.7.0-py3-none-linux_armv6l.whl", hash = "sha256:0cdf20c2b6ff98e37df47b2b0bd3a34aaa155f59a11182c1303cce79be715628"}, - {file = "ruff-0.7.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:496494d350c7fdeb36ca4ef1c9f21d80d182423718782222c29b3e72b3512737"}, - {file = "ruff-0.7.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:214b88498684e20b6b2b8852c01d50f0651f3cc6118dfa113b4def9f14faaf06"}, - {file = "ruff-0.7.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:630fce3fefe9844e91ea5bbf7ceadab4f9981f42b704fae011bb8efcaf5d84be"}, - {file = "ruff-0.7.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:211d877674e9373d4bb0f1c80f97a0201c61bcd1e9d045b6e9726adc42c156aa"}, - {file = "ruff-0.7.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:194d6c46c98c73949a106425ed40a576f52291c12bc21399eb8f13a0f7073495"}, - {file = "ruff-0.7.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:82c2579b82b9973a110fab281860403b397c08c403de92de19568f32f7178598"}, - {file = "ruff-0.7.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9af971fe85dcd5eaed8f585ddbc6bdbe8c217fb8fcf510ea6bca5bdfff56040e"}, - {file = "ruff-0.7.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b641c7f16939b7d24b7bfc0be4102c56562a18281f84f635604e8a6989948914"}, - {file = "ruff-0.7.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d71672336e46b34e0c90a790afeac8a31954fd42872c1f6adaea1dff76fd44f9"}, - {file = "ruff-0.7.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:ab7d98c7eed355166f367597e513a6c82408df4181a937628dbec79abb2a1fe4"}, - {file = "ruff-0.7.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:1eb54986f770f49edb14f71d33312d79e00e629a57387382200b1ef12d6a4ef9"}, - {file = "ruff-0.7.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:dc452ba6f2bb9cf8726a84aa877061a2462afe9ae0ea1d411c53d226661c601d"}, - {file = "ruff-0.7.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:4b406c2dce5be9bad59f2de26139a86017a517e6bcd2688da515481c05a2cb11"}, - {file = "ruff-0.7.0-py3-none-win32.whl", hash = "sha256:f6c968509f767776f524a8430426539587d5ec5c662f6addb6aa25bc2e8195ec"}, - {file = "ruff-0.7.0-py3-none-win_amd64.whl", hash = "sha256:ff4aabfbaaba880e85d394603b9e75d32b0693152e16fa659a3064a85df7fce2"}, - {file = "ruff-0.7.0-py3-none-win_arm64.whl", hash = "sha256:10842f69c245e78d6adec7e1db0a7d9ddc2fff0621d730e61657b64fa36f207e"}, - {file = "ruff-0.7.0.tar.gz", hash = "sha256:47a86360cf62d9cd53ebfb0b5eb0e882193fc191c6d717e8bef4462bc3b9ea2b"}, + {file = "ruff-0.7.1-py3-none-linux_armv6l.whl", hash = "sha256:cb1bc5ed9403daa7da05475d615739cc0212e861b7306f314379d958592aaa89"}, + {file = "ruff-0.7.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:27c1c52a8d199a257ff1e5582d078eab7145129aa02721815ca8fa4f9612dc35"}, + {file = "ruff-0.7.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:588a34e1ef2ea55b4ddfec26bbe76bc866e92523d8c6cdec5e8aceefeff02d99"}, + {file = "ruff-0.7.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94fc32f9cdf72dc75c451e5f072758b118ab8100727168a3df58502b43a599ca"}, + {file = "ruff-0.7.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:985818742b833bffa543a84d1cc11b5e6871de1b4e0ac3060a59a2bae3969250"}, + {file = "ruff-0.7.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32f1e8a192e261366c702c5fb2ece9f68d26625f198a25c408861c16dc2dea9c"}, + {file = "ruff-0.7.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:699085bf05819588551b11751eff33e9ca58b1b86a6843e1b082a7de40da1565"}, + {file = "ruff-0.7.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:344cc2b0814047dc8c3a8ff2cd1f3d808bb23c6658db830d25147339d9bf9ea7"}, + {file = "ruff-0.7.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4316bbf69d5a859cc937890c7ac7a6551252b6a01b1d2c97e8fc96e45a7c8b4a"}, + {file = "ruff-0.7.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79d3af9dca4c56043e738a4d6dd1e9444b6d6c10598ac52d146e331eb155a8ad"}, + {file = "ruff-0.7.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c5c121b46abde94a505175524e51891f829414e093cd8326d6e741ecfc0a9112"}, + {file = "ruff-0.7.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8422104078324ea250886954e48f1373a8fe7de59283d747c3a7eca050b4e378"}, + {file = "ruff-0.7.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:56aad830af8a9db644e80098fe4984a948e2b6fc2e73891538f43bbe478461b8"}, + {file = "ruff-0.7.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:658304f02f68d3a83c998ad8bf91f9b4f53e93e5412b8f2388359d55869727fd"}, + {file = "ruff-0.7.1-py3-none-win32.whl", hash = "sha256:b517a2011333eb7ce2d402652ecaa0ac1a30c114fbbd55c6b8ee466a7f600ee9"}, + {file = "ruff-0.7.1-py3-none-win_amd64.whl", hash = "sha256:f38c41fcde1728736b4eb2b18850f6d1e3eedd9678c914dede554a70d5241307"}, + {file = "ruff-0.7.1-py3-none-win_arm64.whl", hash = "sha256:19aa200ec824c0f36d0c9114c8ec0087082021732979a359d6f3c390a6ff2a37"}, + {file = "ruff-0.7.1.tar.gz", hash = "sha256:9d8a41d4aa2dad1575adb98a82870cf5db5f76b2938cf2206c22c940034a36f4"}, ] [[package]] @@ -4424,13 +4425,13 @@ tornado = ["tornado (>=6)"] [[package]] name = "service-identity" -version = "24.1.0" +version = "24.2.0" description = "Service identity verification for pyOpenSSL & cryptography." optional = false python-versions = ">=3.8" files = [ - {file = "service_identity-24.1.0-py3-none-any.whl", hash = "sha256:a28caf8130c8a5c1c7a6f5293faaf239bbfb7751e4862436920ee6f2616f568a"}, - {file = "service_identity-24.1.0.tar.gz", hash = "sha256:6829c9d62fb832c2e1c435629b0a8c476e1929881f28bee4d20bc24161009221"}, + {file = "service_identity-24.2.0-py3-none-any.whl", hash = "sha256:6b047fbd8a84fd0bb0d55ebce4031e400562b9196e1e0d3e0fe2b8a59f6d4a85"}, + {file = "service_identity-24.2.0.tar.gz", hash = "sha256:b8683ba13f0d39c6cd5d625d2c5f65421d6d707b013b375c355751557cbe8e09"}, ] [package.dependencies] @@ -4440,7 +4441,7 @@ pyasn1 = "*" pyasn1-modules = "*" [package.extras] -dev = ["pyopenssl", "service-identity[idna,mypy,tests]"] +dev = ["coverage[toml] (>=5.0.2)", "idna", "mypy", "pyopenssl", "pytest", "types-pyopenssl"] docs = ["furo", "myst-parser", "pyopenssl", "sphinx", "sphinx-notfound-page"] idna = ["idna"] mypy = ["idna", "mypy", "types-pyopenssl"] @@ -4750,13 +4751,13 @@ wsproto = ">=0.14" [[package]] name = "twilio" -version = "9.3.4" +version = "9.3.6" description = "Twilio API client and TwiML generator" optional = false python-versions = ">=3.7.0" files = [ - {file = "twilio-9.3.4-py2.py3-none-any.whl", hash = "sha256:2cae99f0f7aecbd9da02fa59ad8f11b360db4a9281fc3fb3237ad50be21d8a9b"}, - {file = "twilio-9.3.4.tar.gz", hash = "sha256:38a6ab04752f44313dcf736eae45236a901528d3f53dfc21d3afd33539243c7f"}, + {file = "twilio-9.3.6-py2.py3-none-any.whl", hash = "sha256:c5d7f4cfeb50a7928397b8f819c8f7fb2bb956a1a2cabbda1df1d7a40f9ce1d7"}, + {file = "twilio-9.3.6.tar.gz", hash = "sha256:d42691f7fe1faaa5ba82942f169bfea4d7f01a0a542a456d82018fb49bd1f5b2"}, ] [package.dependencies] diff --git a/schema.yml b/schema.yml index d4f3eb78ac..c920d1ab11 100644 --- a/schema.yml +++ b/schema.yml @@ -33862,6 +33862,11 @@ paths: operationId: stages_identification_list description: IdentificationStage Viewset parameters: + - in: query + name: captcha_stage + schema: + type: string + format: uuid - in: query name: case_insensitive_matching schema: @@ -40204,10 +40209,15 @@ components: challenge: type: object additionalProperties: {} + last_used: + type: string + format: date-time + nullable: true required: - challenge - device_class - device_uid + - last_used DeviceChallengeRequest: type: object description: Single device challenge @@ -40221,10 +40231,15 @@ components: challenge: type: object additionalProperties: {} + last_used: + type: string + format: date-time + nullable: true required: - challenge - device_class - device_uid + - last_used DeviceClassesEnum: enum: - static @@ -42494,6 +42509,8 @@ components: type: string flow_designation: $ref: '#/components/schemas/FlowDesignationEnum' + captcha_stage: + $ref: '#/components/schemas/CaptchaChallenge' enroll_url: type: string recovery_url: @@ -42528,6 +42545,9 @@ components: password: type: string nullable: true + captcha_token: + type: string + nullable: true required: - uid_field IdentificationStage: @@ -42573,6 +42593,12 @@ components: nullable: true description: When set, shows a password field, instead of showing the password field as separate step. + captcha_stage: + type: string + format: uuid + nullable: true + description: When set, adds functionality exactly like a Captcha stage, + but baked into the Identification stage. case_insensitive_matching: type: boolean description: When enabled, user fields are matched regardless of their casing. @@ -42641,6 +42667,12 @@ components: nullable: true description: When set, shows a password field, instead of showing the password field as separate step. + captcha_stage: + type: string + format: uuid + nullable: true + description: When set, adds functionality exactly like a Captcha stage, + but baked into the Identification stage. case_insensitive_matching: type: boolean description: When enabled, user fields are matched regardless of their casing. @@ -48231,6 +48263,12 @@ components: nullable: true description: When set, shows a password field, instead of showing the password field as separate step. + captcha_stage: + type: string + format: uuid + nullable: true + description: When set, adds functionality exactly like a Captcha stage, + but baked into the Identification stage. case_insensitive_matching: type: boolean description: When enabled, user fields are matched regardless of their casing. diff --git a/web/package-lock.json b/web/package-lock.json index cd7081bf4b..7e3799333c 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -23,7 +23,7 @@ "@floating-ui/dom": "^1.6.11", "@formatjs/intl-listformat": "^7.5.7", "@fortawesome/fontawesome-free": "^6.6.0", - "@goauthentik/api": "^2024.8.3-1729699127", + "@goauthentik/api": "^2024.8.3-1729836831", "@lit-labs/ssr": "^3.2.2", "@lit/context": "^1.1.2", "@lit/localize": "^0.12.2", @@ -1775,9 +1775,9 @@ } }, "node_modules/@goauthentik/api": { - "version": "2024.8.3-1729699127", - "resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2024.8.3-1729699127.tgz", - "integrity": "sha512-luo0SAASR6BTTtLszDgfdwofBejv4F3hCHgPxeSoTSFgE8/A2+zJD8EtWPZaa1udDkwPa9lbIeJSSmbgFke3jA==" + "version": "2024.8.3-1729836831", + "resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2024.8.3-1729836831.tgz", + "integrity": "sha512-nOgvjYQiK+HhWuiZ635h/aSsq7Mfj5cDrIyBJt+IJRQuJFtnnHx8nscRXKK/8sBl9obH2zMCoZgeqytK8145bg==" }, "node_modules/@goauthentik/web": { "resolved": "", diff --git a/web/package.json b/web/package.json index 26a2b15b2b..1929e4fdb7 100644 --- a/web/package.json +++ b/web/package.json @@ -11,7 +11,7 @@ "@floating-ui/dom": "^1.6.11", "@formatjs/intl-listformat": "^7.5.7", "@fortawesome/fontawesome-free": "^6.6.0", - "@goauthentik/api": "^2024.8.3-1729699127", + "@goauthentik/api": "^2024.8.3-1729836831", "@lit-labs/ssr": "^3.2.2", "@lit/context": "^1.1.2", "@lit/localize": "^0.12.2", diff --git a/web/src/admin/applications/wizard/methods/ldap/ak-application-wizard-authentication-by-ldap.ts b/web/src/admin/applications/wizard/methods/ldap/ak-application-wizard-authentication-by-ldap.ts index 75b392f714..aa1bbfbf5a 100644 --- a/web/src/admin/applications/wizard/methods/ldap/ak-application-wizard-authentication-by-ldap.ts +++ b/web/src/admin/applications/wizard/methods/ldap/ak-application-wizard-authentication-by-ldap.ts @@ -97,7 +97,7 @@ export class ApplicationWizardApplicationDetails extends WithBrandConfig(BasePro @@ -150,35 +149,36 @@ export class ApplicationWizardAuthenticationByOauth extends BaseProviderPanel { ${msg("Advanced flow settings")} - - -

- ${msg( - "Flow used when a user access this provider and is not authenticated.", - )} -

-
- - + + +

+ ${msg( + "Flow used when a user access this provider and is not authenticated.", + )} +

+
+
-

- ${msg("Flow used when logging out of this provider.")} -

-
+ > + +

+ ${msg("Flow used when logging out of this provider.")} +

+
diff --git a/web/src/admin/applications/wizard/methods/proxy/AuthenticationByProxyPage.ts b/web/src/admin/applications/wizard/methods/proxy/AuthenticationByProxyPage.ts index e6d66aea6f..867efbd0b3 100644 --- a/web/src/admin/applications/wizard/methods/proxy/AuthenticationByProxyPage.ts +++ b/web/src/admin/applications/wizard/methods/proxy/AuthenticationByProxyPage.ts @@ -161,11 +161,9 @@ export class AkTypeProxyApplicationWizardPage extends BaseProviderPanel { @@ -184,35 +182,36 @@ export class AkTypeProxyApplicationWizardPage extends BaseProviderPanel { ${msg("Advanced flow settings")} - - -

- ${msg( - "Flow used when a user access this provider and is not authenticated.", - )} -

-
- - + + +

+ ${msg( + "Flow used when a user access this provider and is not authenticated.", + )} +

+
+
-

- ${msg("Flow used when logging out of this provider.")} -

-
+ > + +

+ ${msg("Flow used when logging out of this provider.")} +

+
diff --git a/web/src/admin/applications/wizard/methods/saml/ak-application-wizard-authentication-by-saml-configuration.ts b/web/src/admin/applications/wizard/methods/saml/ak-application-wizard-authentication-by-saml-configuration.ts index 61c1f6403d..54cbe258ca 100644 --- a/web/src/admin/applications/wizard/methods/saml/ak-application-wizard-authentication-by-saml-configuration.ts +++ b/web/src/admin/applications/wizard/methods/saml/ak-application-wizard-authentication-by-saml-configuration.ts @@ -146,36 +146,37 @@ export class ApplicationWizardProviderSamlConfiguration extends BaseProviderPane - ${msg("Advanced flow settings")} - - -

- ${msg( - "Flow used when a user access this provider and is not authenticated.", - )} -

-
- - ${msg("Advanced flow settings")} +
+ + +

+ ${msg( + "Flow used when a user access this provider and is not authenticated.", + )} +

+
+ -

- ${msg("Flow used when logging out of this provider.")} -

-
+ > + +

+ ${msg("Flow used when logging out of this provider.")} +

+
@@ -199,60 +200,52 @@ export class ApplicationWizardProviderSamlConfiguration extends BaseProviderPane )}

- ${ - this.hasSigningKp - ? html` - +

+ ${msg( + "When enabled, the assertion element of the SAML response will be signed.", + )} +

+
` + : nothing} - ${msg("RAC is in preview.")} - ${msg("Send us feedback!")} - - ${this.provider?.assignedApplicationName + return html`${this.provider?.assignedApplicationName ? html`` : html`
${msg("Warning: Provider is not used by an Application.")} diff --git a/web/src/admin/rbac/ObjectPermissionModal.ts b/web/src/admin/rbac/ObjectPermissionModal.ts index 87892befe7..745596e038 100644 --- a/web/src/admin/rbac/ObjectPermissionModal.ts +++ b/web/src/admin/rbac/ObjectPermissionModal.ts @@ -7,7 +7,6 @@ import { msg } from "@lit/localize"; import { CSSResult, TemplateResult, html } from "lit"; import { customElement, property } from "lit/decorators.js"; -import PFBanner from "@patternfly/patternfly/components/Banner/banner.css"; import PFButton from "@patternfly/patternfly/components/Button/button.css"; import PFBase from "@patternfly/patternfly/patternfly-base.css"; @@ -53,17 +52,13 @@ export class ObjectPermissionModal extends AKElement { objectPk?: string | number; static get styles(): CSSResult[] { - return [PFBase, PFButton, PFBanner]; + return [PFBase, PFButton]; } render(): TemplateResult { return html` ${msg("Update Permissions")} -
- ${msg("RBAC is in preview.")} - ${msg("Send us feedback!")} -
- ${msg("RBAC is in preview.")} - ${msg("Send us feedback!")} -
` + return html` + ${this.model === RbacPermissionsAssignedByUsersListModelEnum.CoreUser + ? this.renderCoreUser() : nothing} - - ${this.model === RbacPermissionsAssignedByUsersListModelEnum.CoreUser - ? this.renderCoreUser() - : nothing} - ${this.model === RbacPermissionsAssignedByUsersListModelEnum.RbacRole - ? this.renderRbacRole() - : nothing} -
-
-
-
${msg("User Object Permissions")}
-
- ${msg("Permissions set on users which affect this object.")} -
-
- - -
+ ${this.model === RbacPermissionsAssignedByUsersListModelEnum.RbacRole + ? this.renderRbacRole() + : nothing} +
+
+
+
${msg("User Object Permissions")}
+
+ ${msg("Permissions set on users which affect this object.")} +
+
+ +
-
-
-
-
-
${msg("Role Object Permissions")}
-
- ${msg("Permissions set on roles which affect this object.")} -
-
- - -
+
+
+
+
+
+
${msg("Role Object Permissions")}
+
+ ${msg("Permissions set on roles which affect this object.")} +
+
+ +
-
- `; +
+
+
`; } renderCoreUser() { diff --git a/web/src/admin/roles/RoleListPage.ts b/web/src/admin/roles/RoleListPage.ts index 98f156cb93..fd68f93c0d 100644 --- a/web/src/admin/roles/RoleListPage.ts +++ b/web/src/admin/roles/RoleListPage.ts @@ -9,12 +9,10 @@ import { TablePage } from "@goauthentik/elements/table/TablePage"; import "@patternfly/elements/pf-tooltip/pf-tooltip.js"; import { msg } from "@lit/localize"; -import { CSSResult, TemplateResult, html } from "lit"; +import { TemplateResult, html } from "lit"; import { customElement, property } from "lit/decorators.js"; import { ifDefined } from "lit/directives/if-defined.js"; -import PFBanner from "@patternfly/patternfly/components/Banner/banner.css"; - import { RbacApi, Role } from "@goauthentik/api"; @customElement("ak-role-list") @@ -37,10 +35,6 @@ export class RoleListPage extends TablePage { @property() order = "name"; - static get styles(): CSSResult[] { - return [...super.styles, PFBanner]; - } - async apiEndpoint(): Promise> { return new RbacApi(DEFAULT_CONFIG).rbacRolesList(await this.defaultEndpointConfig()); } @@ -78,10 +72,6 @@ export class RoleListPage extends TablePage { description=${ifDefined(this.pageDescription())} > -
- ${msg("RBAC is in preview.")} - ${msg("Send us feedback!")} -
${this.renderTable()}
`; diff --git a/web/src/admin/sources/kerberos/KerberosSourceViewPage.ts b/web/src/admin/sources/kerberos/KerberosSourceViewPage.ts index 94a7cebd41..a095764535 100644 --- a/web/src/admin/sources/kerberos/KerberosSourceViewPage.ts +++ b/web/src/admin/sources/kerberos/KerberosSourceViewPage.ts @@ -18,6 +18,7 @@ import { msg } from "@lit/localize"; import { CSSResult, TemplateResult, html } from "lit"; import { customElement, property, state } from "lit/decorators.js"; +import PFBanner from "@patternfly/patternfly/components/Banner/banner.css"; import PFButton from "@patternfly/patternfly/components/Button/button.css"; import PFCard from "@patternfly/patternfly/components/Card/card.css"; import PFContent from "@patternfly/patternfly/components/Content/content.css"; @@ -54,7 +55,17 @@ export class KerberosSourceViewPage extends AKElement { syncState?: SyncStatus; static get styles(): CSSResult[] { - return [PFBase, PFPage, PFButton, PFGrid, PFContent, PFCard, PFDescriptionList, PFList]; + return [ + PFBase, + PFPage, + PFButton, + PFGrid, + PFContent, + PFCard, + PFDescriptionList, + PFBanner, + PFList, + ]; } constructor() { @@ -121,6 +132,12 @@ export class KerberosSourceViewPage extends AKElement { this.load(); }} > +
+ ${msg("Kerberos Source is in preview.")} + ${msg("Send us feedback!")} +
diff --git a/web/src/admin/stages/authenticator_endpoint_gdtc/AuthenticatorEndpointGDTCStageForm.ts b/web/src/admin/stages/authenticator_endpoint_gdtc/AuthenticatorEndpointGDTCStageForm.ts index 414e42d147..fbe8a9a852 100644 --- a/web/src/admin/stages/authenticator_endpoint_gdtc/AuthenticatorEndpointGDTCStageForm.ts +++ b/web/src/admin/stages/authenticator_endpoint_gdtc/AuthenticatorEndpointGDTCStageForm.ts @@ -10,6 +10,8 @@ import { msg } from "@lit/localize"; import { TemplateResult, html } from "lit"; import { customElement } from "lit/decorators.js"; +import PFBanner from "@patternfly/patternfly/components/Banner/banner.css"; + import { AuthenticatorEndpointGDTCStage, StagesApi } from "@goauthentik/api"; @customElement("ak-stage-authenticator-endpoint-gdtc-form") @@ -33,8 +35,16 @@ export class AuthenticatorEndpointGDTCStageForm extends BaseStageForm + return html`
+ ${msg("Endpoint Google Chrome Device Trust is in preview.")} + ${msg("Send us feedback!")} +
+ ${msg( "Stage used to verify users' browsers using Google Chrome Device Trust. This stage can be used in authentication/authorization flows.", )} diff --git a/web/src/admin/stages/identification/IdentificationStageForm.ts b/web/src/admin/stages/identification/IdentificationStageForm.ts index 6a9c65de08..7a20af84d6 100644 --- a/web/src/admin/stages/identification/IdentificationStageForm.ts +++ b/web/src/admin/stages/identification/IdentificationStageForm.ts @@ -21,6 +21,7 @@ import { SourcesApi, Stage, StagesApi, + StagesCaptchaListRequest, StagesPasswordListRequest, UserFieldsEnum, } from "@goauthentik/api"; @@ -140,19 +141,13 @@ export class IdentificationStageForm extends BaseStageForm ).stagesPasswordList(args); return stages.results; }} - .groupBy=${(items: Stage[]) => { - return groupBy(items, (stage) => stage.verboseNamePlural); - }} - .renderElement=${(stage: Stage): string => { - return stage.name; - }} - .value=${(stage: Stage | undefined): string | undefined => { - return stage?.pk; - }} - .selected=${(stage: Stage): boolean => { - return stage.pk === this.instance?.passwordStage; - }} - ?blankable=${true} + .groupBy=${(items: Stage[]) => + groupBy(items, (stage) => stage.verboseNamePlural)} + .renderElement=${(stage: Stage): string => stage.name} + .value=${(stage: Stage | undefined): string | undefined => stage?.pk} + .selected=${(stage: Stage): boolean => + stage.pk === this.instance?.passwordStage} + blankable >

@@ -161,6 +156,35 @@ export class IdentificationStageForm extends BaseStageForm )}

+ + => { + const args: StagesCaptchaListRequest = { + ordering: "name", + }; + if (query !== undefined) { + args.search = query; + } + const stages = await new StagesApi( + DEFAULT_CONFIG, + ).stagesCaptchaList(args); + return stages.results; + }} + .groupBy=${(items: Stage[]) => + groupBy(items, (stage) => stage.verboseNamePlural)} + .renderElement=${(stage: Stage): string => stage.name} + .value=${(stage: Stage | undefined): string | undefined => stage?.pk} + .selected=${(stage: Stage): boolean => + stage.pk === this.instance?.captchaStage} + blankable + > + +

+ ${msg( + "When set, adds functionality exactly like a Captcha stage, but baked into the Identification stage.", + )} +

+