providers/oauth2: remove jwt_alg field and set algorithm based on selected keypair, select HS256 when no keypair is selected
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
parent
89696edbee
commit
2f3026084e
|
@ -146,7 +146,7 @@ class TestCrypto(APITestCase):
|
|||
client_secret=generate_key(),
|
||||
authorization_flow=create_test_flow(),
|
||||
redirect_uris="http://localhost",
|
||||
rsa_key=keypair,
|
||||
signing_key=keypair,
|
||||
)
|
||||
response = self.client.get(
|
||||
reverse(
|
||||
|
|
|
@ -7,25 +7,18 @@ from rest_framework.fields import CharField
|
|||
from rest_framework.generics import get_object_or_404
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.serializers import ValidationError
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
|
||||
from authentik.core.api.providers import ProviderSerializer
|
||||
from authentik.core.api.used_by import UsedByMixin
|
||||
from authentik.core.api.utils import PassiveSerializer
|
||||
from authentik.core.models import Provider
|
||||
from authentik.providers.oauth2.models import JWTAlgorithms, OAuth2Provider
|
||||
from authentik.providers.oauth2.models import OAuth2Provider
|
||||
|
||||
|
||||
class OAuth2ProviderSerializer(ProviderSerializer):
|
||||
"""OAuth2Provider Serializer"""
|
||||
|
||||
def validate_jwt_alg(self, value):
|
||||
"""Ensure that when RS256 is selected, a certificate-key-pair is selected"""
|
||||
if self.initial_data.get("rsa_key", None) is None and value == JWTAlgorithms.RS256:
|
||||
raise ValidationError(_("RS256 requires a Certificate-Key-Pair to be selected."))
|
||||
return value
|
||||
|
||||
class Meta:
|
||||
|
||||
model = OAuth2Provider
|
||||
|
@ -37,8 +30,7 @@ class OAuth2ProviderSerializer(ProviderSerializer):
|
|||
"access_code_validity",
|
||||
"token_validity",
|
||||
"include_claims_in_id_token",
|
||||
"jwt_alg",
|
||||
"rsa_key",
|
||||
"signing_key",
|
||||
"redirect_uris",
|
||||
"sub_mode",
|
||||
"property_mappings",
|
||||
|
@ -73,8 +65,7 @@ class OAuth2ProviderViewSet(UsedByMixin, ModelViewSet):
|
|||
"access_code_validity",
|
||||
"token_validity",
|
||||
"include_claims_in_id_token",
|
||||
"jwt_alg",
|
||||
"rsa_key",
|
||||
"signing_key",
|
||||
"redirect_uris",
|
||||
"sub_mode",
|
||||
"property_mappings",
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
# Generated by Django 4.0 on 2021-12-22 21:04
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('authentik_providers_oauth2', '0007_auto_20201016_1107_squashed_0017_alter_oauth2provider_token_validity'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name='oauth2provider',
|
||||
old_name='rsa_key',
|
||||
new_name='signing_key',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='oauth2provider',
|
||||
name='jwt_alg',
|
||||
),
|
||||
]
|
|
@ -8,6 +8,8 @@ from datetime import datetime
|
|||
from hashlib import sha256
|
||||
from typing import Any, Optional, Type
|
||||
from urllib.parse import urlparse
|
||||
from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePrivateKey
|
||||
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey
|
||||
|
||||
from dacite import from_dict
|
||||
from django.db import models
|
||||
|
@ -88,6 +90,7 @@ class JWTAlgorithms(models.TextChoices):
|
|||
|
||||
HS256 = "HS256", _("HS256 (Symmetric Encryption)")
|
||||
RS256 = "RS256", _("RS256 (Asymmetric Encryption)")
|
||||
EC256 = "EC256", _("EC256 (Asymmetric Encryption)")
|
||||
|
||||
|
||||
class ScopeMapping(PropertyMapping):
|
||||
|
@ -145,13 +148,6 @@ class OAuth2Provider(Provider):
|
|||
verbose_name=_("Client Secret"),
|
||||
default=generate_key,
|
||||
)
|
||||
jwt_alg = models.CharField(
|
||||
max_length=10,
|
||||
choices=JWTAlgorithms.choices,
|
||||
default=JWTAlgorithms.RS256,
|
||||
verbose_name=_("JWT Algorithm"),
|
||||
help_text=_(JWTAlgorithms.__doc__),
|
||||
)
|
||||
redirect_uris = models.TextField(
|
||||
default="",
|
||||
blank=True,
|
||||
|
@ -207,7 +203,7 @@ class OAuth2Provider(Provider):
|
|||
help_text=_(("Configure how the issuer field of the ID Token should be filled.")),
|
||||
)
|
||||
|
||||
rsa_key = models.ForeignKey(
|
||||
signing_key = models.ForeignKey(
|
||||
CertificateKeyPair,
|
||||
verbose_name=_("RSA Key"),
|
||||
on_delete=models.SET_NULL,
|
||||
|
@ -231,29 +227,18 @@ class OAuth2Provider(Provider):
|
|||
token.access_token = token.create_access_token(user, request)
|
||||
return token
|
||||
|
||||
def get_jwt_key(self) -> str:
|
||||
"""
|
||||
Takes a provider and returns the set of keys associated with it.
|
||||
Returns a list of keys.
|
||||
"""
|
||||
if self.jwt_alg == JWTAlgorithms.RS256:
|
||||
# if the user selected RS256 but didn't select a
|
||||
# CertificateKeyPair, we fall back to HS256
|
||||
if not self.rsa_key:
|
||||
Event.new(
|
||||
EventAction.CONFIGURATION_ERROR,
|
||||
provider=self,
|
||||
message="Provider was configured for RS256, but no key was selected.",
|
||||
).save()
|
||||
self.jwt_alg = JWTAlgorithms.HS256
|
||||
self.save()
|
||||
else:
|
||||
return self.rsa_key.key_data
|
||||
|
||||
if self.jwt_alg == JWTAlgorithms.HS256:
|
||||
return self.client_secret
|
||||
|
||||
raise Exception("Unsupported key algorithm.")
|
||||
def get_jwt_key(self) -> tuple[str, str]:
|
||||
"""Get either the configured certificate or the client secret"""
|
||||
if not self.signing_key:
|
||||
# No Certificate at all, assume HS256
|
||||
return self.client_secret, JWTAlgorithms.HS256
|
||||
key: CertificateKeyPair = self.signing_key
|
||||
private_key = key.private_key
|
||||
if isinstance(private_key, RSAPrivateKey):
|
||||
return key.key_data, JWTAlgorithms.RS256
|
||||
if isinstance(private_key, EllipticCurvePrivateKey):
|
||||
return key.key_data, JWTAlgorithms.EC256
|
||||
raise Exception(f"Invalid private key type: {type(private_key)}")
|
||||
|
||||
def get_issuer(self, request: HttpRequest) -> Optional[str]:
|
||||
"""Get issuer, based on request"""
|
||||
|
@ -293,13 +278,13 @@ class OAuth2Provider(Provider):
|
|||
def encode(self, payload: dict[str, Any]) -> str:
|
||||
"""Represent the ID Token as a JSON Web Token (JWT)."""
|
||||
headers = {}
|
||||
if self.rsa_key:
|
||||
headers["kid"] = self.rsa_key.kid
|
||||
key = self.get_jwt_key()
|
||||
if self.signing_key:
|
||||
headers["kid"] = self.signing_key.kid
|
||||
key, alg = self.get_jwt_key()
|
||||
# If the provider does not have an RSA Key assigned, it was switched to Symmetric
|
||||
self.refresh_from_db()
|
||||
# pyright: reportGeneralTypeIssues=false
|
||||
return encode(payload, key, algorithm=self.jwt_alg, headers=headers)
|
||||
return encode(payload, key, algorithm=alg, headers=headers)
|
||||
|
||||
class Meta:
|
||||
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
"""Test oauth2 provider API"""
|
||||
from django.urls import reverse
|
||||
from rest_framework.test import APITestCase
|
||||
|
||||
from authentik.core.tests.utils import create_test_admin_user, create_test_flow
|
||||
from authentik.providers.oauth2.models import JWTAlgorithms
|
||||
|
||||
|
||||
class TestOAuth2ProviderAPI(APITestCase):
|
||||
"""Test oauth2 provider API"""
|
||||
|
||||
def setUp(self) -> None:
|
||||
super().setUp()
|
||||
self.user = create_test_admin_user()
|
||||
self.client.force_login(self.user)
|
||||
|
||||
def test_validate(self):
|
||||
"""Test OAuth2 Provider validation"""
|
||||
response = self.client.post(
|
||||
reverse(
|
||||
"authentik_api:oauth2provider-list",
|
||||
),
|
||||
data={
|
||||
"name": "test",
|
||||
"jwt_alg": str(JWTAlgorithms.RS256),
|
||||
"authorization_flow": create_test_flow().pk,
|
||||
},
|
||||
)
|
||||
self.assertJSONEqual(
|
||||
response.content.decode(),
|
||||
{"jwt_alg": ["RS256 requires a Certificate-Key-Pair to be selected."]},
|
||||
)
|
|
@ -218,7 +218,7 @@ class TestAuthorize(OAuthTestCase):
|
|||
client_secret=generate_key(),
|
||||
authorization_flow=flow,
|
||||
redirect_uris="http://localhost",
|
||||
rsa_key=create_test_cert(),
|
||||
signing_key=create_test_cert(),
|
||||
)
|
||||
Application.objects.create(name="app", slug="app", provider=provider)
|
||||
state = generate_id()
|
||||
|
|
|
@ -25,7 +25,7 @@ class TestJWKS(OAuthTestCase):
|
|||
client_id="test",
|
||||
authorization_flow=create_test_flow(),
|
||||
redirect_uris="http://local.invalid",
|
||||
rsa_key=create_test_cert(),
|
||||
signing_key=create_test_cert(),
|
||||
)
|
||||
app = Application.objects.create(name="test", slug="test", provider=provider)
|
||||
response = self.client.get(
|
||||
|
|
|
@ -35,7 +35,7 @@ class TestToken(OAuthTestCase):
|
|||
client_secret=generate_key(),
|
||||
authorization_flow=create_test_flow(),
|
||||
redirect_uris="http://testserver",
|
||||
rsa_key=create_test_cert(),
|
||||
signing_key=create_test_cert(),
|
||||
)
|
||||
header = b64encode(f"{provider.client_id}:{provider.client_secret}".encode()).decode()
|
||||
user = create_test_admin_user()
|
||||
|
@ -62,7 +62,7 @@ class TestToken(OAuthTestCase):
|
|||
client_secret=generate_key(),
|
||||
authorization_flow=create_test_flow(),
|
||||
redirect_uris="http://testserver",
|
||||
rsa_key=create_test_cert(),
|
||||
signing_key=create_test_cert(),
|
||||
)
|
||||
header = b64encode(f"{provider.client_id}:{provider.client_secret}".encode()).decode()
|
||||
request = self.factory.post(
|
||||
|
@ -85,7 +85,7 @@ class TestToken(OAuthTestCase):
|
|||
client_secret=generate_key(),
|
||||
authorization_flow=create_test_flow(),
|
||||
redirect_uris="http://local.invalid",
|
||||
rsa_key=create_test_cert(),
|
||||
signing_key=create_test_cert(),
|
||||
)
|
||||
header = b64encode(f"{provider.client_id}:{provider.client_secret}".encode()).decode()
|
||||
user = create_test_admin_user()
|
||||
|
@ -114,7 +114,7 @@ class TestToken(OAuthTestCase):
|
|||
client_secret=generate_key(),
|
||||
authorization_flow=create_test_flow(),
|
||||
redirect_uris="http://local.invalid",
|
||||
rsa_key=create_test_cert(),
|
||||
signing_key=create_test_cert(),
|
||||
)
|
||||
# Needs to be assigned to an application for iss to be set
|
||||
self.app.provider = provider
|
||||
|
@ -156,7 +156,7 @@ class TestToken(OAuthTestCase):
|
|||
client_secret=generate_key(),
|
||||
authorization_flow=create_test_flow(),
|
||||
redirect_uris="http://local.invalid",
|
||||
rsa_key=create_test_cert(),
|
||||
signing_key=create_test_cert(),
|
||||
)
|
||||
# Needs to be assigned to an application for iss to be set
|
||||
self.app.provider = provider
|
||||
|
@ -205,7 +205,7 @@ class TestToken(OAuthTestCase):
|
|||
client_secret=generate_key(),
|
||||
authorization_flow=create_test_flow(),
|
||||
redirect_uris="http://local.invalid",
|
||||
rsa_key=create_test_cert(),
|
||||
signing_key=create_test_cert(),
|
||||
)
|
||||
header = b64encode(f"{provider.client_id}:{provider.client_secret}".encode()).decode()
|
||||
user = create_test_admin_user()
|
||||
|
@ -250,7 +250,7 @@ class TestToken(OAuthTestCase):
|
|||
client_secret=generate_key(),
|
||||
authorization_flow=create_test_flow(),
|
||||
redirect_uris="http://testserver",
|
||||
rsa_key=create_test_cert(),
|
||||
signing_key=create_test_cert(),
|
||||
)
|
||||
# Needs to be assigned to an application for iss to be set
|
||||
self.app.provider = provider
|
||||
|
|
|
@ -27,7 +27,7 @@ class TestUserinfo(OAuthTestCase):
|
|||
client_secret=generate_key(),
|
||||
authorization_flow=create_test_flow(),
|
||||
redirect_uris="",
|
||||
rsa_key=create_test_cert(),
|
||||
signing_key=create_test_cert(),
|
||||
)
|
||||
self.provider.property_mappings.set(ScopeMapping.objects.all())
|
||||
# Needs to be assigned to an application for iss to be set
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
from django.test import TestCase
|
||||
from jwt import decode
|
||||
|
||||
from authentik.providers.oauth2.models import JWTAlgorithms, OAuth2Provider, RefreshToken
|
||||
from authentik.providers.oauth2.models import OAuth2Provider, RefreshToken
|
||||
|
||||
|
||||
class OAuthTestCase(TestCase):
|
||||
|
@ -19,13 +19,11 @@ class OAuthTestCase(TestCase):
|
|||
|
||||
def validate_jwt(self, token: RefreshToken, provider: OAuth2Provider):
|
||||
"""Validate that all required fields are set"""
|
||||
key = provider.client_secret
|
||||
if provider.jwt_alg == JWTAlgorithms.RS256:
|
||||
key = provider.rsa_key.public_key
|
||||
key, alg = provider.get_jwt_key()
|
||||
jwt = decode(
|
||||
token.access_token,
|
||||
key,
|
||||
algorithms=[provider.jwt_alg],
|
||||
algorithms=[alg],
|
||||
audience=provider.client_id,
|
||||
)
|
||||
id_token = token.id_token.to_dict()
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
"""authentik OAuth2 JWKS Views"""
|
||||
from base64 import urlsafe_b64encode
|
||||
from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePrivateKey, EllipticCurvePublicKey
|
||||
|
||||
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey
|
||||
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey, RSAPublicKey
|
||||
from django.http import HttpRequest, HttpResponse, JsonResponse
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.views import View
|
||||
|
@ -25,22 +26,38 @@ class JWKSView(View):
|
|||
"""Show RSA Key data for Provider"""
|
||||
application = get_object_or_404(Application, slug=application_slug)
|
||||
provider: OAuth2Provider = get_object_or_404(OAuth2Provider, pk=application.provider_id)
|
||||
private_key = provider.signing_key
|
||||
|
||||
response_data = {}
|
||||
|
||||
if provider.jwt_alg == JWTAlgorithms.RS256 and provider.rsa_key:
|
||||
public_key: RSAPublicKey = provider.rsa_key.private_key.public_key()
|
||||
public_numbers = public_key.public_numbers()
|
||||
response_data["keys"] = [
|
||||
{
|
||||
"kty": "RSA",
|
||||
"alg": "RS256",
|
||||
"use": "sig",
|
||||
"kid": provider.rsa_key.kid,
|
||||
"n": b64_enc(public_numbers.n),
|
||||
"e": b64_enc(public_numbers.e),
|
||||
}
|
||||
]
|
||||
if private_key:
|
||||
if isinstance(private_key, RSAPrivateKey):
|
||||
public_key: RSAPublicKey = private_key.public_key()
|
||||
public_numbers = public_key.public_numbers()
|
||||
response_data["keys"] = [
|
||||
{
|
||||
"kty": "RSA",
|
||||
"alg": JWTAlgorithms.RS256,
|
||||
"use": "sig",
|
||||
"kid": private_key.kid,
|
||||
"n": b64_enc(public_numbers.n),
|
||||
"e": b64_enc(public_numbers.e),
|
||||
}
|
||||
]
|
||||
elif isinstance(private_key, EllipticCurvePrivateKey):
|
||||
public_key: EllipticCurvePublicKey = private_key.public_key()
|
||||
public_numbers = public_key.public_numbers()
|
||||
response_data["keys"] = [
|
||||
{
|
||||
"kty": "EC",
|
||||
"alg": JWTAlgorithms.EC256,
|
||||
"use": "sig",
|
||||
"kid": private_key.kid,
|
||||
"n": b64_enc(public_numbers.n),
|
||||
"e": b64_enc(public_numbers.e),
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
response = JsonResponse(response_data)
|
||||
response["Access-Control-Allow-Origin"] = "*"
|
||||
|
|
|
@ -39,6 +39,7 @@ class ProviderInfoView(View):
|
|||
)
|
||||
if SCOPE_OPENID not in scopes:
|
||||
scopes.append(SCOPE_OPENID)
|
||||
_, supported_alg = provider.get_jwt_key()
|
||||
return {
|
||||
"issuer": provider.get_issuer(self.request),
|
||||
"authorization_endpoint": self.request.build_absolute_uri(
|
||||
|
@ -78,7 +79,7 @@ class ProviderInfoView(View):
|
|||
GRANT_TYPE_REFRESH_TOKEN,
|
||||
GrantTypes.IMPLICIT,
|
||||
],
|
||||
"id_token_signing_alg_values_supported": [provider.jwt_alg],
|
||||
"id_token_signing_alg_values_supported": [supported_alg],
|
||||
# See: http://openid.net/specs/openid-connect-core-1_0.html#SubjectIDTypes
|
||||
"subject_types_supported": ["public"],
|
||||
"token_endpoint_auth_methods_supported": [
|
||||
|
|
|
@ -18,7 +18,6 @@ from authentik.providers.oauth2.constants import (
|
|||
)
|
||||
from authentik.providers.oauth2.models import (
|
||||
ClientTypes,
|
||||
JWTAlgorithms,
|
||||
OAuth2Provider,
|
||||
ScopeMapping,
|
||||
)
|
||||
|
@ -128,8 +127,7 @@ class ProxyProvider(OutpostModel, OAuth2Provider):
|
|||
def set_oauth_defaults(self):
|
||||
"""Ensure all OAuth2-related settings are correct"""
|
||||
self.client_type = ClientTypes.CONFIDENTIAL
|
||||
self.jwt_alg = JWTAlgorithms.HS256
|
||||
self.rsa_key = None
|
||||
self.signing_key = None
|
||||
scopes = ScopeMapping.objects.filter(
|
||||
scope_name__in=[
|
||||
SCOPE_OPENID,
|
||||
|
|
48
schema.yml
48
schema.yml
|
@ -10858,15 +10858,6 @@ paths:
|
|||
- global
|
||||
- per_provider
|
||||
description: Configure how the issuer field of the ID Token should be filled.
|
||||
- in: query
|
||||
name: jwt_alg
|
||||
schema:
|
||||
type: string
|
||||
title: JWT Algorithm
|
||||
enum:
|
||||
- HS256
|
||||
- RS256
|
||||
description: Algorithm used to sign the JWT Token
|
||||
- in: query
|
||||
name: name
|
||||
schema:
|
||||
|
@ -10902,17 +10893,17 @@ paths:
|
|||
name: redirect_uris
|
||||
schema:
|
||||
type: string
|
||||
- in: query
|
||||
name: rsa_key
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
- name: search
|
||||
required: false
|
||||
in: query
|
||||
description: A search term.
|
||||
schema:
|
||||
type: string
|
||||
- in: query
|
||||
name: signing_key
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
- in: query
|
||||
name: sub_mode
|
||||
schema:
|
||||
|
@ -22221,11 +22212,6 @@ components:
|
|||
- global
|
||||
- per_provider
|
||||
type: string
|
||||
JwtAlgEnum:
|
||||
enum:
|
||||
- HS256
|
||||
- RS256
|
||||
type: string
|
||||
KubernetesServiceConnection:
|
||||
type: object
|
||||
description: KubernetesServiceConnection Serializer
|
||||
|
@ -23099,15 +23085,11 @@ components:
|
|||
type: boolean
|
||||
description: Include User claims from scopes in the id_token, for applications
|
||||
that don't access the userinfo endpoint.
|
||||
jwt_alg:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/JwtAlgEnum'
|
||||
title: JWT Algorithm
|
||||
description: Algorithm used to sign the JWT Token
|
||||
rsa_key:
|
||||
signing_key:
|
||||
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:
|
||||
|
@ -23175,15 +23157,11 @@ components:
|
|||
type: boolean
|
||||
description: Include User claims from scopes in the id_token, for applications
|
||||
that don't access the userinfo endpoint.
|
||||
jwt_alg:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/JwtAlgEnum'
|
||||
title: JWT Algorithm
|
||||
description: Algorithm used to sign the JWT Token
|
||||
rsa_key:
|
||||
signing_key:
|
||||
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:
|
||||
|
@ -27509,15 +27487,11 @@ components:
|
|||
type: boolean
|
||||
description: Include User claims from scopes in the id_token, for applications
|
||||
that don't access the userinfo endpoint.
|
||||
jwt_alg:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/JwtAlgEnum'
|
||||
title: JWT Algorithm
|
||||
description: Algorithm used to sign the JWT Token
|
||||
rsa_key:
|
||||
signing_key:
|
||||
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:
|
||||
|
|
|
@ -81,7 +81,7 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
|
|||
client_type=ClientTypes.CONFIDENTIAL,
|
||||
client_id=self.client_id,
|
||||
client_secret=self.client_secret,
|
||||
rsa_key=create_test_cert(),
|
||||
signing_key=create_test_cert(),
|
||||
redirect_uris="http://localhost:3000/",
|
||||
authorization_flow=authorization_flow,
|
||||
)
|
||||
|
@ -123,7 +123,7 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
|
|||
client_type=ClientTypes.CONFIDENTIAL,
|
||||
client_id=self.client_id,
|
||||
client_secret=self.client_secret,
|
||||
rsa_key=create_test_cert(),
|
||||
signing_key=create_test_cert(),
|
||||
redirect_uris="http://localhost:3000/login/generic_oauth",
|
||||
authorization_flow=authorization_flow,
|
||||
)
|
||||
|
@ -178,7 +178,7 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
|
|||
client_type=ClientTypes.CONFIDENTIAL,
|
||||
client_id=self.client_id,
|
||||
client_secret=self.client_secret,
|
||||
rsa_key=create_test_cert(),
|
||||
signing_key=create_test_cert(),
|
||||
redirect_uris="http://localhost:3000/login/generic_oauth",
|
||||
authorization_flow=authorization_flow,
|
||||
)
|
||||
|
@ -243,7 +243,7 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
|
|||
client_type=ClientTypes.CONFIDENTIAL,
|
||||
client_id=self.client_id,
|
||||
client_secret=self.client_secret,
|
||||
rsa_key=create_test_cert(),
|
||||
signing_key=create_test_cert(),
|
||||
redirect_uris="http://localhost:3000/login/generic_oauth",
|
||||
)
|
||||
provider.property_mappings.set(
|
||||
|
@ -315,7 +315,7 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
|
|||
client_type=ClientTypes.CONFIDENTIAL,
|
||||
client_id=self.client_id,
|
||||
client_secret=self.client_secret,
|
||||
rsa_key=create_test_cert(),
|
||||
signing_key=create_test_cert(),
|
||||
redirect_uris="http://localhost:3000/login/generic_oauth",
|
||||
)
|
||||
provider.property_mappings.set(
|
||||
|
|
|
@ -80,7 +80,7 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
|
|||
client_type=ClientTypes.CONFIDENTIAL,
|
||||
client_id=self.client_id,
|
||||
client_secret=self.client_secret,
|
||||
rsa_key=create_test_cert(),
|
||||
signing_key=create_test_cert(),
|
||||
redirect_uris="http://localhost:9009/",
|
||||
authorization_flow=authorization_flow,
|
||||
)
|
||||
|
@ -122,7 +122,7 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
|
|||
client_type=ClientTypes.CONFIDENTIAL,
|
||||
client_id=self.client_id,
|
||||
client_secret=self.client_secret,
|
||||
rsa_key=create_test_cert(),
|
||||
signing_key=create_test_cert(),
|
||||
redirect_uris="http://localhost:9009/auth/callback",
|
||||
authorization_flow=authorization_flow,
|
||||
)
|
||||
|
@ -172,7 +172,7 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
|
|||
client_type=ClientTypes.CONFIDENTIAL,
|
||||
client_id=self.client_id,
|
||||
client_secret=self.client_secret,
|
||||
rsa_key=create_test_cert(),
|
||||
signing_key=create_test_cert(),
|
||||
redirect_uris="http://localhost:9009/auth/callback",
|
||||
)
|
||||
provider.property_mappings.set(
|
||||
|
@ -235,7 +235,7 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
|
|||
client_type=ClientTypes.CONFIDENTIAL,
|
||||
client_id=self.client_id,
|
||||
client_secret=self.client_secret,
|
||||
rsa_key=create_test_cert(),
|
||||
signing_key=create_test_cert(),
|
||||
redirect_uris="http://localhost:9009/auth/callback",
|
||||
)
|
||||
provider.property_mappings.set(
|
||||
|
|
|
@ -80,7 +80,7 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase):
|
|||
client_type=ClientTypes.CONFIDENTIAL,
|
||||
client_id=self.client_id,
|
||||
client_secret=self.client_secret,
|
||||
rsa_key=create_test_cert(),
|
||||
signing_key=create_test_cert(),
|
||||
redirect_uris="http://localhost:9009/",
|
||||
authorization_flow=authorization_flow,
|
||||
)
|
||||
|
@ -122,7 +122,7 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase):
|
|||
client_type=ClientTypes.CONFIDENTIAL,
|
||||
client_id=self.client_id,
|
||||
client_secret=self.client_secret,
|
||||
rsa_key=create_test_cert(),
|
||||
signing_key=create_test_cert(),
|
||||
redirect_uris="http://localhost:9009/implicit/",
|
||||
authorization_flow=authorization_flow,
|
||||
)
|
||||
|
@ -168,7 +168,7 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase):
|
|||
client_type=ClientTypes.CONFIDENTIAL,
|
||||
client_id=self.client_id,
|
||||
client_secret=self.client_secret,
|
||||
rsa_key=create_test_cert(),
|
||||
signing_key=create_test_cert(),
|
||||
redirect_uris="http://localhost:9009/implicit/",
|
||||
)
|
||||
provider.property_mappings.set(
|
||||
|
@ -228,7 +228,7 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase):
|
|||
client_type=ClientTypes.CONFIDENTIAL,
|
||||
client_id=self.client_id,
|
||||
client_secret=self.client_secret,
|
||||
rsa_key=create_test_cert(),
|
||||
signing_key=create_test_cert(),
|
||||
redirect_uris="http://localhost:9009/implicit/",
|
||||
)
|
||||
provider.property_mappings.set(
|
||||
|
|
|
@ -175,9 +175,9 @@ ${this.instance?.redirectUris}</textarea
|
|||
${t`If no explicit redirect URIs are specified, any redirect URI is allowed.`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal label=${t`RSA Key`} name="rsaKey">
|
||||
<ak-form-element-horizontal label=${t`Signing Key`} name="signingKey">
|
||||
<select class="pf-c-form-control">
|
||||
<option value="" ?selected=${this.instance?.rsaKey === undefined}>
|
||||
<option value="" ?selected=${this.instance?.signingKey === undefined}>
|
||||
---------
|
||||
</option>
|
||||
${until(
|
||||
|
@ -188,7 +188,7 @@ ${this.instance?.redirectUris}</textarea
|
|||
})
|
||||
.then((keys) => {
|
||||
return keys.results.map((key) => {
|
||||
let selected = this.instance?.rsaKey === key.pk;
|
||||
let selected = this.instance?.signingKey === key.pk;
|
||||
if (keys.results.length === 1) {
|
||||
selected = true;
|
||||
}
|
||||
|
@ -203,9 +203,7 @@ ${this.instance?.redirectUris}</textarea
|
|||
html`<option>${t`Loading...`}</option>`,
|
||||
)}
|
||||
</select>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`Key used to sign the tokens. Only required when JWT Algorithm is set to RS256.`}
|
||||
</p>
|
||||
<p class="pf-c-form__helper-text">${t`Key used to sign the tokens.`}</p>
|
||||
</ak-form-element-horizontal>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
|
@ -252,29 +250,6 @@ ${this.instance?.redirectUris}</textarea
|
|||
${t`(Format: hours=-1;minutes=-2;seconds=-3).`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${t`JWT Algorithm`}
|
||||
?required=${true}
|
||||
name="jwtAlg"
|
||||
>
|
||||
<select class="pf-c-form-control">
|
||||
<option
|
||||
value=${JwtAlgEnum.Rs256}
|
||||
?selected=${this.instance?.jwtAlg === JwtAlgEnum.Rs256}
|
||||
>
|
||||
${t`RS256 (Asymmetric Encryption)`}
|
||||
</option>
|
||||
<option
|
||||
value=${JwtAlgEnum.Hs256}
|
||||
?selected=${this.instance?.jwtAlg === JwtAlgEnum.Hs256}
|
||||
>
|
||||
${t`HS256 (Symmetric Encryption)`}
|
||||
</option>
|
||||
</select>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`Algorithm used to sign the JWT Tokens.`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal label=${t`Scopes`} name="propertyMappings">
|
||||
<select class="pf-c-form-control" multiple>
|
||||
${until(
|
||||
|
|
|
@ -20,7 +20,6 @@ The following placeholders will be used:
|
|||
Create an OAuth2/OpenID provider with the following parameters:
|
||||
|
||||
- Client Type: `Confidential`
|
||||
- JWT Algorithm: `RS256`
|
||||
- Redirect URIs: `https://guacamole.company/` (depending on your Tomcat setup, you might have to add `/guacamole/` if the application runs in a subfolder)
|
||||
- Scopes: OpenID, Email and Profile
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ title: Budibase
|
|||
From https://github.com/Budibase/budibase
|
||||
|
||||
:::note
|
||||
Budibase is an open source low-code platform, and the easiest way to build internal tools that improve productivity.
|
||||
Budibase is an open source low-code platform, and the easiest way to build internal tools that improve productivity.
|
||||
:::
|
||||
|
||||
## Preparation
|
||||
|
@ -20,7 +20,6 @@ The following placeholders will be used:
|
|||
Create an application in authentik. Create an OAuth2/OpenID provider with the following parameters:
|
||||
|
||||
- Client Type: `Confidential`
|
||||
- JWT Algorithm: `RS256`
|
||||
- Scopes: OpenID, Email and Profile
|
||||
- RSA Key: Select any available key
|
||||
- Redirect URIs: `https://budibase.company/api/global/auth/oidc/callback`
|
||||
|
@ -33,4 +32,4 @@ In Budibase under `Auth` set the following values
|
|||
|
||||
- Config URL: `https://authentik.company/application/o/<Slug of the application from above>/.well-known/openid-configuration`
|
||||
- Client ID: `Client ID from above`
|
||||
- Client Secret: `Client Secret from above`
|
||||
- Client Secret: `Client Secret from above`
|
||||
|
|
|
@ -20,7 +20,6 @@ The following placeholders will be used:
|
|||
Create an application in authentik. Create an OAuth2/OpenID provider with the following parameters:
|
||||
|
||||
- Client Type: `Confidential`
|
||||
- JWT Algorithm: `RS256`
|
||||
- Scopes: OpenID, Email and Profile
|
||||
- RSA Key: Select any available key
|
||||
- Redirect URIs: `https://grafana.company/login/generic_oauth`
|
||||
|
@ -86,12 +85,12 @@ role_attribute_path = contains(groups[*], 'Grafana Admins') && 'Admin' || contai
|
|||
|
||||
In the configuration above you can see an example of a role mapping. Upon login, this configuration looks at the groups of which the current user is a member. If any of the specified group names are found, the user will be granted the resulting role in Grafana.
|
||||
|
||||
In the example shown above, one of the specified group names is "Grafana Admins". If the user is a member of this group, they will be granted the "Admin" role in Grafana.
|
||||
In the example shown above, one of the specified group names is "Grafana Admins". If the user is a member of this group, they will be granted the "Admin" role in Grafana.
|
||||
If the user is not a member of the "Grafana Admins" group, it moves on to see if the user is a member of the "Grafana Editors" group. If they are, they are granted the "Editor" role. Finally, if the user is not found to be a member of either of these groups, it fails back to granting the "Viewer" role.
|
||||
|
||||
```text
|
||||
contains(groups[*], 'Grafana Admins') && 'Admin' || contains(groups[*], 'Grafana Editors') && 'Editor' || 'Viewer'
|
||||
^ attribute to search ^ group to search for ^ role to grant ^ or grant "Viewer" role.
|
||||
^ attribute to search ^ group to search for ^ role to grant ^ or grant "Viewer" role.
|
||||
```
|
||||
|
||||
For more information on group/role mappings, see [Grafana's docs](https://grafana.com/docs/grafana/latest/auth/generic-oauth/#role-mapping).
|
||||
|
@ -105,4 +104,4 @@ If you get `user does not belong to org` error when trying to log into grafana f
|
|||
[users]
|
||||
auto_assign_org = true
|
||||
auto_assign_org_id = <id-of-your-default-organization>
|
||||
```
|
||||
```
|
||||
|
|
|
@ -20,7 +20,6 @@ The following placeholders will be used:
|
|||
Create an OAuth2/OpenID provider with the following parameters:
|
||||
|
||||
- Client Type: `Confidential`
|
||||
- JWT Algorithm: `RS256`
|
||||
- Redirect URIs: `https://harbor.company/c/oidc/callback`
|
||||
- Scopes: OpenID, Email and Profile
|
||||
|
||||
|
|
|
@ -20,7 +20,6 @@ The following placeholders will be used:
|
|||
Create an application in authentik. Create an OAuth2/OpenID provider with the following parameters:
|
||||
|
||||
- Client Type: `Confidential`
|
||||
- JWT Algorithm: `RS256`
|
||||
- Scopes: OpenID, Email and Profile
|
||||
- RSA Key: Select any available key
|
||||
- Redirect URIs: `https://hedgedoc.company/auth/oauth2/callback`
|
||||
|
|
|
@ -21,7 +21,6 @@ The following placeholders will be used:
|
|||
Create an application in authentik. Create an OAuth2/OpenID provider with the following parameters:
|
||||
|
||||
- Client Type: `Confidential`
|
||||
- JWT Algorithm: `RS256`
|
||||
- Scopes: OpenID, Email and Profile
|
||||
- RSA Key: Select any available key
|
||||
- Redirect URIs: `https://matrix.company/_synapse/client/oidc/callback`
|
||||
|
|
|
@ -28,7 +28,6 @@ return {
|
|||
Create an application in authentik. Create an _OAuth2/OpenID Provider_ with the following parameters:
|
||||
|
||||
- Client Type: `Public`
|
||||
- JWT Algorithm: `RS256`
|
||||
- Scopes: OpenID, Email, Profile and the scope you created above
|
||||
- RSA Key: Select any available key
|
||||
- Redirect URIs: `https://minio.company/oauth_callback`
|
||||
|
|
|
@ -20,7 +20,6 @@ The following placeholders will be used:
|
|||
Create an application in authentik. Create an OAuth2/OpenID provider with the following parameters:
|
||||
|
||||
- Client Type: `Confidential`
|
||||
- JWT Algorithm: `RS256`
|
||||
- Scopes: OpenID, Email and Profile
|
||||
- RSA Key: Select any available key
|
||||
- Redirect URIs: `https://wekan.company/_oauth/oidc`
|
||||
|
|
Reference in a new issue