Compare commits
10 Commits
version/20
...
version-20
| Author | SHA1 | Date | |
|---|---|---|---|
| e095e9f694 | |||
| 10e311534f | |||
| 46fdb45273 | |||
| 6d4125cb90 | |||
| bc83176962 | |||
| 0fa8432b72 | |||
| bb9a524b53 | |||
| d31c05625b | |||
| 399223b770 | |||
| 19197d3f9b |
@ -1,5 +1,5 @@
|
|||||||
[bumpversion]
|
[bumpversion]
|
||||||
current_version = 2023.10.6
|
current_version = 2023.10.7
|
||||||
tag = True
|
tag = True
|
||||||
commit = True
|
commit = True
|
||||||
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)
|
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
from os import environ
|
from os import environ
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
__version__ = "2023.10.6"
|
__version__ = "2023.10.7"
|
||||||
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"
|
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -233,9 +233,9 @@ class UserSelfSerializer(ModelSerializer):
|
|||||||
def get_system_permissions(self, user: User) -> list[str]:
|
def get_system_permissions(self, user: User) -> list[str]:
|
||||||
"""Get all system permissions assigned to the user"""
|
"""Get all system permissions assigned to the user"""
|
||||||
return list(
|
return list(
|
||||||
user.user_permissions.filter(
|
x.split(".", maxsplit=1)[1]
|
||||||
content_type__app_label="authentik_rbac", content_type__model="systempermission"
|
for x in user.get_all_permissions()
|
||||||
).values_list("codename", flat=True)
|
if x.startswith("authentik_rbac")
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|||||||
@ -22,8 +22,9 @@ class TestTokenPKCE(OAuthTestCase):
|
|||||||
self.factory = RequestFactory()
|
self.factory = RequestFactory()
|
||||||
self.app = Application.objects.create(name=generate_id(), slug="test")
|
self.app = Application.objects.create(name=generate_id(), slug="test")
|
||||||
|
|
||||||
def test_pkce_missing_in_token(self):
|
def test_pkce_missing_in_authorize(self):
|
||||||
"""Test full with pkce"""
|
"""Test PKCE with code_challenge in authorize request
|
||||||
|
and missing verifier in token request"""
|
||||||
flow = create_test_flow()
|
flow = create_test_flow()
|
||||||
provider = OAuth2Provider.objects.create(
|
provider = OAuth2Provider.objects.create(
|
||||||
name=generate_id(),
|
name=generate_id(),
|
||||||
@ -74,7 +75,77 @@ class TestTokenPKCE(OAuthTestCase):
|
|||||||
)
|
)
|
||||||
self.assertJSONEqual(
|
self.assertJSONEqual(
|
||||||
response.content,
|
response.content,
|
||||||
{"error": "invalid_request", "error_description": "The request is otherwise malformed"},
|
{
|
||||||
|
"error": "invalid_grant",
|
||||||
|
"error_description": (
|
||||||
|
"The provided authorization grant or refresh token is invalid, expired, "
|
||||||
|
"revoked, does not match the redirection URI used in the authorization "
|
||||||
|
"request, or was issued to another client"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, 400)
|
||||||
|
|
||||||
|
def test_pkce_missing_in_token(self):
|
||||||
|
"""Test PKCE with missing code_challenge in authorization request but verifier
|
||||||
|
set in token request"""
|
||||||
|
flow = create_test_flow()
|
||||||
|
provider = OAuth2Provider.objects.create(
|
||||||
|
name=generate_id(),
|
||||||
|
client_id="test",
|
||||||
|
authorization_flow=flow,
|
||||||
|
redirect_uris="foo://localhost",
|
||||||
|
access_code_validity="seconds=100",
|
||||||
|
)
|
||||||
|
Application.objects.create(name="app", slug="app", provider=provider)
|
||||||
|
state = generate_id()
|
||||||
|
user = create_test_admin_user()
|
||||||
|
self.client.force_login(user)
|
||||||
|
header = b64encode(f"{provider.client_id}:{provider.client_secret}".encode()).decode()
|
||||||
|
# Step 1, initiate params and get redirect to flow
|
||||||
|
self.client.get(
|
||||||
|
reverse("authentik_providers_oauth2:authorize"),
|
||||||
|
data={
|
||||||
|
"response_type": "code",
|
||||||
|
"client_id": "test",
|
||||||
|
"state": state,
|
||||||
|
"redirect_uri": "foo://localhost",
|
||||||
|
# "code_challenge": challenge,
|
||||||
|
# "code_challenge_method": "S256",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
response = self.client.get(
|
||||||
|
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
|
||||||
|
)
|
||||||
|
code: AuthorizationCode = AuthorizationCode.objects.filter(user=user).first()
|
||||||
|
self.assertJSONEqual(
|
||||||
|
response.content.decode(),
|
||||||
|
{
|
||||||
|
"component": "xak-flow-redirect",
|
||||||
|
"type": ChallengeTypes.REDIRECT.value,
|
||||||
|
"to": f"foo://localhost?code={code.code}&state={state}",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("authentik_providers_oauth2:token"),
|
||||||
|
data={
|
||||||
|
"grant_type": GRANT_TYPE_AUTHORIZATION_CODE,
|
||||||
|
"code": code.code,
|
||||||
|
"code_verifier": generate_id(),
|
||||||
|
"redirect_uri": "foo://localhost",
|
||||||
|
},
|
||||||
|
HTTP_AUTHORIZATION=f"Basic {header}",
|
||||||
|
)
|
||||||
|
self.assertJSONEqual(
|
||||||
|
response.content,
|
||||||
|
{
|
||||||
|
"error": "invalid_grant",
|
||||||
|
"error_description": (
|
||||||
|
"The provided authorization grant or refresh token is invalid, expired, "
|
||||||
|
"revoked, does not match the redirection URI used in the authorization "
|
||||||
|
"request, or was issued to another client"
|
||||||
|
),
|
||||||
|
},
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, 400)
|
self.assertEqual(response.status_code, 400)
|
||||||
|
|
||||||
|
|||||||
@ -231,7 +231,7 @@ class TokenParams:
|
|||||||
if self.authorization_code.code_challenge:
|
if self.authorization_code.code_challenge:
|
||||||
# Authorization code had PKCE but we didn't get one
|
# Authorization code had PKCE but we didn't get one
|
||||||
if not self.code_verifier:
|
if not self.code_verifier:
|
||||||
raise TokenError("invalid_request")
|
raise TokenError("invalid_grant")
|
||||||
if self.authorization_code.code_challenge_method == PKCE_METHOD_S256:
|
if self.authorization_code.code_challenge_method == PKCE_METHOD_S256:
|
||||||
new_code_challenge = (
|
new_code_challenge = (
|
||||||
urlsafe_b64encode(sha256(self.code_verifier.encode("ascii")).digest())
|
urlsafe_b64encode(sha256(self.code_verifier.encode("ascii")).digest())
|
||||||
@ -244,6 +244,10 @@ class TokenParams:
|
|||||||
if new_code_challenge != self.authorization_code.code_challenge:
|
if new_code_challenge != self.authorization_code.code_challenge:
|
||||||
LOGGER.warning("Code challenge not matching")
|
LOGGER.warning("Code challenge not matching")
|
||||||
raise TokenError("invalid_grant")
|
raise TokenError("invalid_grant")
|
||||||
|
# Token request had a code_verifier but code did not have a code challenge
|
||||||
|
# Prevent downgrade
|
||||||
|
if not self.authorization_code.code_challenge and self.code_verifier:
|
||||||
|
raise TokenError("invalid_grant")
|
||||||
|
|
||||||
def __post_init_refresh(self, raw_token: str, request: HttpRequest):
|
def __post_init_refresh(self, raw_token: str, request: HttpRequest):
|
||||||
if not raw_token:
|
if not raw_token:
|
||||||
|
|||||||
@ -56,6 +56,7 @@ class OAuthSourceSerializer(SourceSerializer):
|
|||||||
"""Get source's type configuration"""
|
"""Get source's type configuration"""
|
||||||
return SourceTypeSerializer(instance.source_type).data
|
return SourceTypeSerializer(instance.source_type).data
|
||||||
|
|
||||||
|
# pylint: disable=too-many-locals
|
||||||
def validate(self, attrs: dict) -> dict:
|
def validate(self, attrs: dict) -> dict:
|
||||||
session = get_http_session()
|
session = get_http_session()
|
||||||
source_type = registry.find_type(attrs["provider_type"])
|
source_type = registry.find_type(attrs["provider_type"])
|
||||||
@ -73,9 +74,17 @@ class OAuthSourceSerializer(SourceSerializer):
|
|||||||
config = well_known_config.json()
|
config = well_known_config.json()
|
||||||
if "issuer" not in config:
|
if "issuer" not in config:
|
||||||
raise ValidationError({"oidc_well_known_url": "Invalid well-known configuration"})
|
raise ValidationError({"oidc_well_known_url": "Invalid well-known configuration"})
|
||||||
attrs["authorization_url"] = config.get("authorization_endpoint", "")
|
field_map = {
|
||||||
attrs["access_token_url"] = config.get("token_endpoint", "")
|
# authentik field to oidc field
|
||||||
attrs["profile_url"] = config.get("userinfo_endpoint", "")
|
"authorization_url": "authorization_endpoint",
|
||||||
|
"access_token_url": "token_endpoint",
|
||||||
|
"profile_url": "userinfo_endpoint",
|
||||||
|
}
|
||||||
|
for ak_key, oidc_key in field_map.items():
|
||||||
|
# Don't overwrite user-set values
|
||||||
|
if ak_key in attrs and attrs[ak_key]:
|
||||||
|
continue
|
||||||
|
attrs[ak_key] = config.get(oidc_key, "")
|
||||||
inferred_oidc_jwks_url = config.get("jwks_uri", "")
|
inferred_oidc_jwks_url = config.get("jwks_uri", "")
|
||||||
|
|
||||||
# Prefer user-entered URL to inferred URL to default URL
|
# Prefer user-entered URL to inferred URL to default URL
|
||||||
|
|||||||
@ -44,3 +44,7 @@ class TestTypeAzureAD(TestCase):
|
|||||||
self.assertEqual(ak_context["username"], AAD_USER["userPrincipalName"])
|
self.assertEqual(ak_context["username"], AAD_USER["userPrincipalName"])
|
||||||
self.assertEqual(ak_context["email"], AAD_USER["mail"])
|
self.assertEqual(ak_context["email"], AAD_USER["mail"])
|
||||||
self.assertEqual(ak_context["name"], AAD_USER["displayName"])
|
self.assertEqual(ak_context["name"], AAD_USER["displayName"])
|
||||||
|
|
||||||
|
def test_user_id(self):
|
||||||
|
"""Test azure AD user ID"""
|
||||||
|
self.assertEqual(AzureADOAuthCallback().get_user_id(AAD_USER), AAD_USER["id"])
|
||||||
|
|||||||
@ -69,9 +69,6 @@ class TestOAuthSource(TestCase):
|
|||||||
"provider_type": "openidconnect",
|
"provider_type": "openidconnect",
|
||||||
"consumer_key": "foo",
|
"consumer_key": "foo",
|
||||||
"consumer_secret": "foo",
|
"consumer_secret": "foo",
|
||||||
"authorization_url": "http://foo",
|
|
||||||
"access_token_url": "http://foo",
|
|
||||||
"profile_url": "http://foo",
|
|
||||||
"oidc_well_known_url": url,
|
"oidc_well_known_url": url,
|
||||||
"oidc_jwks_url": "",
|
"oidc_jwks_url": "",
|
||||||
},
|
},
|
||||||
|
|||||||
@ -25,6 +25,11 @@ class AzureADOAuthCallback(OpenIDConnectOAuth2Callback):
|
|||||||
|
|
||||||
client_class = UserprofileHeaderAuthClient
|
client_class = UserprofileHeaderAuthClient
|
||||||
|
|
||||||
|
def get_user_id(self, info: dict[str, str]) -> str:
|
||||||
|
# Default try to get `id` for the Graph API endpoint
|
||||||
|
# fallback to OpenID logic in case the profile URL was changed
|
||||||
|
return info.get("id", super().get_user_id(info))
|
||||||
|
|
||||||
def get_user_enroll_context(
|
def get_user_enroll_context(
|
||||||
self,
|
self,
|
||||||
info: dict[str, Any],
|
info: dict[str, Any],
|
||||||
@ -50,7 +55,7 @@ class AzureADType(SourceType):
|
|||||||
|
|
||||||
authorization_url = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize"
|
authorization_url = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize"
|
||||||
access_token_url = "https://login.microsoftonline.com/common/oauth2/v2.0/token" # nosec
|
access_token_url = "https://login.microsoftonline.com/common/oauth2/v2.0/token" # nosec
|
||||||
profile_url = "https://login.microsoftonline.com/common/openid/userinfo"
|
profile_url = "https://graph.microsoft.com/v1.0/me"
|
||||||
oidc_well_known_url = (
|
oidc_well_known_url = (
|
||||||
"https://login.microsoftonline.com/common/.well-known/openid-configuration"
|
"https://login.microsoftonline.com/common/.well-known/openid-configuration"
|
||||||
)
|
)
|
||||||
|
|||||||
@ -38,4 +38,11 @@ class Migration(migrations.Migration):
|
|||||||
name="statictoken",
|
name="statictoken",
|
||||||
options={"verbose_name": "Static Token", "verbose_name_plural": "Static Tokens"},
|
options={"verbose_name": "Static Token", "verbose_name_plural": "Static Tokens"},
|
||||||
),
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name="authenticatorstaticstage",
|
||||||
|
options={
|
||||||
|
"verbose_name": "Static Authenticator Setup Stage",
|
||||||
|
"verbose_name_plural": "Static Authenticator Setup Stages",
|
||||||
|
},
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@ -46,11 +46,11 @@ class AuthenticatorStaticStage(ConfigurableStage, FriendlyNamedStage, Stage):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return f"Static Authenticator Stage {self.name}"
|
return f"Static Authenticator Setup Stage {self.name}"
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("Static Authenticator Stage")
|
verbose_name = _("Static Authenticator Setup Stage")
|
||||||
verbose_name_plural = _("Static Authenticator Stages")
|
verbose_name_plural = _("Static Authenticator Setup Stages")
|
||||||
|
|
||||||
|
|
||||||
class StaticDevice(SerializerModel, ThrottlingMixin, Device):
|
class StaticDevice(SerializerModel, ThrottlingMixin, Device):
|
||||||
|
|||||||
@ -300,8 +300,10 @@ class AuthenticatorValidateStageView(ChallengeStageView):
|
|||||||
serializer = SelectableStageSerializer(
|
serializer = SelectableStageSerializer(
|
||||||
data={
|
data={
|
||||||
"pk": stage.pk,
|
"pk": stage.pk,
|
||||||
"name": stage.name,
|
"name": stage.friendly_name or stage.name,
|
||||||
"verbose_name": str(stage._meta.verbose_name),
|
"verbose_name": str(stage._meta.verbose_name)
|
||||||
|
.replace("Setup Stage", "")
|
||||||
|
.strip(),
|
||||||
"meta_model_name": f"{stage._meta.app_label}.{stage._meta.model_name}",
|
"meta_model_name": f"{stage._meta.app_label}.{stage._meta.model_name}",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@ -11,6 +11,7 @@ from authentik.flows.tests import FlowTestCase
|
|||||||
from authentik.flows.views.executor import SESSION_KEY_PLAN
|
from authentik.flows.views.executor import SESSION_KEY_PLAN
|
||||||
from authentik.lib.generators import generate_id, generate_key
|
from authentik.lib.generators import generate_id, generate_key
|
||||||
from authentik.stages.authenticator_duo.models import AuthenticatorDuoStage, DuoDevice
|
from authentik.stages.authenticator_duo.models import AuthenticatorDuoStage, DuoDevice
|
||||||
|
from authentik.stages.authenticator_static.models import AuthenticatorStaticStage
|
||||||
from authentik.stages.authenticator_validate.api import AuthenticatorValidateStageSerializer
|
from authentik.stages.authenticator_validate.api import AuthenticatorValidateStageSerializer
|
||||||
from authentik.stages.authenticator_validate.models import AuthenticatorValidateStage, DeviceClasses
|
from authentik.stages.authenticator_validate.models import AuthenticatorValidateStage, DeviceClasses
|
||||||
from authentik.stages.authenticator_validate.stage import PLAN_CONTEXT_DEVICE_CHALLENGES
|
from authentik.stages.authenticator_validate.stage import PLAN_CONTEXT_DEVICE_CHALLENGES
|
||||||
@ -26,19 +27,22 @@ class AuthenticatorValidateStageTests(FlowTestCase):
|
|||||||
|
|
||||||
def test_not_configured_action(self):
|
def test_not_configured_action(self):
|
||||||
"""Test not_configured_action"""
|
"""Test not_configured_action"""
|
||||||
conf_stage = IdentificationStage.objects.create(
|
ident_stage = IdentificationStage.objects.create(
|
||||||
name=generate_id(),
|
name=generate_id(),
|
||||||
user_fields=[
|
user_fields=[
|
||||||
UserFields.USERNAME,
|
UserFields.USERNAME,
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
conf_stage = AuthenticatorStaticStage.objects.create(
|
||||||
|
name=generate_id(),
|
||||||
|
)
|
||||||
stage = AuthenticatorValidateStage.objects.create(
|
stage = AuthenticatorValidateStage.objects.create(
|
||||||
name=generate_id(),
|
name=generate_id(),
|
||||||
not_configured_action=NotConfiguredAction.CONFIGURE,
|
not_configured_action=NotConfiguredAction.CONFIGURE,
|
||||||
)
|
)
|
||||||
stage.configuration_stages.set([conf_stage])
|
stage.configuration_stages.set([conf_stage])
|
||||||
flow = create_test_flow()
|
flow = create_test_flow()
|
||||||
FlowStageBinding.objects.create(target=flow, stage=conf_stage, order=0)
|
FlowStageBinding.objects.create(target=flow, stage=ident_stage, order=0)
|
||||||
FlowStageBinding.objects.create(target=flow, stage=stage, order=1)
|
FlowStageBinding.objects.create(target=flow, stage=stage, order=1)
|
||||||
|
|
||||||
response = self.client.get(
|
response = self.client.get(
|
||||||
@ -57,27 +61,22 @@ class AuthenticatorValidateStageTests(FlowTestCase):
|
|||||||
self.assertStageResponse(
|
self.assertStageResponse(
|
||||||
response,
|
response,
|
||||||
flow,
|
flow,
|
||||||
component="ak-stage-identification",
|
component="ak-stage-authenticator-static",
|
||||||
password_fields=False,
|
|
||||||
primary_action="Continue",
|
|
||||||
user_fields=["username"],
|
|
||||||
sources=[],
|
|
||||||
show_source_labels=False,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_not_configured_action_multiple(self):
|
def test_not_configured_action_multiple(self):
|
||||||
"""Test not_configured_action"""
|
"""Test not_configured_action"""
|
||||||
conf_stage = IdentificationStage.objects.create(
|
ident_stage = IdentificationStage.objects.create(
|
||||||
name=generate_id(),
|
name=generate_id(),
|
||||||
user_fields=[
|
user_fields=[
|
||||||
UserFields.USERNAME,
|
UserFields.USERNAME,
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
conf_stage2 = IdentificationStage.objects.create(
|
conf_stage = AuthenticatorStaticStage.objects.create(
|
||||||
|
name=generate_id(),
|
||||||
|
)
|
||||||
|
conf_stage2 = AuthenticatorStaticStage.objects.create(
|
||||||
name=generate_id(),
|
name=generate_id(),
|
||||||
user_fields=[
|
|
||||||
UserFields.USERNAME,
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
stage = AuthenticatorValidateStage.objects.create(
|
stage = AuthenticatorValidateStage.objects.create(
|
||||||
name=generate_id(),
|
name=generate_id(),
|
||||||
@ -85,7 +84,7 @@ class AuthenticatorValidateStageTests(FlowTestCase):
|
|||||||
)
|
)
|
||||||
stage.configuration_stages.set([conf_stage, conf_stage2])
|
stage.configuration_stages.set([conf_stage, conf_stage2])
|
||||||
flow = create_test_flow()
|
flow = create_test_flow()
|
||||||
FlowStageBinding.objects.create(target=flow, stage=conf_stage, order=0)
|
FlowStageBinding.objects.create(target=flow, stage=ident_stage, order=0)
|
||||||
FlowStageBinding.objects.create(target=flow, stage=stage, order=1)
|
FlowStageBinding.objects.create(target=flow, stage=stage, order=1)
|
||||||
|
|
||||||
# Get initial identification stage
|
# Get initial identification stage
|
||||||
@ -118,12 +117,7 @@ class AuthenticatorValidateStageTests(FlowTestCase):
|
|||||||
self.assertStageResponse(
|
self.assertStageResponse(
|
||||||
response,
|
response,
|
||||||
flow,
|
flow,
|
||||||
component="ak-stage-identification",
|
component="ak-stage-authenticator-static",
|
||||||
password_fields=False,
|
|
||||||
primary_action="Continue",
|
|
||||||
user_fields=["username"],
|
|
||||||
sources=[],
|
|
||||||
show_source_labels=False,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_stage_validation(self):
|
def test_stage_validation(self):
|
||||||
|
|||||||
@ -14,6 +14,7 @@ entries:
|
|||||||
- attrs:
|
- attrs:
|
||||||
configure_flow: !KeyOf flow
|
configure_flow: !KeyOf flow
|
||||||
token_count: 6
|
token_count: 6
|
||||||
|
friendly_name: Static tokens
|
||||||
identifiers:
|
identifiers:
|
||||||
name: default-authenticator-static-setup
|
name: default-authenticator-static-setup
|
||||||
id: default-authenticator-static-setup
|
id: default-authenticator-static-setup
|
||||||
|
|||||||
@ -14,6 +14,7 @@ entries:
|
|||||||
- attrs:
|
- attrs:
|
||||||
configure_flow: !KeyOf flow
|
configure_flow: !KeyOf flow
|
||||||
digits: 6
|
digits: 6
|
||||||
|
friendly_name: TOTP Device
|
||||||
identifiers:
|
identifiers:
|
||||||
name: default-authenticator-totp-setup
|
name: default-authenticator-totp-setup
|
||||||
id: default-authenticator-totp-setup
|
id: default-authenticator-totp-setup
|
||||||
|
|||||||
@ -13,6 +13,7 @@ entries:
|
|||||||
id: flow
|
id: flow
|
||||||
- attrs:
|
- attrs:
|
||||||
configure_flow: !KeyOf flow
|
configure_flow: !KeyOf flow
|
||||||
|
friendly_name: WebAuthn device
|
||||||
identifiers:
|
identifiers:
|
||||||
name: default-authenticator-webauthn-setup
|
name: default-authenticator-webauthn-setup
|
||||||
id: default-authenticator-webauthn-setup
|
id: default-authenticator-webauthn-setup
|
||||||
|
|||||||
@ -32,7 +32,7 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- redis:/data
|
- redis:/data
|
||||||
server:
|
server:
|
||||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2023.10.6}
|
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2023.10.7}
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
command: server
|
command: server
|
||||||
environment:
|
environment:
|
||||||
@ -53,7 +53,7 @@ services:
|
|||||||
- postgresql
|
- postgresql
|
||||||
- redis
|
- redis
|
||||||
worker:
|
worker:
|
||||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2023.10.6}
|
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2023.10.7}
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
command: worker
|
command: worker
|
||||||
environment:
|
environment:
|
||||||
|
|||||||
2
go.mod
2
go.mod
@ -4,7 +4,6 @@ go 1.21
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
beryju.io/ldap v0.1.0
|
beryju.io/ldap v0.1.0
|
||||||
github.com/Netflix/go-env v0.0.0-20210215222557-e437a7e7f9fb
|
|
||||||
github.com/coreos/go-oidc v2.2.1+incompatible
|
github.com/coreos/go-oidc v2.2.1+incompatible
|
||||||
github.com/getsentry/sentry-go v0.25.0
|
github.com/getsentry/sentry-go v0.25.0
|
||||||
github.com/go-http-utils/etag v0.0.0-20161124023236-513ea8f21eb1
|
github.com/go-http-utils/etag v0.0.0-20161124023236-513ea8f21eb1
|
||||||
@ -24,6 +23,7 @@ require (
|
|||||||
github.com/pires/go-proxyproto v0.7.0
|
github.com/pires/go-proxyproto v0.7.0
|
||||||
github.com/prometheus/client_golang v1.17.0
|
github.com/prometheus/client_golang v1.17.0
|
||||||
github.com/redis/go-redis/v9 v9.2.1
|
github.com/redis/go-redis/v9 v9.2.1
|
||||||
|
github.com/sethvargo/go-envconfig v1.0.0
|
||||||
github.com/sirupsen/logrus v1.9.3
|
github.com/sirupsen/logrus v1.9.3
|
||||||
github.com/spf13/cobra v1.7.0
|
github.com/spf13/cobra v1.7.0
|
||||||
github.com/stretchr/testify v1.8.4
|
github.com/stretchr/testify v1.8.4
|
||||||
|
|||||||
8
go.sum
8
go.sum
@ -37,8 +37,6 @@ github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+
|
|||||||
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
|
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||||
github.com/Netflix/go-env v0.0.0-20210215222557-e437a7e7f9fb h1:w9IDEB7P1VzNcBpOG7kMpFkZp2DkyJIUt0gDx5MBhRU=
|
|
||||||
github.com/Netflix/go-env v0.0.0-20210215222557-e437a7e7f9fb/go.mod h1:9XMFaCeRyW7fC9XJOWQ+NdAv8VLG7ys7l3x4ozEGLUQ=
|
|
||||||
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||||
github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 h1:Kk6a4nehpJ3UuJRqlA3JxYxBZEqCeOmATOvrbT4p9RA=
|
github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 h1:Kk6a4nehpJ3UuJRqlA3JxYxBZEqCeOmATOvrbT4p9RA=
|
||||||
@ -198,8 +196,8 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
|||||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||||
@ -303,6 +301,8 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR
|
|||||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
|
github.com/sethvargo/go-envconfig v1.0.0 h1:1C66wzy4QrROf5ew4KdVw942CQDa55qmlYmw9FZxZdU=
|
||||||
|
github.com/sethvargo/go-envconfig v1.0.0/go.mod h1:Lzc75ghUn5ucmcRGIdGQ33DKJrcjk4kihFYgSTBmjIc=
|
||||||
github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -10,10 +11,11 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
env "github.com/Netflix/go-env"
|
env "github.com/sethvargo/go-envconfig"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"goauthentik.io/authentik/lib"
|
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
|
|
||||||
|
"goauthentik.io/authentik/lib"
|
||||||
)
|
)
|
||||||
|
|
||||||
var cfg *Config
|
var cfg *Config
|
||||||
@ -113,7 +115,8 @@ func (c *Config) LoadConfigFromFile(path string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) fromEnv() error {
|
func (c *Config) fromEnv() error {
|
||||||
_, err := env.UnmarshalFromEnviron(c)
|
ctx := context.Background()
|
||||||
|
err := env.Process(ctx, c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to load environment variables: %w", err)
|
return fmt.Errorf("failed to load environment variables: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,17 +3,17 @@ package config
|
|||||||
type Config struct {
|
type Config struct {
|
||||||
// Core specific config
|
// Core specific config
|
||||||
Paths PathsConfig `yaml:"paths"`
|
Paths PathsConfig `yaml:"paths"`
|
||||||
LogLevel string `yaml:"log_level" env:"AUTHENTIK_LOG_LEVEL"`
|
LogLevel string `yaml:"log_level" env:"AUTHENTIK_LOG_LEVEL, overwrite"`
|
||||||
ErrorReporting ErrorReportingConfig `yaml:"error_reporting"`
|
ErrorReporting ErrorReportingConfig `yaml:"error_reporting" env:", prefix=AUTHENTIK_ERROR_REPORTING__"`
|
||||||
Redis RedisConfig `yaml:"redis"`
|
Redis RedisConfig `yaml:"redis" env:", prefix=AUTHENTIK_REDIS__"`
|
||||||
Outposts OutpostConfig `yaml:"outposts"`
|
Outposts OutpostConfig `yaml:"outposts" env:", prefix=AUTHENTIK_OUTPOSTS__"`
|
||||||
|
|
||||||
// Config for core and embedded outpost
|
// Config for core and embedded outpost
|
||||||
SecretKey string `yaml:"secret_key" env:"AUTHENTIK_SECRET_KEY"`
|
SecretKey string `yaml:"secret_key" env:"AUTHENTIK_SECRET_KEY, overwrite"`
|
||||||
|
|
||||||
// Config for both core and outposts
|
// Config for both core and outposts
|
||||||
Debug bool `yaml:"debug" env:"AUTHENTIK_DEBUG"`
|
Debug bool `yaml:"debug" env:"AUTHENTIK_DEBUG, overwrite"`
|
||||||
Listen ListenConfig `yaml:"listen"`
|
Listen ListenConfig `yaml:"listen" env:", prefix=AUTHENTIK_LISTEN__"`
|
||||||
|
|
||||||
// Outpost specific config
|
// Outpost specific config
|
||||||
// These are only relevant for proxy/ldap outposts, and cannot be set via YAML
|
// These are only relevant for proxy/ldap outposts, and cannot be set via YAML
|
||||||
@ -25,27 +25,28 @@ type Config struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type RedisConfig struct {
|
type RedisConfig struct {
|
||||||
Host string `yaml:"host" env:"AUTHENTIK_REDIS__HOST"`
|
Host string `yaml:"host" env:"HOST, overwrite"`
|
||||||
Port int `yaml:"port" env:"AUTHENTIK_REDIS__PORT"`
|
Port int `yaml:"port" env:"PORT, overwrite"`
|
||||||
Password string `yaml:"password" env:"AUTHENTIK_REDIS__PASSWORD"`
|
DB int `yaml:"db" env:"DB, overwrite"`
|
||||||
TLS bool `yaml:"tls" env:"AUTHENTIK_REDIS__TLS"`
|
Username string `yaml:"username" env:"USERNAME, overwrite"`
|
||||||
TLSReqs string `yaml:"tls_reqs" env:"AUTHENTIK_REDIS__TLS_REQS"`
|
Password string `yaml:"password" env:"PASSWORD, overwrite"`
|
||||||
DB int `yaml:"cache_db" env:"AUTHENTIK_REDIS__DB"`
|
TLS bool `yaml:"tls" env:"TLS, overwrite"`
|
||||||
CacheTimeout int `yaml:"cache_timeout" env:"AUTHENTIK_REDIS__CACHE_TIMEOUT"`
|
TLSReqs string `yaml:"tls_reqs" env:"TLS_REQS, overwrite"`
|
||||||
CacheTimeoutFlows int `yaml:"cache_timeout_flows" env:"AUTHENTIK_REDIS__CACHE_TIMEOUT_FLOWS"`
|
CacheTimeout int `yaml:"cache_timeout" env:"CACHE_TIMEOUT, overwrite"`
|
||||||
CacheTimeoutPolicies int `yaml:"cache_timeout_policies" env:"AUTHENTIK_REDIS__CACHE_TIMEOUT_POLICIES"`
|
CacheTimeoutFlows int `yaml:"cache_timeout_flows" env:"CACHE_TIMEOUT_FLOWS, overwrite"`
|
||||||
CacheTimeoutReputation int `yaml:"cache_timeout_reputation" env:"AUTHENTIK_REDIS__CACHE_TIMEOUT_REPUTATION"`
|
CacheTimeoutPolicies int `yaml:"cache_timeout_policies" env:"CACHE_TIMEOUT_POLICIES, overwrite"`
|
||||||
|
CacheTimeoutReputation int `yaml:"cache_timeout_reputation" env:"CACHE_TIMEOUT_REPUTATION, overwrite"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ListenConfig struct {
|
type ListenConfig struct {
|
||||||
HTTP string `yaml:"listen_http" env:"AUTHENTIK_LISTEN__HTTP"`
|
HTTP string `yaml:"listen_http" env:"HTTP, overwrite"`
|
||||||
HTTPS string `yaml:"listen_https" env:"AUTHENTIK_LISTEN__HTTPS"`
|
HTTPS string `yaml:"listen_https" env:"HTTPS, overwrite"`
|
||||||
LDAP string `yaml:"listen_ldap" env:"AUTHENTIK_LISTEN__LDAP"`
|
LDAP string `yaml:"listen_ldap" env:"LDAP, overwrite"`
|
||||||
LDAPS string `yaml:"listen_ldaps" env:"AUTHENTIK_LISTEN__LDAPS"`
|
LDAPS string `yaml:"listen_ldaps" env:"LDAPS, overwrite"`
|
||||||
Radius string `yaml:"listen_radius" env:"AUTHENTIK_LISTEN__RADIUS"`
|
Radius string `yaml:"listen_radius" env:"RADIUS, overwrite"`
|
||||||
Metrics string `yaml:"listen_metrics" env:"AUTHENTIK_LISTEN__METRICS"`
|
Metrics string `yaml:"listen_metrics" env:"METRICS, overwrite"`
|
||||||
Debug string `yaml:"listen_debug" env:"AUTHENTIK_LISTEN__DEBUG"`
|
Debug string `yaml:"listen_debug" env:"DEBUG, overwrite"`
|
||||||
TrustedProxyCIDRs []string `yaml:"trusted_proxy_cidrs" env:"AUTHENTIK_LISTEN__TRUSTED_PROXY_CIDRS"`
|
TrustedProxyCIDRs []string `yaml:"trusted_proxy_cidrs" env:"TRUSTED_PROXY_CIDRS, overwrite"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type PathsConfig struct {
|
type PathsConfig struct {
|
||||||
@ -53,15 +54,15 @@ type PathsConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ErrorReportingConfig struct {
|
type ErrorReportingConfig struct {
|
||||||
Enabled bool `yaml:"enabled" env:"AUTHENTIK_ERROR_REPORTING__ENABLED"`
|
Enabled bool `yaml:"enabled" env:"ENABLED, overwrite"`
|
||||||
SentryDSN string `yaml:"sentry_dsn" env:"AUTHENTIK_ERROR_REPORTING__SENTRY_DSN"`
|
SentryDSN string `yaml:"sentry_dsn" env:"SENTRY_DSN, overwrite"`
|
||||||
Environment string `yaml:"environment" env:"AUTHENTIK_ERROR_REPORTING__ENVIRONMENT"`
|
Environment string `yaml:"environment" env:"ENVIRONMENT, overwrite"`
|
||||||
SendPII bool `yaml:"send_pii" env:"AUTHENTIK_ERROR_REPORTING__SEND_PII"`
|
SendPII bool `yaml:"send_pii" env:"SEND_PII, overwrite"`
|
||||||
SampleRate float64 `yaml:"sample_rate" env:"AUTHENTIK_ERROR_REPORTING__SAMPLE_RATE"`
|
SampleRate float64 `yaml:"sample_rate" env:"SAMPLE_RATE, overwrite"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type OutpostConfig struct {
|
type OutpostConfig struct {
|
||||||
ContainerImageBase string `yaml:"container_image_base" env:"AUTHENTIK_OUTPOSTS__CONTAINER_IMAGE_BASE"`
|
ContainerImageBase string `yaml:"container_image_base" env:"CONTAINER_IMAGE_BASE, overwrite"`
|
||||||
Discover bool `yaml:"discover" env:"AUTHENTIK_OUTPOSTS__DISCOVER"`
|
Discover bool `yaml:"discover" env:"DISCOVER, overwrite"`
|
||||||
DisableEmbeddedOutpost bool `yaml:"disable_embedded_outpost" env:"AUTHENTIK_OUTPOSTS__DISABLE_EMBEDDED_OUTPOST"`
|
DisableEmbeddedOutpost bool `yaml:"disable_embedded_outpost" env:"DISABLE_EMBEDDED_OUTPOST, overwrite"`
|
||||||
}
|
}
|
||||||
|
|||||||
@ -29,4 +29,4 @@ func UserAgent() string {
|
|||||||
return fmt.Sprintf("authentik@%s", FullVersion())
|
return fmt.Sprintf("authentik@%s", FullVersion())
|
||||||
}
|
}
|
||||||
|
|
||||||
const VERSION = "2023.10.6"
|
const VERSION = "2023.10.7"
|
||||||
|
|||||||
@ -113,7 +113,7 @@ filterwarnings = [
|
|||||||
|
|
||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "authentik"
|
name = "authentik"
|
||||||
version = "2023.10.6"
|
version = "2023.10.7"
|
||||||
description = ""
|
description = ""
|
||||||
authors = ["authentik Team <hello@goauthentik.io>"]
|
authors = ["authentik Team <hello@goauthentik.io>"]
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
openapi: 3.0.3
|
openapi: 3.0.3
|
||||||
info:
|
info:
|
||||||
title: authentik
|
title: authentik
|
||||||
version: 2023.10.6
|
version: 2023.10.7
|
||||||
description: Making authentication simple.
|
description: Making authentication simple.
|
||||||
contact:
|
contact:
|
||||||
email: hello@goauthentik.io
|
email: hello@goauthentik.io
|
||||||
|
|||||||
@ -62,6 +62,7 @@ export class InvitationListPage extends TablePage<Invitation> {
|
|||||||
multipleEnrollmentFlows = false;
|
multipleEnrollmentFlows = false;
|
||||||
|
|
||||||
async apiEndpoint(page: number): Promise<PaginatedResponse<Invitation>> {
|
async apiEndpoint(page: number): Promise<PaginatedResponse<Invitation>> {
|
||||||
|
try {
|
||||||
// Check if any invitation stages exist
|
// Check if any invitation stages exist
|
||||||
const stages = await new StagesApi(DEFAULT_CONFIG).stagesInvitationStagesList({
|
const stages = await new StagesApi(DEFAULT_CONFIG).stagesInvitationStagesList({
|
||||||
noFlows: false,
|
noFlows: false,
|
||||||
@ -76,6 +77,9 @@ export class InvitationListPage extends TablePage<Invitation> {
|
|||||||
this.multipleEnrollmentFlows = true;
|
this.multipleEnrollmentFlows = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
} catch {
|
||||||
|
// assuming we can't fetch stages, ignore the error
|
||||||
|
}
|
||||||
return new StagesApi(DEFAULT_CONFIG).stagesInvitationInvitationsList({
|
return new StagesApi(DEFAULT_CONFIG).stagesInvitationInvitationsList({
|
||||||
ordering: this.order,
|
ordering: this.order,
|
||||||
page: page,
|
page: page,
|
||||||
|
|||||||
@ -3,7 +3,7 @@ export const SUCCESS_CLASS = "pf-m-success";
|
|||||||
export const ERROR_CLASS = "pf-m-danger";
|
export const ERROR_CLASS = "pf-m-danger";
|
||||||
export const PROGRESS_CLASS = "pf-m-in-progress";
|
export const PROGRESS_CLASS = "pf-m-in-progress";
|
||||||
export const CURRENT_CLASS = "pf-m-current";
|
export const CURRENT_CLASS = "pf-m-current";
|
||||||
export const VERSION = "2023.10.6";
|
export const VERSION = "2023.10.7";
|
||||||
export const TITLE_DEFAULT = "authentik";
|
export const TITLE_DEFAULT = "authentik";
|
||||||
export const ROUTE_SEPARATOR = ";";
|
export const ROUTE_SEPARATOR = ";";
|
||||||
|
|
||||||
|
|||||||
@ -257,7 +257,8 @@ select[multiple] option:checked {
|
|||||||
.pf-c-login__main-header-desc {
|
.pf-c-login__main-header-desc {
|
||||||
color: var(--ak-dark-foreground);
|
color: var(--ak-dark-foreground);
|
||||||
}
|
}
|
||||||
.pf-c-login__main-footer-links-item img {
|
.pf-c-login__main-footer-links-item img,
|
||||||
|
.pf-c-login__main-footer-links-item .fas {
|
||||||
filter: invert(1);
|
filter: invert(1);
|
||||||
}
|
}
|
||||||
.pf-c-login__main-footer-band {
|
.pf-c-login__main-footer-band {
|
||||||
|
|||||||
@ -86,7 +86,7 @@ To check if your config has been applied correctly, you can run the following co
|
|||||||
`AUTHENTIK_REDIS__CACHE_TIMEOUT_REPUTATION` only applies to the cache expiry, see [`AUTHENTIK_REPUTATION__EXPIRY`](#authentik_reputation__expiry) to control how long reputation is persisted for.
|
`AUTHENTIK_REDIS__CACHE_TIMEOUT_REPUTATION` only applies to the cache expiry, see [`AUTHENTIK_REPUTATION__EXPIRY`](#authentik_reputation__expiry) to control how long reputation is persisted for.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
## Listen Setting
|
## Listen Settings
|
||||||
|
|
||||||
- `AUTHENTIK_LISTEN__HTTP`: Listening address:port (e.g. `0.0.0.0:9000`) for HTTP (Applies to Server and Proxy outpost)
|
- `AUTHENTIK_LISTEN__HTTP`: Listening address:port (e.g. `0.0.0.0:9000`) for HTTP (Applies to Server and Proxy outpost)
|
||||||
- `AUTHENTIK_LISTEN__HTTPS`: Listening address:port (e.g. `0.0.0.0:9443`) for HTTPS (Applies to Server and Proxy outpost)
|
- `AUTHENTIK_LISTEN__HTTPS`: Listening address:port (e.g. `0.0.0.0:9443`) for HTTPS (Applies to Server and Proxy outpost)
|
||||||
@ -94,7 +94,7 @@ To check if your config has been applied correctly, you can run the following co
|
|||||||
- `AUTHENTIK_LISTEN__LDAPS`: Listening address:port (e.g. `0.0.0.0:6636`) for LDAPS (Applies to LDAP outpost)
|
- `AUTHENTIK_LISTEN__LDAPS`: Listening address:port (e.g. `0.0.0.0:6636`) for LDAPS (Applies to LDAP outpost)
|
||||||
- `AUTHENTIK_LISTEN__METRICS`: Listening address:port (e.g. `0.0.0.0:9300`) for Prometheus metrics (Applies to All)
|
- `AUTHENTIK_LISTEN__METRICS`: Listening address:port (e.g. `0.0.0.0:9300`) for Prometheus metrics (Applies to All)
|
||||||
- `AUTHENTIK_LISTEN__DEBUG`: Listening address:port (e.g. `0.0.0.0:9900`) for Go Debugging metrics (Applies to All)
|
- `AUTHENTIK_LISTEN__DEBUG`: Listening address:port (e.g. `0.0.0.0:9900`) for Go Debugging metrics (Applies to All)
|
||||||
- `AUTHENTIK_LISTEN__TRUSTED_PROXY_CIDRS`: List of CIDRs that proxy headers should be accepted from (Applies to Server)
|
- `AUTHENTIK_LISTEN__TRUSTED_PROXY_CIDRS`: List of comma-separated CIDRs that proxy headers should be accepted from (Applies to Server)
|
||||||
|
|
||||||
Defaults to `127.0.0.0/8`, `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`, `fe80::/10`, `::1/128`.
|
Defaults to `127.0.0.0/8`, `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`, `fe80::/10`, `::1/128`.
|
||||||
|
|
||||||
|
|||||||
27
website/docs/security/CVE-2024-23647.md
Normal file
27
website/docs/security/CVE-2024-23647.md
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
# CVE-2024-23647
|
||||||
|
|
||||||
|
_Reported by [@pieterphilippaerts](https://github.com/pieterphilippaerts)_
|
||||||
|
|
||||||
|
## PKCE downgrade attack in authentik
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
PKCE is a very important countermeasure in OAuth2 , both for public and confidential clients. It protects against CSRF attacks and code injection attacks. Because of this bug, an attacker can circumvent the protection PKCE offers.
|
||||||
|
|
||||||
|
## Patches
|
||||||
|
|
||||||
|
authentik 2023.8.7 and 2023.10.7 fix this issue.
|
||||||
|
|
||||||
|
## Details
|
||||||
|
|
||||||
|
There is a bug in our implementation of PKCE that allows an attacker to circumvent the protection that PKCE offers. PKCE adds the `code_challenge’ parameter to the authorization request and adds the `code_verifier’ parameter to the token request. We recently fixed a downgrade attack (in v2023.8.5 and 2023.10.4) where if the attacker removed the `code_verifier’ parameter in the token request, authentik would allow the request to pass, thus circumventing PKCE’s protection. However, in the latest version of the software, another downgrade scenario is still possible: if the attacker removes the `code_challenge’ parameter from the authorization request, authentik will also not do the PKCE check.
|
||||||
|
|
||||||
|
Note that this type of downgrade enables an attacker to perform a code injection attack, even if the OAuth client is using PKCE (which is supposed to protect against code injection attacks). To start the attack, the attacker must initiate the authorization process without that `code_challenge’ parameter in the authorization request. But this is easy to do (just use a phishing site or email to trick the user into clicking on a link that the attacker controls – the authorization link without that `code_challenge’ parameter).
|
||||||
|
|
||||||
|
The OAuth BCP (https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics) explicitly mentions this particular attack in section 2.1.1: “Authorization servers MUST mitigate PKCE Downgrade Attacks by ensuring that a token request containing a code_verifier parameter is accepted only if a code_challenge parameter was present in the authorization request, see Section 4.8.2 for details.”
|
||||||
|
|
||||||
|
## For more information
|
||||||
|
|
||||||
|
If you have any questions or comments about this advisory:
|
||||||
|
|
||||||
|
- Email us at [security@goauthentik.io](mailto:security@goauthentik.io)
|
||||||
@ -407,6 +407,7 @@ const docsSidebar = {
|
|||||||
},
|
},
|
||||||
items: [
|
items: [
|
||||||
"security/policy",
|
"security/policy",
|
||||||
|
"security/CVE-2024-23647",
|
||||||
"security/CVE-2024-21637",
|
"security/CVE-2024-21637",
|
||||||
"security/CVE-2023-48228",
|
"security/CVE-2023-48228",
|
||||||
"security/GHSA-rjvp-29xq-f62w",
|
"security/GHSA-rjvp-29xq-f62w",
|
||||||
|
|||||||
Reference in New Issue
Block a user