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:
Jens Langhammer 2021-12-22 22:09:49 +01:00
parent 89696edbee
commit 2f3026084e
26 changed files with 126 additions and 205 deletions

View File

@ -146,7 +146,7 @@ class TestCrypto(APITestCase):
client_secret=generate_key(), client_secret=generate_key(),
authorization_flow=create_test_flow(), authorization_flow=create_test_flow(),
redirect_uris="http://localhost", redirect_uris="http://localhost",
rsa_key=keypair, signing_key=keypair,
) )
response = self.client.get( response = self.client.get(
reverse( reverse(

View File

@ -7,25 +7,18 @@ from rest_framework.fields import CharField
from rest_framework.generics import get_object_or_404 from rest_framework.generics import get_object_or_404
from rest_framework.request import Request from rest_framework.request import Request
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.serializers import ValidationError
from rest_framework.viewsets import ModelViewSet from rest_framework.viewsets import ModelViewSet
from authentik.core.api.providers import ProviderSerializer from authentik.core.api.providers import ProviderSerializer
from authentik.core.api.used_by import UsedByMixin from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import PassiveSerializer from authentik.core.api.utils import PassiveSerializer
from authentik.core.models import Provider from authentik.core.models import Provider
from authentik.providers.oauth2.models import JWTAlgorithms, OAuth2Provider from authentik.providers.oauth2.models import OAuth2Provider
class OAuth2ProviderSerializer(ProviderSerializer): class OAuth2ProviderSerializer(ProviderSerializer):
"""OAuth2Provider Serializer""" """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: class Meta:
model = OAuth2Provider model = OAuth2Provider
@ -37,8 +30,7 @@ class OAuth2ProviderSerializer(ProviderSerializer):
"access_code_validity", "access_code_validity",
"token_validity", "token_validity",
"include_claims_in_id_token", "include_claims_in_id_token",
"jwt_alg", "signing_key",
"rsa_key",
"redirect_uris", "redirect_uris",
"sub_mode", "sub_mode",
"property_mappings", "property_mappings",
@ -73,8 +65,7 @@ class OAuth2ProviderViewSet(UsedByMixin, ModelViewSet):
"access_code_validity", "access_code_validity",
"token_validity", "token_validity",
"include_claims_in_id_token", "include_claims_in_id_token",
"jwt_alg", "signing_key",
"rsa_key",
"redirect_uris", "redirect_uris",
"sub_mode", "sub_mode",
"property_mappings", "property_mappings",

View File

@ -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',
),
]

View File

@ -8,6 +8,8 @@ from datetime import datetime
from hashlib import sha256 from hashlib import sha256
from typing import Any, Optional, Type from typing import Any, Optional, Type
from urllib.parse import urlparse 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 dacite import from_dict
from django.db import models from django.db import models
@ -88,6 +90,7 @@ class JWTAlgorithms(models.TextChoices):
HS256 = "HS256", _("HS256 (Symmetric Encryption)") HS256 = "HS256", _("HS256 (Symmetric Encryption)")
RS256 = "RS256", _("RS256 (Asymmetric Encryption)") RS256 = "RS256", _("RS256 (Asymmetric Encryption)")
EC256 = "EC256", _("EC256 (Asymmetric Encryption)")
class ScopeMapping(PropertyMapping): class ScopeMapping(PropertyMapping):
@ -145,13 +148,6 @@ class OAuth2Provider(Provider):
verbose_name=_("Client Secret"), verbose_name=_("Client Secret"),
default=generate_key, 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( redirect_uris = models.TextField(
default="", default="",
blank=True, blank=True,
@ -207,7 +203,7 @@ class OAuth2Provider(Provider):
help_text=_(("Configure how the issuer field of the ID Token should be filled.")), help_text=_(("Configure how the issuer field of the ID Token should be filled.")),
) )
rsa_key = models.ForeignKey( signing_key = models.ForeignKey(
CertificateKeyPair, CertificateKeyPair,
verbose_name=_("RSA Key"), verbose_name=_("RSA Key"),
on_delete=models.SET_NULL, on_delete=models.SET_NULL,
@ -231,29 +227,18 @@ class OAuth2Provider(Provider):
token.access_token = token.create_access_token(user, request) token.access_token = token.create_access_token(user, request)
return token return token
def get_jwt_key(self) -> str: def get_jwt_key(self) -> tuple[str, str]:
""" """Get either the configured certificate or the client secret"""
Takes a provider and returns the set of keys associated with it. if not self.signing_key:
Returns a list of keys. # No Certificate at all, assume HS256
""" return self.client_secret, JWTAlgorithms.HS256
if self.jwt_alg == JWTAlgorithms.RS256: key: CertificateKeyPair = self.signing_key
# if the user selected RS256 but didn't select a private_key = key.private_key
# CertificateKeyPair, we fall back to HS256 if isinstance(private_key, RSAPrivateKey):
if not self.rsa_key: return key.key_data, JWTAlgorithms.RS256
Event.new( if isinstance(private_key, EllipticCurvePrivateKey):
EventAction.CONFIGURATION_ERROR, return key.key_data, JWTAlgorithms.EC256
provider=self, raise Exception(f"Invalid private key type: {type(private_key)}")
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_issuer(self, request: HttpRequest) -> Optional[str]: def get_issuer(self, request: HttpRequest) -> Optional[str]:
"""Get issuer, based on request""" """Get issuer, based on request"""
@ -293,13 +278,13 @@ class OAuth2Provider(Provider):
def encode(self, payload: dict[str, Any]) -> str: def encode(self, payload: dict[str, Any]) -> str:
"""Represent the ID Token as a JSON Web Token (JWT).""" """Represent the ID Token as a JSON Web Token (JWT)."""
headers = {} headers = {}
if self.rsa_key: if self.signing_key:
headers["kid"] = self.rsa_key.kid headers["kid"] = self.signing_key.kid
key = self.get_jwt_key() key, alg = self.get_jwt_key()
# If the provider does not have an RSA Key assigned, it was switched to Symmetric # If the provider does not have an RSA Key assigned, it was switched to Symmetric
self.refresh_from_db() self.refresh_from_db()
# pyright: reportGeneralTypeIssues=false # pyright: reportGeneralTypeIssues=false
return encode(payload, key, algorithm=self.jwt_alg, headers=headers) return encode(payload, key, algorithm=alg, headers=headers)
class Meta: class Meta:

View File

@ -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."]},
)

View File

@ -218,7 +218,7 @@ class TestAuthorize(OAuthTestCase):
client_secret=generate_key(), client_secret=generate_key(),
authorization_flow=flow, authorization_flow=flow,
redirect_uris="http://localhost", redirect_uris="http://localhost",
rsa_key=create_test_cert(), signing_key=create_test_cert(),
) )
Application.objects.create(name="app", slug="app", provider=provider) Application.objects.create(name="app", slug="app", provider=provider)
state = generate_id() state = generate_id()

View File

@ -25,7 +25,7 @@ class TestJWKS(OAuthTestCase):
client_id="test", client_id="test",
authorization_flow=create_test_flow(), authorization_flow=create_test_flow(),
redirect_uris="http://local.invalid", 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) app = Application.objects.create(name="test", slug="test", provider=provider)
response = self.client.get( response = self.client.get(

View File

@ -35,7 +35,7 @@ class TestToken(OAuthTestCase):
client_secret=generate_key(), client_secret=generate_key(),
authorization_flow=create_test_flow(), authorization_flow=create_test_flow(),
redirect_uris="http://testserver", 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() header = b64encode(f"{provider.client_id}:{provider.client_secret}".encode()).decode()
user = create_test_admin_user() user = create_test_admin_user()
@ -62,7 +62,7 @@ class TestToken(OAuthTestCase):
client_secret=generate_key(), client_secret=generate_key(),
authorization_flow=create_test_flow(), authorization_flow=create_test_flow(),
redirect_uris="http://testserver", 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() header = b64encode(f"{provider.client_id}:{provider.client_secret}".encode()).decode()
request = self.factory.post( request = self.factory.post(
@ -85,7 +85,7 @@ class TestToken(OAuthTestCase):
client_secret=generate_key(), client_secret=generate_key(),
authorization_flow=create_test_flow(), authorization_flow=create_test_flow(),
redirect_uris="http://local.invalid", 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() header = b64encode(f"{provider.client_id}:{provider.client_secret}".encode()).decode()
user = create_test_admin_user() user = create_test_admin_user()
@ -114,7 +114,7 @@ class TestToken(OAuthTestCase):
client_secret=generate_key(), client_secret=generate_key(),
authorization_flow=create_test_flow(), authorization_flow=create_test_flow(),
redirect_uris="http://local.invalid", 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 # Needs to be assigned to an application for iss to be set
self.app.provider = provider self.app.provider = provider
@ -156,7 +156,7 @@ class TestToken(OAuthTestCase):
client_secret=generate_key(), client_secret=generate_key(),
authorization_flow=create_test_flow(), authorization_flow=create_test_flow(),
redirect_uris="http://local.invalid", 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 # Needs to be assigned to an application for iss to be set
self.app.provider = provider self.app.provider = provider
@ -205,7 +205,7 @@ class TestToken(OAuthTestCase):
client_secret=generate_key(), client_secret=generate_key(),
authorization_flow=create_test_flow(), authorization_flow=create_test_flow(),
redirect_uris="http://local.invalid", 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() header = b64encode(f"{provider.client_id}:{provider.client_secret}".encode()).decode()
user = create_test_admin_user() user = create_test_admin_user()
@ -250,7 +250,7 @@ class TestToken(OAuthTestCase):
client_secret=generate_key(), client_secret=generate_key(),
authorization_flow=create_test_flow(), authorization_flow=create_test_flow(),
redirect_uris="http://testserver", 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 # Needs to be assigned to an application for iss to be set
self.app.provider = provider self.app.provider = provider

View File

@ -27,7 +27,7 @@ class TestUserinfo(OAuthTestCase):
client_secret=generate_key(), client_secret=generate_key(),
authorization_flow=create_test_flow(), authorization_flow=create_test_flow(),
redirect_uris="", redirect_uris="",
rsa_key=create_test_cert(), signing_key=create_test_cert(),
) )
self.provider.property_mappings.set(ScopeMapping.objects.all()) self.provider.property_mappings.set(ScopeMapping.objects.all())
# Needs to be assigned to an application for iss to be set # Needs to be assigned to an application for iss to be set

View File

@ -2,7 +2,7 @@
from django.test import TestCase from django.test import TestCase
from jwt import decode from jwt import decode
from authentik.providers.oauth2.models import JWTAlgorithms, OAuth2Provider, RefreshToken from authentik.providers.oauth2.models import OAuth2Provider, RefreshToken
class OAuthTestCase(TestCase): class OAuthTestCase(TestCase):
@ -19,13 +19,11 @@ class OAuthTestCase(TestCase):
def validate_jwt(self, token: RefreshToken, provider: OAuth2Provider): def validate_jwt(self, token: RefreshToken, provider: OAuth2Provider):
"""Validate that all required fields are set""" """Validate that all required fields are set"""
key = provider.client_secret key, alg = provider.get_jwt_key()
if provider.jwt_alg == JWTAlgorithms.RS256:
key = provider.rsa_key.public_key
jwt = decode( jwt = decode(
token.access_token, token.access_token,
key, key,
algorithms=[provider.jwt_alg], algorithms=[alg],
audience=provider.client_id, audience=provider.client_id,
) )
id_token = token.id_token.to_dict() id_token = token.id_token.to_dict()

View File

@ -1,7 +1,8 @@
"""authentik OAuth2 JWKS Views""" """authentik OAuth2 JWKS Views"""
from base64 import urlsafe_b64encode 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.http import HttpRequest, HttpResponse, JsonResponse
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.views import View from django.views import View
@ -25,22 +26,38 @@ class JWKSView(View):
"""Show RSA Key data for Provider""" """Show RSA Key data for Provider"""
application = get_object_or_404(Application, slug=application_slug) application = get_object_or_404(Application, slug=application_slug)
provider: OAuth2Provider = get_object_or_404(OAuth2Provider, pk=application.provider_id) provider: OAuth2Provider = get_object_or_404(OAuth2Provider, pk=application.provider_id)
private_key = provider.signing_key
response_data = {} response_data = {}
if provider.jwt_alg == JWTAlgorithms.RS256 and provider.rsa_key: if private_key:
public_key: RSAPublicKey = provider.rsa_key.private_key.public_key() if isinstance(private_key, RSAPrivateKey):
public_numbers = public_key.public_numbers() public_key: RSAPublicKey = private_key.public_key()
response_data["keys"] = [ public_numbers = public_key.public_numbers()
{ response_data["keys"] = [
"kty": "RSA", {
"alg": "RS256", "kty": "RSA",
"use": "sig", "alg": JWTAlgorithms.RS256,
"kid": provider.rsa_key.kid, "use": "sig",
"n": b64_enc(public_numbers.n), "kid": private_key.kid,
"e": b64_enc(public_numbers.e), "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 = JsonResponse(response_data)
response["Access-Control-Allow-Origin"] = "*" response["Access-Control-Allow-Origin"] = "*"

View File

@ -39,6 +39,7 @@ class ProviderInfoView(View):
) )
if SCOPE_OPENID not in scopes: if SCOPE_OPENID not in scopes:
scopes.append(SCOPE_OPENID) scopes.append(SCOPE_OPENID)
_, supported_alg = provider.get_jwt_key()
return { return {
"issuer": provider.get_issuer(self.request), "issuer": provider.get_issuer(self.request),
"authorization_endpoint": self.request.build_absolute_uri( "authorization_endpoint": self.request.build_absolute_uri(
@ -78,7 +79,7 @@ class ProviderInfoView(View):
GRANT_TYPE_REFRESH_TOKEN, GRANT_TYPE_REFRESH_TOKEN,
GrantTypes.IMPLICIT, 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 # See: http://openid.net/specs/openid-connect-core-1_0.html#SubjectIDTypes
"subject_types_supported": ["public"], "subject_types_supported": ["public"],
"token_endpoint_auth_methods_supported": [ "token_endpoint_auth_methods_supported": [

View File

@ -18,7 +18,6 @@ from authentik.providers.oauth2.constants import (
) )
from authentik.providers.oauth2.models import ( from authentik.providers.oauth2.models import (
ClientTypes, ClientTypes,
JWTAlgorithms,
OAuth2Provider, OAuth2Provider,
ScopeMapping, ScopeMapping,
) )
@ -128,8 +127,7 @@ class ProxyProvider(OutpostModel, OAuth2Provider):
def set_oauth_defaults(self): def set_oauth_defaults(self):
"""Ensure all OAuth2-related settings are correct""" """Ensure all OAuth2-related settings are correct"""
self.client_type = ClientTypes.CONFIDENTIAL self.client_type = ClientTypes.CONFIDENTIAL
self.jwt_alg = JWTAlgorithms.HS256 self.signing_key = None
self.rsa_key = None
scopes = ScopeMapping.objects.filter( scopes = ScopeMapping.objects.filter(
scope_name__in=[ scope_name__in=[
SCOPE_OPENID, SCOPE_OPENID,

View File

@ -10858,15 +10858,6 @@ paths:
- global - global
- per_provider - per_provider
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.
- in: query
name: jwt_alg
schema:
type: string
title: JWT Algorithm
enum:
- HS256
- RS256
description: Algorithm used to sign the JWT Token
- in: query - in: query
name: name name: name
schema: schema:
@ -10902,17 +10893,17 @@ paths:
name: redirect_uris name: redirect_uris
schema: schema:
type: string type: string
- in: query
name: rsa_key
schema:
type: string
format: uuid
- name: search - name: search
required: false required: false
in: query in: query
description: A search term. description: A search term.
schema: schema:
type: string type: string
- in: query
name: signing_key
schema:
type: string
format: uuid
- in: query - in: query
name: sub_mode name: sub_mode
schema: schema:
@ -22221,11 +22212,6 @@ components:
- global - global
- per_provider - per_provider
type: string type: string
JwtAlgEnum:
enum:
- HS256
- RS256
type: string
KubernetesServiceConnection: KubernetesServiceConnection:
type: object type: object
description: KubernetesServiceConnection Serializer description: KubernetesServiceConnection Serializer
@ -23099,15 +23085,11 @@ components:
type: boolean type: boolean
description: Include User claims from scopes in the id_token, for applications description: Include User claims from scopes in the id_token, for applications
that don't access the userinfo endpoint. that don't access the userinfo endpoint.
jwt_alg: signing_key:
allOf:
- $ref: '#/components/schemas/JwtAlgEnum'
title: JWT Algorithm
description: Algorithm used to sign the JWT Token
rsa_key:
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:
@ -23175,15 +23157,11 @@ components:
type: boolean type: boolean
description: Include User claims from scopes in the id_token, for applications description: Include User claims from scopes in the id_token, for applications
that don't access the userinfo endpoint. that don't access the userinfo endpoint.
jwt_alg: signing_key:
allOf:
- $ref: '#/components/schemas/JwtAlgEnum'
title: JWT Algorithm
description: Algorithm used to sign the JWT Token
rsa_key:
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:
@ -27509,15 +27487,11 @@ components:
type: boolean type: boolean
description: Include User claims from scopes in the id_token, for applications description: Include User claims from scopes in the id_token, for applications
that don't access the userinfo endpoint. that don't access the userinfo endpoint.
jwt_alg: signing_key:
allOf:
- $ref: '#/components/schemas/JwtAlgEnum'
title: JWT Algorithm
description: Algorithm used to sign the JWT Token
rsa_key:
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:

View File

@ -81,7 +81,7 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
client_type=ClientTypes.CONFIDENTIAL, client_type=ClientTypes.CONFIDENTIAL,
client_id=self.client_id, client_id=self.client_id,
client_secret=self.client_secret, client_secret=self.client_secret,
rsa_key=create_test_cert(), signing_key=create_test_cert(),
redirect_uris="http://localhost:3000/", redirect_uris="http://localhost:3000/",
authorization_flow=authorization_flow, authorization_flow=authorization_flow,
) )
@ -123,7 +123,7 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
client_type=ClientTypes.CONFIDENTIAL, client_type=ClientTypes.CONFIDENTIAL,
client_id=self.client_id, client_id=self.client_id,
client_secret=self.client_secret, client_secret=self.client_secret,
rsa_key=create_test_cert(), signing_key=create_test_cert(),
redirect_uris="http://localhost:3000/login/generic_oauth", redirect_uris="http://localhost:3000/login/generic_oauth",
authorization_flow=authorization_flow, authorization_flow=authorization_flow,
) )
@ -178,7 +178,7 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
client_type=ClientTypes.CONFIDENTIAL, client_type=ClientTypes.CONFIDENTIAL,
client_id=self.client_id, client_id=self.client_id,
client_secret=self.client_secret, client_secret=self.client_secret,
rsa_key=create_test_cert(), signing_key=create_test_cert(),
redirect_uris="http://localhost:3000/login/generic_oauth", redirect_uris="http://localhost:3000/login/generic_oauth",
authorization_flow=authorization_flow, authorization_flow=authorization_flow,
) )
@ -243,7 +243,7 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
client_type=ClientTypes.CONFIDENTIAL, client_type=ClientTypes.CONFIDENTIAL,
client_id=self.client_id, client_id=self.client_id,
client_secret=self.client_secret, client_secret=self.client_secret,
rsa_key=create_test_cert(), signing_key=create_test_cert(),
redirect_uris="http://localhost:3000/login/generic_oauth", redirect_uris="http://localhost:3000/login/generic_oauth",
) )
provider.property_mappings.set( provider.property_mappings.set(
@ -315,7 +315,7 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
client_type=ClientTypes.CONFIDENTIAL, client_type=ClientTypes.CONFIDENTIAL,
client_id=self.client_id, client_id=self.client_id,
client_secret=self.client_secret, client_secret=self.client_secret,
rsa_key=create_test_cert(), signing_key=create_test_cert(),
redirect_uris="http://localhost:3000/login/generic_oauth", redirect_uris="http://localhost:3000/login/generic_oauth",
) )
provider.property_mappings.set( provider.property_mappings.set(

View File

@ -80,7 +80,7 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
client_type=ClientTypes.CONFIDENTIAL, client_type=ClientTypes.CONFIDENTIAL,
client_id=self.client_id, client_id=self.client_id,
client_secret=self.client_secret, client_secret=self.client_secret,
rsa_key=create_test_cert(), signing_key=create_test_cert(),
redirect_uris="http://localhost:9009/", redirect_uris="http://localhost:9009/",
authorization_flow=authorization_flow, authorization_flow=authorization_flow,
) )
@ -122,7 +122,7 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
client_type=ClientTypes.CONFIDENTIAL, client_type=ClientTypes.CONFIDENTIAL,
client_id=self.client_id, client_id=self.client_id,
client_secret=self.client_secret, client_secret=self.client_secret,
rsa_key=create_test_cert(), signing_key=create_test_cert(),
redirect_uris="http://localhost:9009/auth/callback", redirect_uris="http://localhost:9009/auth/callback",
authorization_flow=authorization_flow, authorization_flow=authorization_flow,
) )
@ -172,7 +172,7 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
client_type=ClientTypes.CONFIDENTIAL, client_type=ClientTypes.CONFIDENTIAL,
client_id=self.client_id, client_id=self.client_id,
client_secret=self.client_secret, client_secret=self.client_secret,
rsa_key=create_test_cert(), signing_key=create_test_cert(),
redirect_uris="http://localhost:9009/auth/callback", redirect_uris="http://localhost:9009/auth/callback",
) )
provider.property_mappings.set( provider.property_mappings.set(
@ -235,7 +235,7 @@ class TestProviderOAuth2OIDC(SeleniumTestCase):
client_type=ClientTypes.CONFIDENTIAL, client_type=ClientTypes.CONFIDENTIAL,
client_id=self.client_id, client_id=self.client_id,
client_secret=self.client_secret, client_secret=self.client_secret,
rsa_key=create_test_cert(), signing_key=create_test_cert(),
redirect_uris="http://localhost:9009/auth/callback", redirect_uris="http://localhost:9009/auth/callback",
) )
provider.property_mappings.set( provider.property_mappings.set(

View File

@ -80,7 +80,7 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase):
client_type=ClientTypes.CONFIDENTIAL, client_type=ClientTypes.CONFIDENTIAL,
client_id=self.client_id, client_id=self.client_id,
client_secret=self.client_secret, client_secret=self.client_secret,
rsa_key=create_test_cert(), signing_key=create_test_cert(),
redirect_uris="http://localhost:9009/", redirect_uris="http://localhost:9009/",
authorization_flow=authorization_flow, authorization_flow=authorization_flow,
) )
@ -122,7 +122,7 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase):
client_type=ClientTypes.CONFIDENTIAL, client_type=ClientTypes.CONFIDENTIAL,
client_id=self.client_id, client_id=self.client_id,
client_secret=self.client_secret, client_secret=self.client_secret,
rsa_key=create_test_cert(), signing_key=create_test_cert(),
redirect_uris="http://localhost:9009/implicit/", redirect_uris="http://localhost:9009/implicit/",
authorization_flow=authorization_flow, authorization_flow=authorization_flow,
) )
@ -168,7 +168,7 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase):
client_type=ClientTypes.CONFIDENTIAL, client_type=ClientTypes.CONFIDENTIAL,
client_id=self.client_id, client_id=self.client_id,
client_secret=self.client_secret, client_secret=self.client_secret,
rsa_key=create_test_cert(), signing_key=create_test_cert(),
redirect_uris="http://localhost:9009/implicit/", redirect_uris="http://localhost:9009/implicit/",
) )
provider.property_mappings.set( provider.property_mappings.set(
@ -228,7 +228,7 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase):
client_type=ClientTypes.CONFIDENTIAL, client_type=ClientTypes.CONFIDENTIAL,
client_id=self.client_id, client_id=self.client_id,
client_secret=self.client_secret, client_secret=self.client_secret,
rsa_key=create_test_cert(), signing_key=create_test_cert(),
redirect_uris="http://localhost:9009/implicit/", redirect_uris="http://localhost:9009/implicit/",
) )
provider.property_mappings.set( provider.property_mappings.set(

View File

@ -175,9 +175,9 @@ ${this.instance?.redirectUris}</textarea
${t`If no explicit redirect URIs are specified, any redirect URI is allowed.`} ${t`If no explicit redirect URIs are specified, any redirect URI is allowed.`}
</p> </p>
</ak-form-element-horizontal> </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"> <select class="pf-c-form-control">
<option value="" ?selected=${this.instance?.rsaKey === undefined}> <option value="" ?selected=${this.instance?.signingKey === undefined}>
--------- ---------
</option> </option>
${until( ${until(
@ -188,7 +188,7 @@ ${this.instance?.redirectUris}</textarea
}) })
.then((keys) => { .then((keys) => {
return keys.results.map((key) => { return keys.results.map((key) => {
let selected = this.instance?.rsaKey === key.pk; let selected = this.instance?.signingKey === key.pk;
if (keys.results.length === 1) { if (keys.results.length === 1) {
selected = true; selected = true;
} }
@ -203,9 +203,7 @@ ${this.instance?.redirectUris}</textarea
html`<option>${t`Loading...`}</option>`, html`<option>${t`Loading...`}</option>`,
)} )}
</select> </select>
<p class="pf-c-form__helper-text"> <p class="pf-c-form__helper-text">${t`Key used to sign the tokens.`}</p>
${t`Key used to sign the tokens. Only required when JWT Algorithm is set to RS256.`}
</p>
</ak-form-element-horizontal> </ak-form-element-horizontal>
</div> </div>
</ak-form-group> </ak-form-group>
@ -252,29 +250,6 @@ ${this.instance?.redirectUris}</textarea
${t`(Format: hours=-1;minutes=-2;seconds=-3).`} ${t`(Format: hours=-1;minutes=-2;seconds=-3).`}
</p> </p>
</ak-form-element-horizontal> </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"> <ak-form-element-horizontal label=${t`Scopes`} name="propertyMappings">
<select class="pf-c-form-control" multiple> <select class="pf-c-form-control" multiple>
${until( ${until(

View File

@ -20,7 +20,6 @@ The following placeholders will be used:
Create an OAuth2/OpenID provider with the following parameters: Create an OAuth2/OpenID provider with the following parameters:
- Client Type: `Confidential` - 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) - 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 - Scopes: OpenID, Email and Profile

View File

@ -7,7 +7,7 @@ title: Budibase
From https://github.com/Budibase/budibase From https://github.com/Budibase/budibase
:::note :::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 ## 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: Create an application in authentik. Create an OAuth2/OpenID provider with the following parameters:
- Client Type: `Confidential` - Client Type: `Confidential`
- JWT Algorithm: `RS256`
- Scopes: OpenID, Email and Profile - Scopes: OpenID, Email and Profile
- RSA Key: Select any available key - RSA Key: Select any available key
- Redirect URIs: `https://budibase.company/api/global/auth/oidc/callback` - 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` - Config URL: `https://authentik.company/application/o/<Slug of the application from above>/.well-known/openid-configuration`
- Client ID: `Client ID from above` - Client ID: `Client ID from above`
- Client Secret: `Client Secret from above` - Client Secret: `Client Secret from above`

View File

@ -20,7 +20,6 @@ The following placeholders will be used:
Create an application in authentik. Create an OAuth2/OpenID provider with the following parameters: Create an application in authentik. Create an OAuth2/OpenID provider with the following parameters:
- Client Type: `Confidential` - Client Type: `Confidential`
- JWT Algorithm: `RS256`
- Scopes: OpenID, Email and Profile - Scopes: OpenID, Email and Profile
- RSA Key: Select any available key - RSA Key: Select any available key
- Redirect URIs: `https://grafana.company/login/generic_oauth` - 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 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. 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 ```text
contains(groups[*], 'Grafana Admins') && 'Admin' || contains(groups[*], 'Grafana Editors') && 'Editor' || 'Viewer' 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). 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] [users]
auto_assign_org = true auto_assign_org = true
auto_assign_org_id = <id-of-your-default-organization> auto_assign_org_id = <id-of-your-default-organization>
``` ```

View File

@ -20,7 +20,6 @@ The following placeholders will be used:
Create an OAuth2/OpenID provider with the following parameters: Create an OAuth2/OpenID provider with the following parameters:
- Client Type: `Confidential` - Client Type: `Confidential`
- JWT Algorithm: `RS256`
- Redirect URIs: `https://harbor.company/c/oidc/callback` - Redirect URIs: `https://harbor.company/c/oidc/callback`
- Scopes: OpenID, Email and Profile - Scopes: OpenID, Email and Profile

View File

@ -20,7 +20,6 @@ The following placeholders will be used:
Create an application in authentik. Create an OAuth2/OpenID provider with the following parameters: Create an application in authentik. Create an OAuth2/OpenID provider with the following parameters:
- Client Type: `Confidential` - Client Type: `Confidential`
- JWT Algorithm: `RS256`
- Scopes: OpenID, Email and Profile - Scopes: OpenID, Email and Profile
- RSA Key: Select any available key - RSA Key: Select any available key
- Redirect URIs: `https://hedgedoc.company/auth/oauth2/callback` - Redirect URIs: `https://hedgedoc.company/auth/oauth2/callback`

View File

@ -21,7 +21,6 @@ The following placeholders will be used:
Create an application in authentik. Create an OAuth2/OpenID provider with the following parameters: Create an application in authentik. Create an OAuth2/OpenID provider with the following parameters:
- Client Type: `Confidential` - Client Type: `Confidential`
- JWT Algorithm: `RS256`
- Scopes: OpenID, Email and Profile - Scopes: OpenID, Email and Profile
- RSA Key: Select any available key - RSA Key: Select any available key
- Redirect URIs: `https://matrix.company/_synapse/client/oidc/callback` - Redirect URIs: `https://matrix.company/_synapse/client/oidc/callback`

View File

@ -28,7 +28,6 @@ return {
Create an application in authentik. Create an _OAuth2/OpenID Provider_ with the following parameters: Create an application in authentik. Create an _OAuth2/OpenID Provider_ with the following parameters:
- Client Type: `Public` - Client Type: `Public`
- JWT Algorithm: `RS256`
- Scopes: OpenID, Email, Profile and the scope you created above - Scopes: OpenID, Email, Profile and the scope you created above
- RSA Key: Select any available key - RSA Key: Select any available key
- Redirect URIs: `https://minio.company/oauth_callback` - Redirect URIs: `https://minio.company/oauth_callback`

View File

@ -20,7 +20,6 @@ The following placeholders will be used:
Create an application in authentik. Create an OAuth2/OpenID provider with the following parameters: Create an application in authentik. Create an OAuth2/OpenID provider with the following parameters:
- Client Type: `Confidential` - Client Type: `Confidential`
- JWT Algorithm: `RS256`
- Scopes: OpenID, Email and Profile - Scopes: OpenID, Email and Profile
- RSA Key: Select any available key - RSA Key: Select any available key
- Redirect URIs: `https://wekan.company/_oauth/oidc` - Redirect URIs: `https://wekan.company/_oauth/oidc`