Merge branch 'main' into dev
* main: web/flows: fix error when enrolling multiple WebAuthn devices consecutively (#9545) web: bump ejs from 3.1.9 to 3.1.10 in /tests/wdio (#9542) web: bump API Client version (#9543) providers/saml: fix ecdsa support (#9537) website/integrations: nextcloud: connect to existing user (#9155)
This commit is contained in:
@ -4,7 +4,7 @@ from django.utils.text import slugify
|
||||
|
||||
from authentik.brands.models import Brand
|
||||
from authentik.core.models import Group, User
|
||||
from authentik.crypto.builder import CertificateBuilder
|
||||
from authentik.crypto.builder import CertificateBuilder, PrivateKeyAlg
|
||||
from authentik.crypto.models import CertificateKeyPair
|
||||
from authentik.flows.models import Flow, FlowDesignation
|
||||
from authentik.lib.generators import generate_id
|
||||
@ -50,12 +50,10 @@ def create_test_brand(**kwargs) -> Brand:
|
||||
return Brand.objects.create(domain=uid, default=True, **kwargs)
|
||||
|
||||
|
||||
def create_test_cert(use_ec_private_key=False) -> CertificateKeyPair:
|
||||
def create_test_cert(alg=PrivateKeyAlg.RSA) -> CertificateKeyPair:
|
||||
"""Generate a certificate for testing"""
|
||||
builder = CertificateBuilder(
|
||||
name=f"{generate_id()}.self-signed.goauthentik.io",
|
||||
use_ec_private_key=use_ec_private_key,
|
||||
)
|
||||
builder = CertificateBuilder(f"{generate_id()}.self-signed.goauthentik.io")
|
||||
builder.alg = alg
|
||||
builder.build(
|
||||
subject_alt_names=[f"{generate_id()}.self-signed.goauthentik.io"],
|
||||
validity_days=360,
|
||||
|
@ -14,7 +14,13 @@ from drf_spectacular.types import OpenApiTypes
|
||||
from drf_spectacular.utils import OpenApiParameter, OpenApiResponse, extend_schema
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.exceptions import ValidationError
|
||||
from rest_framework.fields import CharField, DateTimeField, IntegerField, SerializerMethodField
|
||||
from rest_framework.fields import (
|
||||
CharField,
|
||||
ChoiceField,
|
||||
DateTimeField,
|
||||
IntegerField,
|
||||
SerializerMethodField,
|
||||
)
|
||||
from rest_framework.filters import OrderingFilter, SearchFilter
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
@ -26,7 +32,7 @@ from authentik.api.authorization import SecretKeyFilter
|
||||
from authentik.core.api.used_by import UsedByMixin
|
||||
from authentik.core.api.utils import PassiveSerializer
|
||||
from authentik.crypto.apps import MANAGED_KEY
|
||||
from authentik.crypto.builder import CertificateBuilder
|
||||
from authentik.crypto.builder import CertificateBuilder, PrivateKeyAlg
|
||||
from authentik.crypto.models import CertificateKeyPair
|
||||
from authentik.events.models import Event, EventAction
|
||||
from authentik.rbac.decorators import permission_required
|
||||
@ -178,6 +184,7 @@ class CertificateGenerationSerializer(PassiveSerializer):
|
||||
common_name = CharField()
|
||||
subject_alt_name = CharField(required=False, allow_blank=True, label=_("Subject-alt name"))
|
||||
validity_days = IntegerField(initial=365)
|
||||
alg = ChoiceField(default=PrivateKeyAlg.RSA, choices=PrivateKeyAlg.choices)
|
||||
|
||||
|
||||
class CertificateKeyPairFilter(FilterSet):
|
||||
@ -240,6 +247,7 @@ class CertificateKeyPairViewSet(UsedByMixin, ModelViewSet):
|
||||
raw_san = data.validated_data.get("subject_alt_name", "")
|
||||
sans = raw_san.split(",") if raw_san != "" else []
|
||||
builder = CertificateBuilder(data.validated_data["common_name"])
|
||||
builder.alg = data.validated_data["alg"]
|
||||
builder.build(
|
||||
subject_alt_names=sans,
|
||||
validity_days=int(data.validated_data["validity_days"]),
|
||||
|
@ -9,20 +9,28 @@ from cryptography.hazmat.primitives import hashes, serialization
|
||||
from cryptography.hazmat.primitives.asymmetric import ec, rsa
|
||||
from cryptography.hazmat.primitives.asymmetric.types import PrivateKeyTypes
|
||||
from cryptography.x509.oid import NameOID
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from authentik import __version__
|
||||
from authentik.crypto.models import CertificateKeyPair
|
||||
|
||||
|
||||
class PrivateKeyAlg(models.TextChoices):
|
||||
"""Algorithm to create private key with"""
|
||||
|
||||
RSA = "rsa", _("rsa")
|
||||
ECDSA = "ecdsa", _("ecdsa")
|
||||
|
||||
|
||||
class CertificateBuilder:
|
||||
"""Build self-signed certificates"""
|
||||
|
||||
common_name: str
|
||||
alg: PrivateKeyAlg
|
||||
|
||||
_use_ec_private_key: bool
|
||||
|
||||
def __init__(self, name: str, use_ec_private_key=False):
|
||||
self._use_ec_private_key = use_ec_private_key
|
||||
def __init__(self, name: str):
|
||||
self.alg = PrivateKeyAlg.RSA
|
||||
self.__public_key = None
|
||||
self.__private_key = None
|
||||
self.__builder = None
|
||||
@ -42,11 +50,13 @@ class CertificateBuilder:
|
||||
|
||||
def generate_private_key(self) -> PrivateKeyTypes:
|
||||
"""Generate private key"""
|
||||
if self._use_ec_private_key:
|
||||
if self.alg == PrivateKeyAlg.ECDSA:
|
||||
return ec.generate_private_key(curve=ec.SECP256R1())
|
||||
if self.alg == PrivateKeyAlg.RSA:
|
||||
return rsa.generate_private_key(
|
||||
public_exponent=65537, key_size=4096, backend=default_backend()
|
||||
)
|
||||
raise ValueError(f"Invalid alg: {self.alg}")
|
||||
|
||||
def build(
|
||||
self,
|
||||
|
@ -10,6 +10,7 @@ from jwt import PyJWKSet
|
||||
|
||||
from authentik.core.models import Application
|
||||
from authentik.core.tests.utils import create_test_cert, create_test_flow
|
||||
from authentik.crypto.builder import PrivateKeyAlg
|
||||
from authentik.crypto.models import CertificateKeyPair
|
||||
from authentik.lib.generators import generate_id
|
||||
from authentik.providers.oauth2.models import OAuth2Provider
|
||||
@ -82,7 +83,7 @@ class TestJWKS(OAuthTestCase):
|
||||
client_id="test",
|
||||
authorization_flow=create_test_flow(),
|
||||
redirect_uris="http://local.invalid",
|
||||
signing_key=create_test_cert(use_ec_private_key=True),
|
||||
signing_key=create_test_cert(PrivateKeyAlg.ECDSA),
|
||||
)
|
||||
app = Application.objects.create(name="test", slug="test", provider=provider)
|
||||
response = self.client.get(
|
||||
|
@ -0,0 +1,44 @@
|
||||
# Generated by Django 5.0.4 on 2024-05-01 15:32
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_providers_saml", "0013_samlprovider_default_relay_state"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="samlprovider",
|
||||
name="digest_algorithm",
|
||||
field=models.TextField(
|
||||
choices=[
|
||||
("http://www.w3.org/2000/09/xmldsig#sha1", "SHA1"),
|
||||
("http://www.w3.org/2001/04/xmlenc#sha256", "SHA256"),
|
||||
("http://www.w3.org/2001/04/xmldsig-more#sha384", "SHA384"),
|
||||
("http://www.w3.org/2001/04/xmlenc#sha512", "SHA512"),
|
||||
],
|
||||
default="http://www.w3.org/2001/04/xmlenc#sha256",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="samlprovider",
|
||||
name="signature_algorithm",
|
||||
field=models.TextField(
|
||||
choices=[
|
||||
("http://www.w3.org/2000/09/xmldsig#rsa-sha1", "RSA-SHA1"),
|
||||
("http://www.w3.org/2001/04/xmldsig-more#rsa-sha256", "RSA-SHA256"),
|
||||
("http://www.w3.org/2001/04/xmldsig-more#rsa-sha384", "RSA-SHA384"),
|
||||
("http://www.w3.org/2001/04/xmldsig-more#rsa-sha512", "RSA-SHA512"),
|
||||
("http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha1", "ECDSA-SHA1"),
|
||||
("http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256", "ECDSA-SHA256"),
|
||||
("http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha384", "ECDSA-SHA384"),
|
||||
("http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha512", "ECDSA-SHA512"),
|
||||
("http://www.w3.org/2000/09/xmldsig#dsa-sha1", "DSA-SHA1"),
|
||||
],
|
||||
default="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
|
||||
),
|
||||
),
|
||||
]
|
@ -11,6 +11,10 @@ from authentik.crypto.models import CertificateKeyPair
|
||||
from authentik.lib.utils.time import timedelta_string_validator
|
||||
from authentik.sources.saml.processors.constants import (
|
||||
DSA_SHA1,
|
||||
ECDSA_SHA1,
|
||||
ECDSA_SHA256,
|
||||
ECDSA_SHA384,
|
||||
ECDSA_SHA512,
|
||||
RSA_SHA1,
|
||||
RSA_SHA256,
|
||||
RSA_SHA384,
|
||||
@ -92,8 +96,7 @@ class SAMLProvider(Provider):
|
||||
),
|
||||
)
|
||||
|
||||
digest_algorithm = models.CharField(
|
||||
max_length=50,
|
||||
digest_algorithm = models.TextField(
|
||||
choices=(
|
||||
(SHA1, _("SHA1")),
|
||||
(SHA256, _("SHA256")),
|
||||
@ -102,13 +105,16 @@ class SAMLProvider(Provider):
|
||||
),
|
||||
default=SHA256,
|
||||
)
|
||||
signature_algorithm = models.CharField(
|
||||
max_length=50,
|
||||
signature_algorithm = models.TextField(
|
||||
choices=(
|
||||
(RSA_SHA1, _("RSA-SHA1")),
|
||||
(RSA_SHA256, _("RSA-SHA256")),
|
||||
(RSA_SHA384, _("RSA-SHA384")),
|
||||
(RSA_SHA512, _("RSA-SHA512")),
|
||||
(ECDSA_SHA1, _("ECDSA-SHA1")),
|
||||
(ECDSA_SHA256, _("ECDSA-SHA256")),
|
||||
(ECDSA_SHA384, _("ECDSA-SHA384")),
|
||||
(ECDSA_SHA512, _("ECDSA-SHA512")),
|
||||
(DSA_SHA1, _("DSA-SHA1")),
|
||||
),
|
||||
default=RSA_SHA256,
|
||||
|
@ -7,13 +7,14 @@ from lxml import etree # nosec
|
||||
|
||||
from authentik.core.models import Application
|
||||
from authentik.core.tests.utils import create_test_cert, create_test_flow
|
||||
from authentik.crypto.builder import PrivateKeyAlg
|
||||
from authentik.lib.generators import generate_id
|
||||
from authentik.lib.tests.utils import load_fixture
|
||||
from authentik.lib.xml import lxml_from_string
|
||||
from authentik.providers.saml.models import SAMLBindings, SAMLPropertyMapping, SAMLProvider
|
||||
from authentik.providers.saml.processors.metadata import MetadataProcessor
|
||||
from authentik.providers.saml.processors.metadata_parser import ServiceProviderMetadataParser
|
||||
from authentik.sources.saml.processors.constants import NS_MAP, NS_SAML_METADATA
|
||||
from authentik.sources.saml.processors.constants import ECDSA_SHA256, NS_MAP, NS_SAML_METADATA
|
||||
|
||||
|
||||
class TestServiceProviderMetadataParser(TestCase):
|
||||
@ -107,12 +108,41 @@ class TestServiceProviderMetadataParser(TestCase):
|
||||
load_fixture("fixtures/cert.xml").replace("/apps/user_saml", "")
|
||||
)
|
||||
|
||||
def test_signature(self):
|
||||
"""Test signature validation"""
|
||||
def test_signature_rsa(self):
|
||||
"""Test signature validation (RSA)"""
|
||||
provider = SAMLProvider.objects.create(
|
||||
name=generate_id(),
|
||||
authorization_flow=self.flow,
|
||||
signing_kp=create_test_cert(),
|
||||
signing_kp=create_test_cert(PrivateKeyAlg.RSA),
|
||||
)
|
||||
Application.objects.create(
|
||||
name=generate_id(),
|
||||
slug=generate_id(),
|
||||
provider=provider,
|
||||
)
|
||||
request = self.factory.get("/")
|
||||
metadata = MetadataProcessor(provider, request).build_entity_descriptor()
|
||||
|
||||
root = fromstring(metadata.encode())
|
||||
xmlsec.tree.add_ids(root, ["ID"])
|
||||
signature_nodes = root.xpath("/md:EntityDescriptor/ds:Signature", namespaces=NS_MAP)
|
||||
signature_node = signature_nodes[0]
|
||||
ctx = xmlsec.SignatureContext()
|
||||
key = xmlsec.Key.from_memory(
|
||||
provider.signing_kp.certificate_data,
|
||||
xmlsec.constants.KeyDataFormatCertPem,
|
||||
None,
|
||||
)
|
||||
ctx.key = key
|
||||
ctx.verify(signature_node)
|
||||
|
||||
def test_signature_ecdsa(self):
|
||||
"""Test signature validation (ECDSA)"""
|
||||
provider = SAMLProvider.objects.create(
|
||||
name=generate_id(),
|
||||
authorization_flow=self.flow,
|
||||
signing_kp=create_test_cert(PrivateKeyAlg.ECDSA),
|
||||
signature_algorithm=ECDSA_SHA256,
|
||||
)
|
||||
Application.objects.create(
|
||||
name=generate_id(),
|
||||
|
@ -0,0 +1,44 @@
|
||||
# Generated by Django 5.0.4 on 2024-05-01 15:44
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_sources_saml", "0013_samlsource_verification_kp_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="samlsource",
|
||||
name="digest_algorithm",
|
||||
field=models.TextField(
|
||||
choices=[
|
||||
("http://www.w3.org/2000/09/xmldsig#sha1", "SHA1"),
|
||||
("http://www.w3.org/2001/04/xmlenc#sha256", "SHA256"),
|
||||
("http://www.w3.org/2001/04/xmldsig-more#sha384", "SHA384"),
|
||||
("http://www.w3.org/2001/04/xmlenc#sha512", "SHA512"),
|
||||
],
|
||||
default="http://www.w3.org/2001/04/xmlenc#sha256",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="samlsource",
|
||||
name="signature_algorithm",
|
||||
field=models.TextField(
|
||||
choices=[
|
||||
("http://www.w3.org/2000/09/xmldsig#rsa-sha1", "RSA-SHA1"),
|
||||
("http://www.w3.org/2001/04/xmldsig-more#rsa-sha256", "RSA-SHA256"),
|
||||
("http://www.w3.org/2001/04/xmldsig-more#rsa-sha384", "RSA-SHA384"),
|
||||
("http://www.w3.org/2001/04/xmldsig-more#rsa-sha512", "RSA-SHA512"),
|
||||
("http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha1", "ECDSA-SHA1"),
|
||||
("http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256", "ECDSA-SHA256"),
|
||||
("http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha384", "ECDSA-SHA384"),
|
||||
("http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha512", "ECDSA-SHA512"),
|
||||
("http://www.w3.org/2000/09/xmldsig#dsa-sha1", "DSA-SHA1"),
|
||||
],
|
||||
default="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
|
||||
),
|
||||
),
|
||||
]
|
@ -15,6 +15,10 @@ from authentik.flows.models import Flow
|
||||
from authentik.lib.utils.time import timedelta_string_validator
|
||||
from authentik.sources.saml.processors.constants import (
|
||||
DSA_SHA1,
|
||||
ECDSA_SHA1,
|
||||
ECDSA_SHA256,
|
||||
ECDSA_SHA384,
|
||||
ECDSA_SHA512,
|
||||
RSA_SHA1,
|
||||
RSA_SHA256,
|
||||
RSA_SHA384,
|
||||
@ -143,8 +147,7 @@ class SAMLSource(Source):
|
||||
verbose_name=_("Signing Keypair"),
|
||||
)
|
||||
|
||||
digest_algorithm = models.CharField(
|
||||
max_length=50,
|
||||
digest_algorithm = models.TextField(
|
||||
choices=(
|
||||
(SHA1, _("SHA1")),
|
||||
(SHA256, _("SHA256")),
|
||||
@ -153,13 +156,16 @@ class SAMLSource(Source):
|
||||
),
|
||||
default=SHA256,
|
||||
)
|
||||
signature_algorithm = models.CharField(
|
||||
max_length=50,
|
||||
signature_algorithm = models.TextField(
|
||||
choices=(
|
||||
(RSA_SHA1, _("RSA-SHA1")),
|
||||
(RSA_SHA256, _("RSA-SHA256")),
|
||||
(RSA_SHA384, _("RSA-SHA384")),
|
||||
(RSA_SHA512, _("RSA-SHA512")),
|
||||
(ECDSA_SHA1, _("ECDSA-SHA1")),
|
||||
(ECDSA_SHA256, _("ECDSA-SHA256")),
|
||||
(ECDSA_SHA384, _("ECDSA-SHA384")),
|
||||
(ECDSA_SHA512, _("ECDSA-SHA512")),
|
||||
(DSA_SHA1, _("DSA-SHA1")),
|
||||
),
|
||||
default=RSA_SHA256,
|
||||
|
@ -26,9 +26,16 @@ SAML_BINDING_REDIRECT = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
|
||||
|
||||
DSA_SHA1 = "http://www.w3.org/2000/09/xmldsig#dsa-sha1"
|
||||
RSA_SHA1 = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"
|
||||
# https://datatracker.ietf.org/doc/html/rfc4051#section-2.3.2
|
||||
RSA_SHA256 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
|
||||
RSA_SHA384 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384"
|
||||
RSA_SHA512 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512"
|
||||
# https://datatracker.ietf.org/doc/html/rfc4051#section-2.3.6
|
||||
ECDSA_SHA1 = "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha1"
|
||||
ECDSA_SHA224 = "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha224"
|
||||
ECDSA_SHA256 = "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256"
|
||||
ECDSA_SHA384 = "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha384"
|
||||
ECDSA_SHA512 = "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha512"
|
||||
|
||||
SHA1 = "http://www.w3.org/2000/09/xmldsig#sha1"
|
||||
SHA256 = "http://www.w3.org/2001/04/xmlenc#sha256"
|
||||
@ -41,6 +48,11 @@ SIGN_ALGORITHM_TRANSFORM_MAP = {
|
||||
RSA_SHA256: xmlsec.constants.TransformRsaSha256,
|
||||
RSA_SHA384: xmlsec.constants.TransformRsaSha384,
|
||||
RSA_SHA512: xmlsec.constants.TransformRsaSha512,
|
||||
ECDSA_SHA1: xmlsec.constants.TransformEcdsaSha1,
|
||||
ECDSA_SHA224: xmlsec.constants.TransformEcdsaSha224,
|
||||
ECDSA_SHA256: xmlsec.constants.TransformEcdsaSha256,
|
||||
ECDSA_SHA384: xmlsec.constants.TransformEcdsaSha384,
|
||||
ECDSA_SHA512: xmlsec.constants.TransformEcdsaSha512,
|
||||
}
|
||||
|
||||
DIGEST_ALGORITHM_TRANSLATION_MAP = {
|
||||
|
@ -0,0 +1,23 @@
|
||||
# Generated by Django 5.0.4 on 2024-05-01 15:32
|
||||
|
||||
import authentik.lib.utils.time
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_tenants", "0002_tenant_default_token_duration_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="tenant",
|
||||
name="default_token_duration",
|
||||
field=models.TextField(
|
||||
default="days=1",
|
||||
help_text="Default token duration",
|
||||
validators=[authentik.lib.utils.time.timedelta_string_validator],
|
||||
),
|
||||
),
|
||||
]
|
@ -4131,6 +4131,10 @@
|
||||
"http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
|
||||
"http://www.w3.org/2001/04/xmldsig-more#rsa-sha384",
|
||||
"http://www.w3.org/2001/04/xmldsig-more#rsa-sha512",
|
||||
"http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha1",
|
||||
"http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256",
|
||||
"http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha384",
|
||||
"http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha512",
|
||||
"http://www.w3.org/2000/09/xmldsig#dsa-sha1"
|
||||
],
|
||||
"title": "Signature algorithm"
|
||||
@ -4935,6 +4939,10 @@
|
||||
"http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
|
||||
"http://www.w3.org/2001/04/xmldsig-more#rsa-sha384",
|
||||
"http://www.w3.org/2001/04/xmldsig-more#rsa-sha512",
|
||||
"http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha1",
|
||||
"http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256",
|
||||
"http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha384",
|
||||
"http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha512",
|
||||
"http://www.w3.org/2000/09/xmldsig#dsa-sha1"
|
||||
],
|
||||
"title": "Signature algorithm"
|
||||
|
1910
poetry.lock
generated
1910
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@ -89,6 +89,7 @@ channels = { version = "*", extras = ["daphne"] }
|
||||
channels-redis = "*"
|
||||
codespell = "*"
|
||||
colorama = "*"
|
||||
cryptography = "*"
|
||||
dacite = "*"
|
||||
deepmerge = "*"
|
||||
defusedxml = "*"
|
||||
@ -101,7 +102,7 @@ django-redis = "*"
|
||||
django-storages = { extras = ["s3"], version = "*" }
|
||||
# See https://github.com/django-tenants/django-tenants/pull/997
|
||||
django-tenants = { git = "https://github.com/rissson/django-tenants.git", branch="authentik-fixes" }
|
||||
djangorestframework = "*"
|
||||
djangorestframework = "3.14.0"
|
||||
djangorestframework-guardian = "*"
|
||||
docker = "*"
|
||||
drf-spectacular = "*"
|
||||
@ -115,17 +116,11 @@ gunicorn = "*"
|
||||
jsonpatch = "*"
|
||||
kubernetes = "*"
|
||||
ldap3 = "*"
|
||||
lxml = [
|
||||
# 5.0.0 works with libxml2 2.11.x, which is standard on brew
|
||||
{ version = "5.0.0", platform = "darwin" },
|
||||
# 4.9.x works with previous libxml2 versions, which is what we get on linux
|
||||
{ version = "4.9.4", platform = "linux" },
|
||||
]
|
||||
lxml = "*"
|
||||
opencontainers = { extras = ["reggie"], version = "*" }
|
||||
packaging = "*"
|
||||
paramiko = "*"
|
||||
psycopg = { extras = ["c"], version = "*" }
|
||||
pycryptodome = "*"
|
||||
pydantic = "*"
|
||||
pydantic-scim = "*"
|
||||
pyjwt = "*"
|
||||
|
21
schema.yml
21
schema.yml
@ -17051,6 +17051,10 @@ paths:
|
||||
enum:
|
||||
- http://www.w3.org/2000/09/xmldsig#dsa-sha1
|
||||
- http://www.w3.org/2000/09/xmldsig#rsa-sha1
|
||||
- http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha1
|
||||
- http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256
|
||||
- http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha384
|
||||
- http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha512
|
||||
- http://www.w3.org/2001/04/xmldsig-more#rsa-sha256
|
||||
- http://www.w3.org/2001/04/xmldsig-more#rsa-sha384
|
||||
- http://www.w3.org/2001/04/xmldsig-more#rsa-sha512
|
||||
@ -20910,6 +20914,10 @@ paths:
|
||||
enum:
|
||||
- http://www.w3.org/2000/09/xmldsig#dsa-sha1
|
||||
- http://www.w3.org/2000/09/xmldsig#rsa-sha1
|
||||
- http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha1
|
||||
- http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256
|
||||
- http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha384
|
||||
- http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha512
|
||||
- http://www.w3.org/2001/04/xmldsig-more#rsa-sha256
|
||||
- http://www.w3.org/2001/04/xmldsig-more#rsa-sha384
|
||||
- http://www.w3.org/2001/04/xmldsig-more#rsa-sha512
|
||||
@ -30450,6 +30458,11 @@ components:
|
||||
- pending_user
|
||||
- pending_user_avatar
|
||||
- type
|
||||
AlgEnum:
|
||||
enum:
|
||||
- rsa
|
||||
- ecdsa
|
||||
type: string
|
||||
App:
|
||||
type: object
|
||||
description: Serialize Application info
|
||||
@ -32107,6 +32120,10 @@ components:
|
||||
type: string
|
||||
validity_days:
|
||||
type: integer
|
||||
alg:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/AlgEnum'
|
||||
default: rsa
|
||||
required:
|
||||
- common_name
|
||||
- validity_days
|
||||
@ -43658,6 +43675,10 @@ components:
|
||||
- http://www.w3.org/2001/04/xmldsig-more#rsa-sha256
|
||||
- http://www.w3.org/2001/04/xmldsig-more#rsa-sha384
|
||||
- http://www.w3.org/2001/04/xmldsig-more#rsa-sha512
|
||||
- http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha1
|
||||
- http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256
|
||||
- http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha384
|
||||
- http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha512
|
||||
- http://www.w3.org/2000/09/xmldsig#dsa-sha1
|
||||
type: string
|
||||
Source:
|
||||
|
6
tests/wdio/package-lock.json
generated
6
tests/wdio/package-lock.json
generated
@ -2869,9 +2869,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ejs": {
|
||||
"version": "3.1.9",
|
||||
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.9.tgz",
|
||||
"integrity": "sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==",
|
||||
"version": "3.1.10",
|
||||
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz",
|
||||
"integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"jake": "^10.8.5"
|
||||
|
9
web/package-lock.json
generated
9
web/package-lock.json
generated
@ -17,7 +17,7 @@
|
||||
"@codemirror/theme-one-dark": "^6.1.2",
|
||||
"@formatjs/intl-listformat": "^7.5.5",
|
||||
"@fortawesome/fontawesome-free": "^6.5.2",
|
||||
"@goauthentik/api": "^2024.4.1-1714149838",
|
||||
"@goauthentik/api": "^2024.4.1-1714655911",
|
||||
"@lit-labs/task": "^3.1.0",
|
||||
"@lit/context": "^1.1.1",
|
||||
"@lit/localize": "^0.12.1",
|
||||
@ -25,7 +25,6 @@
|
||||
"@open-wc/lit-helpers": "^0.7.0",
|
||||
"@patternfly/elements": "^3.0.1",
|
||||
"@patternfly/patternfly": "^4.224.2",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.17.2",
|
||||
"@sentry/browser": "^7.112.2",
|
||||
"@webcomponents/webcomponentsjs": "^2.8.0",
|
||||
"base64-js": "^1.5.1",
|
||||
@ -2664,9 +2663,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@goauthentik/api": {
|
||||
"version": "2024.4.1-1714149838",
|
||||
"resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2024.4.1-1714149838.tgz",
|
||||
"integrity": "sha512-Diat0nFTRm7gDb57d5KnKLYTumXKegT0ifpB2YdWSHioP9XQWdbIGQyL7+A1xcQdhVJo3vAbpiq0XI3J+gaHJg=="
|
||||
"version": "2024.4.1-1714655911",
|
||||
"resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2024.4.1-1714655911.tgz",
|
||||
"integrity": "sha512-x7ViXuDh928e7B4dbQlOJXCtFnWJGGmOQcGI4IG6eWafUcq9+jgDnFrd0qqkDGcipqjKiY52B4RSdRNRxRsucA=="
|
||||
},
|
||||
"node_modules/@hcaptcha/types": {
|
||||
"version": "1.0.3",
|
||||
|
@ -38,7 +38,7 @@
|
||||
"@codemirror/theme-one-dark": "^6.1.2",
|
||||
"@formatjs/intl-listformat": "^7.5.5",
|
||||
"@fortawesome/fontawesome-free": "^6.5.2",
|
||||
"@goauthentik/api": "^2024.4.1-1714149838",
|
||||
"@goauthentik/api": "^2024.4.1-1714655911",
|
||||
"@lit-labs/task": "^3.1.0",
|
||||
"@lit/context": "^1.1.1",
|
||||
"@lit/localize": "^0.12.1",
|
||||
|
@ -29,5 +29,9 @@ export const signatureAlgorithmOptions = toOptions([
|
||||
["RSA-SHA256", SignatureAlgorithmEnum._200104XmldsigMorersaSha256, true],
|
||||
["RSA-SHA384", SignatureAlgorithmEnum._200104XmldsigMorersaSha384],
|
||||
["RSA-SHA512", SignatureAlgorithmEnum._200104XmldsigMorersaSha512],
|
||||
["ECDSA-SHA1", SignatureAlgorithmEnum._200104XmldsigMoreecdsaSha1],
|
||||
["ECDSA-SHA256", SignatureAlgorithmEnum._200104XmldsigMoreecdsaSha256],
|
||||
["ECDSA-SHA384", SignatureAlgorithmEnum._200104XmldsigMoreecdsaSha384],
|
||||
["ECDSA-SHA512", SignatureAlgorithmEnum._200104XmldsigMoreecdsaSha512],
|
||||
["DSA-SHA1", SignatureAlgorithmEnum._200009XmldsigdsaSha1],
|
||||
]);
|
||||
|
@ -6,7 +6,12 @@ import { msg } from "@lit/localize";
|
||||
import { TemplateResult, html } from "lit";
|
||||
import { customElement } from "lit/decorators.js";
|
||||
|
||||
import { CertificateGenerationRequest, CertificateKeyPair, CryptoApi } from "@goauthentik/api";
|
||||
import {
|
||||
AlgEnum,
|
||||
CertificateGenerationRequest,
|
||||
CertificateKeyPair,
|
||||
CryptoApi,
|
||||
} from "@goauthentik/api";
|
||||
|
||||
@customElement("ak-crypto-certificate-generate-form")
|
||||
export class CertificateKeyPairForm extends Form<CertificateGenerationRequest> {
|
||||
@ -40,6 +45,29 @@ export class CertificateKeyPairForm extends Form<CertificateGenerationRequest> {
|
||||
?required=${true}
|
||||
>
|
||||
<input class="pf-c-form-control" type="number" value="365" />
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Private key Algorithm")}
|
||||
?required=${true}
|
||||
name="alg"
|
||||
>
|
||||
<ak-radio
|
||||
.options=${[
|
||||
{
|
||||
label: msg("RSA"),
|
||||
value: AlgEnum.Rsa,
|
||||
default: true,
|
||||
},
|
||||
{
|
||||
label: msg("ECDSA"),
|
||||
value: AlgEnum.Ecdsa,
|
||||
},
|
||||
]}
|
||||
>
|
||||
</ak-radio>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${msg("Algorithm used to generate the private key.")}
|
||||
</p>
|
||||
</ak-form-element-horizontal> `;
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import "@goauthentik/elements/EmptyState";
|
||||
import { BaseStage } from "@goauthentik/flow/stages/base";
|
||||
|
||||
import { msg, str } from "@lit/localize";
|
||||
import { CSSResult, TemplateResult, css, html, nothing } from "lit";
|
||||
import { CSSResult, PropertyValues, TemplateResult, css, html, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
|
||||
@ -115,7 +115,8 @@ export class WebAuthnAuthenticatorRegisterStage extends BaseStage<
|
||||
});
|
||||
}
|
||||
|
||||
firstUpdated(): void {
|
||||
updated(changedProperties: PropertyValues<this>) {
|
||||
if (changedProperties.has("challenge") && this.challenge !== undefined) {
|
||||
// convert certain members of the PublicKeyCredentialCreateOptions into
|
||||
// byte arrays as expected by the spec.
|
||||
this.publicKeyCredentialCreateOptions = transformCredentialCreateOptions(
|
||||
@ -124,6 +125,7 @@ export class WebAuthnAuthenticatorRegisterStage extends BaseStage<
|
||||
);
|
||||
this.registerWrapper();
|
||||
}
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
return html`<header class="pf-c-login__main-header">
|
||||
|
@ -26,10 +26,6 @@ If you use locally installed databases, the PostgreSQL credentials given to auth
|
||||
Depending on your platform, some native dependencies might be required. On macOS, run `brew install libxmlsec1 libpq`, and for the CLI tools `brew install postgresql redis node@20`
|
||||
:::
|
||||
|
||||
:::info
|
||||
As long as [this issue](https://github.com/xmlsec/python-xmlsec/issues/252) about `libxmlsec-1.3.0` is open, a workaround is required to install a compatible version of `libxmlsec1` with brew, see [this comment](https://github.com/xmlsec/python-xmlsec/issues/254#issuecomment-1612005910).
|
||||
:::
|
||||
|
||||
1. Create an isolated Python environment. To create the environment and install dependencies, run the following commands in the same directory as your local authentik git repository:
|
||||
|
||||
```shell
|
||||
|
@ -51,7 +51,7 @@ authentik already provides some default _scopes_ with _claims_ inside them, such
|
||||
|
||||
##### Custom profile scope
|
||||
|
||||
If you do not need storage quota or group information in Nextcloud [skip to the next step](#provider-and-application).
|
||||
If you do not need storage quota, group information, or to manage already existing users in Nextcloud [skip to the next step](#provider-and-application).
|
||||
|
||||
However, if you want to be able to control how much storage users in Nextcloud can use, as well as which users are recognized as Nextcloud administrators, you would need to make this information available in Nextcloud. To achieve this you would need to create a custom `profile` scope. To do so, go to _Customization_ -> _Property mappings_. Create a _Scope mapping_ with the following parameters:
|
||||
|
||||
@ -74,7 +74,10 @@ return {
|
||||
"name": request.user.name,
|
||||
"groups": groups,
|
||||
# To set a quota set the "nextcloud_quota" property in the user's attributes
|
||||
"quota": user.group_attributes().get("nextcloud_quota", None)
|
||||
"quota": user.group_attributes().get("nextcloud_quota", None),
|
||||
# To connect an already existing user, set the "nextcloud_user_id" property in the
|
||||
# user's attributes to the username of the corresponding user on Nextcloud.
|
||||
"user_id": user.attributes.get("nextcloud_user_id", user.uuid),
|
||||
}
|
||||
```
|
||||
|
||||
@ -84,6 +87,13 @@ To set a quota set the "nextcloud_quota" property in the user's attributes. This
|
||||
If set to a value, for example `1 GB`, user(s) will have 1GB storage quota. If the attribute is not set, user(s) will have unlimited storage.
|
||||
:::
|
||||
|
||||
:::note
|
||||
To connect to an already existing Nextcloud user, set the "nextcloud_user_id" property in the user's attributes. This must be set for each individual user.
|
||||
|
||||
The value of `nextcloud_user_id` must match the field `username` of the user on the Nextcloud instance. On Nextcloud, go to _Users_ to see the username of the user you are trying to connect to (Under user's `Display name`).
|
||||
If set to a value, for example `goauthentik`, it will try to connect to the `goauthentik` user on the Nextcloud instance. Otherwise, the user's UUID will be used.
|
||||
:::
|
||||
|
||||
##### Provider and Application
|
||||
|
||||
Create a provider for Nextcloud. In the Admin Interface, go to _Applications_ -> _Providers_. Create an _OAuth2/OpenID Provider_ with the following parameters:
|
||||
@ -129,7 +139,7 @@ Add a new provider using the `+` button and set the following values:
|
||||
:::
|
||||
- Scope: `email`, `profile` (you can safely omit `openid` if you prefer)
|
||||
- Attribute mappings:
|
||||
- User ID mapping: sub
|
||||
- User ID mapping: sub (or `user_id` if you need to connect to an already existing Nextcloud user)
|
||||
- Display name mapping: name
|
||||
- Email mapping: email
|
||||
- Quota mapping: quota (leave empty if you have skipped the [custom profile scope](#custom-profile-scope) section)
|
||||
@ -137,9 +147,9 @@ Add a new provider using the `+` button and set the following values:
|
||||
:::tip
|
||||
You need to enable the "Use group provisioning" checkmark to be able to write to this field
|
||||
:::
|
||||
- Use unique user ID: If you only have one provider you can uncheck this if you prefer.
|
||||
- Use unique user ID: If you only have one provider you can deselect this if you prefer. This will affect your Federated Cloud ID, which you can check under _Personal settings_ -> _Sharing_ -> _Federated Cloud_. If the box is selected, nextcloud will pick a hashed value here (`437218904321784903214789023@nextcloud.instance` for example). Otherwise, it will use the mapped user ID (`<authentik's sub or user_id>@nextcloud.instance`).
|
||||
:::tip
|
||||
To avoid your group assignment being a hash value, deselect **Use unique user ID**.
|
||||
To avoid your federated cloud id being a hash value, deselect **Use unique user ID** and use `user_id` in the **User ID mapping** field.
|
||||
:::
|
||||
|
||||
At this stage you should be able to login with SSO.
|
||||
|
Reference in New Issue
Block a user