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()
|
LOGGER = get_logger()
|
||||||
USER_ATTRIBUTE_DEBUG = "goauthentik.io/user/debug"
|
USER_ATTRIBUTE_DEBUG = "goauthentik.io/user/debug"
|
||||||
USER_ATTRIBUTE_SA = "goauthentik.io/user/service-account"
|
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_SOURCES = "goauthentik.io/user/sources"
|
||||||
USER_ATTRIBUTE_TOKEN_EXPIRING = "goauthentik.io/user/token-expires" # nosec
|
USER_ATTRIBUTE_TOKEN_EXPIRING = "goauthentik.io/user/token-expires" # nosec
|
||||||
USER_ATTRIBUTE_CHANGE_USERNAME = "goauthentik.io/user/can-change-username"
|
USER_ATTRIBUTE_CHANGE_USERNAME = "goauthentik.io/user/can-change-username"
|
||||||
|
|
|
@ -1,10 +1,18 @@
|
||||||
"""authentik core tasks"""
|
"""authentik core tasks"""
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
from django.contrib.sessions.backends.cache import KEY_PREFIX
|
from django.contrib.sessions.backends.cache import KEY_PREFIX
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
from structlog.stdlib import get_logger
|
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 (
|
from authentik.events.monitored_tasks import (
|
||||||
MonitoredTask,
|
MonitoredTask,
|
||||||
TaskResult,
|
TaskResult,
|
||||||
|
@ -42,3 +50,22 @@ def clean_expired_models(self: MonitoredTask):
|
||||||
LOGGER.debug("Expired sessions", model=AuthenticatedSession, amount=amount)
|
LOGGER.debug("Expired sessions", model=AuthenticatedSession, amount=amount)
|
||||||
messages.append(f"Expired {amount} {AuthenticatedSession._meta.verbose_name_plural}")
|
messages.append(f"Expired {amount} {AuthenticatedSession._meta.verbose_name_plural}")
|
||||||
self.set_status(TaskResult(TaskResultStatus.SUCCESSFUL, messages))
|
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 json import loads
|
||||||
|
|
||||||
from django.urls.base import reverse
|
from django.urls.base import reverse
|
||||||
from django.utils.timezone import now
|
|
||||||
from guardian.shortcuts import get_anonymous_user
|
from guardian.shortcuts import get_anonymous_user
|
||||||
from rest_framework.test import APITestCase
|
from rest_framework.test import APITestCase
|
||||||
|
|
||||||
from authentik.core.models import USER_ATTRIBUTE_TOKEN_EXPIRING, Token, TokenIntents, User
|
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
|
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.intent, TokenIntents.INTENT_API)
|
||||||
self.assertEqual(token.expiring, False)
|
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):
|
def test_list(self):
|
||||||
"""Test Token List (Test normal authentication)"""
|
"""Test Token List (Test normal authentication)"""
|
||||||
token_should: Token = Token.objects.create(
|
token_should: Token = Token.objects.create(
|
||||||
|
|
|
@ -34,6 +34,7 @@ class OAuth2ProviderSerializer(ProviderSerializer):
|
||||||
"sub_mode",
|
"sub_mode",
|
||||||
"property_mappings",
|
"property_mappings",
|
||||||
"issuer_mode",
|
"issuer_mode",
|
||||||
|
"verification_keys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,10 @@ GRANT_TYPE_AUTHORIZATION_CODE = "authorization_code"
|
||||||
GRANT_TYPE_REFRESH_TOKEN = "refresh_token" # nosec
|
GRANT_TYPE_REFRESH_TOKEN = "refresh_token" # nosec
|
||||||
GRANT_TYPE_CLIENT_CREDENTIALS = "client_credentials"
|
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_NONE = "none"
|
||||||
PROMPT_CONSNET = "consent"
|
PROMPT_CONSNET = "consent"
|
||||||
PROMPT_LOGIN = "login"
|
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(
|
signing_key = models.ForeignKey(
|
||||||
CertificateKeyPair,
|
CertificateKeyPair,
|
||||||
verbose_name=_("RSA Key"),
|
verbose_name=_("Signing Key"),
|
||||||
on_delete=models.SET_NULL,
|
on_delete=models.SET_NULL,
|
||||||
null=True,
|
null=True,
|
||||||
help_text=_(
|
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(
|
def create_refresh_token(
|
||||||
self, user: User, scope: list[str], request: HttpRequest
|
self, user: User, scope: list[str], request: HttpRequest
|
||||||
) -> "RefreshToken":
|
) -> "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:
|
if signing_key:
|
||||||
private_key = signing_key.private_key
|
private_key = signing_key.private_key
|
||||||
print(type(private_key))
|
|
||||||
if isinstance(private_key, RSAPrivateKey):
|
if isinstance(private_key, RSAPrivateKey):
|
||||||
public_key: RSAPublicKey = private_key.public_key()
|
public_key: RSAPublicKey = private_key.public_key()
|
||||||
public_numbers = public_key.public_numbers()
|
public_numbers = public_key.public_numbers()
|
||||||
|
|
|
@ -5,14 +5,27 @@ from hashlib import sha256
|
||||||
from typing import Any, Optional
|
from typing import Any, Optional
|
||||||
|
|
||||||
from django.http import HttpRequest, HttpResponse
|
from django.http import HttpRequest, HttpResponse
|
||||||
|
from django.utils.timezone import datetime, now
|
||||||
from django.views import View
|
from django.views import View
|
||||||
|
from jwt import InvalidTokenError, decode
|
||||||
from structlog.stdlib import get_logger
|
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.events.models import Event, EventAction
|
||||||
from authentik.lib.utils.time import timedelta_from_string
|
from authentik.lib.utils.time import timedelta_from_string
|
||||||
from authentik.policies.engine import PolicyEngine
|
from authentik.policies.engine import PolicyEngine
|
||||||
from authentik.providers.oauth2.constants import (
|
from authentik.providers.oauth2.constants import (
|
||||||
|
CLIENT_ASSERTION,
|
||||||
|
CLIENT_ASSERTION_TYPE,
|
||||||
|
CLIENT_ASSERTION_TYPE_JWT,
|
||||||
GRANT_TYPE_AUTHORIZATION_CODE,
|
GRANT_TYPE_AUTHORIZATION_CODE,
|
||||||
GRANT_TYPE_CLIENT_CREDENTIALS,
|
GRANT_TYPE_CLIENT_CREDENTIALS,
|
||||||
GRANT_TYPE_REFRESH_TOKEN,
|
GRANT_TYPE_REFRESH_TOKEN,
|
||||||
|
@ -21,6 +34,7 @@ from authentik.providers.oauth2.errors import TokenError, UserAuthError
|
||||||
from authentik.providers.oauth2.models import (
|
from authentik.providers.oauth2.models import (
|
||||||
AuthorizationCode,
|
AuthorizationCode,
|
||||||
ClientTypes,
|
ClientTypes,
|
||||||
|
JWTAlgorithms,
|
||||||
OAuth2Provider,
|
OAuth2Provider,
|
||||||
RefreshToken,
|
RefreshToken,
|
||||||
)
|
)
|
||||||
|
@ -187,6 +201,8 @@ class TokenParams:
|
||||||
raise TokenError("invalid_grant")
|
raise TokenError("invalid_grant")
|
||||||
|
|
||||||
def __post_init_client_credentials(self, request: HttpRequest):
|
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
|
# Authenticate user based on credentials
|
||||||
username = request.POST.get("username")
|
username = request.POST.get("username")
|
||||||
password = request.POST.get("password")
|
password = request.POST.get("password")
|
||||||
|
@ -222,6 +238,72 @@ class TokenParams:
|
||||||
if not result.passing:
|
if not result.passing:
|
||||||
LOGGER.info("User not authenticated for application", user=self.user, app=app)
|
LOGGER.info("User not authenticated for application", user=self.user, app=app)
|
||||||
raise TokenError("invalid_grant")
|
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):
|
class TokenView(View):
|
||||||
|
|
|
@ -345,6 +345,11 @@ CELERY_BEAT_SCHEDULE = {
|
||||||
"schedule": crontab(hour="*/24", minute=0),
|
"schedule": crontab(hour="*/24", minute=0),
|
||||||
"options": {"queue": "authentik_scheduled"},
|
"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_CREATE_MISSING_QUEUES = True
|
||||||
CELERY_TASK_DEFAULT_QUEUE = "authentik"
|
CELERY_TASK_DEFAULT_QUEUE = "authentik"
|
||||||
|
|
|
@ -6,7 +6,7 @@ from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
class AuthentikSourceSAMLConfig(AppConfig):
|
class AuthentikSourceSAMLConfig(AppConfig):
|
||||||
"""authentik saml_idp app config"""
|
"""authentik saml source app config"""
|
||||||
|
|
||||||
name = "authentik.sources.saml"
|
name = "authentik.sources.saml"
|
||||||
label = "authentik_sources_saml"
|
label = "authentik_sources_saml"
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
"""authentik saml source processor"""
|
"""authentik saml source processor"""
|
||||||
from base64 import b64decode
|
from base64 import b64decode
|
||||||
|
from time import mktime
|
||||||
from typing import TYPE_CHECKING, Any
|
from typing import TYPE_CHECKING, Any
|
||||||
|
|
||||||
import xmlsec
|
import xmlsec
|
||||||
|
@ -7,9 +8,16 @@ from defusedxml.lxml import fromstring
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from django.core.exceptions import SuspiciousOperation
|
from django.core.exceptions import SuspiciousOperation
|
||||||
from django.http import HttpRequest, HttpResponse
|
from django.http import HttpRequest, HttpResponse
|
||||||
|
from django.utils.timezone import now
|
||||||
from structlog.stdlib import get_logger
|
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.models import Flow
|
||||||
from authentik.flows.planner import (
|
from authentik.flows.planner import (
|
||||||
PLAN_CONTEXT_PENDING_USER,
|
PLAN_CONTEXT_PENDING_USER,
|
||||||
|
@ -19,6 +27,7 @@ from authentik.flows.planner import (
|
||||||
FlowPlanner,
|
FlowPlanner,
|
||||||
)
|
)
|
||||||
from authentik.flows.views.executor import NEXT_ARG_NAME, SESSION_KEY_GET, SESSION_KEY_PLAN
|
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.lib.utils.urls import redirect_with_qs
|
||||||
from authentik.policies.utils import delete_none_keys
|
from authentik.policies.utils import delete_none_keys
|
||||||
from authentik.sources.saml.exceptions import (
|
from authentik.sources.saml.exceptions import (
|
||||||
|
@ -124,9 +133,19 @@ class ResponseProcessor:
|
||||||
on logout and periodically."""
|
on logout and periodically."""
|
||||||
# Create a temporary User
|
# Create a temporary User
|
||||||
name_id = self._get_name_id().text
|
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(
|
user: User = User.objects.create(
|
||||||
username=name_id,
|
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)
|
LOGGER.debug("Created temporary user for NameID Transient", username=name_id)
|
||||||
user.set_unusable_password()
|
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 django.http import HttpRequest
|
||||||
from structlog.stdlib import get_logger
|
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()
|
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"""
|
"""Delete temporary user if the `delete_on_logout` flag is enabled"""
|
||||||
if not user:
|
if not user:
|
||||||
return
|
return
|
||||||
if "saml" in user.attributes:
|
if user.attributes.get(USER_ATTRIBUTE_DELETE_ON_LOGOUT, False):
|
||||||
if "delete_on_logout" in user.attributes["saml"]:
|
LOGGER.debug("Deleted temporary user", user=user)
|
||||||
if user.attributes["saml"]["delete_on_logout"]:
|
user.delete()
|
||||||
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
|
type: string
|
||||||
format: uuid
|
format: uuid
|
||||||
nullable: true
|
nullable: true
|
||||||
title: RSA Key
|
|
||||||
description: Key used to sign the tokens. Only required when JWT Algorithm
|
description: Key used to sign the tokens. Only required when JWT Algorithm
|
||||||
is set to RS256.
|
is set to RS256.
|
||||||
redirect_uris:
|
redirect_uris:
|
||||||
|
@ -23106,6 +23105,15 @@ components:
|
||||||
allOf:
|
allOf:
|
||||||
- $ref: '#/components/schemas/IssuerModeEnum'
|
- $ref: '#/components/schemas/IssuerModeEnum'
|
||||||
description: Configure how the issuer field of the ID Token should be filled.
|
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:
|
required:
|
||||||
- assigned_application_name
|
- assigned_application_name
|
||||||
- assigned_application_slug
|
- assigned_application_slug
|
||||||
|
@ -23116,6 +23124,7 @@ components:
|
||||||
- pk
|
- pk
|
||||||
- verbose_name
|
- verbose_name
|
||||||
- verbose_name_plural
|
- verbose_name_plural
|
||||||
|
- verification_keys
|
||||||
OAuth2ProviderRequest:
|
OAuth2ProviderRequest:
|
||||||
type: object
|
type: object
|
||||||
description: OAuth2Provider Serializer
|
description: OAuth2Provider Serializer
|
||||||
|
@ -23163,7 +23172,6 @@ components:
|
||||||
type: string
|
type: string
|
||||||
format: uuid
|
format: uuid
|
||||||
nullable: true
|
nullable: true
|
||||||
title: RSA Key
|
|
||||||
description: Key used to sign the tokens. Only required when JWT Algorithm
|
description: Key used to sign the tokens. Only required when JWT Algorithm
|
||||||
is set to RS256.
|
is set to RS256.
|
||||||
redirect_uris:
|
redirect_uris:
|
||||||
|
@ -23178,9 +23186,19 @@ components:
|
||||||
allOf:
|
allOf:
|
||||||
- $ref: '#/components/schemas/IssuerModeEnum'
|
- $ref: '#/components/schemas/IssuerModeEnum'
|
||||||
description: Configure how the issuer field of the ID Token should be filled.
|
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:
|
required:
|
||||||
- authorization_flow
|
- authorization_flow
|
||||||
- name
|
- name
|
||||||
|
- verification_keys
|
||||||
OAuth2ProviderSetupURLs:
|
OAuth2ProviderSetupURLs:
|
||||||
type: object
|
type: object
|
||||||
description: OAuth2 Provider Metadata serializer
|
description: OAuth2 Provider Metadata serializer
|
||||||
|
@ -27488,7 +27506,6 @@ components:
|
||||||
type: string
|
type: string
|
||||||
format: uuid
|
format: uuid
|
||||||
nullable: true
|
nullable: true
|
||||||
title: RSA Key
|
|
||||||
description: Key used to sign the tokens. Only required when JWT Algorithm
|
description: Key used to sign the tokens. Only required when JWT Algorithm
|
||||||
is set to RS256.
|
is set to RS256.
|
||||||
redirect_uris:
|
redirect_uris:
|
||||||
|
@ -27503,6 +27520,15 @@ components:
|
||||||
allOf:
|
allOf:
|
||||||
- $ref: '#/components/schemas/IssuerModeEnum'
|
- $ref: '#/components/schemas/IssuerModeEnum'
|
||||||
description: Configure how the issuer field of the ID Token should be filled.
|
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:
|
PatchedOAuthSourceRequest:
|
||||||
type: object
|
type: object
|
||||||
description: OAuth Source Serializer
|
description: OAuth Source Serializer
|
||||||
|
|
|
@ -2327,6 +2327,7 @@ msgstr "Interne Konten ausblenden"
|
||||||
#: src/pages/events/RuleForm.ts
|
#: src/pages/events/RuleForm.ts
|
||||||
#: src/pages/outposts/OutpostForm.ts
|
#: src/pages/outposts/OutpostForm.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/saml/SAMLProviderForm.ts
|
#: src/pages/providers/saml/SAMLProviderForm.ts
|
||||||
#: src/pages/sources/ldap/LDAPSourceForm.ts
|
#: src/pages/sources/ldap/LDAPSourceForm.ts
|
||||||
|
@ -2582,6 +2583,10 @@ msgstr "Ausstellermodus"
|
||||||
#~ msgid "JWT Algorithm"
|
#~ msgid "JWT Algorithm"
|
||||||
#~ msgstr "JWT Algorithmus"
|
#~ 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
|
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||||
msgid "Key used to sign the tokens."
|
msgid "Key used to sign the tokens."
|
||||||
msgstr "Schlüssel zum Signieren der Token."
|
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/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
|
#: 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"
|
msgid "Verification Certificate"
|
||||||
msgstr "Zertifikat zur Überprüfung"
|
msgstr "Zertifikat zur Überprüfung"
|
||||||
|
|
||||||
|
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||||
|
msgid "Verification certificates"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/stages/email/EmailStageForm.ts
|
#: 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."
|
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."
|
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/events/RuleForm.ts
|
||||||
#: src/pages/outposts/OutpostForm.ts
|
#: src/pages/outposts/OutpostForm.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/saml/SAMLProviderForm.ts
|
#: src/pages/providers/saml/SAMLProviderForm.ts
|
||||||
#: src/pages/sources/ldap/LDAPSourceForm.ts
|
#: src/pages/sources/ldap/LDAPSourceForm.ts
|
||||||
|
@ -2624,6 +2625,10 @@ msgstr "Issuer mode"
|
||||||
#~ msgid "JWT Algorithm"
|
#~ msgid "JWT Algorithm"
|
||||||
#~ msgstr "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
|
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||||
msgid "Key used to sign the tokens."
|
msgid "Key used to sign the tokens."
|
||||||
msgstr "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/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
|
#: 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"
|
msgid "Verification Certificate"
|
||||||
msgstr "Verification Certificate"
|
msgstr "Verification Certificate"
|
||||||
|
|
||||||
|
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||||
|
msgid "Verification certificates"
|
||||||
|
msgstr "Verification certificates"
|
||||||
|
|
||||||
#: src/pages/stages/email/EmailStageForm.ts
|
#: 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."
|
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."
|
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/events/RuleForm.ts
|
||||||
#: src/pages/outposts/OutpostForm.ts
|
#: src/pages/outposts/OutpostForm.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/saml/SAMLProviderForm.ts
|
#: src/pages/providers/saml/SAMLProviderForm.ts
|
||||||
#: src/pages/sources/ldap/LDAPSourceForm.ts
|
#: src/pages/sources/ldap/LDAPSourceForm.ts
|
||||||
|
@ -2575,6 +2576,10 @@ msgstr "Modo emisor"
|
||||||
#~ msgid "JWT Algorithm"
|
#~ msgid "JWT Algorithm"
|
||||||
#~ msgstr "algoritmo JWT"
|
#~ 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
|
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||||
msgid "Key used to sign the tokens."
|
msgid "Key used to sign the tokens."
|
||||||
msgstr "Clave utilizada para firmar los 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/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
|
#: 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"
|
msgid "Verification Certificate"
|
||||||
msgstr "Certificado de verificación"
|
msgstr "Certificado de verificación"
|
||||||
|
|
||||||
|
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||||
|
msgid "Verification certificates"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/stages/email/EmailStageForm.ts
|
#: 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."
|
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."
|
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/events/RuleForm.ts
|
||||||
#: src/pages/outposts/OutpostForm.ts
|
#: src/pages/outposts/OutpostForm.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/saml/SAMLProviderForm.ts
|
#: src/pages/providers/saml/SAMLProviderForm.ts
|
||||||
#: src/pages/sources/ldap/LDAPSourceForm.ts
|
#: src/pages/sources/ldap/LDAPSourceForm.ts
|
||||||
|
@ -2605,6 +2606,10 @@ msgstr "Mode de l'émetteur"
|
||||||
#~ msgid "JWT Algorithm"
|
#~ msgid "JWT Algorithm"
|
||||||
#~ msgstr "Algorithme JWT"
|
#~ 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
|
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||||
msgid "Key used to sign the tokens."
|
msgid "Key used to sign the tokens."
|
||||||
msgstr ""
|
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/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
|
#: 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"
|
msgid "Verification Certificate"
|
||||||
msgstr "Certificat de validation"
|
msgstr "Certificat de validation"
|
||||||
|
|
||||||
|
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||||
|
msgid "Verification certificates"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/stages/email/EmailStageForm.ts
|
#: 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."
|
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."
|
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/events/RuleForm.ts
|
||||||
#: src/pages/outposts/OutpostForm.ts
|
#: src/pages/outposts/OutpostForm.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/saml/SAMLProviderForm.ts
|
#: src/pages/providers/saml/SAMLProviderForm.ts
|
||||||
#: src/pages/sources/ldap/LDAPSourceForm.ts
|
#: src/pages/sources/ldap/LDAPSourceForm.ts
|
||||||
|
@ -2572,6 +2573,10 @@ msgstr "Tryb wystawcy"
|
||||||
#~ msgid "JWT Algorithm"
|
#~ msgid "JWT Algorithm"
|
||||||
#~ msgstr "Algorytm JWT"
|
#~ 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
|
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||||
msgid "Key used to sign the tokens."
|
msgid "Key used to sign the tokens."
|
||||||
msgstr "Klucz używany do podpisywania tokenów."
|
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/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
|
#: 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"
|
msgid "Verification Certificate"
|
||||||
msgstr "Certyfikat weryfikacji"
|
msgstr "Certyfikat weryfikacji"
|
||||||
|
|
||||||
|
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||||
|
msgid "Verification certificates"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/stages/email/EmailStageForm.ts
|
#: 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."
|
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."
|
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/events/RuleForm.ts
|
||||||
#: src/pages/outposts/OutpostForm.ts
|
#: src/pages/outposts/OutpostForm.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/saml/SAMLProviderForm.ts
|
#: src/pages/providers/saml/SAMLProviderForm.ts
|
||||||
#: src/pages/sources/ldap/LDAPSourceForm.ts
|
#: src/pages/sources/ldap/LDAPSourceForm.ts
|
||||||
|
@ -2614,6 +2615,10 @@ msgstr ""
|
||||||
#~ msgid "JWT Algorithm"
|
#~ msgid "JWT Algorithm"
|
||||||
#~ msgstr ""
|
#~ 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
|
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||||
msgid "Key used to sign the tokens."
|
msgid "Key used to sign the tokens."
|
||||||
msgstr ""
|
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/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
|
#: 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"
|
msgid "Verification Certificate"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||||
|
msgid "Verification certificates"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/stages/email/EmailStageForm.ts
|
#: 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."
|
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 ""
|
msgstr ""
|
||||||
|
|
|
@ -2318,6 +2318,7 @@ msgstr "Hizmet hesaplarını gizle"
|
||||||
#: src/pages/events/RuleForm.ts
|
#: src/pages/events/RuleForm.ts
|
||||||
#: src/pages/outposts/OutpostForm.ts
|
#: src/pages/outposts/OutpostForm.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/saml/SAMLProviderForm.ts
|
#: src/pages/providers/saml/SAMLProviderForm.ts
|
||||||
#: src/pages/sources/ldap/LDAPSourceForm.ts
|
#: src/pages/sources/ldap/LDAPSourceForm.ts
|
||||||
|
@ -2576,6 +2577,10 @@ msgstr "Yayımcı kipi"
|
||||||
#~ msgid "JWT Algorithm"
|
#~ msgid "JWT Algorithm"
|
||||||
#~ msgstr "JWT Algoritması"
|
#~ 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
|
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||||
msgid "Key used to sign the tokens."
|
msgid "Key used to sign the tokens."
|
||||||
msgstr "Anahtar belirteçleri imzalamak için kullanılır."
|
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/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
|
#: 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"
|
msgid "Verification Certificate"
|
||||||
msgstr "Doğrulama Sertifikası"
|
msgstr "Doğrulama Sertifikası"
|
||||||
|
|
||||||
|
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||||
|
msgid "Verification certificates"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/stages/email/EmailStageForm.ts
|
#: 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."
|
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."
|
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/events/RuleForm.ts src/pages/outposts/OutpostForm.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/saml/SAMLProviderForm.ts
|
#: src/pages/providers/saml/SAMLProviderForm.ts
|
||||||
#: src/pages/sources/ldap/LDAPSourceForm.ts
|
#: src/pages/sources/ldap/LDAPSourceForm.ts
|
||||||
|
@ -2612,6 +2613,10 @@ msgstr "Issuer 模式"
|
||||||
#~ msgid "JWT Algorithm"
|
#~ msgid "JWT Algorithm"
|
||||||
#~ msgstr "JWT 算法"
|
#~ 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
|
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||||
msgid "Key used to sign the tokens."
|
msgid "Key used to sign the tokens."
|
||||||
msgstr "用于签名令牌的密钥。"
|
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/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
|
#: 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"
|
msgid "Verification Certificate"
|
||||||
msgstr "验证证书"
|
msgstr "验证证书"
|
||||||
|
|
||||||
|
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||||
|
msgid "Verification certificates"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/stages/email/EmailStageForm.ts
|
#: src/pages/stages/email/EmailStageForm.ts
|
||||||
msgid ""
|
msgid ""
|
||||||
"Verify the user's email address by sending them a one-time-link. Can also be"
|
"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/events/RuleForm.ts src/pages/outposts/OutpostForm.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/saml/SAMLProviderForm.ts
|
#: src/pages/providers/saml/SAMLProviderForm.ts
|
||||||
#: src/pages/sources/ldap/LDAPSourceForm.ts
|
#: src/pages/sources/ldap/LDAPSourceForm.ts
|
||||||
|
@ -2611,6 +2612,10 @@ msgstr "Issuer mode"
|
||||||
#~ msgid "JWT Algorithm"
|
#~ msgid "JWT Algorithm"
|
||||||
#~ msgstr "JWT 算法"
|
#~ 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
|
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||||
msgid "Key used to sign the tokens."
|
msgid "Key used to sign the tokens."
|
||||||
msgstr "用于对令牌进行签名的密钥。"
|
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/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
|
#: 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"
|
msgid "Verification Certificate"
|
||||||
msgstr "验证证书"
|
msgstr "验证证书"
|
||||||
|
|
||||||
|
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||||
|
msgid "Verification certificates"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/stages/email/EmailStageForm.ts
|
#: src/pages/stages/email/EmailStageForm.ts
|
||||||
msgid ""
|
msgid ""
|
||||||
"Verify the user's email address by sending them a one-time-link. Can also be"
|
"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/events/RuleForm.ts src/pages/outposts/OutpostForm.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/saml/SAMLProviderForm.ts
|
#: src/pages/providers/saml/SAMLProviderForm.ts
|
||||||
#: src/pages/sources/ldap/LDAPSourceForm.ts
|
#: src/pages/sources/ldap/LDAPSourceForm.ts
|
||||||
|
@ -2611,6 +2612,10 @@ msgstr "Issuer mode"
|
||||||
#~ msgid "JWT Algorithm"
|
#~ msgid "JWT Algorithm"
|
||||||
#~ msgstr "JWT 算法"
|
#~ 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
|
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||||
msgid "Key used to sign the tokens."
|
msgid "Key used to sign the tokens."
|
||||||
msgstr "用于对令牌进行签名的密钥。"
|
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/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
|
#: 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"
|
msgid "Verification Certificate"
|
||||||
msgstr "验证证书"
|
msgstr "验证证书"
|
||||||
|
|
||||||
|
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||||
|
msgid "Verification certificates"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/stages/email/EmailStageForm.ts
|
#: src/pages/stages/email/EmailStageForm.ts
|
||||||
msgid ""
|
msgid ""
|
||||||
"Verify the user's email address by sending them a one-time-link. Can also be"
|
"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 {
|
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">
|
<div class="pf-c-table__expandable-row-content">
|
||||||
<dl class="pf-c-description-list pf-m-horizontal">
|
<dl class="pf-c-description-list pf-m-horizontal">
|
||||||
<div class="pf-c-description-list__group">
|
<div class="pf-c-description-list__group">
|
||||||
|
|
|
@ -292,6 +292,41 @@ ${this.instance?.redirectUris}</textarea
|
||||||
${t`Hold control/command to select multiple items.`}
|
${t`Hold control/command to select multiple items.`}
|
||||||
</p>
|
</p>
|
||||||
</ak-form-element-horizontal>
|
</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
|
<ak-form-element-horizontal
|
||||||
label=${t`Subject mode`}
|
label=${t`Subject mode`}
|
||||||
?required=${true}
|
?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
|
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 |
|
| 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`:
|
||||||
|
|
||||||
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.
|
See [Machine-to-machine authentication](./client_credentials)
|
||||||
|
|
||||||
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.
|
|
|
@ -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.
|
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",
|
type: "category",
|
||||||
label: "Providers",
|
label: "Providers",
|
||||||
items: [
|
items: [
|
||||||
"providers/oauth2",
|
{
|
||||||
|
type: "category",
|
||||||
|
label: "OAuth2 Provider",
|
||||||
|
items: [
|
||||||
|
"providers/oauth2/index",
|
||||||
|
"providers/oauth2/client_credentials",
|
||||||
|
],
|
||||||
|
},
|
||||||
"providers/saml",
|
"providers/saml",
|
||||||
{
|
{
|
||||||
type: "category",
|
type: "category",
|
||||||
|
|
Reference in New Issue