providers/oauth2: add client_assertion_type jwt bearer support (#2618)
This commit is contained in:
parent
996bd05ba6
commit
bb8af2f19b
|
@ -36,6 +36,9 @@ from authentik.policies.models import PolicyBindingModel
|
|||
LOGGER = get_logger()
|
||||
USER_ATTRIBUTE_DEBUG = "goauthentik.io/user/debug"
|
||||
USER_ATTRIBUTE_SA = "goauthentik.io/user/service-account"
|
||||
USER_ATTRIBUTE_GENERATED = "goauthentik.io/user/generated"
|
||||
USER_ATTRIBUTE_EXPIRES = "goauthentik.io/user/expires"
|
||||
USER_ATTRIBUTE_DELETE_ON_LOGOUT = "goauthentik.io/user/delete-on-logout"
|
||||
USER_ATTRIBUTE_SOURCES = "goauthentik.io/user/sources"
|
||||
USER_ATTRIBUTE_TOKEN_EXPIRING = "goauthentik.io/user/token-expires" # nosec
|
||||
USER_ATTRIBUTE_CHANGE_USERNAME = "goauthentik.io/user/can-change-username"
|
||||
|
|
|
@ -1,10 +1,18 @@
|
|||
"""authentik core tasks"""
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from django.contrib.sessions.backends.cache import KEY_PREFIX
|
||||
from django.core.cache import cache
|
||||
from django.utils.timezone import now
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.core.models import AuthenticatedSession, ExpiringModel
|
||||
from authentik.core.models import (
|
||||
USER_ATTRIBUTE_EXPIRES,
|
||||
USER_ATTRIBUTE_GENERATED,
|
||||
AuthenticatedSession,
|
||||
ExpiringModel,
|
||||
User,
|
||||
)
|
||||
from authentik.events.monitored_tasks import (
|
||||
MonitoredTask,
|
||||
TaskResult,
|
||||
|
@ -42,3 +50,22 @@ def clean_expired_models(self: MonitoredTask):
|
|||
LOGGER.debug("Expired sessions", model=AuthenticatedSession, amount=amount)
|
||||
messages.append(f"Expired {amount} {AuthenticatedSession._meta.verbose_name_plural}")
|
||||
self.set_status(TaskResult(TaskResultStatus.SUCCESSFUL, messages))
|
||||
|
||||
|
||||
@CELERY_APP.task(bind=True, base=MonitoredTask)
|
||||
@prefill_task
|
||||
def clean_temporary_users(self: MonitoredTask):
|
||||
"""Remove temporary users created by SAML Sources"""
|
||||
_now = datetime.now()
|
||||
messages = []
|
||||
deleted_users = 0
|
||||
for user in User.objects.filter(**{f"attributes__{USER_ATTRIBUTE_GENERATED}": True}):
|
||||
delta: timedelta = _now - datetime.fromtimestamp(
|
||||
user.attributes.get(USER_ATTRIBUTE_EXPIRES)
|
||||
)
|
||||
if delta.total_seconds() > 0:
|
||||
LOGGER.debug("User is expired and will be deleted.", user=user, delta=delta)
|
||||
user.delete()
|
||||
deleted_users += 1
|
||||
messages.append(f"Successfully deleted {deleted_users} users.")
|
||||
self.set_status(TaskResult(TaskResultStatus.SUCCESSFUL, messages))
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
"""Test tasks"""
|
||||
from time import mktime
|
||||
|
||||
from django.utils.timezone import now
|
||||
from guardian.shortcuts import get_anonymous_user
|
||||
from rest_framework.test import APITestCase
|
||||
|
||||
from authentik.core.models import (
|
||||
USER_ATTRIBUTE_EXPIRES,
|
||||
USER_ATTRIBUTE_GENERATED,
|
||||
Token,
|
||||
TokenIntents,
|
||||
User,
|
||||
)
|
||||
from authentik.core.tasks import clean_expired_models, clean_temporary_users
|
||||
from authentik.core.tests.utils import create_test_admin_user
|
||||
from authentik.lib.generators import generate_id
|
||||
|
||||
|
||||
class TestTasks(APITestCase):
|
||||
"""Test token API"""
|
||||
|
||||
def setUp(self) -> None:
|
||||
super().setUp()
|
||||
self.user = User.objects.create(username="testuser")
|
||||
self.admin = create_test_admin_user()
|
||||
self.client.force_login(self.user)
|
||||
|
||||
def test_token_expire(self):
|
||||
"""Test Token expire task"""
|
||||
token: Token = Token.objects.create(
|
||||
expires=now(), user=get_anonymous_user(), intent=TokenIntents.INTENT_API
|
||||
)
|
||||
key = token.key
|
||||
clean_expired_models.delay().get()
|
||||
token.refresh_from_db()
|
||||
self.assertNotEqual(key, token.key)
|
||||
|
||||
def test_clean_temporary_users(self):
|
||||
"""Test clean_temporary_users task"""
|
||||
username = generate_id
|
||||
User.objects.create(
|
||||
username=username,
|
||||
attributes={
|
||||
USER_ATTRIBUTE_GENERATED: True,
|
||||
USER_ATTRIBUTE_EXPIRES: mktime(now().timetuple()),
|
||||
},
|
||||
)
|
||||
clean_temporary_users.delay().get()
|
||||
self.assertFalse(User.objects.filter(username=username))
|
|
@ -2,12 +2,10 @@
|
|||
from json import loads
|
||||
|
||||
from django.urls.base import reverse
|
||||
from django.utils.timezone import now
|
||||
from guardian.shortcuts import get_anonymous_user
|
||||
from rest_framework.test import APITestCase
|
||||
|
||||
from authentik.core.models import USER_ATTRIBUTE_TOKEN_EXPIRING, Token, TokenIntents, User
|
||||
from authentik.core.tasks import clean_expired_models
|
||||
from authentik.core.tests.utils import create_test_admin_user
|
||||
|
||||
|
||||
|
@ -53,16 +51,6 @@ class TestTokenAPI(APITestCase):
|
|||
self.assertEqual(token.intent, TokenIntents.INTENT_API)
|
||||
self.assertEqual(token.expiring, False)
|
||||
|
||||
def test_token_expire(self):
|
||||
"""Test Token expire task"""
|
||||
token: Token = Token.objects.create(
|
||||
expires=now(), user=get_anonymous_user(), intent=TokenIntents.INTENT_API
|
||||
)
|
||||
key = token.key
|
||||
clean_expired_models.delay().get()
|
||||
token.refresh_from_db()
|
||||
self.assertNotEqual(key, token.key)
|
||||
|
||||
def test_list(self):
|
||||
"""Test Token List (Test normal authentication)"""
|
||||
token_should: Token = Token.objects.create(
|
||||
|
|
|
@ -34,6 +34,7 @@ class OAuth2ProviderSerializer(ProviderSerializer):
|
|||
"sub_mode",
|
||||
"property_mappings",
|
||||
"issuer_mode",
|
||||
"verification_keys",
|
||||
]
|
||||
|
||||
|
||||
|
|
|
@ -4,6 +4,10 @@ GRANT_TYPE_AUTHORIZATION_CODE = "authorization_code"
|
|||
GRANT_TYPE_REFRESH_TOKEN = "refresh_token" # nosec
|
||||
GRANT_TYPE_CLIENT_CREDENTIALS = "client_credentials"
|
||||
|
||||
CLIENT_ASSERTION_TYPE = "client_assertion_type"
|
||||
CLIENT_ASSERTION = "client_assertion"
|
||||
CLIENT_ASSERTION_TYPE_JWT = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
|
||||
|
||||
PROMPT_NONE = "none"
|
||||
PROMPT_CONSNET = "consent"
|
||||
PROMPT_LOGIN = "login"
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
# Generated by Django 4.0.3 on 2022-03-29 19:37
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_crypto", "0003_certificatekeypair_managed"),
|
||||
("authentik_providers_oauth2", "0008_rename_rsa_key_oauth2provider_signing_key_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="oauth2provider",
|
||||
name="verification_keys",
|
||||
field=models.ManyToManyField(
|
||||
help_text="JWTs created with the configured certificates can authenticate with this provider.",
|
||||
related_name="+",
|
||||
to="authentik_crypto.certificatekeypair",
|
||||
verbose_name="Allowed certificates for JWT-based client_credentials",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="oauth2provider",
|
||||
name="signing_key",
|
||||
field=models.ForeignKey(
|
||||
help_text="Key used to sign the tokens. Only required when JWT Algorithm is set to RS256.",
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
to="authentik_crypto.certificatekeypair",
|
||||
verbose_name="Signing Key",
|
||||
),
|
||||
),
|
||||
]
|
|
@ -212,7 +212,7 @@ class OAuth2Provider(Provider):
|
|||
|
||||
signing_key = models.ForeignKey(
|
||||
CertificateKeyPair,
|
||||
verbose_name=_("RSA Key"),
|
||||
verbose_name=_("Signing Key"),
|
||||
on_delete=models.SET_NULL,
|
||||
null=True,
|
||||
help_text=_(
|
||||
|
@ -220,6 +220,15 @@ class OAuth2Provider(Provider):
|
|||
),
|
||||
)
|
||||
|
||||
verification_keys = models.ManyToManyField(
|
||||
CertificateKeyPair,
|
||||
verbose_name=_("Allowed certificates for JWT-based client_credentials"),
|
||||
help_text=_(
|
||||
"JWTs created with the configured certificates can authenticate with this provider."
|
||||
),
|
||||
related_name="+",
|
||||
)
|
||||
|
||||
def create_refresh_token(
|
||||
self, user: User, scope: list[str], request: HttpRequest
|
||||
) -> "RefreshToken":
|
||||
|
|
|
@ -0,0 +1,206 @@
|
|||
"""Test token view"""
|
||||
from datetime import datetime, timedelta
|
||||
from json import loads
|
||||
|
||||
from django.test import RequestFactory
|
||||
from django.urls import reverse
|
||||
from jwt import decode
|
||||
|
||||
from authentik.core.models import USER_ATTRIBUTE_SA, Application, Group
|
||||
from authentik.core.tests.utils import create_test_admin_user, create_test_cert, create_test_flow
|
||||
from authentik.lib.generators import generate_id, generate_key
|
||||
from authentik.managed.manager import ObjectManager
|
||||
from authentik.policies.models import PolicyBinding
|
||||
from authentik.providers.oauth2.constants import (
|
||||
GRANT_TYPE_CLIENT_CREDENTIALS,
|
||||
SCOPE_OPENID,
|
||||
SCOPE_OPENID_EMAIL,
|
||||
SCOPE_OPENID_PROFILE,
|
||||
)
|
||||
from authentik.providers.oauth2.models import OAuth2Provider, ScopeMapping
|
||||
from authentik.providers.oauth2.tests.utils import OAuthTestCase
|
||||
|
||||
|
||||
class TestTokenClientCredentialsJWT(OAuthTestCase):
|
||||
"""Test token (client_credentials, with JWT) view"""
|
||||
|
||||
def setUp(self) -> None:
|
||||
super().setUp()
|
||||
ObjectManager().run()
|
||||
self.factory = RequestFactory()
|
||||
self.cert = create_test_cert()
|
||||
self.provider: OAuth2Provider = OAuth2Provider.objects.create(
|
||||
name="test",
|
||||
client_id=generate_id(),
|
||||
client_secret=generate_key(),
|
||||
authorization_flow=create_test_flow(),
|
||||
redirect_uris="http://testserver",
|
||||
signing_key=self.cert,
|
||||
)
|
||||
self.provider.verification_keys.set([self.cert])
|
||||
self.provider.property_mappings.set(ScopeMapping.objects.all())
|
||||
self.app = Application.objects.create(name="test", slug="test", provider=self.provider)
|
||||
self.user = create_test_admin_user("sa")
|
||||
self.user.attributes[USER_ATTRIBUTE_SA] = True
|
||||
self.user.save()
|
||||
|
||||
def test_invalid_type(self):
|
||||
"""test invalid type"""
|
||||
response = self.client.post(
|
||||
reverse("authentik_providers_oauth2:token"),
|
||||
{
|
||||
"grant_type": GRANT_TYPE_CLIENT_CREDENTIALS,
|
||||
"scope": f"{SCOPE_OPENID} {SCOPE_OPENID_EMAIL} {SCOPE_OPENID_PROFILE}",
|
||||
"client_id": self.provider.client_id,
|
||||
"client_assertion_type": "foo",
|
||||
"client_assertion": "foo.bar",
|
||||
},
|
||||
)
|
||||
self.assertEqual(response.status_code, 400)
|
||||
body = loads(response.content.decode())
|
||||
self.assertEqual(body["error"], "invalid_grant")
|
||||
|
||||
def test_invalid_jwt(self):
|
||||
"""test invalid JWT"""
|
||||
response = self.client.post(
|
||||
reverse("authentik_providers_oauth2:token"),
|
||||
{
|
||||
"grant_type": GRANT_TYPE_CLIENT_CREDENTIALS,
|
||||
"scope": f"{SCOPE_OPENID} {SCOPE_OPENID_EMAIL} {SCOPE_OPENID_PROFILE}",
|
||||
"client_id": self.provider.client_id,
|
||||
"client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
|
||||
"client_assertion": "foo.bar",
|
||||
},
|
||||
)
|
||||
self.assertEqual(response.status_code, 400)
|
||||
body = loads(response.content.decode())
|
||||
self.assertEqual(body["error"], "invalid_grant")
|
||||
|
||||
def test_invalid_signautre(self):
|
||||
"""test invalid JWT"""
|
||||
token = self.provider.encode(
|
||||
{
|
||||
"sub": "foo",
|
||||
"exp": datetime.now() + timedelta(hours=2),
|
||||
}
|
||||
)
|
||||
response = self.client.post(
|
||||
reverse("authentik_providers_oauth2:token"),
|
||||
{
|
||||
"grant_type": GRANT_TYPE_CLIENT_CREDENTIALS,
|
||||
"scope": f"{SCOPE_OPENID} {SCOPE_OPENID_EMAIL} {SCOPE_OPENID_PROFILE}",
|
||||
"client_id": self.provider.client_id,
|
||||
"client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
|
||||
"client_assertion": token + "foo",
|
||||
},
|
||||
)
|
||||
self.assertEqual(response.status_code, 400)
|
||||
body = loads(response.content.decode())
|
||||
self.assertEqual(body["error"], "invalid_grant")
|
||||
|
||||
def test_invalid_expired(self):
|
||||
"""test invalid JWT"""
|
||||
token = self.provider.encode(
|
||||
{
|
||||
"sub": "foo",
|
||||
"exp": datetime.now() - timedelta(hours=2),
|
||||
}
|
||||
)
|
||||
response = self.client.post(
|
||||
reverse("authentik_providers_oauth2:token"),
|
||||
{
|
||||
"grant_type": GRANT_TYPE_CLIENT_CREDENTIALS,
|
||||
"scope": f"{SCOPE_OPENID} {SCOPE_OPENID_EMAIL} {SCOPE_OPENID_PROFILE}",
|
||||
"client_id": self.provider.client_id,
|
||||
"client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
|
||||
"client_assertion": token,
|
||||
},
|
||||
)
|
||||
self.assertEqual(response.status_code, 400)
|
||||
body = loads(response.content.decode())
|
||||
self.assertEqual(body["error"], "invalid_grant")
|
||||
|
||||
def test_invalid_no_app(self):
|
||||
"""test invalid JWT"""
|
||||
self.app.provider = None
|
||||
self.app.save()
|
||||
token = self.provider.encode(
|
||||
{
|
||||
"sub": "foo",
|
||||
"exp": datetime.now() + timedelta(hours=2),
|
||||
}
|
||||
)
|
||||
response = self.client.post(
|
||||
reverse("authentik_providers_oauth2:token"),
|
||||
{
|
||||
"grant_type": GRANT_TYPE_CLIENT_CREDENTIALS,
|
||||
"scope": f"{SCOPE_OPENID} {SCOPE_OPENID_EMAIL} {SCOPE_OPENID_PROFILE}",
|
||||
"client_id": self.provider.client_id,
|
||||
"client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
|
||||
"client_assertion": token,
|
||||
},
|
||||
)
|
||||
self.assertEqual(response.status_code, 400)
|
||||
body = loads(response.content.decode())
|
||||
self.assertEqual(body["error"], "invalid_grant")
|
||||
|
||||
def test_invalid_access_denied(self):
|
||||
"""test invalid JWT"""
|
||||
group = Group.objects.create(name="foo")
|
||||
PolicyBinding.objects.create(
|
||||
group=group,
|
||||
target=self.app,
|
||||
order=0,
|
||||
)
|
||||
token = self.provider.encode(
|
||||
{
|
||||
"sub": "foo",
|
||||
"exp": datetime.now() + timedelta(hours=2),
|
||||
}
|
||||
)
|
||||
response = self.client.post(
|
||||
reverse("authentik_providers_oauth2:token"),
|
||||
{
|
||||
"grant_type": GRANT_TYPE_CLIENT_CREDENTIALS,
|
||||
"scope": f"{SCOPE_OPENID} {SCOPE_OPENID_EMAIL} {SCOPE_OPENID_PROFILE}",
|
||||
"client_id": self.provider.client_id,
|
||||
"client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
|
||||
"client_assertion": token,
|
||||
},
|
||||
)
|
||||
self.assertEqual(response.status_code, 400)
|
||||
body = loads(response.content.decode())
|
||||
self.assertEqual(body["error"], "invalid_grant")
|
||||
|
||||
def test_successful(self):
|
||||
"""test successful"""
|
||||
token = self.provider.encode(
|
||||
{
|
||||
"sub": "foo",
|
||||
"exp": datetime.now() + timedelta(hours=2),
|
||||
}
|
||||
)
|
||||
response = self.client.post(
|
||||
reverse("authentik_providers_oauth2:token"),
|
||||
{
|
||||
"grant_type": GRANT_TYPE_CLIENT_CREDENTIALS,
|
||||
"scope": f"{SCOPE_OPENID} {SCOPE_OPENID_EMAIL} {SCOPE_OPENID_PROFILE}",
|
||||
"client_id": self.provider.client_id,
|
||||
"client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
|
||||
"client_assertion": token,
|
||||
},
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
body = loads(response.content.decode())
|
||||
self.assertEqual(body["token_type"], "bearer")
|
||||
_, alg = self.provider.get_jwt_key()
|
||||
jwt = decode(
|
||||
body["access_token"],
|
||||
key=self.provider.signing_key.public_key,
|
||||
algorithms=[alg],
|
||||
audience=self.provider.client_id,
|
||||
)
|
||||
self.assertEqual(
|
||||
jwt["given_name"], "Autogenerated user from application test (client credentials JWT)"
|
||||
)
|
||||
self.assertEqual(jwt["preferred_username"], "test-foo")
|
|
@ -36,7 +36,6 @@ class JWKSView(View):
|
|||
|
||||
if signing_key:
|
||||
private_key = signing_key.private_key
|
||||
print(type(private_key))
|
||||
if isinstance(private_key, RSAPrivateKey):
|
||||
public_key: RSAPublicKey = private_key.public_key()
|
||||
public_numbers = public_key.public_numbers()
|
||||
|
|
|
@ -5,14 +5,27 @@ from hashlib import sha256
|
|||
from typing import Any, Optional
|
||||
|
||||
from django.http import HttpRequest, HttpResponse
|
||||
from django.utils.timezone import datetime, now
|
||||
from django.views import View
|
||||
from jwt import InvalidTokenError, decode
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.core.models import USER_ATTRIBUTE_SA, Application, Token, TokenIntents, User
|
||||
from authentik.core.models import (
|
||||
USER_ATTRIBUTE_EXPIRES,
|
||||
USER_ATTRIBUTE_GENERATED,
|
||||
USER_ATTRIBUTE_SA,
|
||||
Application,
|
||||
Token,
|
||||
TokenIntents,
|
||||
User,
|
||||
)
|
||||
from authentik.events.models import Event, EventAction
|
||||
from authentik.lib.utils.time import timedelta_from_string
|
||||
from authentik.policies.engine import PolicyEngine
|
||||
from authentik.providers.oauth2.constants import (
|
||||
CLIENT_ASSERTION,
|
||||
CLIENT_ASSERTION_TYPE,
|
||||
CLIENT_ASSERTION_TYPE_JWT,
|
||||
GRANT_TYPE_AUTHORIZATION_CODE,
|
||||
GRANT_TYPE_CLIENT_CREDENTIALS,
|
||||
GRANT_TYPE_REFRESH_TOKEN,
|
||||
|
@ -21,6 +34,7 @@ from authentik.providers.oauth2.errors import TokenError, UserAuthError
|
|||
from authentik.providers.oauth2.models import (
|
||||
AuthorizationCode,
|
||||
ClientTypes,
|
||||
JWTAlgorithms,
|
||||
OAuth2Provider,
|
||||
RefreshToken,
|
||||
)
|
||||
|
@ -187,6 +201,8 @@ class TokenParams:
|
|||
raise TokenError("invalid_grant")
|
||||
|
||||
def __post_init_client_credentials(self, request: HttpRequest):
|
||||
if request.POST.get(CLIENT_ASSERTION_TYPE, "") != "":
|
||||
return self.__post_init_client_credentials_jwt(request)
|
||||
# Authenticate user based on credentials
|
||||
username = request.POST.get("username")
|
||||
password = request.POST.get("password")
|
||||
|
@ -222,6 +238,72 @@ class TokenParams:
|
|||
if not result.passing:
|
||||
LOGGER.info("User not authenticated for application", user=self.user, app=app)
|
||||
raise TokenError("invalid_grant")
|
||||
return None
|
||||
|
||||
def __post_init_client_credentials_jwt(self, request: HttpRequest):
|
||||
assertion_type = request.POST.get(CLIENT_ASSERTION_TYPE, "")
|
||||
if assertion_type != CLIENT_ASSERTION_TYPE_JWT:
|
||||
raise TokenError("invalid_grant")
|
||||
|
||||
client_secret = request.POST.get("client_secret", None)
|
||||
assertion = request.POST.get(CLIENT_ASSERTION, client_secret)
|
||||
if not assertion:
|
||||
raise TokenError("invalid_grant")
|
||||
|
||||
token = None
|
||||
for cert in self.provider.verification_keys.all():
|
||||
LOGGER.debug("verifying jwt with key", key=cert.name)
|
||||
try:
|
||||
token = decode(
|
||||
assertion,
|
||||
cert.certificate.public_key(),
|
||||
algorithms=[JWTAlgorithms.RS256, JWTAlgorithms.EC256],
|
||||
options={
|
||||
"verify_aud": False,
|
||||
},
|
||||
)
|
||||
except (InvalidTokenError, ValueError) as last_exc:
|
||||
LOGGER.warning("failed to validate jwt", last_exc=last_exc)
|
||||
if not token:
|
||||
raise TokenError("invalid_grant")
|
||||
|
||||
exp = datetime.fromtimestamp(token["exp"])
|
||||
# Non-timezone aware check since we assume `exp` is in UTC
|
||||
if datetime.now() >= exp:
|
||||
LOGGER.info("JWT token expired")
|
||||
raise TokenError("invalid_grant")
|
||||
|
||||
app = Application.objects.filter(provider=self.provider).first()
|
||||
if not app or not app.provider:
|
||||
LOGGER.info("client_credentials grant for provider without application")
|
||||
raise TokenError("invalid_grant")
|
||||
|
||||
engine = PolicyEngine(app, self.user, request)
|
||||
engine.request.context["JWT"] = token
|
||||
engine.build()
|
||||
result = engine.result
|
||||
if not result.passing:
|
||||
LOGGER.info("JWT not authenticated for application", app=app)
|
||||
raise TokenError("invalid_grant")
|
||||
self.user, _ = User.objects.update_or_create(
|
||||
username=f"{self.provider.name}-{token.get('sub')}",
|
||||
defaults={
|
||||
"attributes": {
|
||||
USER_ATTRIBUTE_GENERATED: True,
|
||||
USER_ATTRIBUTE_EXPIRES: token["exp"],
|
||||
},
|
||||
"last_login": now(),
|
||||
"name": f"Autogenerated user from application {app.name} (client credentials JWT)",
|
||||
},
|
||||
)
|
||||
|
||||
Event.new(
|
||||
action=EventAction.LOGIN,
|
||||
PLAN_CONTEXT_METHOD="jwt",
|
||||
PLAN_CONTEXT_METHOD_ARGS={
|
||||
"jwt": token,
|
||||
},
|
||||
).from_http(request, user=self.user)
|
||||
|
||||
|
||||
class TokenView(View):
|
||||
|
|
|
@ -345,6 +345,11 @@ CELERY_BEAT_SCHEDULE = {
|
|||
"schedule": crontab(hour="*/24", minute=0),
|
||||
"options": {"queue": "authentik_scheduled"},
|
||||
},
|
||||
"user_cleanup": {
|
||||
"task": "authentik.core.tasks.clean_temporary_users",
|
||||
"schedule": crontab(minute="*/5"),
|
||||
"options": {"queue": "authentik_scheduled"},
|
||||
},
|
||||
}
|
||||
CELERY_TASK_CREATE_MISSING_QUEUES = True
|
||||
CELERY_TASK_DEFAULT_QUEUE = "authentik"
|
||||
|
|
|
@ -6,7 +6,7 @@ from django.apps import AppConfig
|
|||
|
||||
|
||||
class AuthentikSourceSAMLConfig(AppConfig):
|
||||
"""authentik saml_idp app config"""
|
||||
"""authentik saml source app config"""
|
||||
|
||||
name = "authentik.sources.saml"
|
||||
label = "authentik_sources_saml"
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
"""authentik saml source processor"""
|
||||
from base64 import b64decode
|
||||
from time import mktime
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
import xmlsec
|
||||
|
@ -7,9 +8,16 @@ from defusedxml.lxml import fromstring
|
|||
from django.core.cache import cache
|
||||
from django.core.exceptions import SuspiciousOperation
|
||||
from django.http import HttpRequest, HttpResponse
|
||||
from django.utils.timezone import now
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.core.models import User
|
||||
from authentik.core.models import (
|
||||
USER_ATTRIBUTE_DELETE_ON_LOGOUT,
|
||||
USER_ATTRIBUTE_EXPIRES,
|
||||
USER_ATTRIBUTE_GENERATED,
|
||||
USER_ATTRIBUTE_SOURCES,
|
||||
User,
|
||||
)
|
||||
from authentik.flows.models import Flow
|
||||
from authentik.flows.planner import (
|
||||
PLAN_CONTEXT_PENDING_USER,
|
||||
|
@ -19,6 +27,7 @@ from authentik.flows.planner import (
|
|||
FlowPlanner,
|
||||
)
|
||||
from authentik.flows.views.executor import NEXT_ARG_NAME, SESSION_KEY_GET, SESSION_KEY_PLAN
|
||||
from authentik.lib.utils.time import timedelta_from_string
|
||||
from authentik.lib.utils.urls import redirect_with_qs
|
||||
from authentik.policies.utils import delete_none_keys
|
||||
from authentik.sources.saml.exceptions import (
|
||||
|
@ -124,9 +133,19 @@ class ResponseProcessor:
|
|||
on logout and periodically."""
|
||||
# Create a temporary User
|
||||
name_id = self._get_name_id().text
|
||||
expiry = mktime(
|
||||
(now() + timedelta_from_string(self._source.temporary_user_delete_after)).timetuple()
|
||||
)
|
||||
user: User = User.objects.create(
|
||||
username=name_id,
|
||||
attributes={"saml": {"source": self._source.pk.hex, "delete_on_logout": True}},
|
||||
attributes={
|
||||
USER_ATTRIBUTE_GENERATED: True,
|
||||
USER_ATTRIBUTE_SOURCES: [
|
||||
self._source.name,
|
||||
],
|
||||
USER_ATTRIBUTE_DELETE_ON_LOGOUT: True,
|
||||
USER_ATTRIBUTE_EXPIRES: expiry,
|
||||
},
|
||||
)
|
||||
LOGGER.debug("Created temporary user for NameID Transient", username=name_id)
|
||||
user.set_unusable_password()
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
"""saml source settings"""
|
||||
from celery.schedules import crontab
|
||||
|
||||
CELERY_BEAT_SCHEDULE = {
|
||||
"saml_source_cleanup": {
|
||||
"task": "authentik.sources.saml.tasks.clean_temporary_users",
|
||||
"schedule": crontab(minute="*/5"),
|
||||
"options": {"queue": "authentik_scheduled"},
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@ from django.dispatch import receiver
|
|||
from django.http import HttpRequest
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.core.models import User
|
||||
from authentik.core.models import USER_ATTRIBUTE_DELETE_ON_LOGOUT, User
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
||||
|
@ -15,8 +15,6 @@ def on_user_logged_out(sender, request: HttpRequest, user: User, **_):
|
|||
"""Delete temporary user if the `delete_on_logout` flag is enabled"""
|
||||
if not user:
|
||||
return
|
||||
if "saml" in user.attributes:
|
||||
if "delete_on_logout" in user.attributes["saml"]:
|
||||
if user.attributes["saml"]["delete_on_logout"]:
|
||||
LOGGER.debug("Deleted temporary user", user=user)
|
||||
user.delete()
|
||||
if user.attributes.get(USER_ATTRIBUTE_DELETE_ON_LOGOUT, False):
|
||||
LOGGER.debug("Deleted temporary user", user=user)
|
||||
user.delete()
|
||||
|
|
|
@ -1,42 +0,0 @@
|
|||
"""authentik saml source tasks"""
|
||||
from django.utils.timezone import now
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.core.models import AuthenticatedSession, User
|
||||
from authentik.events.monitored_tasks import (
|
||||
MonitoredTask,
|
||||
TaskResult,
|
||||
TaskResultStatus,
|
||||
prefill_task,
|
||||
)
|
||||
from authentik.lib.utils.time import timedelta_from_string
|
||||
from authentik.root.celery import CELERY_APP
|
||||
from authentik.sources.saml.models import SAMLSource
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
||||
|
||||
@CELERY_APP.task(bind=True, base=MonitoredTask)
|
||||
@prefill_task
|
||||
def clean_temporary_users(self: MonitoredTask):
|
||||
"""Remove temporary users created by SAML Sources"""
|
||||
_now = now()
|
||||
messages = []
|
||||
deleted_users = 0
|
||||
for user in User.objects.filter(attributes__saml__isnull=False):
|
||||
sources = SAMLSource.objects.filter(pk=user.attributes.get("saml", {}).get("source", ""))
|
||||
if not sources.exists():
|
||||
LOGGER.warning("User has an invalid SAML Source and won't be deleted!", user=user)
|
||||
messages.append(f"User {user} has an invalid SAML Source and won't be deleted!")
|
||||
continue
|
||||
source = sources.first()
|
||||
source_delta = timedelta_from_string(source.temporary_user_delete_after)
|
||||
if (
|
||||
_now - user.last_login >= source_delta
|
||||
and not AuthenticatedSession.objects.filter(user=user).exists()
|
||||
):
|
||||
LOGGER.debug("User is expired and will be deleted.", user=user, delta=source_delta)
|
||||
user.delete()
|
||||
deleted_users += 1
|
||||
messages.append(f"Successfully deleted {deleted_users} users.")
|
||||
self.set_status(TaskResult(TaskResultStatus.SUCCESSFUL, messages))
|
32
schema.yml
32
schema.yml
|
@ -23091,7 +23091,6 @@ components:
|
|||
type: string
|
||||
format: uuid
|
||||
nullable: true
|
||||
title: RSA Key
|
||||
description: Key used to sign the tokens. Only required when JWT Algorithm
|
||||
is set to RS256.
|
||||
redirect_uris:
|
||||
|
@ -23106,6 +23105,15 @@ components:
|
|||
allOf:
|
||||
- $ref: '#/components/schemas/IssuerModeEnum'
|
||||
description: Configure how the issuer field of the ID Token should be filled.
|
||||
verification_keys:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
format: uuid
|
||||
title: Allowed certificates for JWT-based client_credentials
|
||||
title: Allowed certificates for JWT-based client_credentials
|
||||
description: JWTs created with the configured certificates can authenticate
|
||||
with this provider.
|
||||
required:
|
||||
- assigned_application_name
|
||||
- assigned_application_slug
|
||||
|
@ -23116,6 +23124,7 @@ components:
|
|||
- pk
|
||||
- verbose_name
|
||||
- verbose_name_plural
|
||||
- verification_keys
|
||||
OAuth2ProviderRequest:
|
||||
type: object
|
||||
description: OAuth2Provider Serializer
|
||||
|
@ -23163,7 +23172,6 @@ components:
|
|||
type: string
|
||||
format: uuid
|
||||
nullable: true
|
||||
title: RSA Key
|
||||
description: Key used to sign the tokens. Only required when JWT Algorithm
|
||||
is set to RS256.
|
||||
redirect_uris:
|
||||
|
@ -23178,9 +23186,19 @@ components:
|
|||
allOf:
|
||||
- $ref: '#/components/schemas/IssuerModeEnum'
|
||||
description: Configure how the issuer field of the ID Token should be filled.
|
||||
verification_keys:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
format: uuid
|
||||
title: Allowed certificates for JWT-based client_credentials
|
||||
title: Allowed certificates for JWT-based client_credentials
|
||||
description: JWTs created with the configured certificates can authenticate
|
||||
with this provider.
|
||||
required:
|
||||
- authorization_flow
|
||||
- name
|
||||
- verification_keys
|
||||
OAuth2ProviderSetupURLs:
|
||||
type: object
|
||||
description: OAuth2 Provider Metadata serializer
|
||||
|
@ -27488,7 +27506,6 @@ components:
|
|||
type: string
|
||||
format: uuid
|
||||
nullable: true
|
||||
title: RSA Key
|
||||
description: Key used to sign the tokens. Only required when JWT Algorithm
|
||||
is set to RS256.
|
||||
redirect_uris:
|
||||
|
@ -27503,6 +27520,15 @@ components:
|
|||
allOf:
|
||||
- $ref: '#/components/schemas/IssuerModeEnum'
|
||||
description: Configure how the issuer field of the ID Token should be filled.
|
||||
verification_keys:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
format: uuid
|
||||
title: Allowed certificates for JWT-based client_credentials
|
||||
title: Allowed certificates for JWT-based client_credentials
|
||||
description: JWTs created with the configured certificates can authenticate
|
||||
with this provider.
|
||||
PatchedOAuthSourceRequest:
|
||||
type: object
|
||||
description: OAuth Source Serializer
|
||||
|
|
|
@ -2327,6 +2327,7 @@ msgstr "Interne Konten ausblenden"
|
|||
#: src/pages/events/RuleForm.ts
|
||||
#: src/pages/outposts/OutpostForm.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
#: src/pages/providers/saml/SAMLProviderForm.ts
|
||||
#: src/pages/sources/ldap/LDAPSourceForm.ts
|
||||
|
@ -2582,6 +2583,10 @@ msgstr "Ausstellermodus"
|
|||
#~ msgid "JWT Algorithm"
|
||||
#~ msgstr "JWT Algorithmus"
|
||||
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
msgid "JWTs signed by certificates configured here can be used to authenticate to the provider."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
msgid "Key used to sign the tokens."
|
||||
msgstr "Schlüssel zum Signieren der Token."
|
||||
|
@ -2745,6 +2750,7 @@ msgstr "Wird geladen"
|
|||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
|
@ -5906,6 +5912,10 @@ msgstr "Überprüfung"
|
|||
msgid "Verification Certificate"
|
||||
msgstr "Zertifikat zur Überprüfung"
|
||||
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
msgid "Verification certificates"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/stages/email/EmailStageForm.ts
|
||||
msgid "Verify the user's email address by sending them a one-time-link. Can also be used for recovery to verify the user's authenticity."
|
||||
msgstr "Überprüfen Sie die E-Mail-Adresse des Benutzers, indem Sie ihm einen einmaligen Link senden. Kann auch für die Wiederherstellung verwendet werden, um die Authentizität des Benutzers zu überprüfen."
|
||||
|
|
|
@ -2360,6 +2360,7 @@ msgstr "Hide service-accounts"
|
|||
#: src/pages/events/RuleForm.ts
|
||||
#: src/pages/outposts/OutpostForm.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
#: src/pages/providers/saml/SAMLProviderForm.ts
|
||||
#: src/pages/sources/ldap/LDAPSourceForm.ts
|
||||
|
@ -2624,6 +2625,10 @@ msgstr "Issuer mode"
|
|||
#~ msgid "JWT Algorithm"
|
||||
#~ msgstr "JWT Algorithm"
|
||||
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
msgid "JWTs signed by certificates configured here can be used to authenticate to the provider."
|
||||
msgstr "JWTs signed by certificates configured here can be used to authenticate to the provider."
|
||||
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
msgid "Key used to sign the tokens."
|
||||
msgstr "Key used to sign the tokens."
|
||||
|
@ -2789,6 +2794,7 @@ msgstr "Loading"
|
|||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
|
@ -6022,6 +6028,10 @@ msgstr "Verification"
|
|||
msgid "Verification Certificate"
|
||||
msgstr "Verification Certificate"
|
||||
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
msgid "Verification certificates"
|
||||
msgstr "Verification certificates"
|
||||
|
||||
#: src/pages/stages/email/EmailStageForm.ts
|
||||
msgid "Verify the user's email address by sending them a one-time-link. Can also be used for recovery to verify the user's authenticity."
|
||||
msgstr "Verify the user's email address by sending them a one-time-link. Can also be used for recovery to verify the user's authenticity."
|
||||
|
|
|
@ -2318,6 +2318,7 @@ msgstr "Ocultar cuentas de servicio"
|
|||
#: src/pages/events/RuleForm.ts
|
||||
#: src/pages/outposts/OutpostForm.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
#: src/pages/providers/saml/SAMLProviderForm.ts
|
||||
#: src/pages/sources/ldap/LDAPSourceForm.ts
|
||||
|
@ -2575,6 +2576,10 @@ msgstr "Modo emisor"
|
|||
#~ msgid "JWT Algorithm"
|
||||
#~ msgstr "algoritmo JWT"
|
||||
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
msgid "JWTs signed by certificates configured here can be used to authenticate to the provider."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
msgid "Key used to sign the tokens."
|
||||
msgstr "Clave utilizada para firmar los tokens."
|
||||
|
@ -2738,6 +2743,7 @@ msgstr "Cargando"
|
|||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
|
@ -5900,6 +5906,10 @@ msgstr "Verificación"
|
|||
msgid "Verification Certificate"
|
||||
msgstr "Certificado de verificación"
|
||||
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
msgid "Verification certificates"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/stages/email/EmailStageForm.ts
|
||||
msgid "Verify the user's email address by sending them a one-time-link. Can also be used for recovery to verify the user's authenticity."
|
||||
msgstr "Verifique la dirección de correo electrónico del usuario enviándole un enlace único. También se puede utilizar para la recuperación para verificar la autenticidad del usuario."
|
||||
|
|
|
@ -2344,6 +2344,7 @@ msgstr "Cacher les comptes de service"
|
|||
#: src/pages/events/RuleForm.ts
|
||||
#: src/pages/outposts/OutpostForm.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
#: src/pages/providers/saml/SAMLProviderForm.ts
|
||||
#: src/pages/sources/ldap/LDAPSourceForm.ts
|
||||
|
@ -2605,6 +2606,10 @@ msgstr "Mode de l'émetteur"
|
|||
#~ msgid "JWT Algorithm"
|
||||
#~ msgstr "Algorithme JWT"
|
||||
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
msgid "JWTs signed by certificates configured here can be used to authenticate to the provider."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
msgid "Key used to sign the tokens."
|
||||
msgstr ""
|
||||
|
@ -2769,6 +2774,7 @@ msgstr "Chargement en cours"
|
|||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
|
@ -5961,6 +5967,10 @@ msgstr "Vérification"
|
|||
msgid "Verification Certificate"
|
||||
msgstr "Certificat de validation"
|
||||
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
msgid "Verification certificates"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/stages/email/EmailStageForm.ts
|
||||
msgid "Verify the user's email address by sending them a one-time-link. Can also be used for recovery to verify the user's authenticity."
|
||||
msgstr "Vérifier le courriel de l'utilisateur en lui envoyant un lien à usage unique. Peut également être utilisé lors de la récupération afin de vérifier l'authenticité de l'utilisateur."
|
||||
|
|
|
@ -2315,6 +2315,7 @@ msgstr "Ukryj konta serwisowe"
|
|||
#: src/pages/events/RuleForm.ts
|
||||
#: src/pages/outposts/OutpostForm.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
#: src/pages/providers/saml/SAMLProviderForm.ts
|
||||
#: src/pages/sources/ldap/LDAPSourceForm.ts
|
||||
|
@ -2572,6 +2573,10 @@ msgstr "Tryb wystawcy"
|
|||
#~ msgid "JWT Algorithm"
|
||||
#~ msgstr "Algorytm JWT"
|
||||
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
msgid "JWTs signed by certificates configured here can be used to authenticate to the provider."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
msgid "Key used to sign the tokens."
|
||||
msgstr "Klucz używany do podpisywania tokenów."
|
||||
|
@ -2735,6 +2740,7 @@ msgstr "Ładowanie"
|
|||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
|
@ -5897,6 +5903,10 @@ msgstr "Weryfikacja"
|
|||
msgid "Verification Certificate"
|
||||
msgstr "Certyfikat weryfikacji"
|
||||
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
msgid "Verification certificates"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/stages/email/EmailStageForm.ts
|
||||
msgid "Verify the user's email address by sending them a one-time-link. Can also be used for recovery to verify the user's authenticity."
|
||||
msgstr "Zweryfikuj adres e-mail użytkownika, wysyłając mu jednorazowy link. Może być również używany do odzyskiwania w celu weryfikacji autentyczności użytkownika."
|
||||
|
|
|
@ -2352,6 +2352,7 @@ msgstr ""
|
|||
#: src/pages/events/RuleForm.ts
|
||||
#: src/pages/outposts/OutpostForm.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
#: src/pages/providers/saml/SAMLProviderForm.ts
|
||||
#: src/pages/sources/ldap/LDAPSourceForm.ts
|
||||
|
@ -2614,6 +2615,10 @@ msgstr ""
|
|||
#~ msgid "JWT Algorithm"
|
||||
#~ msgstr ""
|
||||
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
msgid "JWTs signed by certificates configured here can be used to authenticate to the provider."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
msgid "Key used to sign the tokens."
|
||||
msgstr ""
|
||||
|
@ -2779,6 +2784,7 @@ msgstr ""
|
|||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
|
@ -6002,6 +6008,10 @@ msgstr ""
|
|||
msgid "Verification Certificate"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
msgid "Verification certificates"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/stages/email/EmailStageForm.ts
|
||||
msgid "Verify the user's email address by sending them a one-time-link. Can also be used for recovery to verify the user's authenticity."
|
||||
msgstr ""
|
||||
|
|
|
@ -2318,6 +2318,7 @@ msgstr "Hizmet hesaplarını gizle"
|
|||
#: src/pages/events/RuleForm.ts
|
||||
#: src/pages/outposts/OutpostForm.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
#: src/pages/providers/saml/SAMLProviderForm.ts
|
||||
#: src/pages/sources/ldap/LDAPSourceForm.ts
|
||||
|
@ -2576,6 +2577,10 @@ msgstr "Yayımcı kipi"
|
|||
#~ msgid "JWT Algorithm"
|
||||
#~ msgstr "JWT Algoritması"
|
||||
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
msgid "JWTs signed by certificates configured here can be used to authenticate to the provider."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
msgid "Key used to sign the tokens."
|
||||
msgstr "Anahtar belirteçleri imzalamak için kullanılır."
|
||||
|
@ -2739,6 +2744,7 @@ msgstr "Yükleniyor"
|
|||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
|
@ -5902,6 +5908,10 @@ msgstr "Doğrulama"
|
|||
msgid "Verification Certificate"
|
||||
msgstr "Doğrulama Sertifikası"
|
||||
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
msgid "Verification certificates"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/stages/email/EmailStageForm.ts
|
||||
msgid "Verify the user's email address by sending them a one-time-link. Can also be used for recovery to verify the user's authenticity."
|
||||
msgstr "Kullanıcının e-posta adresini bir kerelik bağlantı göndererek doğrulayın. Kullanıcının orijinalliğini doğrulamak için kurtarma için de kullanılabilir."
|
||||
|
|
|
@ -2333,6 +2333,7 @@ msgstr "隐藏服务账户"
|
|||
|
||||
#: src/pages/events/RuleForm.ts src/pages/outposts/OutpostForm.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
#: src/pages/providers/saml/SAMLProviderForm.ts
|
||||
#: src/pages/sources/ldap/LDAPSourceForm.ts
|
||||
|
@ -2612,6 +2613,10 @@ msgstr "Issuer 模式"
|
|||
#~ msgid "JWT Algorithm"
|
||||
#~ msgstr "JWT 算法"
|
||||
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
msgid "JWTs signed by certificates configured here can be used to authenticate to the provider."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
msgid "Key used to sign the tokens."
|
||||
msgstr "用于签名令牌的密钥。"
|
||||
|
@ -2771,6 +2776,7 @@ msgstr "正在加载"
|
|||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
|
@ -6015,6 +6021,10 @@ msgstr "验证"
|
|||
msgid "Verification Certificate"
|
||||
msgstr "验证证书"
|
||||
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
msgid "Verification certificates"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/stages/email/EmailStageForm.ts
|
||||
msgid ""
|
||||
"Verify the user's email address by sending them a one-time-link. Can also be"
|
||||
|
|
|
@ -2332,6 +2332,7 @@ msgstr "隐藏服务账户"
|
|||
|
||||
#: src/pages/events/RuleForm.ts src/pages/outposts/OutpostForm.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
#: src/pages/providers/saml/SAMLProviderForm.ts
|
||||
#: src/pages/sources/ldap/LDAPSourceForm.ts
|
||||
|
@ -2611,6 +2612,10 @@ msgstr "Issuer mode"
|
|||
#~ msgid "JWT Algorithm"
|
||||
#~ msgstr "JWT 算法"
|
||||
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
msgid "JWTs signed by certificates configured here can be used to authenticate to the provider."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
msgid "Key used to sign the tokens."
|
||||
msgstr "用于对令牌进行签名的密钥。"
|
||||
|
@ -2770,6 +2775,7 @@ msgstr "正在加载"
|
|||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
|
@ -6015,6 +6021,10 @@ msgstr "验证"
|
|||
msgid "Verification Certificate"
|
||||
msgstr "验证证书"
|
||||
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
msgid "Verification certificates"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/stages/email/EmailStageForm.ts
|
||||
msgid ""
|
||||
"Verify the user's email address by sending them a one-time-link. Can also be"
|
||||
|
|
|
@ -2332,6 +2332,7 @@ msgstr "隐藏服务账户"
|
|||
|
||||
#: src/pages/events/RuleForm.ts src/pages/outposts/OutpostForm.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
#: src/pages/providers/saml/SAMLProviderForm.ts
|
||||
#: src/pages/sources/ldap/LDAPSourceForm.ts
|
||||
|
@ -2611,6 +2612,10 @@ msgstr "Issuer mode"
|
|||
#~ msgid "JWT Algorithm"
|
||||
#~ msgstr "JWT 算法"
|
||||
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
msgid "JWTs signed by certificates configured here can be used to authenticate to the provider."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
msgid "Key used to sign the tokens."
|
||||
msgstr "用于对令牌进行签名的密钥。"
|
||||
|
@ -2770,6 +2775,7 @@ msgstr "正在加载"
|
|||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
|
@ -6015,6 +6021,10 @@ msgstr "验证"
|
|||
msgid "Verification Certificate"
|
||||
msgstr "验证证书"
|
||||
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
msgid "Verification certificates"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/stages/email/EmailStageForm.ts
|
||||
msgid ""
|
||||
"Verify the user's email address by sending them a one-time-link. Can also be"
|
||||
|
|
|
@ -117,7 +117,7 @@ export class CertificateKeyPairListPage extends TablePage<CertificateKeyPair> {
|
|||
}
|
||||
|
||||
renderExpanded(item: CertificateKeyPair): TemplateResult {
|
||||
return html`<td role="cell" colspan="3">
|
||||
return html`<td role="cell" colspan="4">
|
||||
<div class="pf-c-table__expandable-row-content">
|
||||
<dl class="pf-c-description-list pf-m-horizontal">
|
||||
<div class="pf-c-description-list__group">
|
||||
|
|
|
@ -292,6 +292,41 @@ ${this.instance?.redirectUris}</textarea
|
|||
${t`Hold control/command to select multiple items.`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${t`Verification certificates`}
|
||||
name="verificationKeys"
|
||||
>
|
||||
<select class="pf-c-form-control" multiple>
|
||||
${until(
|
||||
new CryptoApi(DEFAULT_CONFIG)
|
||||
.cryptoCertificatekeypairsList({
|
||||
ordering: "name",
|
||||
})
|
||||
.then((keys) => {
|
||||
return keys.results.map((key) => {
|
||||
const selected = (
|
||||
this.instance?.verificationKeys || []
|
||||
).some((su) => {
|
||||
return su == key.pk;
|
||||
});
|
||||
return html`<option
|
||||
value=${key.pk}
|
||||
?selected=${selected}
|
||||
>
|
||||
${key.name} (${key.privateKeyType?.toUpperCase()})
|
||||
</option>`;
|
||||
});
|
||||
}),
|
||||
html`<option>${t`Loading...`}</option>`,
|
||||
)}
|
||||
</select>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`JWTs signed by certificates configured here can be used to authenticate to the provider.`}
|
||||
</p>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`Hold control/command to select multiple items.`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${t`Subject mode`}
|
||||
?required=${true}
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
# Machine-to-machine authentication
|
||||
|
||||
Client credentials can be used for machine-to-machine communication authentication. Clients can authenticate themselves using service-accounts; standard client_id + client_secret is not sufficient. This behavior is due to providers only being able to have a single secret at any given time.
|
||||
|
||||
### Static authentication
|
||||
|
||||
Hence identification is based on service-accounts, and authentication is based on App-password tokens. These objects can be created in a single step using the *Create Service account* function.
|
||||
|
||||
An example request can look like this:
|
||||
|
||||
```
|
||||
POST /application/o/token/ HTTP/1.1
|
||||
Host: authentik.company
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
|
||||
grant_type=client_credentials&
|
||||
client_id=application_client_id&
|
||||
username=my-service-account&
|
||||
password=my-token
|
||||
```
|
||||
|
||||
This will return a JSON response with an `access_token`, which is a signed JWT token. This token can be sent along requests to other hosts, which can then validate the JWT based on the signing key configured in authentik.
|
||||
|
||||
### JWT-authentication
|
||||
|
||||
Starting with authentik 2022.4, you can authenticate and get a token using an existing JWT.
|
||||
|
||||
(For readability we will refer to the JWT issued by the external issuer/platform as input JWT, and the resulting JWT from authentik as the output JWT)
|
||||
|
||||
To configure this, the certificate used to sign the input JWT must be created in authentik. The certificate is enough, a private key is not required. Afterwards, configure the certificate in the OAuth2 provider settings under *Verification certificates*.
|
||||
|
||||
With this configure, any JWT issued by the configured certificates can be used to authenticate:
|
||||
|
||||
```
|
||||
POST /application/o/token/ HTTP/1.1
|
||||
Host: authentik.company
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
|
||||
grant_type=client_credentials&
|
||||
client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer&
|
||||
client_assertion=$inputJWT&
|
||||
client_id=application_client_id
|
||||
```
|
||||
|
||||
Alternatively, you can set the `client_secret` parameter to the `$inputJWT`, for applications which can set the password from a file but not other parameters.
|
||||
|
||||
Input JWTs are checked to be signed by any of the selected *Verification certificates*, and their `exp` attribute must not be now or in the past.
|
||||
|
||||
To do additional checks, you can use *[Expression policies](../../policies/expression)*:
|
||||
|
||||
```python
|
||||
return request.context["JWT"]["iss"] == "https://my.issuer"
|
||||
```
|
|
@ -4,7 +4,7 @@ title: OAuth2 Provider
|
|||
|
||||
This provider supports both generic OAuth2 as well as OpenID Connect
|
||||
|
||||
Scopes can be configured using Scope Mappings, a type of [Property Mappings](../property-mappings/#scope-mapping).
|
||||
Scopes can be configured using Scope Mappings, a type of [Property Mappings](../../property-mappings/#scope-mapping).
|
||||
|
||||
| Endpoint | URL |
|
||||
| -------------------- | -------------------------------------------------------------------- |
|
||||
|
@ -42,18 +42,4 @@ Refresh tokens can be used as long-lived tokens to access user data, and further
|
|||
|
||||
### `client_credentials`:
|
||||
|
||||
Client credentials can be used for machine-to-machine communication authentication. Clients can authenticate themselves using service-accounts; standard client_id + client_secret is not sufficient. This behavior is due to providers only being able to have a single secret at any given time.
|
||||
|
||||
Hence identification is based on service-accounts, and authentication is based on App-password tokens. These objects can be created in a single step using the *Create Service account* function.
|
||||
|
||||
An example request can look like this:
|
||||
|
||||
```
|
||||
POST /application/o/token/ HTTP/1.1
|
||||
Host: authentik.company
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
|
||||
grant_type=client_credentials&username=my-service-account&password=my-token&client_id=application_client_id
|
||||
```
|
||||
|
||||
This will return a JSON response with an `access_token`, which is a signed JWT token. This token can be sent along requests to other hosts, which can then validate the JWT based on the signing key configured in authentik.
|
||||
See [Machine-to-machine authentication](./client_credentials)
|
|
@ -73,4 +73,4 @@ This upgrade only applies if you are upgrading from a running 0.9 instance. auth
|
|||
|
||||
Because this upgrade brings the new OAuth2 Provider, the old providers will be lost in the process. Make sure to take note of the providers you want to bring over.
|
||||
|
||||
Another side-effect of this upgrade is the change of OAuth2 URLs, see [here](../providers/oauth2.md).
|
||||
Another side-effect of this upgrade is the change of OAuth2 URLs, see [here](../providers/oauth2).
|
||||
|
|
|
@ -33,7 +33,14 @@ module.exports = {
|
|||
type: "category",
|
||||
label: "Providers",
|
||||
items: [
|
||||
"providers/oauth2",
|
||||
{
|
||||
type: "category",
|
||||
label: "OAuth2 Provider",
|
||||
items: [
|
||||
"providers/oauth2/index",
|
||||
"providers/oauth2/client_credentials",
|
||||
],
|
||||
},
|
||||
"providers/saml",
|
||||
{
|
||||
type: "category",
|
||||
|
|
Reference in New Issue