Merge branch 'main' into application-wizard-2-with-api-and-tests
* main: (58 commits) web: Replace ad-hoc toggle control with ak-toggle-group (#6470) blueprints: fix tag values not resolved correctly (#6653) web: bump @codemirror/lang-javascript from 6.1.9 to 6.2.0 in /web (#6647) core: bump ruff from 0.0.285 to 0.0.286 (#6649) web: bump the eslint group in /web with 1 update (#6646) web: bump @rollup/plugin-typescript from 11.1.2 to 11.1.3 in /web (#6648) core: bump python from 3.11.4-slim-bookworm to 3.11.5-slim-bookworm (#6650) web/admin: only show token expiry when token is set to expire (#6643) providers/proxy: fix JWKS url in embedded outpost (#6644) providers/oauth2: fix id_token being saved incorrectly leading to lost claims (#6645) web/user: only render expand element when required (#6641) root: re-fix docker build paths web/admin: set required flag to false for user attributes (#6418) root: fix docker build root: fix config loading for outposts (#6640) core: compile backend translations (#6639) translate: Updates for file locale/en/LC_MESSAGES/django.po in nl on branch main (#6635) translate: Updates for file web/xliff/en.xlf in nl on branch main (#6634) core: fix filtering users by type attribute (#6638) web/elements: improve table error handling, prevent infinite loading … (#6636) ...
|
@ -7,3 +7,4 @@ build/**
|
|||
build_docs/**
|
||||
Dockerfile
|
||||
authentik/enterprise
|
||||
blueprints/local
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
---
|
||||
# See https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#force-deleting-cache-entries
|
||||
name: Cleanup cache after PR is closed
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- closed
|
||||
|
||||
jobs:
|
||||
cleanup:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Cleanup
|
||||
run: |
|
||||
gh extension install actions/gh-actions-cache
|
||||
|
||||
REPO=${{ github.repository }}
|
||||
BRANCH="refs/pull/${{ github.event.pull_request.number }}/merge"
|
||||
|
||||
echo "Fetching list of cache key"
|
||||
cacheKeysForPR=$(gh actions-cache list -R $REPO -B $BRANCH -L 100 | cut -f 1 )
|
||||
|
||||
# Setting this to not fail the workflow while deleting cache keys.
|
||||
set +e
|
||||
echo "Deleting caches..."
|
||||
for cacheKey in $cacheKeysForPR; do
|
||||
gh actions-cache delete $cacheKey -R $REPO -B $BRANCH --confirm
|
||||
done
|
||||
echo "Done"
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
@ -5,6 +5,11 @@ on:
|
|||
branches:
|
||||
- main
|
||||
|
||||
env:
|
||||
POSTGRES_DB: authentik
|
||||
POSTGRES_USER: authentik
|
||||
POSTGRES_PASSWORD: "EK-5jnKfjrGRm<77"
|
||||
|
||||
jobs:
|
||||
publish-source-docs:
|
||||
runs-on: ubuntu-latest
|
||||
|
@ -15,6 +20,7 @@ jobs:
|
|||
uses: ./.github/actions/setup
|
||||
- name: generate docs
|
||||
run: |
|
||||
poetry run make migrate
|
||||
poetry run ak build_source_docs
|
||||
- name: Publish
|
||||
uses: netlify/actions/cli@master
|
||||
|
|
11
Dockerfile
|
@ -20,7 +20,7 @@ WORKDIR /work/web
|
|||
RUN npm ci --include=dev && npm run build
|
||||
|
||||
# Stage 3: Poetry to requirements.txt export
|
||||
FROM docker.io/python:3.11.4-slim-bullseye AS poetry-locker
|
||||
FROM docker.io/python:3.11.5-slim-bookworm AS poetry-locker
|
||||
|
||||
WORKDIR /work
|
||||
COPY ./pyproject.toml /work
|
||||
|
@ -31,7 +31,7 @@ RUN pip install --no-cache-dir poetry && \
|
|||
poetry export -f requirements.txt --dev --output requirements-dev.txt
|
||||
|
||||
# Stage 4: Build go proxy
|
||||
FROM docker.io/golang:1.21.0-bullseye AS go-builder
|
||||
FROM docker.io/golang:1.21.0-bookworm AS go-builder
|
||||
|
||||
WORKDIR /work
|
||||
|
||||
|
@ -39,12 +39,13 @@ COPY --from=web-builder /work/web/robots.txt /work/web/robots.txt
|
|||
COPY --from=web-builder /work/web/security.txt /work/web/security.txt
|
||||
|
||||
COPY ./cmd /work/cmd
|
||||
COPY ./authentik/lib /work/authentik/lib
|
||||
COPY ./web/static.go /work/web/static.go
|
||||
COPY ./internal /work/internal
|
||||
COPY ./go.mod /work/go.mod
|
||||
COPY ./go.sum /work/go.sum
|
||||
|
||||
RUN go build -o /work/authentik ./cmd/server/
|
||||
RUN go build -o /work/bin/authentik ./cmd/server/
|
||||
|
||||
# Stage 5: MaxMind GeoIP
|
||||
FROM ghcr.io/maxmind/geoipupdate:v6.0 as geoip
|
||||
|
@ -61,7 +62,7 @@ RUN --mount=type=secret,id=GEOIPUPDATE_ACCOUNT_ID \
|
|||
/bin/sh -c "/usr/bin/entry.sh || echo 'Failed to get GeoIP database, disabling'; exit 0"
|
||||
|
||||
# Stage 6: Run
|
||||
FROM docker.io/python:3.11.4-slim-bullseye AS final-image
|
||||
FROM docker.io/python:3.11.5-slim-bookworm AS final-image
|
||||
|
||||
ARG GIT_BUILD_HASH
|
||||
ARG VERSION
|
||||
|
@ -104,7 +105,7 @@ COPY ./tests /tests
|
|||
COPY ./manage.py /
|
||||
COPY ./blueprints /blueprints
|
||||
COPY ./lifecycle/ /lifecycle
|
||||
COPY --from=go-builder /work/authentik /bin/authentik
|
||||
COPY --from=go-builder /work/bin/authentik /bin/authentik
|
||||
COPY --from=web-builder /work/web/dist/ /web/dist/
|
||||
COPY --from=web-builder /work/web/authentik/ /web/authentik/
|
||||
COPY --from=website-builder /work/website/help/ /website/help/
|
||||
|
|
|
@ -27,6 +27,8 @@ To report a vulnerability, send an email to [security@goauthentik.io](mailto:se
|
|||
|
||||
authentik reserves the right to reclassify CVSS as necessary. To determine severity, we will use the CVSS calculator from NVD (https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator). The calculated CVSS score will then be translated into one of the following categories:
|
||||
|
||||
| Score | Severity |
|
||||
| --- | --- |
|
||||
| 0.0 | None |
|
||||
| 0.1 – 3.9 | Low |
|
||||
| 4.0 – 6.9 | Medium |
|
||||
|
|
|
@ -45,3 +45,8 @@ entries:
|
|||
attrs:
|
||||
name: "%(uid)s"
|
||||
password: "%(uid)s"
|
||||
- model: authentik_core.user
|
||||
identifiers:
|
||||
username: "%(uid)s-no-password"
|
||||
attrs:
|
||||
name: "%(uid)s"
|
||||
|
|
|
@ -36,6 +36,7 @@ entries:
|
|||
model: authentik_policies_expression.expressionpolicy
|
||||
- attrs:
|
||||
attributes:
|
||||
env_null: !Env [bar-baz, null]
|
||||
policy_pk1:
|
||||
!Format [
|
||||
"%s-%s",
|
||||
|
|
|
@ -213,8 +213,9 @@ class TestBlueprintsV1(TransactionTestCase):
|
|||
},
|
||||
},
|
||||
"nested_context": "context-nested-value",
|
||||
"env_null": None,
|
||||
}
|
||||
)
|
||||
).exists()
|
||||
)
|
||||
self.assertTrue(
|
||||
OAuthSource.objects.filter(
|
||||
|
|
|
@ -51,3 +51,9 @@ class TestBlueprintsV1ConditionalFields(TransactionTestCase):
|
|||
user: User = User.objects.filter(username=self.uid).first()
|
||||
self.assertIsNotNone(user)
|
||||
self.assertTrue(user.check_password(self.uid))
|
||||
|
||||
def test_user_null(self):
|
||||
"""Test user"""
|
||||
user: User = User.objects.filter(username=f"{self.uid}-no-password").first()
|
||||
self.assertIsNotNone(user)
|
||||
self.assertFalse(user.has_usable_password())
|
||||
|
|
|
@ -224,11 +224,11 @@ class Env(YAMLTag):
|
|||
if isinstance(node, ScalarNode):
|
||||
self.key = node.value
|
||||
if isinstance(node, SequenceNode):
|
||||
self.key = node.value[0].value
|
||||
self.default = node.value[1].value
|
||||
self.key = loader.construct_object(node.value[0])
|
||||
self.default = loader.construct_object(node.value[1])
|
||||
|
||||
def resolve(self, entry: BlueprintEntry, blueprint: Blueprint) -> Any:
|
||||
return getenv(self.key, self.default)
|
||||
return getenv(self.key) or self.default
|
||||
|
||||
|
||||
class Context(YAMLTag):
|
||||
|
@ -243,8 +243,8 @@ class Context(YAMLTag):
|
|||
if isinstance(node, ScalarNode):
|
||||
self.key = node.value
|
||||
if isinstance(node, SequenceNode):
|
||||
self.key = node.value[0].value
|
||||
self.default = node.value[1].value
|
||||
self.key = loader.construct_object(node.value[0])
|
||||
self.default = loader.construct_object(node.value[1])
|
||||
|
||||
def resolve(self, entry: BlueprintEntry, blueprint: Blueprint) -> Any:
|
||||
value = self.default
|
||||
|
@ -263,7 +263,7 @@ class Format(YAMLTag):
|
|||
|
||||
def __init__(self, loader: "BlueprintLoader", node: SequenceNode) -> None:
|
||||
super().__init__()
|
||||
self.format_string = node.value[0].value
|
||||
self.format_string = loader.construct_object(node.value[0])
|
||||
self.args = []
|
||||
for raw_node in node.value[1:]:
|
||||
self.args.append(loader.construct_object(raw_node))
|
||||
|
@ -342,7 +342,7 @@ class Condition(YAMLTag):
|
|||
|
||||
def __init__(self, loader: "BlueprintLoader", node: SequenceNode) -> None:
|
||||
super().__init__()
|
||||
self.mode = node.value[0].value
|
||||
self.mode = loader.construct_object(node.value[0])
|
||||
self.args = []
|
||||
for raw_node in node.value[1:]:
|
||||
self.args.append(loader.construct_object(raw_node))
|
||||
|
@ -417,7 +417,7 @@ class Enumerate(YAMLTag, YAMLTagContext):
|
|||
def __init__(self, loader: "BlueprintLoader", node: SequenceNode) -> None:
|
||||
super().__init__()
|
||||
self.iterable = loader.construct_object(node.value[0])
|
||||
self.output_body = node.value[1].value
|
||||
self.output_body = loader.construct_object(node.value[1])
|
||||
self.item_body = loader.construct_object(node.value[2])
|
||||
self.__current_context: tuple[Any, Any] = tuple()
|
||||
|
||||
|
|
|
@ -123,27 +123,35 @@ class UserSerializer(ModelSerializer):
|
|||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
if SERIALIZER_CONTEXT_BLUEPRINT in self.context:
|
||||
self.fields["password"] = CharField(required=False)
|
||||
self.fields["password"] = CharField(required=False, allow_null=True)
|
||||
|
||||
def create(self, validated_data: dict) -> User:
|
||||
"""If this serializer is used in the blueprint context, we allow for
|
||||
directly setting a password. However should be done via the `set_password`
|
||||
method instead of directly setting it like rest_framework."""
|
||||
password = validated_data.pop("password", None)
|
||||
instance: User = super().create(validated_data)
|
||||
if SERIALIZER_CONTEXT_BLUEPRINT in self.context and "password" in validated_data:
|
||||
instance.set_password(validated_data["password"])
|
||||
instance.save()
|
||||
self._set_password(instance, password)
|
||||
return instance
|
||||
|
||||
def update(self, instance: User, validated_data: dict) -> User:
|
||||
"""Same as `create` above, set the password directly if we're in a blueprint
|
||||
context"""
|
||||
password = validated_data.pop("password", None)
|
||||
instance = super().update(instance, validated_data)
|
||||
if SERIALIZER_CONTEXT_BLUEPRINT in self.context and "password" in validated_data:
|
||||
instance.set_password(validated_data["password"])
|
||||
instance.save()
|
||||
self._set_password(instance, password)
|
||||
return instance
|
||||
|
||||
def _set_password(self, instance: User, password: Optional[str]):
|
||||
"""Set password of user if we're in a blueprint context, and if it's an empty
|
||||
string then use an unusable password"""
|
||||
if SERIALIZER_CONTEXT_BLUEPRINT in self.context and password:
|
||||
instance.set_password(password)
|
||||
instance.save()
|
||||
if len(instance.password) == 0:
|
||||
instance.set_unusable_password()
|
||||
instance.save()
|
||||
|
||||
def validate_path(self, path: str) -> str:
|
||||
"""Validate path"""
|
||||
if path[:1] == "/" or path[-1] == "/":
|
||||
|
@ -309,7 +317,7 @@ class UsersFilter(FilterSet):
|
|||
path = CharFilter(field_name="path")
|
||||
path_startswith = CharFilter(field_name="path", lookup_expr="startswith")
|
||||
|
||||
type = MultipleChoiceFilter(field_name="type")
|
||||
type = MultipleChoiceFilter(choices=UserTypes.choices, field_name="type")
|
||||
|
||||
groups_by_name = ModelMultipleChoiceFilter(
|
||||
field_name="ak_groups__name",
|
||||
|
|
|
@ -28,6 +28,19 @@ class TestUsersAPI(APITestCase):
|
|||
self.admin = create_test_admin_user()
|
||||
self.user = User.objects.create(username="test-user")
|
||||
|
||||
def test_filter_type(self):
|
||||
"""Test API filtering by type"""
|
||||
self.client.force_login(self.admin)
|
||||
user = create_test_admin_user(type=UserTypes.EXTERNAL)
|
||||
response = self.client.get(
|
||||
reverse("authentik_api:user-list"),
|
||||
data={
|
||||
"type": UserTypes.EXTERNAL,
|
||||
"username": user.username,
|
||||
},
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_metrics(self):
|
||||
"""Test user's metrics"""
|
||||
self.client.force_login(self.admin)
|
||||
|
|
|
@ -21,7 +21,7 @@ def create_test_flow(
|
|||
)
|
||||
|
||||
|
||||
def create_test_admin_user(name: Optional[str] = None) -> User:
|
||||
def create_test_admin_user(name: Optional[str] = None, **kwargs) -> User:
|
||||
"""Generate a test-admin user"""
|
||||
uid = generate_id(20) if not name else name
|
||||
group = Group.objects.create(name=uid, is_superuser=True)
|
||||
|
@ -29,6 +29,7 @@ def create_test_admin_user(name: Optional[str] = None) -> User:
|
|||
username=uid,
|
||||
name=uid,
|
||||
email=f"{uid}@goauthentik.io",
|
||||
**kwargs,
|
||||
)
|
||||
user.set_password(uid)
|
||||
user.save()
|
||||
|
@ -36,12 +37,12 @@ def create_test_admin_user(name: Optional[str] = None) -> User:
|
|||
return user
|
||||
|
||||
|
||||
def create_test_tenant() -> Tenant:
|
||||
def create_test_tenant(**kwargs) -> Tenant:
|
||||
"""Generate a test tenant, removing all other tenants to make sure this one
|
||||
matches."""
|
||||
uid = generate_id(20)
|
||||
Tenant.objects.all().delete()
|
||||
return Tenant.objects.create(domain=uid, default=True)
|
||||
return Tenant.objects.create(domain=uid, default=True, **kwargs)
|
||||
|
||||
|
||||
def create_test_cert(use_ec_private_key=False) -> CertificateKeyPair:
|
||||
|
|
|
@ -35,13 +35,13 @@ class LicenseSerializer(ModelSerializer):
|
|||
"name",
|
||||
"key",
|
||||
"expiry",
|
||||
"users",
|
||||
"internal_users",
|
||||
"external_users",
|
||||
]
|
||||
extra_kwargs = {
|
||||
"name": {"read_only": True},
|
||||
"expiry": {"read_only": True},
|
||||
"users": {"read_only": True},
|
||||
"internal_users": {"read_only": True},
|
||||
"external_users": {"read_only": True},
|
||||
}
|
||||
|
||||
|
@ -49,7 +49,7 @@ class LicenseSerializer(ModelSerializer):
|
|||
class LicenseSummary(PassiveSerializer):
|
||||
"""Serializer for license status"""
|
||||
|
||||
users = IntegerField(required=True)
|
||||
internal_users = IntegerField(required=True)
|
||||
external_users = IntegerField(required=True)
|
||||
valid = BooleanField()
|
||||
show_admin_warning = BooleanField()
|
||||
|
@ -62,9 +62,9 @@ class LicenseSummary(PassiveSerializer):
|
|||
class LicenseForecastSerializer(PassiveSerializer):
|
||||
"""Serializer for license forecast"""
|
||||
|
||||
users = IntegerField(required=True)
|
||||
internal_users = IntegerField(required=True)
|
||||
external_users = IntegerField(required=True)
|
||||
forecasted_users = IntegerField(required=True)
|
||||
forecasted_internal_users = IntegerField(required=True)
|
||||
forecasted_external_users = IntegerField(required=True)
|
||||
|
||||
|
||||
|
@ -111,7 +111,7 @@ class LicenseViewSet(UsedByMixin, ModelViewSet):
|
|||
latest_valid = datetime.fromtimestamp(total.exp)
|
||||
response = LicenseSummary(
|
||||
data={
|
||||
"users": total.users,
|
||||
"internal_users": total.internal_users,
|
||||
"external_users": total.external_users,
|
||||
"valid": total.is_valid(),
|
||||
"show_admin_warning": show_admin_warning,
|
||||
|
@ -135,8 +135,8 @@ class LicenseViewSet(UsedByMixin, ModelViewSet):
|
|||
def forecast(self, request: Request) -> Response:
|
||||
"""Forecast how many users will be required in a year"""
|
||||
last_month = now() - timedelta(days=30)
|
||||
# Forecast for default users
|
||||
users_in_last_month = User.objects.filter(
|
||||
# Forecast for internal users
|
||||
internal_in_last_month = User.objects.filter(
|
||||
type=UserTypes.INTERNAL, date_joined__gte=last_month
|
||||
).count()
|
||||
# Forecast for external users
|
||||
|
@ -144,9 +144,9 @@ class LicenseViewSet(UsedByMixin, ModelViewSet):
|
|||
forecast_for_months = 12
|
||||
response = LicenseForecastSerializer(
|
||||
data={
|
||||
"users": LicenseKey.get_default_user_count(),
|
||||
"internal_users": LicenseKey.get_default_user_count(),
|
||||
"external_users": LicenseKey.get_external_user_count(),
|
||||
"forecasted_users": (users_in_last_month * forecast_for_months),
|
||||
"forecasted_internal_users": (internal_in_last_month * forecast_for_months),
|
||||
"forecasted_external_users": (external_in_last_month * forecast_for_months),
|
||||
}
|
||||
)
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
# Generated by Django 4.2.4 on 2023-08-23 10:06
|
||||
|
||||
import django.contrib.postgres.indexes
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("authentik_enterprise", "0001_initial"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name="license",
|
||||
old_name="users",
|
||||
new_name="internal_users",
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="license",
|
||||
name="key",
|
||||
field=models.TextField(),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="license",
|
||||
index=django.contrib.postgres.indexes.HashIndex(
|
||||
fields=["key"], name="authentik_e_key_523e13_hash"
|
||||
),
|
||||
),
|
||||
]
|
|
@ -11,6 +11,7 @@ from uuid import uuid4
|
|||
from cryptography.exceptions import InvalidSignature
|
||||
from cryptography.x509 import Certificate, load_der_x509_certificate, load_pem_x509_certificate
|
||||
from dacite import from_dict
|
||||
from django.contrib.postgres.indexes import HashIndex
|
||||
from django.db import models
|
||||
from django.db.models.query import QuerySet
|
||||
from django.utils.timezone import now
|
||||
|
@ -46,8 +47,8 @@ class LicenseKey:
|
|||
exp: int
|
||||
|
||||
name: str
|
||||
users: int
|
||||
external_users: int
|
||||
internal_users: int = 0
|
||||
external_users: int = 0
|
||||
flags: list[LicenseFlags] = field(default_factory=list)
|
||||
|
||||
@staticmethod
|
||||
|
@ -87,7 +88,7 @@ class LicenseKey:
|
|||
active_licenses = License.objects.filter(expiry__gte=now())
|
||||
total = LicenseKey(get_license_aud(), 0, "Summarized license", 0, 0)
|
||||
for lic in active_licenses:
|
||||
total.users += lic.users
|
||||
total.internal_users += lic.internal_users
|
||||
total.external_users += lic.external_users
|
||||
exp_ts = int(mktime(lic.expiry.timetuple()))
|
||||
if total.exp == 0:
|
||||
|
@ -123,7 +124,7 @@ class LicenseKey:
|
|||
|
||||
Only checks the current count, no historical data is checked"""
|
||||
default_users = self.get_default_user_count()
|
||||
if default_users > self.users:
|
||||
if default_users > self.internal_users:
|
||||
return False
|
||||
active_users = self.get_external_user_count()
|
||||
if active_users > self.external_users:
|
||||
|
@ -153,11 +154,11 @@ class License(models.Model):
|
|||
"""An authentik enterprise license"""
|
||||
|
||||
license_uuid = models.UUIDField(primary_key=True, editable=False, default=uuid4)
|
||||
key = models.TextField(unique=True)
|
||||
key = models.TextField()
|
||||
|
||||
name = models.TextField()
|
||||
expiry = models.DateTimeField()
|
||||
users = models.BigIntegerField()
|
||||
internal_users = models.BigIntegerField()
|
||||
external_users = models.BigIntegerField()
|
||||
|
||||
@property
|
||||
|
@ -165,6 +166,9 @@ class License(models.Model):
|
|||
"""Get parsed license status"""
|
||||
return LicenseKey.validate(self.key)
|
||||
|
||||
class Meta:
|
||||
indexes = (HashIndex(fields=("key",)),)
|
||||
|
||||
|
||||
def usage_expiry():
|
||||
"""Keep license usage records for 3 months"""
|
||||
|
|
|
@ -13,6 +13,6 @@ def pre_save_license(sender: type[License], instance: License, **_):
|
|||
"""Extract data from license jwt and save it into model"""
|
||||
status = instance.status
|
||||
instance.name = status.name
|
||||
instance.users = status.users
|
||||
instance.internal_users = status.internal_users
|
||||
instance.external_users = status.external_users
|
||||
instance.expiry = datetime.fromtimestamp(status.exp, tz=get_current_timezone())
|
||||
|
|
|
@ -23,7 +23,7 @@ class TestEnterpriseLicense(TestCase):
|
|||
aud="",
|
||||
exp=_exp,
|
||||
name=generate_id(),
|
||||
users=100,
|
||||
internal_users=100,
|
||||
external_users=100,
|
||||
)
|
||||
),
|
||||
|
@ -32,7 +32,7 @@ class TestEnterpriseLicense(TestCase):
|
|||
"""Check license verification"""
|
||||
lic = License.objects.create(key=generate_id())
|
||||
self.assertTrue(lic.status.is_valid())
|
||||
self.assertEqual(lic.users, 100)
|
||||
self.assertEqual(lic.internal_users, 100)
|
||||
|
||||
def test_invalid(self):
|
||||
"""Test invalid license"""
|
||||
|
@ -46,7 +46,7 @@ class TestEnterpriseLicense(TestCase):
|
|||
aud="",
|
||||
exp=_exp,
|
||||
name=generate_id(),
|
||||
users=100,
|
||||
internal_users=100,
|
||||
external_users=100,
|
||||
)
|
||||
),
|
||||
|
@ -58,7 +58,7 @@ class TestEnterpriseLicense(TestCase):
|
|||
lic2 = License.objects.create(key=generate_id())
|
||||
self.assertTrue(lic2.status.is_valid())
|
||||
total = LicenseKey.get_total()
|
||||
self.assertEqual(total.users, 200)
|
||||
self.assertEqual(total.internal_users, 200)
|
||||
self.assertEqual(total.external_users, 200)
|
||||
self.assertEqual(total.exp, _exp)
|
||||
self.assertTrue(total.is_valid())
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
package lib
|
||||
|
||||
import _ "embed"
|
||||
|
||||
//go:embed default.yml
|
||||
var defaultConfig []byte
|
||||
|
||||
func DefaultConfig() []byte {
|
||||
return defaultConfig
|
||||
}
|
|
@ -11,7 +11,11 @@ postgresql:
|
|||
listen:
|
||||
listen_http: 0.0.0.0:9000
|
||||
listen_https: 0.0.0.0:9443
|
||||
listen_ldap: 0.0.0.0:3389
|
||||
listen_ldaps: 0.0.0.0:6636
|
||||
listen_radius: 0.0.0.0:1812
|
||||
listen_metrics: 0.0.0.0:9300
|
||||
listen_debug: 0.0.0.0:9900
|
||||
trusted_proxy_cidrs:
|
||||
- 127.0.0.0/8
|
||||
- 10.0.0.0/8
|
||||
|
@ -32,6 +36,9 @@ redis:
|
|||
cache_timeout_policies: 300
|
||||
cache_timeout_reputation: 300
|
||||
|
||||
paths:
|
||||
media: ./media
|
||||
|
||||
debug: false
|
||||
remote_debug: false
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
import base64
|
||||
import binascii
|
||||
import json
|
||||
from dataclasses import asdict
|
||||
from functools import cached_property
|
||||
from hashlib import sha256
|
||||
from typing import Any, Optional
|
||||
|
@ -358,7 +359,7 @@ class AccessToken(SerializerModel, ExpiringModel, BaseGrantModel):
|
|||
@id_token.setter
|
||||
def id_token(self, value: IDToken):
|
||||
self.token = value.to_access_token(self.provider)
|
||||
self._id_token = json.dumps(value.to_dict())
|
||||
self._id_token = json.dumps(asdict(value))
|
||||
|
||||
@property
|
||||
def at_hash(self):
|
||||
|
@ -400,7 +401,7 @@ class RefreshToken(SerializerModel, ExpiringModel, BaseGrantModel):
|
|||
|
||||
@id_token.setter
|
||||
def id_token(self, value: IDToken):
|
||||
self._id_token = json.dumps(value.to_dict())
|
||||
self._id_token = json.dumps(asdict(value))
|
||||
|
||||
@property
|
||||
def serializer(self) -> Serializer:
|
||||
|
|
|
@ -151,6 +151,14 @@ class TestTokenClientCredentials(OAuthTestCase):
|
|||
)
|
||||
self.assertEqual(jwt["given_name"], self.user.name)
|
||||
self.assertEqual(jwt["preferred_username"], self.user.username)
|
||||
jwt = decode(
|
||||
body["id_token"],
|
||||
key=self.provider.signing_key.public_key,
|
||||
algorithms=[alg],
|
||||
audience=self.provider.client_id,
|
||||
)
|
||||
self.assertEqual(jwt["given_name"], self.user.name)
|
||||
self.assertEqual(jwt["preferred_username"], self.user.username)
|
||||
|
||||
def test_successful_password(self):
|
||||
"""test successful (password grant)"""
|
||||
|
|
|
@ -68,7 +68,7 @@ class SCIMClient(Generic[T, SchemaType]):
|
|||
"""Get Service provider config"""
|
||||
default_config = ServiceProviderConfiguration.default()
|
||||
try:
|
||||
return ServiceProviderConfiguration.parse_obj(
|
||||
return ServiceProviderConfiguration.model_validate(
|
||||
self._request("GET", "/ServiceProviderConfig")
|
||||
)
|
||||
except (ValidationError, SCIMRequestException) as exc:
|
||||
|
|
|
@ -74,7 +74,7 @@ class SCIMGroupClient(SCIMClient[Group, SCIMGroupSchema]):
|
|||
if not raw_scim_group:
|
||||
raise StopSync(ValueError("No group mappings configured"), obj)
|
||||
try:
|
||||
scim_group = SCIMGroupSchema.parse_obj(delete_none_values(raw_scim_group))
|
||||
scim_group = SCIMGroupSchema.model_validate(delete_none_values(raw_scim_group))
|
||||
except ValidationError as exc:
|
||||
raise StopSync(exc, obj) from exc
|
||||
if not scim_group.externalId:
|
||||
|
@ -99,7 +99,8 @@ class SCIMGroupClient(SCIMClient[Group, SCIMGroupSchema]):
|
|||
response = self._request(
|
||||
"POST",
|
||||
"/Groups",
|
||||
data=scim_group.json(
|
||||
json=scim_group.model_dump(
|
||||
mode="json",
|
||||
exclude_unset=True,
|
||||
),
|
||||
)
|
||||
|
@ -113,7 +114,8 @@ class SCIMGroupClient(SCIMClient[Group, SCIMGroupSchema]):
|
|||
return self._request(
|
||||
"PUT",
|
||||
f"/Groups/{scim_group.id}",
|
||||
data=scim_group.json(
|
||||
json=scim_group.model_dump(
|
||||
mode="json",
|
||||
exclude_unset=True,
|
||||
),
|
||||
)
|
||||
|
@ -160,7 +162,13 @@ class SCIMGroupClient(SCIMClient[Group, SCIMGroupSchema]):
|
|||
*ops: PatchOperation,
|
||||
):
|
||||
req = PatchRequest(Operations=ops)
|
||||
self._request("PATCH", f"/Groups/{group_id}", data=req.json())
|
||||
self._request(
|
||||
"PATCH",
|
||||
f"/Groups/{group_id}",
|
||||
json=req.model_dump(
|
||||
mode="json",
|
||||
),
|
||||
)
|
||||
|
||||
def _patch_add_users(self, group: Group, users_set: set[int]):
|
||||
"""Add users in users_set to group"""
|
||||
|
|
|
@ -52,7 +52,7 @@ class ServiceProviderConfiguration(BaseServiceProviderConfiguration):
|
|||
class PatchRequest(BasePatchRequest):
|
||||
"""PatchRequest which correctly sets schemas"""
|
||||
|
||||
schemas: tuple[str] = ["urn:ietf:params:scim:api:messages:2.0:PatchOp"]
|
||||
schemas: tuple[str] = ("urn:ietf:params:scim:api:messages:2.0:PatchOp",)
|
||||
|
||||
|
||||
class SCIMError(BaseSCIMError):
|
||||
|
|
|
@ -64,7 +64,7 @@ class SCIMUserClient(SCIMClient[User, SCIMUserSchema]):
|
|||
if not raw_scim_user:
|
||||
raise StopSync(ValueError("No user mappings configured"), obj)
|
||||
try:
|
||||
scim_user = SCIMUserSchema.parse_obj(delete_none_values(raw_scim_user))
|
||||
scim_user = SCIMUserSchema.model_validate(delete_none_values(raw_scim_user))
|
||||
except ValidationError as exc:
|
||||
raise StopSync(exc, obj) from exc
|
||||
if not scim_user.externalId:
|
||||
|
@ -77,7 +77,8 @@ class SCIMUserClient(SCIMClient[User, SCIMUserSchema]):
|
|||
response = self._request(
|
||||
"POST",
|
||||
"/Users",
|
||||
data=scim_user.json(
|
||||
json=scim_user.model_dump(
|
||||
mode="json",
|
||||
exclude_unset=True,
|
||||
),
|
||||
)
|
||||
|
@ -90,7 +91,8 @@ class SCIMUserClient(SCIMClient[User, SCIMUserSchema]):
|
|||
self._request(
|
||||
"PUT",
|
||||
f"/Users/{connection.id}",
|
||||
data=scim_user.json(
|
||||
json=scim_user.model_dump(
|
||||
mode="json",
|
||||
exclude_unset=True,
|
||||
),
|
||||
)
|
||||
|
|
|
@ -47,6 +47,7 @@ class SCIMMembershipTests(TestCase):
|
|||
def test_member_add(self):
|
||||
"""Test member add"""
|
||||
config = ServiceProviderConfiguration.default()
|
||||
# pylint: disable=assigning-non-slot
|
||||
config.patch.supported = True
|
||||
user_scim_id = generate_id()
|
||||
group_scim_id = generate_id()
|
||||
|
@ -60,7 +61,7 @@ class SCIMMembershipTests(TestCase):
|
|||
with Mocker() as mocker:
|
||||
mocker.get(
|
||||
"https://localhost/ServiceProviderConfig",
|
||||
json=config.dict(),
|
||||
json=config.model_dump(),
|
||||
)
|
||||
mocker.post(
|
||||
"https://localhost/Users",
|
||||
|
@ -104,7 +105,7 @@ class SCIMMembershipTests(TestCase):
|
|||
with Mocker() as mocker:
|
||||
mocker.get(
|
||||
"https://localhost/ServiceProviderConfig",
|
||||
json=config.dict(),
|
||||
json=config.model_dump(),
|
||||
)
|
||||
mocker.patch(
|
||||
f"https://localhost/Groups/{group_scim_id}",
|
||||
|
@ -131,6 +132,7 @@ class SCIMMembershipTests(TestCase):
|
|||
def test_member_remove(self):
|
||||
"""Test member remove"""
|
||||
config = ServiceProviderConfiguration.default()
|
||||
# pylint: disable=assigning-non-slot
|
||||
config.patch.supported = True
|
||||
user_scim_id = generate_id()
|
||||
group_scim_id = generate_id()
|
||||
|
@ -144,7 +146,7 @@ class SCIMMembershipTests(TestCase):
|
|||
with Mocker() as mocker:
|
||||
mocker.get(
|
||||
"https://localhost/ServiceProviderConfig",
|
||||
json=config.dict(),
|
||||
json=config.model_dump(),
|
||||
)
|
||||
mocker.post(
|
||||
"https://localhost/Users",
|
||||
|
@ -188,7 +190,7 @@ class SCIMMembershipTests(TestCase):
|
|||
with Mocker() as mocker:
|
||||
mocker.get(
|
||||
"https://localhost/ServiceProviderConfig",
|
||||
json=config.dict(),
|
||||
json=config.model_dump(),
|
||||
)
|
||||
mocker.patch(
|
||||
f"https://localhost/Groups/{group_scim_id}",
|
||||
|
@ -215,7 +217,7 @@ class SCIMMembershipTests(TestCase):
|
|||
with Mocker() as mocker:
|
||||
mocker.get(
|
||||
"https://localhost/ServiceProviderConfig",
|
||||
json=config.dict(),
|
||||
json=config.model_dump(),
|
||||
)
|
||||
mocker.patch(
|
||||
f"https://localhost/Groups/{group_scim_id}",
|
||||
|
|
|
@ -62,7 +62,8 @@ class OAuthSourceSerializer(SourceSerializer):
|
|||
well_known_config = session.get(well_known)
|
||||
well_known_config.raise_for_status()
|
||||
except RequestException as exc:
|
||||
raise ValidationError(exc.response.text)
|
||||
text = exc.response.text if exc.response else str(exc)
|
||||
raise ValidationError(text)
|
||||
config = well_known_config.json()
|
||||
try:
|
||||
attrs["authorization_url"] = config["authorization_endpoint"]
|
||||
|
@ -78,7 +79,8 @@ class OAuthSourceSerializer(SourceSerializer):
|
|||
jwks_config = session.get(jwks_url)
|
||||
jwks_config.raise_for_status()
|
||||
except RequestException as exc:
|
||||
raise ValidationError(exc.response.text)
|
||||
text = exc.response.text if exc.response else str(exc)
|
||||
raise ValidationError(text)
|
||||
config = jwks_config.json()
|
||||
attrs["oidc_jwks"] = config
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
"""Validation stage challenge checking"""
|
||||
from json import dumps, loads
|
||||
from typing import Optional
|
||||
from urllib.parse import urlencode
|
||||
|
||||
|
@ -17,7 +16,6 @@ from webauthn.authentication.generate_authentication_options import generate_aut
|
|||
from webauthn.authentication.verify_authentication_response import verify_authentication_response
|
||||
from webauthn.helpers.base64url_to_bytes import base64url_to_bytes
|
||||
from webauthn.helpers.exceptions import InvalidAuthenticationResponse
|
||||
from webauthn.helpers.options_to_json import options_to_json
|
||||
from webauthn.helpers.structs import AuthenticationCredential
|
||||
|
||||
from authentik.core.api.utils import PassiveSerializer
|
||||
|
@ -68,7 +66,12 @@ def get_webauthn_challenge_without_user(
|
|||
)
|
||||
request.session[SESSION_KEY_WEBAUTHN_CHALLENGE] = authentication_options.challenge
|
||||
|
||||
return loads(options_to_json(authentication_options))
|
||||
return authentication_options.model_dump(
|
||||
mode="json",
|
||||
by_alias=True,
|
||||
exclude_unset=False,
|
||||
exclude_none=True,
|
||||
)
|
||||
|
||||
|
||||
def get_webauthn_challenge(
|
||||
|
@ -93,7 +96,12 @@ def get_webauthn_challenge(
|
|||
|
||||
request.session[SESSION_KEY_WEBAUTHN_CHALLENGE] = authentication_options.challenge
|
||||
|
||||
return loads(options_to_json(authentication_options))
|
||||
return authentication_options.model_dump(
|
||||
mode="json",
|
||||
by_alias=True,
|
||||
exclude_unset=False,
|
||||
exclude_none=True,
|
||||
)
|
||||
|
||||
|
||||
def select_challenge(request: HttpRequest, device: Device):
|
||||
|
@ -144,7 +152,7 @@ def validate_challenge_webauthn(data: dict, stage_view: StageView, user: User) -
|
|||
|
||||
try:
|
||||
authentication_verification = verify_authentication_response(
|
||||
credential=AuthenticationCredential.parse_raw(dumps(data)),
|
||||
credential=AuthenticationCredential.model_validate(data),
|
||||
expected_challenge=challenge,
|
||||
expected_rp_id=get_rp_id(request),
|
||||
expected_origin=get_origin(request),
|
||||
|
|
|
@ -234,14 +234,12 @@ class AuthenticatorValidateStageWebAuthnTests(FlowTestCase):
|
|||
"assertionClientExtensions": "{}",
|
||||
"response": {
|
||||
"clientDataJSON": (
|
||||
(
|
||||
"eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiZzk4STUxbVF2WlhvN"
|
||||
"Wx4TGZockQyemZvbGhaYkxSeUNncWtrWWFwMWp3U2FKMTNCZ3VvSldDRjlfTGczQW"
|
||||
"dPNFdoLUJxYTU1NkpFMjBvS3NZYmw2UkEiLCJvcmlnaW4iOiJodHRwOi8vbG9jYWx"
|
||||
"ob3N0OjkwMDAiLCJjcm9zc09yaWdpbiI6ZmFsc2UsIm90aGVyX2tleXNfY2FuX2Jl"
|
||||
"X2FkZGVkX2hlcmUiOiJkbyBub3QgY29tcGFyZSBjbGllbnREYXRhSlNPTiBhZ2Fpb"
|
||||
"nN0IGEgdGVtcGxhdGUuIFNlZSBodHRwczovL2dvby5nbC95YWJQZXgifQ=="
|
||||
),
|
||||
"eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiZzk4STUxbVF2WlhvN"
|
||||
"Wx4TGZockQyemZvbGhaYkxSeUNncWtrWWFwMWp3U2FKMTNCZ3VvSldDRjlfTGczQW"
|
||||
"dPNFdoLUJxYTU1NkpFMjBvS3NZYmw2UkEiLCJvcmlnaW4iOiJodHRwOi8vbG9jYWx"
|
||||
"ob3N0OjkwMDAiLCJjcm9zc09yaWdpbiI6ZmFsc2UsIm90aGVyX2tleXNfY2FuX2Jl"
|
||||
"X2FkZGVkX2hlcmUiOiJkbyBub3QgY29tcGFyZSBjbGllbnREYXRhSlNPTiBhZ2Fpb"
|
||||
"nN0IGEgdGVtcGxhdGUuIFNlZSBodHRwczovL2dvby5nbC95YWJQZXgifQ=="
|
||||
),
|
||||
"signature": (
|
||||
"MEQCIFNlrHf9ablJAalXLWkrqvHB8oIu8kwvRpH3X3rbJVpI"
|
||||
|
@ -306,14 +304,12 @@ class AuthenticatorValidateStageWebAuthnTests(FlowTestCase):
|
|||
"assertionClientExtensions": "{}",
|
||||
"response": {
|
||||
"clientDataJSON": (
|
||||
(
|
||||
"eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiZzk4STUxbVF2Wlhv"
|
||||
"NWx4TGZockQyemZvbGhaYkxSeUNncWtrWWFwMWp3U2FKMTNCZ3VvSldDRjlfTGcz"
|
||||
"QWdPNFdoLUJxYTU1NkpFMjBvS3NZYmw2UkEiLCJvcmlnaW4iOiJodHRwOi8vbG9j"
|
||||
"YWxob3N0OjkwMDAiLCJjcm9zc09yaWdpbiI6ZmFsc2UsIm90aGVyX2tleXNfY2Fu"
|
||||
"X2JlX2FkZGVkX2hlcmUiOiJkbyBub3QgY29tcGFyZSBjbGllbnREYXRhSlNPTiBh"
|
||||
"Z2FpbnN0IGEgdGVtcGxhdGUuIFNlZSBodHRwczovL2dvby5nbC95YWJQZXgifQ=="
|
||||
),
|
||||
"eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiZzk4STUxbVF2Wlhv"
|
||||
"NWx4TGZockQyemZvbGhaYkxSeUNncWtrWWFwMWp3U2FKMTNCZ3VvSldDRjlfTGcz"
|
||||
"QWdPNFdoLUJxYTU1NkpFMjBvS3NZYmw2UkEiLCJvcmlnaW4iOiJodHRwOi8vbG9j"
|
||||
"YWxob3N0OjkwMDAiLCJjcm9zc09yaWdpbiI6ZmFsc2UsIm90aGVyX2tleXNfY2Fu"
|
||||
"X2JlX2FkZGVkX2hlcmUiOiJkbyBub3QgY29tcGFyZSBjbGllbnREYXRhSlNPTiBh"
|
||||
"Z2FpbnN0IGEgdGVtcGxhdGUuIFNlZSBodHRwczovL2dvby5nbC95YWJQZXgifQ=="
|
||||
),
|
||||
"signature": (
|
||||
"MEQCIFNlrHf9ablJAalXLWkrqvHB8oIu8kwvRpH3X3rbJVpI"
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
"""WebAuthn stage"""
|
||||
from json import dumps, loads
|
||||
|
||||
from django.http import HttpRequest, HttpResponse
|
||||
from django.http.request import QueryDict
|
||||
from rest_framework.fields import CharField, JSONField
|
||||
from rest_framework.serializers import ValidationError
|
||||
from webauthn.helpers.bytes_to_base64url import bytes_to_base64url
|
||||
from webauthn.helpers.exceptions import InvalidRegistrationResponse
|
||||
from webauthn.helpers.options_to_json import options_to_json
|
||||
from webauthn.helpers.structs import (
|
||||
AuthenticatorSelectionCriteria,
|
||||
PublicKeyCredentialCreationOptions,
|
||||
|
@ -55,7 +52,7 @@ class AuthenticatorWebAuthnChallengeResponse(ChallengeResponse):
|
|||
|
||||
try:
|
||||
registration: VerifiedRegistration = verify_registration_response(
|
||||
credential=RegistrationCredential.parse_raw(dumps(response)),
|
||||
credential=RegistrationCredential.model_validate(response),
|
||||
expected_challenge=challenge,
|
||||
expected_rp_id=get_rp_id(self.request),
|
||||
expected_origin=get_origin(self.request),
|
||||
|
@ -108,7 +105,12 @@ class AuthenticatorWebAuthnStageView(ChallengeStageView):
|
|||
return AuthenticatorWebAuthnChallenge(
|
||||
data={
|
||||
"type": ChallengeTypes.NATIVE.value,
|
||||
"registration": loads(options_to_json(registration_options)),
|
||||
"registration": registration_options.model_dump(
|
||||
mode="json",
|
||||
by_alias=True,
|
||||
exclude_unset=False,
|
||||
exclude_none=True,
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
|
|
@ -8471,7 +8471,10 @@
|
|||
"title": "Type"
|
||||
},
|
||||
"password": {
|
||||
"type": "string",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"minLength": 1,
|
||||
"title": "Password"
|
||||
}
|
||||
|
|
6
go.mod
|
@ -13,20 +13,20 @@ require (
|
|||
github.com/go-openapi/runtime v0.26.0
|
||||
github.com/go-openapi/strfmt v0.21.7
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/google/uuid v1.3.1
|
||||
github.com/gorilla/handlers v1.5.1
|
||||
github.com/gorilla/mux v1.8.0
|
||||
github.com/gorilla/securecookie v1.1.1
|
||||
github.com/gorilla/sessions v1.2.1
|
||||
github.com/gorilla/websocket v1.5.0
|
||||
github.com/jellydator/ttlcache/v3 v3.0.1
|
||||
github.com/jellydator/ttlcache/v3 v3.1.0
|
||||
github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484
|
||||
github.com/pires/go-proxyproto v0.7.0
|
||||
github.com/prometheus/client_golang v1.16.0
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/spf13/cobra v1.7.0
|
||||
github.com/stretchr/testify v1.8.4
|
||||
goauthentik.io/api/v3 v3.2023061.12
|
||||
goauthentik.io/api/v3 v3.2023061.13
|
||||
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab
|
||||
golang.org/x/oauth2 v0.11.0
|
||||
golang.org/x/sync v0.3.0
|
||||
|
|
15
go.sum
|
@ -859,8 +859,9 @@ github.com/google/s2a-go v0.1.3/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkj
|
|||
github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
|
||||
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg=
|
||||
|
@ -903,8 +904,8 @@ github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:
|
|||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/jellydator/ttlcache/v3 v3.0.1 h1:cHgCSMS7TdQcoprXnWUptJZzyFsqs18Lt8VVhRuZYVU=
|
||||
github.com/jellydator/ttlcache/v3 v3.0.1/go.mod h1:WwTaEmcXQ3MTjOm4bsZoDFiCu/hMvNWLO1w67RXz6h4=
|
||||
github.com/jellydator/ttlcache/v3 v3.1.0 h1:0gPFG0IHHP6xyUyXq+JaD8fwkDCqgqwohXNJBcYE71g=
|
||||
github.com/jellydator/ttlcache/v3 v3.1.0/go.mod h1:hi7MGFdMAwZna5n2tuvh63DvFLzVKySzCVW6+0gA2n4=
|
||||
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
|
@ -1069,9 +1070,9 @@ go.opentelemetry.io/otel/trace v1.14.0/go.mod h1:8avnQLK+CG77yNLUae4ea2JDQ6iT+go
|
|||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
||||
go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U=
|
||||
go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U=
|
||||
go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0=
|
||||
goauthentik.io/api/v3 v3.2023061.12 h1:VtxeDeOpEtO8DXHVSYnhVQwtD80zIrwWSnI8b7lJlb0=
|
||||
goauthentik.io/api/v3 v3.2023061.12/go.mod h1:sP1/Ak/vGw96xNgpyoObHgXfyAElcTN5CbbC+VdPQXk=
|
||||
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
|
||||
goauthentik.io/api/v3 v3.2023061.13 h1:0V5XrryJdMrOug/5wWazmH+D3Y/dDGPyLDhWcbJ5Gm0=
|
||||
goauthentik.io/api/v3 v3.2023061.13/go.mod h1:sP1/Ak/vGw96xNgpyoObHgXfyAElcTN5CbbC+VdPQXk=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
||||
|
@ -1133,7 +1134,6 @@ golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRu
|
|||
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug=
|
||||
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
|
@ -1454,7 +1454,6 @@ golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
|
|||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4=
|
||||
golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
|
|
@ -1,67 +1,102 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
env "github.com/Netflix/go-env"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"goauthentik.io/authentik/lib"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
var cfg *Config
|
||||
|
||||
const defaultConfigPath = "./authentik/lib/default.yml"
|
||||
|
||||
func getConfigPaths() []string {
|
||||
configPaths := []string{defaultConfigPath, "/etc/authentik/config.yml", ""}
|
||||
globConfigPaths, _ := filepath.Glob("/etc/authentik/config.d/*.yml")
|
||||
configPaths = append(configPaths, globConfigPaths...)
|
||||
|
||||
environment := "local"
|
||||
if v, ok := os.LookupEnv("AUTHENTIK_ENV"); ok {
|
||||
environment = v
|
||||
}
|
||||
|
||||
computedConfigPaths := []string{}
|
||||
|
||||
for _, path := range configPaths {
|
||||
path, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if stat, err := os.Stat(path); err == nil {
|
||||
if !stat.IsDir() {
|
||||
computedConfigPaths = append(computedConfigPaths, path)
|
||||
} else {
|
||||
envPaths := []string{
|
||||
filepath.Join(path, environment+".yml"),
|
||||
filepath.Join(path, environment+".env.yml"),
|
||||
}
|
||||
for _, envPath := range envPaths {
|
||||
if stat, err = os.Stat(envPath); err == nil && !stat.IsDir() {
|
||||
computedConfigPaths = append(computedConfigPaths, envPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return computedConfigPaths
|
||||
}
|
||||
|
||||
func Get() *Config {
|
||||
if cfg == nil {
|
||||
c := defaultConfig()
|
||||
c.Setup("./authentik/lib/default.yml", "/etc/authentik/config.yml", "./local.env.yml")
|
||||
c := &Config{}
|
||||
c.Setup(getConfigPaths()...)
|
||||
cfg = c
|
||||
}
|
||||
return cfg
|
||||
}
|
||||
|
||||
func defaultConfig() *Config {
|
||||
return &Config{
|
||||
Debug: false,
|
||||
Listen: ListenConfig{
|
||||
HTTP: "0.0.0.0:9000",
|
||||
HTTPS: "0.0.0.0:9443",
|
||||
LDAP: "0.0.0.0:3389",
|
||||
LDAPS: "0.0.0.0:6636",
|
||||
Radius: "0.0.0.0:1812",
|
||||
Metrics: "0.0.0.0:9300",
|
||||
Debug: "0.0.0.0:9900",
|
||||
},
|
||||
Paths: PathsConfig{
|
||||
Media: "./media",
|
||||
},
|
||||
LogLevel: "info",
|
||||
ErrorReporting: ErrorReportingConfig{
|
||||
Enabled: false,
|
||||
SampleRate: 1,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Config) Setup(paths ...string) {
|
||||
// initially try to load the default config which is compiled in
|
||||
err := c.LoadConfig(lib.DefaultConfig())
|
||||
// this should never fail
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("failed to load inbuilt config: %v", err))
|
||||
}
|
||||
log.WithField("path", "inbuilt-default").Debug("Loaded config")
|
||||
for _, path := range paths {
|
||||
err := c.LoadConfig(path)
|
||||
err := c.LoadConfigFromFile(path)
|
||||
if err != nil {
|
||||
log.WithError(err).Info("failed to load config, skipping")
|
||||
}
|
||||
}
|
||||
err := c.fromEnv()
|
||||
err = c.fromEnv()
|
||||
if err != nil {
|
||||
log.WithError(err).Info("failed to load env vars")
|
||||
}
|
||||
c.configureLogger()
|
||||
}
|
||||
|
||||
func (c *Config) LoadConfig(path string) error {
|
||||
func (c *Config) LoadConfig(raw []byte) error {
|
||||
err := yaml.Unmarshal(raw, c)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse YAML: %w", err)
|
||||
}
|
||||
c.walkScheme(c)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Config) LoadConfigFromFile(path string) error {
|
||||
raw, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
|
@ -69,11 +104,10 @@ func (c *Config) LoadConfig(path string) error {
|
|||
}
|
||||
return fmt.Errorf("failed to load config file: %w", err)
|
||||
}
|
||||
err = yaml.Unmarshal(raw, c)
|
||||
err = c.LoadConfig(raw)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse YAML: %w", err)
|
||||
return err
|
||||
}
|
||||
c.walkScheme(c)
|
||||
log.WithField("path", path).Debug("Loaded config")
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -55,6 +55,8 @@ type Application struct {
|
|||
|
||||
errorTemplates *template.Template
|
||||
authHeaderCache *ttlcache.Cache[string, Claims]
|
||||
|
||||
isEmbedded bool
|
||||
}
|
||||
|
||||
type Server interface {
|
||||
|
@ -86,15 +88,15 @@ func NewApplication(p api.ProxyOutpostConfig, c *http.Client, server Server) (*A
|
|||
CallbackSignature: []string{"true"},
|
||||
}.Encode()
|
||||
|
||||
managed := false
|
||||
isEmbedded := false
|
||||
if m := server.API().Outpost.Managed.Get(); m != nil {
|
||||
managed = *m == "goauthentik.io/outposts/embedded"
|
||||
isEmbedded = *m == "goauthentik.io/outposts/embedded"
|
||||
}
|
||||
// Configure an OpenID Connect aware OAuth2 client.
|
||||
endpoint := GetOIDCEndpoint(
|
||||
p,
|
||||
server.API().Outpost.Config["authentik_host"].(string),
|
||||
managed,
|
||||
isEmbedded,
|
||||
)
|
||||
|
||||
verifier := oidc.NewVerifier(endpoint.Issuer, ks, &oidc.Config{
|
||||
|
@ -132,6 +134,7 @@ func NewApplication(p api.ProxyOutpostConfig, c *http.Client, server Server) (*A
|
|||
ak: server.API(),
|
||||
authHeaderCache: ttlcache.New(ttlcache.WithDisableTouchOnHit[string, Claims]()),
|
||||
srv: server,
|
||||
isEmbedded: isEmbedded,
|
||||
}
|
||||
go a.authHeaderCache.Start()
|
||||
a.sessions = a.getStore(p, externalHost)
|
||||
|
|
|
@ -30,6 +30,7 @@ func updateURL(rawUrl string, scheme string, host string) string {
|
|||
func GetOIDCEndpoint(p api.ProxyOutpostConfig, authentikHost string, embedded bool) OIDCEndpoint {
|
||||
authUrl := p.OidcConfiguration.AuthorizationEndpoint
|
||||
endUrl := p.OidcConfiguration.EndSessionEndpoint
|
||||
jwksUri := p.OidcConfiguration.JwksUri
|
||||
issuer := p.OidcConfiguration.Issuer
|
||||
ep := OIDCEndpoint{
|
||||
Endpoint: oauth2.Endpoint{
|
||||
|
@ -38,10 +39,14 @@ func GetOIDCEndpoint(p api.ProxyOutpostConfig, authentikHost string, embedded bo
|
|||
AuthStyle: oauth2.AuthStyleInParams,
|
||||
},
|
||||
EndSessionEndpoint: endUrl,
|
||||
JwksUri: p.OidcConfiguration.JwksUri,
|
||||
JwksUri: jwksUri,
|
||||
TokenIntrospection: p.OidcConfiguration.IntrospectionEndpoint,
|
||||
Issuer: issuer,
|
||||
}
|
||||
aku, err := url.Parse(authentikHost)
|
||||
if err != nil {
|
||||
return ep
|
||||
}
|
||||
// For the embedded outpost, we use the configure `authentik_host` for the browser URLs
|
||||
// and localhost (which is what we've got from the API) for backchannel URLs
|
||||
//
|
||||
|
@ -51,27 +56,24 @@ func GetOIDCEndpoint(p api.ProxyOutpostConfig, authentikHost string, embedded bo
|
|||
if !embedded && hostBrowser == "" {
|
||||
return ep
|
||||
}
|
||||
var newHost *url.URL
|
||||
var newHost *url.URL = aku
|
||||
var newBrowserHost *url.URL
|
||||
if embedded {
|
||||
if authentikHost == "" {
|
||||
log.Warning("Outpost has localhost/blank API Connection but no authentik_host is configured.")
|
||||
return ep
|
||||
}
|
||||
aku, err := url.Parse(authentikHost)
|
||||
if err != nil {
|
||||
return ep
|
||||
}
|
||||
newHost = aku
|
||||
newBrowserHost = aku
|
||||
} else if hostBrowser != "" {
|
||||
aku, err := url.Parse(hostBrowser)
|
||||
browser, err := url.Parse(hostBrowser)
|
||||
if err != nil {
|
||||
return ep
|
||||
}
|
||||
newHost = aku
|
||||
newBrowserHost = browser
|
||||
}
|
||||
// Update all browser-accessed URLs to use the new host and scheme
|
||||
ep.AuthURL = updateURL(authUrl, newHost.Scheme, newHost.Host)
|
||||
ep.EndSessionEndpoint = updateURL(endUrl, newHost.Scheme, newHost.Host)
|
||||
ep.AuthURL = updateURL(authUrl, newBrowserHost.Scheme, newBrowserHost.Host)
|
||||
ep.EndSessionEndpoint = updateURL(endUrl, newBrowserHost.Scheme, newBrowserHost.Host)
|
||||
// Update issuer to use the same host and scheme, which would normally break as we don't
|
||||
// change the token URL here, but the token HTTP transport overwrites the Host header
|
||||
//
|
||||
|
@ -79,6 +81,7 @@ func GetOIDCEndpoint(p api.ProxyOutpostConfig, authentikHost string, embedded bo
|
|||
// is routed correctly
|
||||
if embedded {
|
||||
ep.Issuer = updateURL(ep.Issuer, newHost.Scheme, newHost.Host)
|
||||
ep.JwksUri = updateURL(jwksUri, newHost.Scheme, newHost.Host)
|
||||
}
|
||||
return ep
|
||||
}
|
||||
|
|
|
@ -82,7 +82,7 @@ func TestEndpointEmbedded(t *testing.T) {
|
|||
assert.Equal(t, "https://authentik-host.test.goauthentik.io/application/o/authorize/", ep.AuthURL)
|
||||
assert.Equal(t, "https://authentik-host.test.goauthentik.io/application/o/test-app/", ep.Issuer)
|
||||
assert.Equal(t, "https://test.goauthentik.io/application/o/token/", ep.TokenURL)
|
||||
assert.Equal(t, "https://test.goauthentik.io/application/o/test-app/jwks/", ep.JwksUri)
|
||||
assert.Equal(t, "https://authentik-host.test.goauthentik.io/application/o/test-app/jwks/", ep.JwksUri)
|
||||
assert.Equal(t, "https://authentik-host.test.goauthentik.io/application/o/test-app/end-session/", ep.EndSessionEndpoint)
|
||||
assert.Equal(t, "https://test.goauthentik.io/application/o/introspect/", ep.TokenIntrospection)
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ func (a *Application) getStore(p api.ProxyOutpostConfig, externalHost *url.URL)
|
|||
// Add one to the validity to ensure we don't have a session with indefinite length
|
||||
maxAge = int(*t) + 1
|
||||
}
|
||||
if config.Get().Redis.Host != "" {
|
||||
if a.isEmbedded {
|
||||
rs, err := redistore.NewRediStoreWithDB(10, "tcp", fmt.Sprintf("%s:%d", config.Get().Redis.Host, config.Get().Redis.Port), config.Get().Redis.Password, strconv.Itoa(config.Get().Redis.DB))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Stage 1: Build
|
||||
FROM docker.io/golang:1.21.0-bullseye AS builder
|
||||
FROM docker.io/golang:1.21.0-bookworm AS builder
|
||||
|
||||
WORKDIR /go/src/goauthentik.io
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2023-08-17 17:37+0000\n"
|
||||
"POT-Creation-Date: 2023-08-23 10:04+0000\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
|
@ -98,125 +98,125 @@ msgstr ""
|
|||
msgid "Users added to this group will be superusers."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:162
|
||||
#: authentik/core/models.py:142
|
||||
msgid "User's display name."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:256 authentik/providers/oauth2/models.py:294
|
||||
#: authentik/core/models.py:268 authentik/providers/oauth2/models.py:294
|
||||
msgid "User"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:257
|
||||
#: authentik/core/models.py:269
|
||||
msgid "Users"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:270
|
||||
#: authentik/core/models.py:282
|
||||
msgid ""
|
||||
"Flow used for authentication when the associated application is accessed by "
|
||||
"an un-authenticated user."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:280
|
||||
#: authentik/core/models.py:292
|
||||
msgid "Flow used when authorizing this provider."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:292
|
||||
#: authentik/core/models.py:304
|
||||
msgid ""
|
||||
"Accessed from applications; optional backchannel providers for protocols "
|
||||
"like LDAP and SCIM."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:347
|
||||
#: authentik/core/models.py:359
|
||||
msgid "Application's display Name."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:348
|
||||
#: authentik/core/models.py:360
|
||||
msgid "Internal application name, used in URLs."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:360
|
||||
#: authentik/core/models.py:372
|
||||
msgid "Open launch URL in a new browser tab or window."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:424
|
||||
#: authentik/core/models.py:436
|
||||
msgid "Application"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:425
|
||||
#: authentik/core/models.py:437
|
||||
msgid "Applications"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:431
|
||||
#: authentik/core/models.py:443
|
||||
msgid "Use the source-specific identifier"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:433
|
||||
#: authentik/core/models.py:445
|
||||
msgid ""
|
||||
"Link to a user with identical email address. Can have security implications "
|
||||
"when a source doesn't validate email addresses."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:437
|
||||
#: authentik/core/models.py:449
|
||||
msgid ""
|
||||
"Use the user's email address, but deny enrollment when the email address "
|
||||
"already exists."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:440
|
||||
#: authentik/core/models.py:452
|
||||
msgid ""
|
||||
"Link to a user with identical username. Can have security implications when "
|
||||
"a username is used with another source."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:444
|
||||
#: authentik/core/models.py:456
|
||||
msgid ""
|
||||
"Use the user's username, but deny enrollment when the username already "
|
||||
"exists."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:451
|
||||
#: authentik/core/models.py:463
|
||||
msgid "Source's display Name."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:452
|
||||
#: authentik/core/models.py:464
|
||||
msgid "Internal source name, used in URLs."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:471
|
||||
#: authentik/core/models.py:483
|
||||
msgid "Flow to use when authenticating existing users."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:480
|
||||
#: authentik/core/models.py:492
|
||||
msgid "Flow to use when enrolling new users."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:488
|
||||
#: authentik/core/models.py:500
|
||||
msgid ""
|
||||
"How the source determines if an existing user should be authenticated or a "
|
||||
"new user enrolled."
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:660
|
||||
#: authentik/core/models.py:672
|
||||
msgid "Token"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:661
|
||||
#: authentik/core/models.py:673
|
||||
msgid "Tokens"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:702
|
||||
#: authentik/core/models.py:714
|
||||
msgid "Property Mapping"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:703
|
||||
#: authentik/core/models.py:715
|
||||
msgid "Property Mappings"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:738
|
||||
#: authentik/core/models.py:750
|
||||
msgid "Authenticated Session"
|
||||
msgstr ""
|
||||
|
||||
#: authentik/core/models.py:739
|
||||
#: authentik/core/models.py:751
|
||||
msgid "Authenticated Sessions"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand.
|
||||
# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand.
|
||||
|
||||
[[package]]
|
||||
name = "aiohttp"
|
||||
|
@ -150,6 +150,17 @@ files = [
|
|||
[package.dependencies]
|
||||
vine = ">=5.0.0"
|
||||
|
||||
[[package]]
|
||||
name = "annotated-types"
|
||||
version = "0.5.0"
|
||||
description = "Reusable constraint types to use with typing.Annotated"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "annotated_types-0.5.0-py3-none-any.whl", hash = "sha256:58da39888f92c276ad970249761ebea80ba544b77acddaa1a4d6cf78287d45fd"},
|
||||
{file = "annotated_types-0.5.0.tar.gz", hash = "sha256:47cdc3490d9ac1506ce92c7aaa76c579dc3509ff11e098fc867e5130ab7be802"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyio"
|
||||
version = "3.7.1"
|
||||
|
@ -2674,56 +2685,141 @@ files = [
|
|||
|
||||
[[package]]
|
||||
name = "pydantic"
|
||||
version = "1.10.12"
|
||||
description = "Data validation and settings management using python type hints"
|
||||
version = "2.3.0"
|
||||
description = "Data validation using Python type hints"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "pydantic-1.10.12-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a1fcb59f2f355ec350073af41d927bf83a63b50e640f4dbaa01053a28b7a7718"},
|
||||
{file = "pydantic-1.10.12-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b7ccf02d7eb340b216ec33e53a3a629856afe1c6e0ef91d84a4e6f2fb2ca70fe"},
|
||||
{file = "pydantic-1.10.12-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8fb2aa3ab3728d950bcc885a2e9eff6c8fc40bc0b7bb434e555c215491bcf48b"},
|
||||
{file = "pydantic-1.10.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:771735dc43cf8383959dc9b90aa281f0b6092321ca98677c5fb6125a6f56d58d"},
|
||||
{file = "pydantic-1.10.12-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ca48477862372ac3770969b9d75f1bf66131d386dba79506c46d75e6b48c1e09"},
|
||||
{file = "pydantic-1.10.12-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a5e7add47a5b5a40c49b3036d464e3c7802f8ae0d1e66035ea16aa5b7a3923ed"},
|
||||
{file = "pydantic-1.10.12-cp310-cp310-win_amd64.whl", hash = "sha256:e4129b528c6baa99a429f97ce733fff478ec955513630e61b49804b6cf9b224a"},
|
||||
{file = "pydantic-1.10.12-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b0d191db0f92dfcb1dec210ca244fdae5cbe918c6050b342d619c09d31eea0cc"},
|
||||
{file = "pydantic-1.10.12-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:795e34e6cc065f8f498c89b894a3c6da294a936ee71e644e4bd44de048af1405"},
|
||||
{file = "pydantic-1.10.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69328e15cfda2c392da4e713443c7dbffa1505bc9d566e71e55abe14c97ddc62"},
|
||||
{file = "pydantic-1.10.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2031de0967c279df0d8a1c72b4ffc411ecd06bac607a212892757db7462fc494"},
|
||||
{file = "pydantic-1.10.12-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ba5b2e6fe6ca2b7e013398bc7d7b170e21cce322d266ffcd57cca313e54fb246"},
|
||||
{file = "pydantic-1.10.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2a7bac939fa326db1ab741c9d7f44c565a1d1e80908b3797f7f81a4f86bc8d33"},
|
||||
{file = "pydantic-1.10.12-cp311-cp311-win_amd64.whl", hash = "sha256:87afda5539d5140cb8ba9e8b8c8865cb5b1463924d38490d73d3ccfd80896b3f"},
|
||||
{file = "pydantic-1.10.12-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:549a8e3d81df0a85226963611950b12d2d334f214436a19537b2efed61b7639a"},
|
||||
{file = "pydantic-1.10.12-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:598da88dfa127b666852bef6d0d796573a8cf5009ffd62104094a4fe39599565"},
|
||||
{file = "pydantic-1.10.12-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba5c4a8552bff16c61882db58544116d021d0b31ee7c66958d14cf386a5b5350"},
|
||||
{file = "pydantic-1.10.12-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c79e6a11a07da7374f46970410b41d5e266f7f38f6a17a9c4823db80dadf4303"},
|
||||
{file = "pydantic-1.10.12-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ab26038b8375581dc832a63c948f261ae0aa21f1d34c1293469f135fa92972a5"},
|
||||
{file = "pydantic-1.10.12-cp37-cp37m-win_amd64.whl", hash = "sha256:e0a16d274b588767602b7646fa05af2782576a6cf1022f4ba74cbb4db66f6ca8"},
|
||||
{file = "pydantic-1.10.12-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6a9dfa722316f4acf4460afdf5d41d5246a80e249c7ff475c43a3a1e9d75cf62"},
|
||||
{file = "pydantic-1.10.12-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a73f489aebd0c2121ed974054cb2759af8a9f747de120acd2c3394cf84176ccb"},
|
||||
{file = "pydantic-1.10.12-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b30bcb8cbfccfcf02acb8f1a261143fab622831d9c0989707e0e659f77a18e0"},
|
||||
{file = "pydantic-1.10.12-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fcfb5296d7877af406ba1547dfde9943b1256d8928732267e2653c26938cd9c"},
|
||||
{file = "pydantic-1.10.12-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2f9a6fab5f82ada41d56b0602606a5506aab165ca54e52bc4545028382ef1c5d"},
|
||||
{file = "pydantic-1.10.12-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:dea7adcc33d5d105896401a1f37d56b47d443a2b2605ff8a969a0ed5543f7e33"},
|
||||
{file = "pydantic-1.10.12-cp38-cp38-win_amd64.whl", hash = "sha256:1eb2085c13bce1612da8537b2d90f549c8cbb05c67e8f22854e201bde5d98a47"},
|
||||
{file = "pydantic-1.10.12-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ef6c96b2baa2100ec91a4b428f80d8f28a3c9e53568219b6c298c1125572ebc6"},
|
||||
{file = "pydantic-1.10.12-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6c076be61cd0177a8433c0adcb03475baf4ee91edf5a4e550161ad57fc90f523"},
|
||||
{file = "pydantic-1.10.12-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d5a58feb9a39f481eda4d5ca220aa8b9d4f21a41274760b9bc66bfd72595b86"},
|
||||
{file = "pydantic-1.10.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5f805d2d5d0a41633651a73fa4ecdd0b3d7a49de4ec3fadf062fe16501ddbf1"},
|
||||
{file = "pydantic-1.10.12-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:1289c180abd4bd4555bb927c42ee42abc3aee02b0fb2d1223fb7c6e5bef87dbe"},
|
||||
{file = "pydantic-1.10.12-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5d1197e462e0364906cbc19681605cb7c036f2475c899b6f296104ad42b9f5fb"},
|
||||
{file = "pydantic-1.10.12-cp39-cp39-win_amd64.whl", hash = "sha256:fdbdd1d630195689f325c9ef1a12900524dceb503b00a987663ff4f58669b93d"},
|
||||
{file = "pydantic-1.10.12-py3-none-any.whl", hash = "sha256:b749a43aa51e32839c9d71dc67eb1e4221bb04af1033a32e3923d46f9effa942"},
|
||||
{file = "pydantic-1.10.12.tar.gz", hash = "sha256:0fe8a415cea8f340e7a9af9c54fc71a649b43e8ca3cc732986116b3cb135d303"},
|
||||
{file = "pydantic-2.3.0-py3-none-any.whl", hash = "sha256:45b5e446c6dfaad9444819a293b921a40e1db1aa61ea08aede0522529ce90e81"},
|
||||
{file = "pydantic-2.3.0.tar.gz", hash = "sha256:1607cc106602284cd4a00882986570472f193fde9cb1259bceeaedb26aa79a6d"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
email-validator = {version = ">=1.0.3", optional = true, markers = "extra == \"email\""}
|
||||
typing-extensions = ">=4.2.0"
|
||||
annotated-types = ">=0.4.0"
|
||||
email-validator = {version = ">=2.0.0", optional = true, markers = "extra == \"email\""}
|
||||
pydantic-core = "2.6.3"
|
||||
typing-extensions = ">=4.6.1"
|
||||
|
||||
[package.extras]
|
||||
dotenv = ["python-dotenv (>=0.10.4)"]
|
||||
email = ["email-validator (>=1.0.3)"]
|
||||
email = ["email-validator (>=2.0.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic-core"
|
||||
version = "2.6.3"
|
||||
description = ""
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "pydantic_core-2.6.3-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:1a0ddaa723c48af27d19f27f1c73bdc615c73686d763388c8683fe34ae777bad"},
|
||||
{file = "pydantic_core-2.6.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5cfde4fab34dd1e3a3f7f3db38182ab6c95e4ea91cf322242ee0be5c2f7e3d2f"},
|
||||
{file = "pydantic_core-2.6.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5493a7027bfc6b108e17c3383959485087d5942e87eb62bbac69829eae9bc1f7"},
|
||||
{file = "pydantic_core-2.6.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:84e87c16f582f5c753b7f39a71bd6647255512191be2d2dbf49458c4ef024588"},
|
||||
{file = "pydantic_core-2.6.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:522a9c4a4d1924facce7270c84b5134c5cabcb01513213662a2e89cf28c1d309"},
|
||||
{file = "pydantic_core-2.6.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aaafc776e5edc72b3cad1ccedb5fd869cc5c9a591f1213aa9eba31a781be9ac1"},
|
||||
{file = "pydantic_core-2.6.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a750a83b2728299ca12e003d73d1264ad0440f60f4fc9cee54acc489249b728"},
|
||||
{file = "pydantic_core-2.6.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9e8b374ef41ad5c461efb7a140ce4730661aadf85958b5c6a3e9cf4e040ff4bb"},
|
||||
{file = "pydantic_core-2.6.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b594b64e8568cf09ee5c9501ede37066b9fc41d83d58f55b9952e32141256acd"},
|
||||
{file = "pydantic_core-2.6.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2a20c533cb80466c1d42a43a4521669ccad7cf2967830ac62c2c2f9cece63e7e"},
|
||||
{file = "pydantic_core-2.6.3-cp310-none-win32.whl", hash = "sha256:04fe5c0a43dec39aedba0ec9579001061d4653a9b53a1366b113aca4a3c05ca7"},
|
||||
{file = "pydantic_core-2.6.3-cp310-none-win_amd64.whl", hash = "sha256:6bf7d610ac8f0065a286002a23bcce241ea8248c71988bda538edcc90e0c39ad"},
|
||||
{file = "pydantic_core-2.6.3-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:6bcc1ad776fffe25ea5c187a028991c031a00ff92d012ca1cc4714087e575973"},
|
||||
{file = "pydantic_core-2.6.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:df14f6332834444b4a37685810216cc8fe1fe91f447332cd56294c984ecbff1c"},
|
||||
{file = "pydantic_core-2.6.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0b7486d85293f7f0bbc39b34e1d8aa26210b450bbd3d245ec3d732864009819"},
|
||||
{file = "pydantic_core-2.6.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a892b5b1871b301ce20d40b037ffbe33d1407a39639c2b05356acfef5536d26a"},
|
||||
{file = "pydantic_core-2.6.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:883daa467865e5766931e07eb20f3e8152324f0adf52658f4d302242c12e2c32"},
|
||||
{file = "pydantic_core-2.6.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d4eb77df2964b64ba190eee00b2312a1fd7a862af8918ec70fc2d6308f76ac64"},
|
||||
{file = "pydantic_core-2.6.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ce8c84051fa292a5dc54018a40e2a1926fd17980a9422c973e3ebea017aa8da"},
|
||||
{file = "pydantic_core-2.6.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:22134a4453bd59b7d1e895c455fe277af9d9d9fbbcb9dc3f4a97b8693e7e2c9b"},
|
||||
{file = "pydantic_core-2.6.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:02e1c385095efbd997311d85c6021d32369675c09bcbfff3b69d84e59dc103f6"},
|
||||
{file = "pydantic_core-2.6.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d79f1f2f7ebdb9b741296b69049ff44aedd95976bfee38eb4848820628a99b50"},
|
||||
{file = "pydantic_core-2.6.3-cp311-none-win32.whl", hash = "sha256:430ddd965ffd068dd70ef4e4d74f2c489c3a313adc28e829dd7262cc0d2dd1e8"},
|
||||
{file = "pydantic_core-2.6.3-cp311-none-win_amd64.whl", hash = "sha256:84f8bb34fe76c68c9d96b77c60cef093f5e660ef8e43a6cbfcd991017d375950"},
|
||||
{file = "pydantic_core-2.6.3-cp311-none-win_arm64.whl", hash = "sha256:5a2a3c9ef904dcdadb550eedf3291ec3f229431b0084666e2c2aa8ff99a103a2"},
|
||||
{file = "pydantic_core-2.6.3-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:8421cf496e746cf8d6b677502ed9a0d1e4e956586cd8b221e1312e0841c002d5"},
|
||||
{file = "pydantic_core-2.6.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bb128c30cf1df0ab78166ded1ecf876620fb9aac84d2413e8ea1594b588c735d"},
|
||||
{file = "pydantic_core-2.6.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37a822f630712817b6ecc09ccc378192ef5ff12e2c9bae97eb5968a6cdf3b862"},
|
||||
{file = "pydantic_core-2.6.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:240a015102a0c0cc8114f1cba6444499a8a4d0333e178bc504a5c2196defd456"},
|
||||
{file = "pydantic_core-2.6.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f90e5e3afb11268628c89f378f7a1ea3f2fe502a28af4192e30a6cdea1e7d5e"},
|
||||
{file = "pydantic_core-2.6.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:340e96c08de1069f3d022a85c2a8c63529fd88709468373b418f4cf2c949fb0e"},
|
||||
{file = "pydantic_core-2.6.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1480fa4682e8202b560dcdc9eeec1005f62a15742b813c88cdc01d44e85308e5"},
|
||||
{file = "pydantic_core-2.6.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f14546403c2a1d11a130b537dda28f07eb6c1805a43dae4617448074fd49c282"},
|
||||
{file = "pydantic_core-2.6.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a87c54e72aa2ef30189dc74427421e074ab4561cf2bf314589f6af5b37f45e6d"},
|
||||
{file = "pydantic_core-2.6.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f93255b3e4d64785554e544c1c76cd32f4a354fa79e2eeca5d16ac2e7fdd57aa"},
|
||||
{file = "pydantic_core-2.6.3-cp312-none-win32.whl", hash = "sha256:f70dc00a91311a1aea124e5f64569ea44c011b58433981313202c46bccbec0e1"},
|
||||
{file = "pydantic_core-2.6.3-cp312-none-win_amd64.whl", hash = "sha256:23470a23614c701b37252618e7851e595060a96a23016f9a084f3f92f5ed5881"},
|
||||
{file = "pydantic_core-2.6.3-cp312-none-win_arm64.whl", hash = "sha256:1ac1750df1b4339b543531ce793b8fd5c16660a95d13aecaab26b44ce11775e9"},
|
||||
{file = "pydantic_core-2.6.3-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:a53e3195f134bde03620d87a7e2b2f2046e0e5a8195e66d0f244d6d5b2f6d31b"},
|
||||
{file = "pydantic_core-2.6.3-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:f2969e8f72c6236c51f91fbb79c33821d12a811e2a94b7aa59c65f8dbdfad34a"},
|
||||
{file = "pydantic_core-2.6.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:672174480a85386dd2e681cadd7d951471ad0bb028ed744c895f11f9d51b9ebe"},
|
||||
{file = "pydantic_core-2.6.3-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:002d0ea50e17ed982c2d65b480bd975fc41086a5a2f9c924ef8fc54419d1dea3"},
|
||||
{file = "pydantic_core-2.6.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3ccc13afee44b9006a73d2046068d4df96dc5b333bf3509d9a06d1b42db6d8bf"},
|
||||
{file = "pydantic_core-2.6.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:439a0de139556745ae53f9cc9668c6c2053444af940d3ef3ecad95b079bc9987"},
|
||||
{file = "pydantic_core-2.6.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d63b7545d489422d417a0cae6f9898618669608750fc5e62156957e609e728a5"},
|
||||
{file = "pydantic_core-2.6.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b44c42edc07a50a081672e25dfe6022554b47f91e793066a7b601ca290f71e42"},
|
||||
{file = "pydantic_core-2.6.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1c721bfc575d57305dd922e6a40a8fe3f762905851d694245807a351ad255c58"},
|
||||
{file = "pydantic_core-2.6.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:5e4a2cf8c4543f37f5dc881de6c190de08096c53986381daebb56a355be5dfe6"},
|
||||
{file = "pydantic_core-2.6.3-cp37-none-win32.whl", hash = "sha256:d9b4916b21931b08096efed090327f8fe78e09ae8f5ad44e07f5c72a7eedb51b"},
|
||||
{file = "pydantic_core-2.6.3-cp37-none-win_amd64.whl", hash = "sha256:a8acc9dedd304da161eb071cc7ff1326aa5b66aadec9622b2574ad3ffe225525"},
|
||||
{file = "pydantic_core-2.6.3-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:5e9c068f36b9f396399d43bfb6defd4cc99c36215f6ff33ac8b9c14ba15bdf6b"},
|
||||
{file = "pydantic_core-2.6.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e61eae9b31799c32c5f9b7be906be3380e699e74b2db26c227c50a5fc7988698"},
|
||||
{file = "pydantic_core-2.6.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d85463560c67fc65cd86153a4975d0b720b6d7725cf7ee0b2d291288433fc21b"},
|
||||
{file = "pydantic_core-2.6.3-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9616567800bdc83ce136e5847d41008a1d602213d024207b0ff6cab6753fe645"},
|
||||
{file = "pydantic_core-2.6.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9e9b65a55bbabda7fccd3500192a79f6e474d8d36e78d1685496aad5f9dbd92c"},
|
||||
{file = "pydantic_core-2.6.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f468d520f47807d1eb5d27648393519655eadc578d5dd862d06873cce04c4d1b"},
|
||||
{file = "pydantic_core-2.6.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9680dd23055dd874173a3a63a44e7f5a13885a4cfd7e84814be71be24fba83db"},
|
||||
{file = "pydantic_core-2.6.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9a718d56c4d55efcfc63f680f207c9f19c8376e5a8a67773535e6f7e80e93170"},
|
||||
{file = "pydantic_core-2.6.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8ecbac050856eb6c3046dea655b39216597e373aa8e50e134c0e202f9c47efec"},
|
||||
{file = "pydantic_core-2.6.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:788be9844a6e5c4612b74512a76b2153f1877cd845410d756841f6c3420230eb"},
|
||||
{file = "pydantic_core-2.6.3-cp38-none-win32.whl", hash = "sha256:07a1aec07333bf5adebd8264047d3dc518563d92aca6f2f5b36f505132399efc"},
|
||||
{file = "pydantic_core-2.6.3-cp38-none-win_amd64.whl", hash = "sha256:621afe25cc2b3c4ba05fff53525156d5100eb35c6e5a7cf31d66cc9e1963e378"},
|
||||
{file = "pydantic_core-2.6.3-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:813aab5bfb19c98ae370952b6f7190f1e28e565909bfc219a0909db168783465"},
|
||||
{file = "pydantic_core-2.6.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:50555ba3cb58f9861b7a48c493636b996a617db1a72c18da4d7f16d7b1b9952b"},
|
||||
{file = "pydantic_core-2.6.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19e20f8baedd7d987bd3f8005c146e6bcbda7cdeefc36fad50c66adb2dd2da48"},
|
||||
{file = "pydantic_core-2.6.3-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b0a5d7edb76c1c57b95df719af703e796fc8e796447a1da939f97bfa8a918d60"},
|
||||
{file = "pydantic_core-2.6.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f06e21ad0b504658a3a9edd3d8530e8cea5723f6ea5d280e8db8efc625b47e49"},
|
||||
{file = "pydantic_core-2.6.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea053cefa008fda40f92aab937fb9f183cf8752e41dbc7bc68917884454c6362"},
|
||||
{file = "pydantic_core-2.6.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:171a4718860790f66d6c2eda1d95dd1edf64f864d2e9f9115840840cf5b5713f"},
|
||||
{file = "pydantic_core-2.6.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5ed7ceca6aba5331ece96c0e328cd52f0dcf942b8895a1ed2642de50800b79d3"},
|
||||
{file = "pydantic_core-2.6.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:acafc4368b289a9f291e204d2c4c75908557d4f36bd3ae937914d4529bf62a76"},
|
||||
{file = "pydantic_core-2.6.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1aa712ba150d5105814e53cb141412217146fedc22621e9acff9236d77d2a5ef"},
|
||||
{file = "pydantic_core-2.6.3-cp39-none-win32.whl", hash = "sha256:44b4f937b992394a2e81a5c5ce716f3dcc1237281e81b80c748b2da6dd5cf29a"},
|
||||
{file = "pydantic_core-2.6.3-cp39-none-win_amd64.whl", hash = "sha256:9b33bf9658cb29ac1a517c11e865112316d09687d767d7a0e4a63d5c640d1b17"},
|
||||
{file = "pydantic_core-2.6.3-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:d7050899026e708fb185e174c63ebc2c4ee7a0c17b0a96ebc50e1f76a231c057"},
|
||||
{file = "pydantic_core-2.6.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:99faba727727b2e59129c59542284efebbddade4f0ae6a29c8b8d3e1f437beb7"},
|
||||
{file = "pydantic_core-2.6.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fa159b902d22b283b680ef52b532b29554ea2a7fc39bf354064751369e9dbd7"},
|
||||
{file = "pydantic_core-2.6.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:046af9cfb5384f3684eeb3f58a48698ddab8dd870b4b3f67f825353a14441418"},
|
||||
{file = "pydantic_core-2.6.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:930bfe73e665ebce3f0da2c6d64455098aaa67e1a00323c74dc752627879fc67"},
|
||||
{file = "pydantic_core-2.6.3-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:85cc4d105747d2aa3c5cf3e37dac50141bff779545ba59a095f4a96b0a460e70"},
|
||||
{file = "pydantic_core-2.6.3-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b25afe9d5c4f60dcbbe2b277a79be114e2e65a16598db8abee2a2dcde24f162b"},
|
||||
{file = "pydantic_core-2.6.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:e49ce7dc9f925e1fb010fc3d555250139df61fa6e5a0a95ce356329602c11ea9"},
|
||||
{file = "pydantic_core-2.6.3-pp37-pypy37_pp73-macosx_10_7_x86_64.whl", hash = "sha256:2dd50d6a1aef0426a1d0199190c6c43ec89812b1f409e7fe44cb0fbf6dfa733c"},
|
||||
{file = "pydantic_core-2.6.3-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6595b0d8c8711e8e1dc389d52648b923b809f68ac1c6f0baa525c6440aa0daa"},
|
||||
{file = "pydantic_core-2.6.3-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ef724a059396751aef71e847178d66ad7fc3fc969a1a40c29f5aac1aa5f8784"},
|
||||
{file = "pydantic_core-2.6.3-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3c8945a105f1589ce8a693753b908815e0748f6279959a4530f6742e1994dcb6"},
|
||||
{file = "pydantic_core-2.6.3-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c8c6660089a25d45333cb9db56bb9e347241a6d7509838dbbd1931d0e19dbc7f"},
|
||||
{file = "pydantic_core-2.6.3-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:692b4ff5c4e828a38716cfa92667661a39886e71136c97b7dac26edef18767f7"},
|
||||
{file = "pydantic_core-2.6.3-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:f1a5d8f18877474c80b7711d870db0eeef9442691fcdb00adabfc97e183ee0b0"},
|
||||
{file = "pydantic_core-2.6.3-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:3796a6152c545339d3b1652183e786df648ecdf7c4f9347e1d30e6750907f5bb"},
|
||||
{file = "pydantic_core-2.6.3-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:b962700962f6e7a6bd77e5f37320cabac24b4c0f76afeac05e9f93cf0c620014"},
|
||||
{file = "pydantic_core-2.6.3-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56ea80269077003eaa59723bac1d8bacd2cd15ae30456f2890811efc1e3d4413"},
|
||||
{file = "pydantic_core-2.6.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75c0ebbebae71ed1e385f7dfd9b74c1cff09fed24a6df43d326dd7f12339ec34"},
|
||||
{file = "pydantic_core-2.6.3-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:252851b38bad3bfda47b104ffd077d4f9604a10cb06fe09d020016a25107bf98"},
|
||||
{file = "pydantic_core-2.6.3-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:6656a0ae383d8cd7cc94e91de4e526407b3726049ce8d7939049cbfa426518c8"},
|
||||
{file = "pydantic_core-2.6.3-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:d9140ded382a5b04a1c030b593ed9bf3088243a0a8b7fa9f071a5736498c5483"},
|
||||
{file = "pydantic_core-2.6.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:d38bbcef58220f9c81e42c255ef0bf99735d8f11edef69ab0b499da77105158a"},
|
||||
{file = "pydantic_core-2.6.3-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:c9d469204abcca28926cbc28ce98f28e50e488767b084fb3fbdf21af11d3de26"},
|
||||
{file = "pydantic_core-2.6.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:48c1ed8b02ffea4d5c9c220eda27af02b8149fe58526359b3c07eb391cb353a2"},
|
||||
{file = "pydantic_core-2.6.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b2b1bfed698fa410ab81982f681f5b1996d3d994ae8073286515ac4d165c2e7"},
|
||||
{file = "pydantic_core-2.6.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf9d42a71a4d7a7c1f14f629e5c30eac451a6fc81827d2beefd57d014c006c4a"},
|
||||
{file = "pydantic_core-2.6.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4292ca56751aebbe63a84bbfc3b5717abb09b14d4b4442cc43fd7c49a1529efd"},
|
||||
{file = "pydantic_core-2.6.3-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:7dc2ce039c7290b4ef64334ec7e6ca6494de6eecc81e21cb4f73b9b39991408c"},
|
||||
{file = "pydantic_core-2.6.3-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:615a31b1629e12445c0e9fc8339b41aaa6cc60bd53bf802d5fe3d2c0cda2ae8d"},
|
||||
{file = "pydantic_core-2.6.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:1fa1f6312fb84e8c281f32b39affe81984ccd484da6e9d65b3d18c202c666149"},
|
||||
{file = "pydantic_core-2.6.3.tar.gz", hash = "sha256:1508f37ba9e3ddc0189e6ff4e2228bd2d3c3a4641cbe8c07177162f76ed696c7"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0"
|
||||
|
||||
[[package]]
|
||||
name = "pydantic-scim"
|
||||
|
@ -3309,28 +3405,28 @@ pyasn1 = ">=0.1.3"
|
|||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.285"
|
||||
version = "0.0.286"
|
||||
description = "An extremely fast Python linter, written in Rust."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "ruff-0.0.285-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:72a3a0936369b986b0e959f9090206ed3c18f9e5e439ea5b8e6867c6707aded5"},
|
||||
{file = "ruff-0.0.285-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:0d9ab6ad16742eb78919e0fba09f914f042409df40ad63423c34bb20d350162a"},
|
||||
{file = "ruff-0.0.285-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c48926156288b8ac005eb1db5e77c15e8a37309ae49d9fb6771d5cf5f777590"},
|
||||
{file = "ruff-0.0.285-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1d2a60c102e7a5e147b58fc2cbea12a563c565383effc527c987ea2086a05742"},
|
||||
{file = "ruff-0.0.285-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b02aae62f922d088bb01943e1dbd861688ada13d735b78b8348a7d90121fd292"},
|
||||
{file = "ruff-0.0.285-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:f572c4296d8c7ddd22c3204de4031965be524fdd1fdaaef273945932912b28c5"},
|
||||
{file = "ruff-0.0.285-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80effdf4fe69763d69eb4ab9443e186fd09e668b59fe70ba4b49f4c077d15a1b"},
|
||||
{file = "ruff-0.0.285-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5977ce304da35c263f5e082901bd7ac0bd2be845a8fcfd1a29e4d6680cddb307"},
|
||||
{file = "ruff-0.0.285-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72a087712d474fa17b915d7cb9ef807e1256182b12ddfafb105eb00aeee48d1a"},
|
||||
{file = "ruff-0.0.285-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:7ce67736cd8dfe97162d1e7adfc2d9a1bac0efb9aaaff32e4042c7cde079f54b"},
|
||||
{file = "ruff-0.0.285-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:5473a4c6cac34f583bff08c5f63b8def5599a0ea4dc96c0302fbd2cc0b3ecbad"},
|
||||
{file = "ruff-0.0.285-py3-none-musllinux_1_2_i686.whl", hash = "sha256:e6b1c961d608d373a032f047a20bf3c55ad05f56c32e7b96dcca0830a2a72348"},
|
||||
{file = "ruff-0.0.285-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:2933cc9631f453305399c7b8fb72b113ad76b49ae1d7103cc4afd3a423bed164"},
|
||||
{file = "ruff-0.0.285-py3-none-win32.whl", hash = "sha256:770c5eb6376de024111443022cda534fb28980a9dd3b4abc83992a8770167ba6"},
|
||||
{file = "ruff-0.0.285-py3-none-win_amd64.whl", hash = "sha256:a8c6ad6b9cd77489bf6d1510950cbbe47a843aa234adff0960bae64bd06c3b6d"},
|
||||
{file = "ruff-0.0.285-py3-none-win_arm64.whl", hash = "sha256:de44fbc6c3b25fccee473ddf851416fd4e246fc6027b2197c395b1b3b3897921"},
|
||||
{file = "ruff-0.0.285.tar.gz", hash = "sha256:45866048d1dcdcc80855998cb26c4b2b05881f9e043d2e3bfe1aa36d9a2e8f28"},
|
||||
{file = "ruff-0.0.286-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:8e22cb557e7395893490e7f9cfea1073d19a5b1dd337f44fd81359b2767da4e9"},
|
||||
{file = "ruff-0.0.286-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:68ed8c99c883ae79a9133cb1a86d7130feee0397fdf5ba385abf2d53e178d3fa"},
|
||||
{file = "ruff-0.0.286-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8301f0bb4ec1a5b29cfaf15b83565136c47abefb771603241af9d6038f8981e8"},
|
||||
{file = "ruff-0.0.286-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:acc4598f810bbc465ce0ed84417ac687e392c993a84c7eaf3abf97638701c1ec"},
|
||||
{file = "ruff-0.0.286-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88c8e358b445eb66d47164fa38541cfcc267847d1e7a92dd186dddb1a0a9a17f"},
|
||||
{file = "ruff-0.0.286-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:0433683d0c5dbcf6162a4beb2356e820a593243f1fa714072fec15e2e4f4c939"},
|
||||
{file = "ruff-0.0.286-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ddb61a0c4454cbe4623f4a07fef03c5ae921fe04fede8d15c6e36703c0a73b07"},
|
||||
{file = "ruff-0.0.286-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:47549c7c0be24c8ae9f2bce6f1c49fbafea83bca80142d118306f08ec7414041"},
|
||||
{file = "ruff-0.0.286-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:559aa793149ac23dc4310f94f2c83209eedb16908a0343663be19bec42233d25"},
|
||||
{file = "ruff-0.0.286-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:d73cfb1c3352e7aa0ce6fb2321f36fa1d4a2c48d2ceac694cb03611ddf0e4db6"},
|
||||
{file = "ruff-0.0.286-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:3dad93b1f973c6d1db4b6a5da8690c5625a3fa32bdf38e543a6936e634b83dc3"},
|
||||
{file = "ruff-0.0.286-py3-none-musllinux_1_2_i686.whl", hash = "sha256:26afc0851f4fc3738afcf30f5f8b8612a31ac3455cb76e611deea80f5c0bf3ce"},
|
||||
{file = "ruff-0.0.286-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:9b6b116d1c4000de1b9bf027131dbc3b8a70507788f794c6b09509d28952c512"},
|
||||
{file = "ruff-0.0.286-py3-none-win32.whl", hash = "sha256:556e965ac07c1e8c1c2d759ac512e526ecff62c00fde1a046acb088d3cbc1a6c"},
|
||||
{file = "ruff-0.0.286-py3-none-win_amd64.whl", hash = "sha256:5d295c758961376c84aaa92d16e643d110be32add7465e197bfdaec5a431a107"},
|
||||
{file = "ruff-0.0.286-py3-none-win_arm64.whl", hash = "sha256:1d6142d53ab7f164204b3133d053c4958d4d11ec3a39abf23a40b13b0784e3f0"},
|
||||
{file = "ruff-0.0.286.tar.gz", hash = "sha256:f1e9d169cce81a384a26ee5bb8c919fe9ae88255f39a1a69fd1ebab233a85ed2"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3552,22 +3648,22 @@ files = [
|
|||
|
||||
[[package]]
|
||||
name = "tornado"
|
||||
version = "6.3.2"
|
||||
version = "6.3.3"
|
||||
description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed."
|
||||
optional = false
|
||||
python-versions = ">= 3.8"
|
||||
files = [
|
||||
{file = "tornado-6.3.2-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:c367ab6c0393d71171123ca5515c61ff62fe09024fa6bf299cd1339dc9456829"},
|
||||
{file = "tornado-6.3.2-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b46a6ab20f5c7c1cb949c72c1994a4585d2eaa0be4853f50a03b5031e964fc7c"},
|
||||
{file = "tornado-6.3.2-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c2de14066c4a38b4ecbbcd55c5cc4b5340eb04f1c5e81da7451ef555859c833f"},
|
||||
{file = "tornado-6.3.2-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:05615096845cf50a895026f749195bf0b10b8909f9be672f50b0fe69cba368e4"},
|
||||
{file = "tornado-6.3.2-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5b17b1cf5f8354efa3d37c6e28fdfd9c1c1e5122f2cb56dac121ac61baa47cbe"},
|
||||
{file = "tornado-6.3.2-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:29e71c847a35f6e10ca3b5c2990a52ce38b233019d8e858b755ea6ce4dcdd19d"},
|
||||
{file = "tornado-6.3.2-cp38-abi3-musllinux_1_1_i686.whl", hash = "sha256:834ae7540ad3a83199a8da8f9f2d383e3c3d5130a328889e4cc991acc81e87a0"},
|
||||
{file = "tornado-6.3.2-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:6a0848f1aea0d196a7c4f6772197cbe2abc4266f836b0aac76947872cd29b411"},
|
||||
{file = "tornado-6.3.2-cp38-abi3-win32.whl", hash = "sha256:7efcbcc30b7c654eb6a8c9c9da787a851c18f8ccd4a5a3a95b05c7accfa068d2"},
|
||||
{file = "tornado-6.3.2-cp38-abi3-win_amd64.whl", hash = "sha256:0c325e66c8123c606eea33084976c832aa4e766b7dff8aedd7587ea44a604cdf"},
|
||||
{file = "tornado-6.3.2.tar.gz", hash = "sha256:4b927c4f19b71e627b13f3db2324e4ae660527143f9e1f2e2fb404f3a187e2ba"},
|
||||
{file = "tornado-6.3.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:502fba735c84450974fec147340016ad928d29f1e91f49be168c0a4c18181e1d"},
|
||||
{file = "tornado-6.3.3-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:805d507b1f588320c26f7f097108eb4023bbaa984d63176d1652e184ba24270a"},
|
||||
{file = "tornado-6.3.3-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bd19ca6c16882e4d37368e0152f99c099bad93e0950ce55e71daed74045908f"},
|
||||
{file = "tornado-6.3.3-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ac51f42808cca9b3613f51ffe2a965c8525cb1b00b7b2d56828b8045354f76a"},
|
||||
{file = "tornado-6.3.3-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:71a8db65160a3c55d61839b7302a9a400074c9c753040455494e2af74e2501f2"},
|
||||
{file = "tornado-6.3.3-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:ceb917a50cd35882b57600709dd5421a418c29ddc852da8bcdab1f0db33406b0"},
|
||||
{file = "tornado-6.3.3-cp38-abi3-musllinux_1_1_i686.whl", hash = "sha256:7d01abc57ea0dbb51ddfed477dfe22719d376119844e33c661d873bf9c0e4a16"},
|
||||
{file = "tornado-6.3.3-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:9dc4444c0defcd3929d5c1eb5706cbe1b116e762ff3e0deca8b715d14bf6ec17"},
|
||||
{file = "tornado-6.3.3-cp38-abi3-win32.whl", hash = "sha256:65ceca9500383fbdf33a98c0087cb975b2ef3bfb874cb35b8de8740cf7f41bd3"},
|
||||
{file = "tornado-6.3.3-cp38-abi3-win_amd64.whl", hash = "sha256:22d3c2fa10b5793da13c807e6fc38ff49a4f6e1e3868b0a6f4164768bb8e20f5"},
|
||||
{file = "tornado-6.3.3.tar.gz", hash = "sha256:e7d8db41c0181c80d76c982aacc442c0783a2c54d6400fe028954201a2e032fe"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3607,13 +3703,13 @@ wsproto = ">=0.14"
|
|||
|
||||
[[package]]
|
||||
name = "twilio"
|
||||
version = "8.5.0"
|
||||
version = "8.7.0"
|
||||
description = "Twilio API client and TwiML generator"
|
||||
optional = false
|
||||
python-versions = ">=3.7.0"
|
||||
files = [
|
||||
{file = "twilio-8.5.0-py2.py3-none-any.whl", hash = "sha256:a6fdea2252cb7a8a47b5750d58abe1888bba9777482bac8e9bc3be47970facc7"},
|
||||
{file = "twilio-8.5.0.tar.gz", hash = "sha256:f55da9b485f9070aef09836e56230d0e6fd83811d2e6668f20d9057dd3668143"},
|
||||
{file = "twilio-8.7.0-py2.py3-none-any.whl", hash = "sha256:0e8db896c8a2adefa0c1f8e725443e0da928db1de02a40687782e5f704738f98"},
|
||||
{file = "twilio-8.7.0.tar.gz", hash = "sha256:ffc38ccf05cffe050670f211e872c5d8bfcad420f2ea3dcb361cb42e228b27fa"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
|
@ -4347,4 +4443,4 @@ files = [
|
|||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.11"
|
||||
content-hash = "5a15115f5ec615e5d2e34924b676df3efc4e8de83f8cdedb7eab055d41338121"
|
||||
content-hash = "8604e4dac9b0dcc55daccab83d4c182981d21201ef9901cf9c1acdc24288f979"
|
||||
|
|
|
@ -8,7 +8,7 @@ WORKDIR /static
|
|||
RUN npm ci --include=dev && npm run build-proxy
|
||||
|
||||
# Stage 2: Build
|
||||
FROM docker.io/golang:1.21.0-bullseye AS builder
|
||||
FROM docker.io/golang:1.21.0-bookworm AS builder
|
||||
|
||||
WORKDIR /go/src/goauthentik.io
|
||||
|
||||
|
|
|
@ -152,7 +152,7 @@ packaging = "*"
|
|||
paramiko = "*"
|
||||
psycopg = { extras = ["c"], version = "*" }
|
||||
pycryptodome = "*"
|
||||
pydantic = "<2.0.0"
|
||||
pydantic = "<3.0.0"
|
||||
pydantic-scim = "^0.0.7"
|
||||
pyjwt = "*"
|
||||
python = "^3.11"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Stage 1: Build
|
||||
FROM docker.io/golang:1.21.0-bullseye AS builder
|
||||
FROM docker.io/golang:1.21.0-bookworm AS builder
|
||||
|
||||
WORKDIR /go/src/goauthentik.io
|
||||
|
||||
|
|
16
schema.yml
|
@ -31747,7 +31747,7 @@ components:
|
|||
type: string
|
||||
format: date-time
|
||||
readOnly: true
|
||||
users:
|
||||
internal_users:
|
||||
type: integer
|
||||
readOnly: true
|
||||
external_users:
|
||||
|
@ -31756,27 +31756,27 @@ components:
|
|||
required:
|
||||
- expiry
|
||||
- external_users
|
||||
- internal_users
|
||||
- key
|
||||
- license_uuid
|
||||
- name
|
||||
- users
|
||||
LicenseForecast:
|
||||
type: object
|
||||
description: Serializer for license forecast
|
||||
properties:
|
||||
users:
|
||||
internal_users:
|
||||
type: integer
|
||||
external_users:
|
||||
type: integer
|
||||
forecasted_users:
|
||||
forecasted_internal_users:
|
||||
type: integer
|
||||
forecasted_external_users:
|
||||
type: integer
|
||||
required:
|
||||
- external_users
|
||||
- forecasted_external_users
|
||||
- forecasted_users
|
||||
- users
|
||||
- forecasted_internal_users
|
||||
- internal_users
|
||||
LicenseRequest:
|
||||
type: object
|
||||
description: License Serializer
|
||||
|
@ -31790,7 +31790,7 @@ components:
|
|||
type: object
|
||||
description: Serializer for license status
|
||||
properties:
|
||||
users:
|
||||
internal_users:
|
||||
type: integer
|
||||
external_users:
|
||||
type: integer
|
||||
|
@ -31810,11 +31810,11 @@ components:
|
|||
required:
|
||||
- external_users
|
||||
- has_license
|
||||
- internal_users
|
||||
- latest_valid
|
||||
- read_only
|
||||
- show_admin_warning
|
||||
- show_user_warning
|
||||
- users
|
||||
- valid
|
||||
Link:
|
||||
type: object
|
||||
|
|
|
@ -16,9 +16,6 @@ with open("local.env.yml", "w", encoding="utf-8") as _config:
|
|||
"container_image_base": "ghcr.io/goauthentik/dev-%(type)s:gh-%(build_hash)s",
|
||||
},
|
||||
"blueprints_dir": "./blueprints",
|
||||
"web": {
|
||||
"outpost_port_offset": 100,
|
||||
},
|
||||
"cert_discovery_dir": "./certs",
|
||||
"geoip": "tests/GeoLite2-City-Test.mmdb",
|
||||
},
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
"sourceType": "module"
|
||||
},
|
||||
"plugins": ["@typescript-eslint", "lit", "custom-elements"],
|
||||
"ignorePatterns": ["authentik-live-tests/**"],
|
||||
"rules": {
|
||||
"indent": "off",
|
||||
"linebreak-style": ["error", "unix"],
|
||||
|
|
|
@ -8,8 +8,8 @@
|
|||
"build-locales": "run-s build-locales:build",
|
||||
"build-locales:build": "lit-localize build",
|
||||
"build-locales:repair": "prettier --write ./src/locale-codes.ts",
|
||||
"rollup:build": "node --max-old-space-size=4096 node_modules/.bin/rollup -c ./rollup.config.js",
|
||||
"rollup:build-proxy": "node --max-old-space-size=4096 node_modules/.bin/rollup -c ./rollup.proxy.js",
|
||||
"rollup:build": "node --max-old-space-size=4096 node_modules/.bin/rollup -c ./rollup.config.mjs",
|
||||
"rollup:build-proxy": "node --max-old-space-size=4096 node_modules/.bin/rollup -c ./rollup.proxy.mjs",
|
||||
"rollup:watch": "node --max-old-space-size=8192 node_modules/.bin/rollup -c -w",
|
||||
"build": "run-s build-locales rollup:build",
|
||||
"build-proxy": "run-s build-locales rollup:build-proxy",
|
||||
|
@ -27,24 +27,24 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@codemirror/lang-html": "^6.4.5",
|
||||
"@codemirror/lang-javascript": "^6.1.9",
|
||||
"@codemirror/lang-javascript": "^6.2.0",
|
||||
"@codemirror/lang-python": "^6.1.3",
|
||||
"@codemirror/lang-xml": "^6.0.2",
|
||||
"@codemirror/legacy-modes": "^6.3.3",
|
||||
"@codemirror/theme-one-dark": "^6.1.2",
|
||||
"@formatjs/intl-listformat": "^7.4.0",
|
||||
"@fortawesome/fontawesome-free": "^6.4.2",
|
||||
"@goauthentik/api": "^2023.6.1-1692308915",
|
||||
"@goauthentik/api": "^2023.6.1-1692789666",
|
||||
"@lit-labs/context": "^0.4.0",
|
||||
"@lit-labs/task": "^3.0.1",
|
||||
"@lit/localize": "^0.11.4",
|
||||
"@patternfly/elements": "^2.3.2",
|
||||
"@patternfly/elements": "^2.4.0",
|
||||
"@patternfly/patternfly": "^4.224.2",
|
||||
"@sentry/browser": "^7.64.0",
|
||||
"@sentry/tracing": "^7.64.0",
|
||||
"@webcomponents/webcomponentsjs": "^2.8.0",
|
||||
"base64-js": "^1.5.1",
|
||||
"chart.js": "^4.3.3",
|
||||
"chart.js": "^4.4.0",
|
||||
"chartjs-adapter-moment": "^1.0.1",
|
||||
"codemirror": "^6.0.1",
|
||||
"construct-style-sheets-polyfill": "^3.1.0",
|
||||
|
@ -52,30 +52,31 @@
|
|||
"country-flag-icons": "^1.5.7",
|
||||
"fuse.js": "^6.6.2",
|
||||
"lit": "^2.8.0",
|
||||
"mermaid": "^10.3.1",
|
||||
"mermaid": "^10.4.0",
|
||||
"rapidoc": "^9.3.4",
|
||||
"style-mod": "^4.0.3",
|
||||
"style-mod": "^4.1.0",
|
||||
"webcomponent-qr-code": "^1.2.0",
|
||||
"yaml": "^2.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.22.10",
|
||||
"@babel/core": "^7.22.11",
|
||||
"@babel/plugin-proposal-class-properties": "^7.18.6",
|
||||
"@babel/plugin-proposal-decorators": "^7.22.10",
|
||||
"@babel/plugin-proposal-private-methods": "^7.18.6",
|
||||
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
||||
"@babel/plugin-transform-runtime": "^7.22.10",
|
||||
"@babel/preset-env": "^7.22.10",
|
||||
"@babel/preset-typescript": "^7.22.5",
|
||||
"@babel/preset-typescript": "^7.22.11",
|
||||
"@hcaptcha/types": "^1.0.3",
|
||||
"@jackfranklin/rollup-plugin-markdown": "^0.4.0",
|
||||
"@jeysal/storybook-addon-css-user-preferences": "^0.2.0",
|
||||
"@lit/localize-tools": "^0.6.9",
|
||||
"@rollup/plugin-babel": "^6.0.3",
|
||||
"@rollup/plugin-commonjs": "^25.0.4",
|
||||
"@rollup/plugin-node-resolve": "^15.2.0",
|
||||
"@rollup/plugin-node-resolve": "^15.2.1",
|
||||
"@rollup/plugin-replace": "^5.0.2",
|
||||
"@rollup/plugin-typescript": "^11.1.2",
|
||||
"@rollup/plugin-typescript": "^11.1.3",
|
||||
"@rollup/plugin-terser": "^0.4.3",
|
||||
"@storybook/addon-essentials": "^7.3.2",
|
||||
"@storybook/addon-links": "^7.3.2",
|
||||
"@storybook/blocks": "^7.1.1",
|
||||
|
@ -83,13 +84,13 @@
|
|||
"@storybook/web-components-vite": "^7.3.2",
|
||||
"@trivago/prettier-plugin-sort-imports": "^4.2.0",
|
||||
"@types/chart.js": "^2.9.37",
|
||||
"@types/codemirror": "5.60.8",
|
||||
"@types/codemirror": "5.60.9",
|
||||
"@types/grecaptcha": "^3.0.4",
|
||||
"@typescript-eslint/eslint-plugin": "^6.4.0",
|
||||
"@typescript-eslint/parser": "^6.4.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.4.1",
|
||||
"@typescript-eslint/parser": "^6.4.1",
|
||||
"babel-plugin-macros": "^3.1.0",
|
||||
"babel-plugin-tsconfig-paths": "^1.0.3",
|
||||
"eslint": "^8.47.0",
|
||||
"eslint": "^8.48.0",
|
||||
"eslint-config-google": "^0.14.0",
|
||||
"eslint-plugin-custom-elements": "0.0.8",
|
||||
"eslint-plugin-lit": "^1.9.1",
|
||||
|
@ -97,21 +98,19 @@
|
|||
"lit-analyzer": "^1.2.1",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"prettier": "^3.0.2",
|
||||
"pyright": "^1.1.323",
|
||||
"pyright": "^1.1.324",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"rollup": "^2.79.1",
|
||||
"rollup": "^3.28.1",
|
||||
"rollup-plugin-copy": "^3.4.0",
|
||||
"rollup-plugin-cssimport": "^1.0.3",
|
||||
"rollup-plugin-minify-html-literals": "^1.2.6",
|
||||
"rollup-plugin-postcss-lit": "^2.1.0",
|
||||
"rollup-plugin-terser": "^7.0.2",
|
||||
"storybook": "^7.3.2",
|
||||
"storybook-addon-mock": "^4.2.1",
|
||||
"ts-lit-plugin": "^1.2.1",
|
||||
"tslib": "^2.6.2",
|
||||
"turnstile-types": "^1.1.2",
|
||||
"typescript": "^5.1.6",
|
||||
"typescript": "^5.2.2",
|
||||
"vite-tsconfig-paths": "^4.2.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
|
|
|
@ -3,10 +3,10 @@ import babel from "@rollup/plugin-babel";
|
|||
import commonjs from "@rollup/plugin-commonjs";
|
||||
import { nodeResolve } from "@rollup/plugin-node-resolve";
|
||||
import replace from "@rollup/plugin-replace";
|
||||
import terser from "@rollup/plugin-terser";
|
||||
import { cwd } from "process";
|
||||
import copy from "rollup-plugin-copy";
|
||||
import cssimport from "rollup-plugin-cssimport";
|
||||
import { terser } from "rollup-plugin-terser";
|
||||
|
||||
// https://github.com/d3/d3-interpolate/issues/58
|
||||
const IGNORED_WARNINGS = /Circular dependency(.*d3-[interpolate|selection])|(.*@lit\/localize.*)/;
|
|
@ -1,3 +0,0 @@
|
|||
import { POLY, standalone } from "./rollup.config";
|
||||
|
||||
export default [POLY, ...standalone];
|
|
@ -0,0 +1,3 @@
|
|||
import { POLY, standalone } from "./rollup.config.mjs";
|
||||
|
||||
export default [POLY, ...standalone];
|
|
@ -12,7 +12,6 @@ import { AdminApi, Version } from "@goauthentik/api";
|
|||
|
||||
@customElement("ak-admin-status-version")
|
||||
export class VersionStatusCard extends AdminStatusCard<Version> {
|
||||
headerLink = "https://goauthentik.io/docs/releases";
|
||||
icon = "pf-icon pf-icon-bundle";
|
||||
|
||||
getPrimaryValue(): Promise<Version> {
|
||||
|
@ -43,16 +42,12 @@ export class VersionStatusCard extends AdminStatusCard<Version> {
|
|||
}
|
||||
|
||||
renderValue(): TemplateResult {
|
||||
let text = this.value?.versionCurrent;
|
||||
let link = `https://goauthentik.io/docs/releases/${this.value?.versionCurrent}`;
|
||||
if (this.value?.buildHash) {
|
||||
return html`
|
||||
<a
|
||||
href="https://github.com/goauthentik/authentik/commit/${this.value.buildHash}"
|
||||
target="_blank"
|
||||
>
|
||||
${this.value.buildHash?.substring(0, 7)}
|
||||
</a>
|
||||
`;
|
||||
text = this.value.buildHash?.substring(0, 7);
|
||||
link = `https://github.com/goauthentik/authentik/commit/${this.value.buildHash}`;
|
||||
}
|
||||
return html`${this.value?.versionCurrent}`;
|
||||
return html`<a href=${link} target="_blank">${text}</a>`;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -170,11 +170,11 @@ export class EnterpriseLicenseListPage extends TablePage<License> {
|
|||
icon="pf-icon pf-icon-user"
|
||||
header=${msg("Forecast internal users")}
|
||||
subtext=${msg(
|
||||
str`Estimated user count one year from now based on ${this.forecast?.users} current internal users and ${this.forecast?.forecastedUsers} forecasted internal users.`,
|
||||
str`Estimated user count one year from now based on ${this.forecast?.internalUsers} current internal users and ${this.forecast?.forecastedInternalUsers} forecasted internal users.`,
|
||||
)}
|
||||
>
|
||||
~ ${(this.forecast?.users || 0) +
|
||||
(this.forecast?.forecastedUsers || 0)}
|
||||
~ ${(this.forecast?.internalUsers || 0) +
|
||||
(this.forecast?.forecastedInternalUsers || 0)}
|
||||
</ak-aggregate-card>
|
||||
<ak-aggregate-card
|
||||
class="pf-l-grid__item"
|
||||
|
@ -217,10 +217,8 @@ export class EnterpriseLicenseListPage extends TablePage<License> {
|
|||
}
|
||||
return [
|
||||
html`<div>${item.name}</div>`,
|
||||
html`<div>
|
||||
<small>0 / ${item.users}</small>
|
||||
<small>0 / ${item.externalUsers}</small>
|
||||
</div>`,
|
||||
html`<div>${msg(str`Internal: ${item.internalUsers}`)}</div>
|
||||
<div>${msg(str`External: ${item.externalUsers}`)}</div>`,
|
||||
html`<ak-label color=${color}> ${item.expiry?.toLocaleString()} </ak-label>`,
|
||||
html`<ak-forms-modal>
|
||||
<span slot="submit"> ${msg("Update")} </span>
|
||||
|
|
|
@ -246,8 +246,8 @@ export class OAuth2ProviderFormPage extends ModelForm<OAuth2Provider, number> {
|
|||
</ak-textarea-input>
|
||||
|
||||
<ak-form-element-horizontal label=${msg("Signing Key")} name="signingKey">
|
||||
<!-- NOTE: 'null' cast to 'undefined' on signingKey to satisfy Lit requirements -->
|
||||
<ak-crypto-certificate-search
|
||||
name="certificate"
|
||||
certificate=${ifDefined(this.instance?.signingKey ?? undefined)}
|
||||
singleton
|
||||
></ak-crypto-certificate-search>
|
||||
|
|
|
@ -8,16 +8,21 @@ import "@goauthentik/elements/forms/SearchSelect";
|
|||
|
||||
import { msg } from "@lit/localize";
|
||||
import { TemplateResult, html } from "lit";
|
||||
import { customElement } from "lit/decorators.js";
|
||||
import { customElement, state } from "lit/decorators.js";
|
||||
|
||||
import { CoreApi, CoreUsersListRequest, IntentEnum, Token, User } from "@goauthentik/api";
|
||||
|
||||
@customElement("ak-token-form")
|
||||
export class TokenForm extends ModelForm<Token, string> {
|
||||
loadInstance(pk: string): Promise<Token> {
|
||||
return new CoreApi(DEFAULT_CONFIG).coreTokensRetrieve({
|
||||
@state()
|
||||
showExpiry = true;
|
||||
|
||||
async loadInstance(pk: string): Promise<Token> {
|
||||
const token = await new CoreApi(DEFAULT_CONFIG).coreTokensRetrieve({
|
||||
identifier: pk,
|
||||
});
|
||||
this.showExpiry = token.expiring || true;
|
||||
return token;
|
||||
}
|
||||
|
||||
getSuccessMessage(): string {
|
||||
|
@ -41,6 +46,17 @@ export class TokenForm extends ModelForm<Token, string> {
|
|||
}
|
||||
}
|
||||
|
||||
renderExpiry(): TemplateResult {
|
||||
return html`<ak-form-element-horizontal label=${msg("Expires on")} name="expires">
|
||||
<input
|
||||
type="datetime-local"
|
||||
data-type="datetime-local"
|
||||
value="${dateTimeLocal(first(this.instance?.expires, new Date()))}"
|
||||
class="pf-c-form-control"
|
||||
/>
|
||||
</ak-form-element-horizontal>`;
|
||||
}
|
||||
|
||||
renderForm(): TemplateResult {
|
||||
return html`<form class="pf-c-form pf-m-horizontal">
|
||||
<ak-form-element-horizontal
|
||||
|
@ -117,6 +133,10 @@ export class TokenForm extends ModelForm<Token, string> {
|
|||
class="pf-c-switch__input"
|
||||
type="checkbox"
|
||||
?checked=${first(this.instance?.expiring, true)}
|
||||
@change=${(ev: Event) => {
|
||||
const el = ev.target as HTMLInputElement;
|
||||
this.showExpiry = el.checked;
|
||||
}}
|
||||
/>
|
||||
<span class="pf-c-switch__toggle">
|
||||
<span class="pf-c-switch__toggle-icon">
|
||||
|
@ -131,14 +151,7 @@ export class TokenForm extends ModelForm<Token, string> {
|
|||
)}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal label=${msg("Expires on")} name="expires">
|
||||
<input
|
||||
type="datetime-local"
|
||||
data-type="datetime-local"
|
||||
value="${dateTimeLocal(first(this.instance?.expires, new Date()))}"
|
||||
class="pf-c-form-control"
|
||||
/>
|
||||
</ak-form-element-horizontal>
|
||||
${this.showExpiry ? this.renderExpiry() : html``}
|
||||
</form>`;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,10 @@ import { CoreApi, User } from "@goauthentik/api";
|
|||
|
||||
@customElement("ak-user-form")
|
||||
export class UserForm extends ModelForm<User, number> {
|
||||
static get defaultUserAttributes(): { [key: string]: unknown } {
|
||||
return {};
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return super.styles.concat(css`
|
||||
.pf-c-button.pf-m-control {
|
||||
|
@ -43,6 +47,9 @@ export class UserForm extends ModelForm<User, number> {
|
|||
}
|
||||
|
||||
async send(data: User): Promise<User> {
|
||||
if (data.attributes === null) {
|
||||
data.attributes = UserForm.defaultUserAttributes;
|
||||
}
|
||||
if (this.instance?.pk) {
|
||||
return new CoreApi(DEFAULT_CONFIG).coreUsersPartialUpdate({
|
||||
id: this.instance.pk,
|
||||
|
@ -145,12 +152,14 @@ export class UserForm extends ModelForm<User, number> {
|
|||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Attributes")}
|
||||
?required=${true}
|
||||
?required=${false}
|
||||
name="attributes"
|
||||
>
|
||||
<ak-codemirror
|
||||
mode="yaml"
|
||||
value="${YAML.stringify(first(this.instance?.attributes, {}))}"
|
||||
value="${YAML.stringify(
|
||||
first(this.instance?.attributes, UserForm.defaultUserAttributes),
|
||||
)}"
|
||||
>
|
||||
</ak-codemirror>
|
||||
<p class="pf-c-form__helper-text">
|
||||
|
|
|
@ -63,7 +63,7 @@ export function getMetaContent(key: string): string {
|
|||
}
|
||||
|
||||
export const DEFAULT_CONFIG = new Configuration({
|
||||
basePath: process.env.AK_API_BASE_PATH + "/api/v3",
|
||||
basePath: (process.env.AK_API_BASE_PATH || window.location.origin) + "/api/v3",
|
||||
headers: {
|
||||
"sentry-trace": getMetaContent("sentry-trace"),
|
||||
},
|
||||
|
|
|
@ -25,8 +25,13 @@ export class LoggingMiddleware implements Middleware {
|
|||
|
||||
post(context: ResponseContext): Promise<Response | void> {
|
||||
let msg = `authentik/api[${this.tenant.matchedDomain}]: `;
|
||||
msg += `${context.response.status} ${context.init.method} ${context.url}`;
|
||||
console.debug(msg);
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/console#styling_console_output
|
||||
msg += `%c${context.response.status}%c ${context.init.method} ${context.url}`;
|
||||
let style = "";
|
||||
if (context.response.status >= 400) {
|
||||
style = "color: red; font-weight: bold;";
|
||||
}
|
||||
console.debug(msg, style, "");
|
||||
return Promise.resolve(context.response);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ import PFToolbar from "@patternfly/patternfly/components/Toolbar/toolbar.css";
|
|||
import PFBullseye from "@patternfly/patternfly/layouts/Bullseye/bullseye.css";
|
||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||
|
||||
import { Pagination } from "@goauthentik/api";
|
||||
import { Pagination, ResponseError } from "@goauthentik/api";
|
||||
|
||||
export class TableColumn {
|
||||
title: string;
|
||||
|
@ -260,7 +260,9 @@ export abstract class Table<T> extends AKElement {
|
|||
|
||||
renderError(): TemplateResult {
|
||||
return html`<ak-empty-state header="${msg("Failed to fetch objects.")}" icon="fa-times">
|
||||
<div slot="body">${this.hasError?.toString()}</div>
|
||||
${this.hasError instanceof ResponseError
|
||||
? html` <div slot="body">${this.hasError.message}</div> `
|
||||
: html`<div slot="body">${this.hasError?.toString()}</div>`}
|
||||
</ak-empty-state>`;
|
||||
}
|
||||
|
||||
|
@ -268,8 +270,8 @@ export abstract class Table<T> extends AKElement {
|
|||
if (this.hasError) {
|
||||
return [this.renderEmpty(this.renderError())];
|
||||
}
|
||||
if (!this.data) {
|
||||
return;
|
||||
if (!this.data || this.isLoading) {
|
||||
return [this.renderLoading()];
|
||||
}
|
||||
if (this.data.pagination.count === 0) {
|
||||
return [this.renderEmpty()];
|
||||
|
@ -499,7 +501,7 @@ export abstract class Table<T> extends AKElement {
|
|||
${this.columns().map((col) => col.render(this))}
|
||||
</tr>
|
||||
</thead>
|
||||
${this.isLoading || !this.data ? this.renderLoading() : this.renderRows()}
|
||||
${this.renderRows()}
|
||||
</table>
|
||||
${this.paginated
|
||||
? html` <div class="pf-c-pagination pf-m-bottom">
|
||||
|
|
|
@ -67,6 +67,13 @@ export class LibraryApplication extends AKElement {
|
|||
return html`<ak-spinner></ak-spinner>`;
|
||||
}
|
||||
const me = rootInterface<UserInterface>()?.me;
|
||||
let expandable = false;
|
||||
if (rootInterface()?.uiConfig?.enabledFeatures.applicationEdit && me?.user.isSuperuser) {
|
||||
expandable = true;
|
||||
}
|
||||
if (this.application.metaPublisher !== "" || this.application.metaDescription !== "") {
|
||||
expandable = true;
|
||||
}
|
||||
return html` <div
|
||||
class="pf-c-card pf-m-hoverable pf-m-compact ${this.selected
|
||||
? "pf-m-selectable pf-m-selected"
|
||||
|
@ -89,22 +96,25 @@ export class LibraryApplication extends AKElement {
|
|||
>
|
||||
</div>
|
||||
<div class="expander"></div>
|
||||
<ak-expand textOpen=${msg("Less details")} textClosed=${msg("More details")}>
|
||||
<div class="pf-c-content">
|
||||
<small>${this.application.metaPublisher}</small>
|
||||
</div>
|
||||
${truncateWords(this.application.metaDescription || "", 10)}
|
||||
${rootInterface()?.uiConfig?.enabledFeatures.applicationEdit && me?.user.isSuperuser
|
||||
? html`
|
||||
<a
|
||||
class="pf-c-button pf-m-control pf-m-small pf-m-block"
|
||||
href="/if/admin/#/core/applications/${this.application?.slug}"
|
||||
>
|
||||
<i class="fas fa-edit"></i> ${msg("Edit")}
|
||||
</a>
|
||||
`
|
||||
: html``}
|
||||
</ak-expand>
|
||||
${expandable
|
||||
? html`<ak-expand textOpen=${msg("Less details")} textClosed=${msg("More details")}>
|
||||
<div class="pf-c-content">
|
||||
<small>${this.application.metaPublisher}</small>
|
||||
</div>
|
||||
${truncateWords(this.application.metaDescription || "", 10)}
|
||||
${rootInterface()?.uiConfig?.enabledFeatures.applicationEdit &&
|
||||
me?.user.isSuperuser
|
||||
? html`
|
||||
<a
|
||||
class="pf-c-button pf-m-control pf-m-small pf-m-block"
|
||||
href="/if/admin/#/core/applications/${this.application?.slug}"
|
||||
>
|
||||
<i class="fas fa-edit"></i> ${msg("Edit")}
|
||||
</a>
|
||||
`
|
||||
: html``}
|
||||
</ak-expand>`
|
||||
: html``}
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -116,43 +116,63 @@ export class UserSettingsPage extends AKElement {
|
|||
data-tab-title="${msg("Sessions")}"
|
||||
class="pf-c-page__main-section pf-m-no-padding-mobile"
|
||||
>
|
||||
<ak-user-session-list
|
||||
targetUser=${ifDefined(
|
||||
rootInterface<UserInterface>()?.me?.user.username,
|
||||
)}
|
||||
></ak-user-session-list>
|
||||
<div class="pf-c-card">
|
||||
<div class="pf-c-card__body">
|
||||
<ak-user-session-list
|
||||
targetUser=${ifDefined(
|
||||
rootInterface<UserInterface>()?.me?.user.username,
|
||||
)}
|
||||
></ak-user-session-list>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section
|
||||
slot="page-consents"
|
||||
data-tab-title="${msg("Consent")}"
|
||||
class="pf-c-page__main-section pf-m-no-padding-mobile"
|
||||
>
|
||||
<ak-user-consent-list
|
||||
userId=${ifDefined(rootInterface<UserInterface>()?.me?.user.pk)}
|
||||
></ak-user-consent-list>
|
||||
<div class="pf-c-card">
|
||||
<div class="pf-c-card__body">
|
||||
<ak-user-consent-list
|
||||
userId=${ifDefined(rootInterface<UserInterface>()?.me?.user.pk)}
|
||||
></ak-user-consent-list>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section
|
||||
slot="page-mfa"
|
||||
data-tab-title="${msg("MFA Devices")}"
|
||||
class="pf-c-page__main-section pf-m-no-padding-mobile"
|
||||
>
|
||||
<ak-user-settings-mfa
|
||||
.userSettings=${this.userSettings}
|
||||
></ak-user-settings-mfa>
|
||||
<div class="pf-c-card">
|
||||
<div class="pf-c-card__body">
|
||||
<ak-user-settings-mfa
|
||||
.userSettings=${this.userSettings}
|
||||
></ak-user-settings-mfa>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section
|
||||
slot="page-sources"
|
||||
data-tab-title="${msg("Connected services")}"
|
||||
class="pf-c-page__main-section pf-m-no-padding-mobile"
|
||||
>
|
||||
<ak-user-settings-source></ak-user-settings-source>
|
||||
<div class="pf-c-card">
|
||||
<div class="pf-c-card__body">
|
||||
<ak-user-settings-source></ak-user-settings-source>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section
|
||||
slot="page-tokens"
|
||||
data-tab-title="${msg("Tokens and App passwords")}"
|
||||
class="pf-c-page__main-section pf-m-no-padding-mobile"
|
||||
>
|
||||
<ak-user-token-list></ak-user-token-list>
|
||||
<div class="pf-c-card">
|
||||
<div class="pf-c-card__body">
|
||||
<ak-user-token-list></ak-user-token-list>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</ak-tabs>
|
||||
</main>
|
||||
|
|
|
@ -5806,7 +5806,7 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||
<source>Forecast internal users</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sde9a3f41977ec1f8">
|
||||
<source>Estimated user count one year from now based on <x id="0" equiv-text="${this.forecast?.users}"/> current internal users and <x id="1" equiv-text="${this.forecast?.forecastedUsers}"/> forecasted internal users.</source>
|
||||
<source>Estimated user count one year from now based on <x id="0" equiv-text="${this.forecast?.internalUsers}"/> current internal users and <x id="1" equiv-text="${this.forecast?.forecastedInternalUsers}"/> forecasted internal users.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s4557b6b9da258643">
|
||||
<source>Forecast external users</source>
|
||||
|
@ -5888,6 +5888,12 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||
</trans-unit>
|
||||
<trans-unit id="s6931695c4f563bc4">
|
||||
<source>The length of the individual generated tokens. Can be increased to improve security.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s0dd031b58ed4017c">
|
||||
<source>Internal: <x id="0" equiv-text="${item.internalUsers}"/></source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s57b07e524f8f5c2a">
|
||||
<source>External: <x id="0" equiv-text="${item.externalUsers}"/></source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
|
|
|
@ -6122,7 +6122,7 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||
<source>Forecast internal users</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sde9a3f41977ec1f8">
|
||||
<source>Estimated user count one year from now based on <x id="0" equiv-text="${this.forecast?.users}"/> current internal users and <x id="1" equiv-text="${this.forecast?.forecastedUsers}"/> forecasted internal users.</source>
|
||||
<source>Estimated user count one year from now based on <x id="0" equiv-text="${this.forecast?.internalUsers}"/> current internal users and <x id="1" equiv-text="${this.forecast?.forecastedInternalUsers}"/> forecasted internal users.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s4557b6b9da258643">
|
||||
<source>Forecast external users</source>
|
||||
|
@ -6204,6 +6204,12 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||
</trans-unit>
|
||||
<trans-unit id="s6931695c4f563bc4">
|
||||
<source>The length of the individual generated tokens. Can be increased to improve security.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s0dd031b58ed4017c">
|
||||
<source>Internal: <x id="0" equiv-text="${item.internalUsers}"/></source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s57b07e524f8f5c2a">
|
||||
<source>External: <x id="0" equiv-text="${item.externalUsers}"/></source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
|
|
|
@ -5714,7 +5714,7 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||
<source>Forecast internal users</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sde9a3f41977ec1f8">
|
||||
<source>Estimated user count one year from now based on <x id="0" equiv-text="${this.forecast?.users}"/> current internal users and <x id="1" equiv-text="${this.forecast?.forecastedUsers}"/> forecasted internal users.</source>
|
||||
<source>Estimated user count one year from now based on <x id="0" equiv-text="${this.forecast?.internalUsers}"/> current internal users and <x id="1" equiv-text="${this.forecast?.forecastedInternalUsers}"/> forecasted internal users.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s4557b6b9da258643">
|
||||
<source>Forecast external users</source>
|
||||
|
@ -5796,6 +5796,12 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||
</trans-unit>
|
||||
<trans-unit id="s6931695c4f563bc4">
|
||||
<source>The length of the individual generated tokens. Can be increased to improve security.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s0dd031b58ed4017c">
|
||||
<source>Internal: <x id="0" equiv-text="${item.internalUsers}"/></source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s57b07e524f8f5c2a">
|
||||
<source>External: <x id="0" equiv-text="${item.externalUsers}"/></source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
|
|
|
@ -5821,7 +5821,7 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||
<source>Forecast internal users</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sde9a3f41977ec1f8">
|
||||
<source>Estimated user count one year from now based on <x id="0" equiv-text="${this.forecast?.users}"/> current internal users and <x id="1" equiv-text="${this.forecast?.forecastedUsers}"/> forecasted internal users.</source>
|
||||
<source>Estimated user count one year from now based on <x id="0" equiv-text="${this.forecast?.internalUsers}"/> current internal users and <x id="1" equiv-text="${this.forecast?.forecastedInternalUsers}"/> forecasted internal users.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s4557b6b9da258643">
|
||||
<source>Forecast external users</source>
|
||||
|
@ -5903,6 +5903,12 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||
</trans-unit>
|
||||
<trans-unit id="s6931695c4f563bc4">
|
||||
<source>The length of the individual generated tokens. Can be increased to improve security.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s0dd031b58ed4017c">
|
||||
<source>Internal: <x id="0" equiv-text="${item.internalUsers}"/></source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s57b07e524f8f5c2a">
|
||||
<source>External: <x id="0" equiv-text="${item.externalUsers}"/></source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
|
|
|
@ -5953,7 +5953,7 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||
<source>Forecast internal users</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sde9a3f41977ec1f8">
|
||||
<source>Estimated user count one year from now based on <x id="0" equiv-text="${this.forecast?.users}"/> current internal users and <x id="1" equiv-text="${this.forecast?.forecastedUsers}"/> forecasted internal users.</source>
|
||||
<source>Estimated user count one year from now based on <x id="0" equiv-text="${this.forecast?.internalUsers}"/> current internal users and <x id="1" equiv-text="${this.forecast?.forecastedInternalUsers}"/> forecasted internal users.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s4557b6b9da258643">
|
||||
<source>Forecast external users</source>
|
||||
|
@ -6035,6 +6035,12 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||
</trans-unit>
|
||||
<trans-unit id="s6931695c4f563bc4">
|
||||
<source>The length of the individual generated tokens. Can be increased to improve security.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s0dd031b58ed4017c">
|
||||
<source>Internal: <x id="0" equiv-text="${item.internalUsers}"/></source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s57b07e524f8f5c2a">
|
||||
<source>External: <x id="0" equiv-text="${item.externalUsers}"/></source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
|
|
|
@ -6057,7 +6057,7 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||
<source>Forecast internal users</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sde9a3f41977ec1f8">
|
||||
<source>Estimated user count one year from now based on <x id="0" equiv-text="${this.forecast?.users}"/> current internal users and <x id="1" equiv-text="${this.forecast?.forecastedUsers}"/> forecasted internal users.</source>
|
||||
<source>Estimated user count one year from now based on <x id="0" equiv-text="${this.forecast?.internalUsers}"/> current internal users and <x id="1" equiv-text="${this.forecast?.forecastedInternalUsers}"/> forecasted internal users.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s4557b6b9da258643">
|
||||
<source>Forecast external users</source>
|
||||
|
@ -6139,6 +6139,12 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||
</trans-unit>
|
||||
<trans-unit id="s6931695c4f563bc4">
|
||||
<source>The length of the individual generated tokens. Can be increased to improve security.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s0dd031b58ed4017c">
|
||||
<source>Internal: <x id="0" equiv-text="${item.internalUsers}"/></source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s57b07e524f8f5c2a">
|
||||
<source>External: <x id="0" equiv-text="${item.externalUsers}"/></source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
|
|
|
@ -5704,7 +5704,7 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||
<source>Forecast internal users</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sde9a3f41977ec1f8">
|
||||
<source>Estimated user count one year from now based on <x id="0" equiv-text="${this.forecast?.users}"/> current internal users and <x id="1" equiv-text="${this.forecast?.forecastedUsers}"/> forecasted internal users.</source>
|
||||
<source>Estimated user count one year from now based on <x id="0" equiv-text="${this.forecast?.internalUsers}"/> current internal users and <x id="1" equiv-text="${this.forecast?.forecastedInternalUsers}"/> forecasted internal users.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s4557b6b9da258643">
|
||||
<source>Forecast external users</source>
|
||||
|
@ -5786,6 +5786,12 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||
</trans-unit>
|
||||
<trans-unit id="s6931695c4f563bc4">
|
||||
<source>The length of the individual generated tokens. Can be increased to improve security.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s0dd031b58ed4017c">
|
||||
<source>Internal: <x id="0" equiv-text="${item.internalUsers}"/></source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s57b07e524f8f5c2a">
|
||||
<source>External: <x id="0" equiv-text="${item.externalUsers}"/></source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version="1.0"?><xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
|
||||
<?xml version="1.0" ?><xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
|
||||
<file target-language="zh-Hans" source-language="en" original="lit-localize-inputs" datatype="plaintext">
|
||||
<body>
|
||||
<trans-unit id="s4caed5b7a7e5d89b">
|
||||
|
@ -618,9 +618,9 @@
|
|||
|
||||
</trans-unit>
|
||||
<trans-unit id="saa0e2675da69651b">
|
||||
<source>The URL "<x id="0" equiv-text="${this.url}"/>" was not found.</source>
|
||||
<target>未找到 URL "
|
||||
<x id="0" equiv-text="${this.url}"/>"。</target>
|
||||
<source>The URL "<x id="0" equiv-text="${this.url}"/>" was not found.</source>
|
||||
<target>未找到 URL "
|
||||
<x id="0" equiv-text="${this.url}"/>"。</target>
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="s58cd9c2fe836d9c6">
|
||||
|
@ -1072,8 +1072,8 @@
|
|||
|
||||
</trans-unit>
|
||||
<trans-unit id="sa8384c9c26731f83">
|
||||
<source>To allow any redirect URI, set this value to ".*". Be aware of the possible security implications this can have.</source>
|
||||
<target>要允许任何重定向 URI,请将此值设置为 ".*"。请注意这可能带来的安全影响。</target>
|
||||
<source>To allow any redirect URI, set this value to ".*". Be aware of the possible security implications this can have.</source>
|
||||
<target>要允许任何重定向 URI,请将此值设置为 ".*"。请注意这可能带来的安全影响。</target>
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="s55787f4dfcdce52b">
|
||||
|
@ -1819,8 +1819,8 @@
|
|||
|
||||
</trans-unit>
|
||||
<trans-unit id="sa90b7809586c35ce">
|
||||
<source>Either input a full URL, a relative path, or use 'fa://fa-test' to use the Font Awesome icon "fa-test".</source>
|
||||
<target>输入完整 URL、相对路径,或者使用 'fa://fa-test' 来使用 Font Awesome 图标 "fa-test"。</target>
|
||||
<source>Either input a full URL, a relative path, or use 'fa://fa-test' to use the Font Awesome icon "fa-test".</source>
|
||||
<target>输入完整 URL、相对路径,或者使用 'fa://fa-test' 来使用 Font Awesome 图标 "fa-test"。</target>
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="s0410779cb47de312">
|
||||
|
@ -3248,8 +3248,8 @@ doesn't pass when either or both of the selected options are equal or above the
|
|||
|
||||
</trans-unit>
|
||||
<trans-unit id="s76768bebabb7d543">
|
||||
<source>Field which contains members of a group. Note that if using the "memberUid" field, the value is assumed to contain a relative distinguished name. e.g. 'memberUid=some-user' instead of 'memberUid=cn=some-user,ou=groups,...'</source>
|
||||
<target>包含组成员的字段。请注意,如果使用 "memberUid" 字段,则假定该值包含相对可分辨名称。例如,'memberUid=some-user' 而不是 'memberUid=cn=some-user,ou=groups,...'</target>
|
||||
<source>Field which contains members of a group. Note that if using the "memberUid" field, the value is assumed to contain a relative distinguished name. e.g. 'memberUid=some-user' instead of 'memberUid=cn=some-user,ou=groups,...'</source>
|
||||
<target>包含组成员的字段。请注意,如果使用 "memberUid" 字段,则假定该值包含相对可分辨名称。例如,'memberUid=some-user' 而不是 'memberUid=cn=some-user,ou=groups,...'</target>
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="s026555347e589f0e">
|
||||
|
@ -4046,8 +4046,8 @@ doesn't pass when either or both of the selected options are equal or above the
|
|||
|
||||
</trans-unit>
|
||||
<trans-unit id="s7b1fba26d245cb1c">
|
||||
<source>When using an external logging solution for archiving, this can be set to "minutes=5".</source>
|
||||
<target>使用外部日志记录解决方案进行存档时,可以将其设置为 "minutes=5"。</target>
|
||||
<source>When using an external logging solution for archiving, this can be set to "minutes=5".</source>
|
||||
<target>使用外部日志记录解决方案进行存档时,可以将其设置为 "minutes=5"。</target>
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="s44536d20bb5c8257">
|
||||
|
@ -4056,8 +4056,8 @@ doesn't pass when either or both of the selected options are equal or above the
|
|||
|
||||
</trans-unit>
|
||||
<trans-unit id="s3bb51cabb02b997e">
|
||||
<source>Format: "weeks=3;days=2;hours=3,seconds=2".</source>
|
||||
<target>格式:"weeks=3;days=2;hours=3,seconds=2"。</target>
|
||||
<source>Format: "weeks=3;days=2;hours=3,seconds=2".</source>
|
||||
<target>格式:"weeks=3;days=2;hours=3,seconds=2"。</target>
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="s04bfd02201db5ab8">
|
||||
|
@ -4253,10 +4253,10 @@ doesn't pass when either or both of the selected options are equal or above the
|
|||
|
||||
</trans-unit>
|
||||
<trans-unit id="sa95a538bfbb86111">
|
||||
<source>Are you sure you want to update <x id="0" equiv-text="${this.objectLabel}"/> "<x id="1" equiv-text="${this.obj?.name}"/>"?</source>
|
||||
<source>Are you sure you want to update <x id="0" equiv-text="${this.objectLabel}"/> "<x id="1" equiv-text="${this.obj?.name}"/>"?</source>
|
||||
<target>您确定要更新
|
||||
<x id="0" equiv-text="${this.objectLabel}"/>"
|
||||
<x id="1" equiv-text="${this.obj?.name}"/>" 吗?</target>
|
||||
<x id="0" equiv-text="${this.objectLabel}"/>"
|
||||
<x id="1" equiv-text="${this.obj?.name}"/>" 吗?</target>
|
||||
|
||||
</trans-unit>
|
||||
<trans-unit id="sc92d7cfb6ee1fec6">
|
||||
|
@ -5372,7 +5372,7 @@ doesn't pass when either or both of the selected options are equal or above the
|
|||
|
||||
</trans-unit>
|
||||
<trans-unit id="sdf1d8edef27236f0">
|
||||
<source>A "roaming" authenticator, like a YubiKey</source>
|
||||
<source>A "roaming" authenticator, like a YubiKey</source>
|
||||
<target>像 YubiKey 这样的“漫游”身份验证器</target>
|
||||
|
||||
</trans-unit>
|
||||
|
@ -5712,10 +5712,10 @@ doesn't pass when either or both of the selected options are equal or above the
|
|||
|
||||
</trans-unit>
|
||||
<trans-unit id="s2d5f69929bb7221d">
|
||||
<source><x id="0" equiv-text="${prompt.name}"/> ("<x id="1" equiv-text="${prompt.fieldKey}"/>", of type <x id="2" equiv-text="${prompt.type}"/>)</source>
|
||||
<source><x id="0" equiv-text="${prompt.name}"/> ("<x id="1" equiv-text="${prompt.fieldKey}"/>", of type <x id="2" equiv-text="${prompt.type}"/>)</source>
|
||||
<target>
|
||||
<x id="0" equiv-text="${prompt.name}"/>("
|
||||
<x id="1" equiv-text="${prompt.fieldKey}"/>",类型为
|
||||
<x id="0" equiv-text="${prompt.name}"/>("
|
||||
<x id="1" equiv-text="${prompt.fieldKey}"/>",类型为
|
||||
<x id="2" equiv-text="${prompt.type}"/>)</target>
|
||||
|
||||
</trans-unit>
|
||||
|
@ -5764,7 +5764,7 @@ doesn't pass when either or both of the selected options are equal or above the
|
|||
|
||||
</trans-unit>
|
||||
<trans-unit id="s1608b2f94fa0dbd4">
|
||||
<source>If set to a duration above 0, the user will have the option to choose to "stay signed in", which will extend their session by the time specified here.</source>
|
||||
<source>If set to a duration above 0, the user will have the option to choose to "stay signed in", which will extend their session by the time specified here.</source>
|
||||
<target>如果设置时长大于 0,用户可以选择“保持登录”选项,这将使用户的会话延长此处设置的时间。</target>
|
||||
|
||||
</trans-unit>
|
||||
|
@ -7658,8 +7658,8 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||
<target>预测内部用户</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="sde9a3f41977ec1f8">
|
||||
<source>Estimated user count one year from now based on <x id="0" equiv-text="${this.forecast?.users}"/> current internal users and <x id="1" equiv-text="${this.forecast?.forecastedUsers}"/> forecasted internal users.</source>
|
||||
<target>根据当前 <x id="0" equiv-text="${this.forecast?.users}"/> 名内部用户和 <x id="1" equiv-text="${this.forecast?.forecastedUsers}"/> 名预测的内部用户,估算从此时起一年后的用户数。</target>
|
||||
<source>Estimated user count one year from now based on <x id="0" equiv-text="${this.forecast?.internalUsers}"/> current internal users and <x id="1" equiv-text="${this.forecast?.forecastedInternalUsers}"/> forecasted internal users.</source>
|
||||
<target>根据当前 <x id="0" equiv-text="${this.forecast?.internalUsers}"/> 名内部用户和 <x id="1" equiv-text="${this.forecast?.forecastedInternalUsers}"/> 名预测的内部用户,估算从此时起一年后的用户数。</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s4557b6b9da258643">
|
||||
<source>Forecast external users</source>
|
||||
|
@ -7759,13 +7759,24 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||
</trans-unit>
|
||||
<trans-unit id="s7caa8f7edb920909">
|
||||
<source>The number of tokens generated whenever this stage is used. Every token generated per stage execution will be attached to a single static device.</source>
|
||||
<target>使用此阶段时生成的令牌数量。每次阶段执行中生成的每个令牌都会被附加到单个静态设备上。</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s4aacc4e0277c1042">
|
||||
<source>Token length</source>
|
||||
<target>令牌长度</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s6931695c4f563bc4">
|
||||
<source>The length of the individual generated tokens. Can be increased to improve security.</source>
|
||||
<target>每个生成令牌的长度。可以增加以增强安全性。</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s0dd031b58ed4017c">
|
||||
<source>Internal: <x id="0" equiv-text="${item.internalUsers}"/></source>
|
||||
<target>内部:<x id="0" equiv-text="${item.internalUsers}"/></target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s57b07e524f8f5c2a">
|
||||
<source>External: <x id="0" equiv-text="${item.externalUsers}"/></source>
|
||||
<target>外部:<x id="0" equiv-text="${item.externalUsers}"/></target>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
||||
</xliff>
|
|
@ -5759,7 +5759,7 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||
<source>Forecast internal users</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sde9a3f41977ec1f8">
|
||||
<source>Estimated user count one year from now based on <x id="0" equiv-text="${this.forecast?.users}"/> current internal users and <x id="1" equiv-text="${this.forecast?.forecastedUsers}"/> forecasted internal users.</source>
|
||||
<source>Estimated user count one year from now based on <x id="0" equiv-text="${this.forecast?.internalUsers}"/> current internal users and <x id="1" equiv-text="${this.forecast?.forecastedInternalUsers}"/> forecasted internal users.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s4557b6b9da258643">
|
||||
<source>Forecast external users</source>
|
||||
|
@ -5841,6 +5841,12 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||
</trans-unit>
|
||||
<trans-unit id="s6931695c4f563bc4">
|
||||
<source>The length of the individual generated tokens. Can be increased to improve security.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s0dd031b58ed4017c">
|
||||
<source>Internal: <x id="0" equiv-text="${item.internalUsers}"/></source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s57b07e524f8f5c2a">
|
||||
<source>External: <x id="0" equiv-text="${item.externalUsers}"/></source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
|
|
|
@ -7658,8 +7658,8 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||
<target>预测内部用户</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="sde9a3f41977ec1f8">
|
||||
<source>Estimated user count one year from now based on <x id="0" equiv-text="${this.forecast?.users}"/> current internal users and <x id="1" equiv-text="${this.forecast?.forecastedUsers}"/> forecasted internal users.</source>
|
||||
<target>根据当前 <x id="0" equiv-text="${this.forecast?.users}"/> 名内部用户和 <x id="1" equiv-text="${this.forecast?.forecastedUsers}"/> 名预测的内部用户,估算从此时起一年后的用户数。</target>
|
||||
<source>Estimated user count one year from now based on <x id="0" equiv-text="${this.forecast?.internalUsers}"/> current internal users and <x id="1" equiv-text="${this.forecast?.forecastedInternalUsers}"/> forecasted internal users.</source>
|
||||
<target>根据当前 <x id="0" equiv-text="${this.forecast?.internalUsers}"/> 名内部用户和 <x id="1" equiv-text="${this.forecast?.forecastedInternalUsers}"/> 名预测的内部用户,估算从此时起一年后的用户数。</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s4557b6b9da258643">
|
||||
<source>Forecast external users</source>
|
||||
|
@ -7768,6 +7768,14 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||
<trans-unit id="s6931695c4f563bc4">
|
||||
<source>The length of the individual generated tokens. Can be increased to improve security.</source>
|
||||
<target>每个生成令牌的长度。可以增加以增强安全性。</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s0dd031b58ed4017c">
|
||||
<source>Internal: <x id="0" equiv-text="${item.internalUsers}"/></source>
|
||||
<target>内部:<x id="0" equiv-text="${item.internalUsers}"/></target>
|
||||
</trans-unit>
|
||||
<trans-unit id="s57b07e524f8f5c2a">
|
||||
<source>External: <x id="0" equiv-text="${item.externalUsers}"/></source>
|
||||
<target>外部:<x id="0" equiv-text="${item.externalUsers}"/></target>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
|
|
|
@ -5758,7 +5758,7 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||
<source>Forecast internal users</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="sde9a3f41977ec1f8">
|
||||
<source>Estimated user count one year from now based on <x id="0" equiv-text="${this.forecast?.users}"/> current internal users and <x id="1" equiv-text="${this.forecast?.forecastedUsers}"/> forecasted internal users.</source>
|
||||
<source>Estimated user count one year from now based on <x id="0" equiv-text="${this.forecast?.internalUsers}"/> current internal users and <x id="1" equiv-text="${this.forecast?.forecastedInternalUsers}"/> forecasted internal users.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s4557b6b9da258643">
|
||||
<source>Forecast external users</source>
|
||||
|
@ -5840,6 +5840,12 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||
</trans-unit>
|
||||
<trans-unit id="s6931695c4f563bc4">
|
||||
<source>The length of the individual generated tokens. Can be increased to improve security.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s0dd031b58ed4017c">
|
||||
<source>Internal: <x id="0" equiv-text="${item.internalUsers}"/></source>
|
||||
</trans-unit>
|
||||
<trans-unit id="s57b07e524f8f5c2a">
|
||||
<source>External: <x id="0" equiv-text="${item.externalUsers}"/></source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
|
|
|
@ -28,7 +28,7 @@ The danger with letting identity become boring is that we’re not engaging in t
|
|||
|
||||
> My pitch: Let’s make identity fun again. And in doing so, let’s think through a better way to decide whether to build or buy software.
|
||||
|
||||
!["Image by <a href="https://pixabay.com/users/jplenio-7645255/?utm_source=link-attribution&utm_medium=referral&utm_campaign=image&utm_content=3092026">Joe</a> from <a href="https://pixabay.com//?utm_source=link-attribution&utm_medium=referral&utm_campaign=image&utm_content=3092026">Pixabay</a>"](./image1.jpg)
|
||||
[![Image1](./image1.jpg)](https://pixabay.com/users/jplenio-7645255/ "Image by jplenio on pixabay")
|
||||
|
||||
<!--truncate-->
|
||||
|
||||
|
|
After Width: | Height: | Size: 2.3 MiB |
|
@ -0,0 +1,67 @@
|
|||
---
|
||||
title: "My hobby became my job, 50% extra pay, just needed to let go of GPLv3"
|
||||
slug: 2023-08-23-my-hobby-became-my-job
|
||||
authors:
|
||||
- name: Jens Langhammer
|
||||
title: CTO at Authentik Security Inc
|
||||
url: https://github.com/BeryJu
|
||||
image_url: https://github.com/BeryJu.png
|
||||
tags:
|
||||
- founder
|
||||
- SSO
|
||||
- open source
|
||||
- identity provider
|
||||
- licensing
|
||||
- gpl
|
||||
- mit
|
||||
- security
|
||||
- authentication
|
||||
hide_table_of_contents: false
|
||||
image: ./image1.jpg
|
||||
---
|
||||
|
||||
There’s been a lot of discussion about licensing in the news, with [Red Hat](https://www.redhat.com/en/blog/furthering-evolution-centos-stream) and now [Hashicorp](https://www.hashicorp.com/blog/hashicorp-adopts-business-source-license) notably adjusting their licensing models to be more “business friendly,” and [Codecov](https://blog.sentry.io/lets-talk-about-open-source/) (proudly, and mistakenly) [pronouncing](https://about.codecov.io/blog/codecov-is-now-open-source/) they are now “open source.”
|
||||
|
||||
“Like the rest of them, they have redefined ‘Open’ as in ‘Open for business’”—[jquast on Hacker News](https://news.ycombinator.com/item?id=37021360)
|
||||
|
||||
This is a common tension when you’re building commercially on top of open source, so I wanted to share some reflections from my own experience of going from MIT, to GPL, back to MIT.
|
||||
|
||||
!["Photo by <a href="https://unsplash.com/@gcalebjones?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Caleb Jones</a> on <a href="https://unsplash.com/photos/J3JMyXWQHXU?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a>"](./image1.jpg)
|
||||
|
||||
<!--truncate-->
|
||||
|
||||
I started working on the project that led to [authentik](https://github.com/goauthentik/authentik) when I was 20. My original vision was a single pane of glass for emails, domains, applications, hosting, and so on. This was overly ambitious for one person and their hobby project, and I ended up spending most of my time on the SSO part. This became its own project: Passbook (later [renamed to authentik](https://github.com/goauthentik/authentik/pull/361) due to a [naming conflict](https://techcrunch.com/2015/06/08/apple-rebrands-passbook-to-wallet/)).
|
||||
|
||||
Initially, authentik used the MIT license. When [Elastic called out AWS](https://www.elastic.co/blog/why-license-change-aws) for trademark abuse (offering Elasticsearch as an AWS service without collaborating with Elastic), I [changed it to GPLv3](https://github.com/goauthentik/authentik/commit/4671d4afb4d32988ca0058a33888862bd9652b16) because I didn’t like what AWS did in principle, and didn’t want it to happen to authentik.
|
||||
|
||||
# An opportunity, and a compromise
|
||||
|
||||
Two years later, [Sid](https://www.linkedin.com/in/sijbrandij/) at [Open Core Ventures](https://opencoreventures.com/) (OCV) contacted me about [creating a company](../2022-11-02-the-next-step-for-authentik/item.md), building on the features and functionality of authentik. It was a dream opportunity: work full time on my hobby project and make 25% more in the process. But I had to let go of the GPL license.
|
||||
|
||||
With an open core model customers are usually using code from both the open source and proprietary codebases. This necessitates a dual license structure, meaning customers need to accept both licenses.
|
||||
|
||||
The drawback of building commercially on top of open source software using GPL is that the copyleft aspect can put some people off. Not every person or business wants to have to expose their code for every minor change or bug fix they may add, and they will sooner find a competitor with a more permissive license than adopt your software. This is obviously not ideal when you’re trying to get traction and grow a business.
|
||||
|
||||
OCV proposed we switch back to MIT.
|
||||
|
||||
# Considerations and tradeoffs
|
||||
|
||||
I was very conflicted about reverting to MIT because we had chosen GPL for a reason, but the circumstances had changed. As a company and a real legal entity, we would have recourse if something like AWS/Elasticsearch were to happen—it wouldn’t just be me trying to defend myself while also doing my day job. The decision forced me to reflect on what it means to build a company on top of an existing open source project.
|
||||
|
||||
For me, it was an opportunity to work full time on a passion project, with more resources to invest in building and maintaining the open core of the project. The opportunity came with tradeoffs to be made, and a responsibility to be a good steward of the open source project.
|
||||
|
||||
I know how volatile startups can be. I had put so much time into authentik already, and my biggest concern was around what happens if things don’t work out. I wanted to make sure that the open source version stays free, vibrant, and open for use by all.
|
||||
|
||||
## A license isn’t the only way to guarantee good behavior
|
||||
|
||||
With a permissive license, the risk of [bait and switch](https://opencoreventures.com/blog/2022-10-preventing-the-bait-and-switch-open-core/) is always there. A commercial company needs to become profitable and there is precedent for changing to more limited licenses when it suits the business. People naturally see this as a dichotomy: you either have a copyleft license and therefore your intentions are enshrined in the license, or a permissive one and can’t be trusted to uphold open source ideals.
|
||||
|
||||
There is a third path though, which is the route we eventually took with [Authentik Security](https://goauthentik.io/), the company we were building on top of the project. We incorporated as a public benefit company, which means that we are legally bound by the terms in the [OCV Public Benefit Company Charter](https://github.com/OpenCoreVentures/ocv-public-benefit-company/blob/main/ocv-public-benefit-company-charter.md). This includes commitments to keeping open source products open source, and ensuring the majority of new features added in a calendar year are made available under an open source license. Being a public benefit company means we are still held accountable, just through a different mechanism than the license.
|
||||
|
||||
# The process of changing the license
|
||||
|
||||
Changing licenses is a sensitive issue. I consulted with the top contributors to authentik to hear their feedback while we were in the process of setting up Authentik Security. Nobody objected, so we [switched back to MIT](https://github.com/goauthentik/authentik/commit/47132faffbac1098dadba73435164e655901e9e7) and announced the change in the [company announcement post](https://goauthentik.io/blog/2022-11-02-the-next-step-for-authentik). I think I was surprised there wasn’t a backlash or accusations of putting profit over principle (we have all seen [how](https://news.ycombinator.com/item?id=37081306) [impassioned](https://news.ycombinator.com/item?id=36971490) [people](https://news.ycombinator.com/item?id=37003489) [get](https://news.ycombinator.com/item?id=36990036) about open source and ideals). I like to think that people saw the pragmatism in the decision: that MIT lets us further the work of authentik.
|
||||
|
||||
# Reflections
|
||||
|
||||
While a copyleft license is one way to hold companies accountable to upholding the principles of open source, with Authentik Security we struck a balance between commercial viability with the more permissive MIT license and the values I wanted to entrench with becoming a Public Benefit Company. I now get to work full time on my hobby, and the core of authentik is still open source.
|
|
@ -124,7 +124,7 @@ If you have any questions or comments about this advisory:
|
|||
Subject: `Notice of upcoming authentik Security releases 2022.10.3 and 2022.11.3`
|
||||
|
||||
```markdown
|
||||
We'll be publishing a security Issue (CVE-2022-xxxxx) and accompanying fix on _date_, 13:00 UTC with the Criticality level High. Fixed versions x, y and z will be released alongside a workaround for previous versions. For more info, see the authentik Security policy here: https://goauthentik.io/docs/security/policy.
|
||||
We'll be publishing a security Issue (CVE-2022-xxxxx) and accompanying fix on _date_, 13:00 UTC with the Severity level High. Fixed versions x, y and z will be released alongside a workaround for previous versions. For more info, see the authentik Security policy here: https://goauthentik.io/docs/security/policy.
|
||||
```
|
||||
|
||||
</p>
|
||||
|
@ -134,7 +134,7 @@ We'll be publishing a security Issue (CVE-2022-xxxxx) and accompanying fix on _d
|
|||
<p>
|
||||
|
||||
```markdown
|
||||
@everyone We'll be publishing a security Issue (CVE-2022-xxxxx) and accompanying fix on _date_, 13:00 UTC with the Criticality level High. Fixed versions x, y and z will be released alongside a workaround for previous versions. For more info, see the authentik Security policy here: https://goauthentik.io/docs/security/policy.
|
||||
@everyone We'll be publishing a security Issue (CVE-2022-xxxxx) and accompanying fix on _date_, 13:00 UTC with the Severity level High. Fixed versions x, y and z will be released alongside a workaround for previous versions. For more info, see the authentik Security policy here: https://goauthentik.io/docs/security/policy.
|
||||
```
|
||||
|
||||
</p>
|
||||
|
|
Before Width: | Height: | Size: 10 MiB After Width: | Height: | Size: 2.8 MiB |
Before Width: | Height: | Size: 10 MiB After Width: | Height: | Size: 2.8 MiB |
Before Width: | Height: | Size: 8.2 MiB After Width: | Height: | Size: 2.2 MiB |
Before Width: | Height: | Size: 8.2 MiB After Width: | Height: | Size: 2.3 MiB |
Before Width: | Height: | Size: 10 MiB After Width: | Height: | Size: 2.8 MiB |
|
@ -1,57 +0,0 @@
|
|||
---
|
||||
title: Release 2023.7
|
||||
slug: "/releases/2023.7"
|
||||
---
|
||||
|
||||
## Breaking changes
|
||||
|
||||
- Removal of PostgreSQL 11 support
|
||||
|
||||
As announced in the [2023.5](./v2023.5.md) release notes (and postponed by a release), this release requires PostgreSQL 12 or newer. This is due to a changed requirement in a framework we use, Django.
|
||||
|
||||
This does not affect docker-compose installations (as these already ship with PostgreSQL 12), however it is still recommended to upgrade to a newer version when convenient.
|
||||
|
||||
For Kubernetes install, a manual one-time migration has to be done: [Upgrading PostgreSQL on Kubernetes](../../troubleshooting/postgres/upgrade_kubernetes.md)
|
||||
|
||||
- Changed nested Group membership behaviour
|
||||
|
||||
In previous versions, nested groups were handled very inconsistently. Binding a group to an application/etc would check the membership recursively, however when using `user.ak_groups.all()` would only return direct memberships. Additionally, using `user.group_attributes()` would do the same and only merge all group attributes for direct memberships.
|
||||
|
||||
This has been changed to always use the same logic as when checking for access, which means dealing with complex group structures is a lot more consistent.
|
||||
|
||||
Policies that do use `user.ak_groups.all()` will retain the current behaviour, to use the new behaviour replace the call with `user.all_groups()`.
|
||||
|
||||
## New features
|
||||
|
||||
## Upgrading
|
||||
|
||||
This release does not introduce any new requirements.
|
||||
|
||||
### docker-compose
|
||||
|
||||
To upgrade, download the new docker-compose file and update the Docker stack with the new version, using these commands:
|
||||
|
||||
```
|
||||
wget -O docker-compose.yml https://goauthentik.io/version/2023.7/docker-compose.yml
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
The `-O` flag retains the downloaded file's name, overwriting any existing local file with the same name.
|
||||
|
||||
### Kubernetes
|
||||
|
||||
Update your values to use the new images:
|
||||
|
||||
```yaml
|
||||
image:
|
||||
repository: ghcr.io/goauthentik/server
|
||||
tag: 2023.7.0
|
||||
```
|
||||
|
||||
## Minor changes/fixes
|
||||
|
||||
<!-- _Insert the output of `make gen-changelog` here_ -->
|
||||
|
||||
## API Changes
|
||||
|
||||
<!-- _Insert output of `make gen-diff` here_ -->
|
|
@ -1,9 +1,4 @@
|
|||
const fs = require("fs").promises;
|
||||
const sidebar = require("./sidebars.js");
|
||||
|
||||
const releases = sidebar.docs
|
||||
.filter((doc) => doc.link?.slug === "releases")[0]
|
||||
.items.filter((release) => typeof release === "string");
|
||||
|
||||
/** @type {import('@docusaurus/types').DocusaurusConfig} */
|
||||
module.exports = async function () {
|
||||
|
@ -21,8 +16,8 @@ module.exports = async function () {
|
|||
organizationName: "Authentik Security Inc.",
|
||||
projectName: "authentik",
|
||||
themeConfig: {
|
||||
image: "img/social.png",
|
||||
navbar: {
|
||||
title: "authentik",
|
||||
logo: {
|
||||
alt: "authentik logo",
|
||||
src: "img/icon_left_brand.svg",
|
||||
|
@ -49,26 +44,6 @@ module.exports = async function () {
|
|||
label: "Pricing",
|
||||
position: "left",
|
||||
},
|
||||
{
|
||||
type: "dropdown",
|
||||
label: `Version: ${releases[0].replace(
|
||||
/releases\/\d+\/v/,
|
||||
"",
|
||||
)}`,
|
||||
position: "right",
|
||||
items: releases.map((release) => {
|
||||
const version = release.replace(
|
||||
/releases\/\d+\/v/,
|
||||
"",
|
||||
);
|
||||
const subdomain = version.replace(".", "-");
|
||||
const label = `Version: ${version}`;
|
||||
return {
|
||||
label: label,
|
||||
href: `https://version-${subdomain}.goauthentik.io`,
|
||||
};
|
||||
}),
|
||||
},
|
||||
{
|
||||
href: "https://github.com/goauthentik/authentik",
|
||||
className: "header-github-link",
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
module.exports = {
|
||||
const generateVersionDropdown =
|
||||
require("./src/utils.js").generateVersionDropdown;
|
||||
|
||||
const docsSidebar = {
|
||||
docs: [
|
||||
{
|
||||
type: "html",
|
||||
},
|
||||
{
|
||||
type: "doc",
|
||||
id: "index",
|
||||
|
@ -351,3 +357,6 @@ module.exports = {
|
|||
},
|
||||
],
|
||||
};
|
||||
|
||||
docsSidebar.docs[0].value = generateVersionDropdown(docsSidebar);
|
||||
module.exports = docsSidebar;
|
||||
|
|
|
@ -1,5 +1,13 @@
|
|||
const docsSidebar = require("./sidebars.js");
|
||||
const generateVersionDropdown =
|
||||
require("./src/utils.js").generateVersionDropdown;
|
||||
|
||||
module.exports = {
|
||||
docs: [
|
||||
{
|
||||
type: "html",
|
||||
value: generateVersionDropdown(docsSidebar),
|
||||
},
|
||||
{
|
||||
type: "doc",
|
||||
id: "index",
|
||||
|
|
|
@ -1,5 +1,13 @@
|
|||
const docsSidebar = require("./sidebars.js");
|
||||
const generateVersionDropdown =
|
||||
require("./src/utils.js").generateVersionDropdown;
|
||||
|
||||
module.exports = {
|
||||
integrations: [
|
||||
{
|
||||
type: "html",
|
||||
value: generateVersionDropdown(docsSidebar),
|
||||
},
|
||||
{
|
||||
type: "category",
|
||||
label: "Applications",
|
||||
|
|
|
@ -15,7 +15,7 @@ export default function Comparison() {
|
|||
"Requires additional product: Web Application Proxy";
|
||||
return (
|
||||
<>
|
||||
<a id="comparison"></a>
|
||||
<div id="comparison"></div>
|
||||
<h1 className="header">Why authentik?</h1>
|
||||
<div className="table-responsive">
|
||||
<div className="table">
|
||||
|
|
|
@ -26,18 +26,15 @@
|
|||
box-shadow: none;
|
||||
}
|
||||
|
||||
/* Don't display text title */
|
||||
.navbar__title {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.navbar__logo {
|
||||
margin: 0 0.75rem;
|
||||
}
|
||||
|
||||
/* Match color of light/dark theme switch */
|
||||
.navbar__items--right svg {
|
||||
.navbar__items--right svg,
|
||||
.navbar-sidebar__brand svg {
|
||||
color: var(--white);
|
||||
stroke: var(--white);
|
||||
}
|
||||
|
||||
.hero--primary {
|
||||
|
@ -116,3 +113,28 @@ body {
|
|||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* styling for version selector in sidebar */
|
||||
.theme-doc-sidebar-menu .dropdown {
|
||||
display: block;
|
||||
padding: 0;
|
||||
}
|
||||
.theme-doc-sidebar-menu .navbar__link {
|
||||
color: var(--ifm-menu-color);
|
||||
}
|
||||
.theme-doc-sidebar-menu .dropdown__menu {
|
||||
left: 0;
|
||||
}
|
||||
.theme-doc-sidebar-menu hr {
|
||||
margin-top: calc(var(--ifm-hr-margin-vertical) / 2);
|
||||
margin-right: -0.5rem;
|
||||
}
|
||||
|
||||
/* Nav header background color on mobile */
|
||||
.navbar-sidebar__brand,
|
||||
.navbar-sidebar__items {
|
||||
background-color: var(--ifm-color-primary);
|
||||
}
|
||||
.navbar-sidebar__items .menu__link {
|
||||
color: var(--white);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
function generateVersionDropdown(sidebar) {
|
||||
const releases = sidebar.docs
|
||||
.filter((doc) => doc.link?.slug === "releases")[0]
|
||||
.items.filter((release) => typeof release === "string");
|
||||
const latest = releases[0].replace(/releases\/\d+\/v/, "");
|
||||
return `<div class="navbar__item dropdown dropdown--hoverable dropdown--right">
|
||||
<div aria-haspopup="true" aria-expanded="false" role="button" class="navbar__link menu__link">
|
||||
Version: ${latest}
|
||||
</div>
|
||||
<ul class="dropdown__menu">
|
||||
${releases
|
||||
.map((release) => {
|
||||
const version = release.replace(/releases\/\d+\/v/, "");
|
||||
const subdomain = `version-${version.replace(".", "-")}`;
|
||||
const label = `Version: ${version}`;
|
||||
return `<li>
|
||||
<a
|
||||
href="https://${subdomain}.goauthentik.io/docs"
|
||||
target="_blank" rel="noopener noreferrer"
|
||||
class="dropdown__link">${label}</a>
|
||||
</li>`;
|
||||
})
|
||||
.join("")}
|
||||
</ul>
|
||||
</div>
|
||||
<hr>`;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
generateVersionDropdown,
|
||||
};
|
After Width: | Height: | Size: 25 KiB |
|
@ -6,6 +6,9 @@ import glob from "glob";
|
|||
const getSidebarItems = () => {
|
||||
const allItems = [];
|
||||
const mapper = (category) => {
|
||||
if (!category.items) {
|
||||
return;
|
||||
}
|
||||
category.items.forEach((item) => {
|
||||
if (item.constructor === String) {
|
||||
allItems.push(item);
|
||||
|
|