Compare commits

..

31 Commits

Author SHA1 Message Date
Teffen Ellis
b0f8c29d69 Translate noscript content. 2025-04-17 15:09:52 +02:00
Teffen Ellis
f20d7a200e core: Format and lint. 2025-04-17 02:08:45 +02:00
Teffen Ellis
3eb2c9bcc0 core: Format interface templates. 2025-04-17 02:08:27 +02:00
Teffen Ellis
037ef3a2a0 core: Format email templates. 2025-04-17 02:00:56 +02:00
Teffen Ellis
61bc89959b core: Tidy skeleton. 2025-04-17 02:00:04 +02:00
Teffen Ellis
2797d26b93 core: Add noscript. 2025-04-17 01:58:14 +02:00
Marc 'risson' Schmitt
51609d696d policies/geoip: fix result when only dynamic results are used (#14107) 2025-04-16 15:50:26 +00:00
Marc 'risson' Schmitt
c0d08df161 core: bump opentelemetry-api from 1.32.0 to v1.32.1 (#14102) 2025-04-16 15:50:10 +00:00
Marc 'risson' Schmitt
643a97f0a5 core: bump rsa from 4.9 to v4.9.1 (#14103) 2025-04-16 09:51:53 -04:00
Marc 'risson' Schmitt
155a31fd70 sources/oauth: introduce authorization code auth method (#14034)
Co-authored-by: Rsgm <rsgm123@gmail.com>
2025-04-16 13:00:08 +00:00
authentik-automation[bot]
c6f9d5df7b core, web: update translations (#14096)
Co-authored-by: rissson <18313093+rissson@users.noreply.github.com>
2025-04-16 13:16:06 +02:00
Teffen Ellis
ea85331a7e web/api: Fix Hoisted exports across entrypoints. Update Axios. (#14089)
* web/api: Fix issue where hoisted exports across entrypoints do not
order.

* web/api: Override OpenAPI transitive dep.
2025-04-15 20:09:41 +02:00
transifex-integration[bot]
4f4c5253dd translate: Updates for file web/xliff/en.xlf in fr (#14091)
Translate web/xliff/en.xlf in fr

100% translated source file: 'web/xliff/en.xlf'
on 'fr'.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2025-04-15 17:11:40 +00:00
transifex-integration[bot]
83b2fc36df translate: Updates for file locale/en/LC_MESSAGES/django.po in fr (#14090)
Translate locale/en/LC_MESSAGES/django.po in fr

100% translated source file: 'locale/en/LC_MESSAGES/django.po'
on 'fr'.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2025-04-15 17:08:09 +00:00
transifex-integration[bot]
d99d2b8bdc translate: Updates for file locale/en/LC_MESSAGES/django.po in zh-Hans (#14087)
Translate django.po in zh-Hans

100% translated source file: 'django.po'
on 'zh-Hans'.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2025-04-15 18:40:19 +02:00
transifex-integration[bot]
9b96d04b3a translate: Updates for file web/xliff/en.xlf in zh-Hans (#14086)
Translate web/xliff/en.xlf in zh-Hans

100% translated source file: 'web/xliff/en.xlf'
on 'zh-Hans'.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2025-04-15 18:40:02 +02:00
transifex-integration[bot]
ca5b99eb16 translate: Updates for file web/xliff/en.xlf in zh_CN (#14084)
Translate web/xliff/en.xlf in zh_CN

100% translated source file: 'web/xliff/en.xlf'
on 'zh_CN'.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2025-04-15 18:39:49 +02:00
transifex-integration[bot]
4c1676e97c translate: Updates for file locale/en/LC_MESSAGES/django.po in zh_CN (#14083)
Translate locale/en/LC_MESSAGES/django.po in zh_CN

100% translated source file: 'locale/en/LC_MESSAGES/django.po'
on 'zh_CN'.

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2025-04-15 18:39:36 +02:00
Marc 'risson' Schmitt
81855cf2fe core: bump google-auth from 2.38.0 to v2.39.0 (#14076) 2025-04-15 08:32:54 -04:00
Marc 'risson' Schmitt
bd904027be core: bump sentry-sdk from 2.25.1 to v2.26.1 (#14079) 2025-04-15 08:32:14 -04:00
Marc 'risson' Schmitt
0ffc97db15 core: bump prompt-toolkit from 3.0.50 to v3.0.51 (#14078) 2025-04-15 08:31:41 -04:00
Marc 'risson' Schmitt
2c515b1e17 core: bump boto3 from 1.37.33 to v1.37.34 (#14074) 2025-04-15 08:31:12 -04:00
Marc 'risson' Schmitt
f8900fbaf3 core: bump msgraph-sdk from 1.27.0 to v1.28.0 (#14077) 2025-04-15 08:30:44 -04:00
Dan T Langsam
0f4a98d9c6 website/docs: fix minor typo in working_with_policies.md (#14071)
Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-04-15 11:40:23 +00:00
authentik-automation[bot]
8853f25b45 core, web: update translations (#14064)
Co-authored-by: rissson <18313093+rissson@users.noreply.github.com>
2025-04-15 13:26:02 +02:00
authentik-automation[bot]
1c40f7b95a stages/authenticator_webauthn: Update FIDO MDS3 & Passkey aaguid blobs (#14065)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2025-04-15 13:25:13 +02:00
dependabot[bot]
9b5d6ec1af core: bump goauthentik.io/api/v3 from 3.2025024.4 to 3.2025024.6 (#14069)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-15 13:24:28 +02:00
James Armstrong
36d29a9ae1 Small fix for Actual-Budget wiki guide (#14066)
Remove ending slash from redirect uri

Signed-off-by: James Armstrong <32995055+jmarmstrong1207@users.noreply.github.com>
2025-04-15 09:43:59 +01:00
Marc 'risson' Schmitt
0606b1aba4 root: support db pool (#13534) 2025-04-14 16:05:31 +00:00
Simonyi Gergő
03d5dad867 rbac: add InitialPermissions (#13795)
* add `InitialPermissions` model to RBAC

This is a powerful construct between Permission and Role to set initial
permissions for newly created objects.

* use safer `request.user`

* fixup! use safer `request.user`

* force all self-defined serializers to descend from our custom one

See https://github.com/goauthentik/authentik/pull/10139

* reorganize initial permission assignment

* fixup! reorganize initial permission assignment
2025-04-14 17:55:49 +02:00
authentik-automation[bot]
38a9e46af3 web: bump API Client version (#14058)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2025-04-14 17:24:47 +02:00
100 changed files with 3706 additions and 1286 deletions

View File

@@ -3,15 +3,20 @@
{% load authentik_core %}
{% block title %}
API Browser - {{ brand.branding_title }}
API Browser - {{ brand.branding_title }}
{% endblock %}
{% block head %}
<script src="{% versioned_script 'dist/standalone/api-browser/index-%v.js' %}" type="module"></script>
<meta name="theme-color" content="#151515" media="(prefers-color-scheme: light)">
<meta name="theme-color" content="#151515" media="(prefers-color-scheme: dark)">
<meta name="theme-color" content="#18191a" media="(prefers-color-scheme: dark)">
<meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)">
<script
data-test-id="entrypoint"
src="{% versioned_script 'dist/standalone/api-browser/index-%v.js' %}"
type="module">
</script>
{% endblock %}
{% block body %}
<ak-api-browser schemaPath="{{ path }}"></ak-api-browser>
<ak-api-browser schemaPath="{{ path }}"></ak-api-browser>
{% endblock %}

View File

@@ -7,7 +7,7 @@ from rest_framework.exceptions import ValidationError
from rest_framework.fields import CharField, DateTimeField
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.serializers import ListSerializer, ModelSerializer
from rest_framework.serializers import ListSerializer
from rest_framework.viewsets import ModelViewSet
from authentik.blueprints.models import BlueprintInstance
@@ -15,7 +15,7 @@ from authentik.blueprints.v1.importer import Importer
from authentik.blueprints.v1.oci import OCI_PREFIX
from authentik.blueprints.v1.tasks import apply_blueprint, blueprints_find_dict
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import JSONDictField, PassiveSerializer
from authentik.core.api.utils import JSONDictField, ModelSerializer, PassiveSerializer
from authentik.rbac.decorators import permission_required

View File

@@ -20,6 +20,8 @@ from rest_framework.serializers import (
raise_errors_on_nested_writes,
)
from authentik.rbac.permissions import assign_initial_permissions
def is_dict(value: Any):
"""Ensure a value is a dictionary, useful for JSONFields"""
@@ -29,6 +31,14 @@ def is_dict(value: Any):
class ModelSerializer(BaseModelSerializer):
def create(self, validated_data):
instance = super().create(validated_data)
request = self.context.get("request")
if request and hasattr(request, "user") and not request.user.is_anonymous:
assign_initial_permissions(request.user, instance)
return instance
def update(self, instance: Model, validated_data):
raise_errors_on_nested_writes("update", self, validated_data)

View File

@@ -2,30 +2,35 @@
{% get_current_language as LANGUAGE_CODE %}
<script>
window.authentik = {
locale: "{{ LANGUAGE_CODE }}",
config: JSON.parse('{{ config_json|escapejs }}'),
brand: JSON.parse('{{ brand_json|escapejs }}'),
versionFamily: "{{ version_family }}",
versionSubdomain: "{{ version_subdomain }}",
build: "{{ build }}",
api: {
base: "{{ base_url }}",
relBase: "{{ base_url_rel }}",
},
};
window.addEventListener("DOMContentLoaded", function () {
{% for message in messages %}
window.dispatchEvent(
new CustomEvent("ak-message", {
bubbles: true,
composed: true,
detail: {
level: "{{ message.tags|escapejs }}",
message: "{{ message.message|escapejs }}",
},
}),
);
{% endfor %}
});
window.authentik = {
locale: "{{ LANGUAGE_CODE }}",
config: JSON.parse('{{ config_json|escapejs }}' || '{}'),
brand: JSON.parse('{{ brand_json|escapejs }}' || '{}'),
versionFamily: '{{ version_family }}',
versionSubdomain: '{{ version_subdomain }}',
build: '{{ build }}',
api: {
base: '{{ base_url }}',
relBase: '{{ base_url_rel }}',
},
};
</script>
{% if messages %}
<script>
window.addEventListener("DOMContentLoaded", function () {
{% for message in messages %}
window.dispatchEvent(
new CustomEvent("ak-message", {
bubbles: true,
composed: true,
detail: {
level: "{{ message.tags|escapejs }}",
message: "{{ message.message|escapejs }}",
},
}),
);
{% endfor %}
});
</script>
{% endif %}

View File

@@ -2,31 +2,85 @@
{% load i18n %}
{% load authentik_core %}
<!DOCTYPE html>
<!doctype html>
<html lang="{{ get_current_language }}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
{# Darkreader breaks the site regardless of theme as its not compatible with webcomponents, and we default to a dark theme based on preferred colour-scheme #}
<meta name="darkreader-lock">
<title>{% block title %}{% trans title|default:brand.branding_title %}{% endblock %}</title>
<link rel="icon" href="{{ brand.branding_favicon_url }}">
<link rel="shortcut icon" href="{{ brand.branding_favicon_url }}">
{% block head_before %}
{% endblock %}
<link rel="stylesheet" type="text/css" href="{% static 'dist/authentik.css' %}">
<style>{{ brand.branding_custom_css }}</style>
<script src="{% versioned_script 'dist/poly-%v.js' %}" type="module"></script>
<script src="{% versioned_script 'dist/standalone/loading/index-%v.js' %}" type="module"></script>
{% block head %}
{% endblock %}
<meta name="sentry-trace" content="{{ sentry_trace }}" />
</head>
<body>
{% block body %}
{% endblock %}
{% block scripts %}
{% endblock %}
</body>
{% comment %}
Darkreader breaks the site regardless of theme as its not compatible with webcomponents,
and we default to a dark theme based on preferred colour-scheme.
{% endcomment %}
<meta name="darkreader-lock">
<title>{% block title %}{% trans title|default:brand.branding_title %}{% endblock %}</title>
<link rel="icon" href="{{ brand.branding_favicon_url }}">
<link rel="shortcut icon" href="{{ brand.branding_favicon_url }}">
{% block head_before %}
{% endblock %}
<link rel="stylesheet" type="text/css" href="{% static 'dist/authentik.css' %}">
<style data-test-id="color-scheme">
@media (prefers-color-scheme: dark) {
:root {
color-scheme: dark light;
}
}
@media (prefers-color-scheme: light) {
:root {
color-scheme: light dark;
}
}
</style>
<style data-test-id="custom-branding-css">
{{ brand.branding_custom_css }}
</style>
<script
data-test-id="entrypoint-polyfill"
src="{% versioned_script 'dist/poly-%v.js' %}"
type="module">
</script>
<script
data-test-id="entrypoint-loading"
src="{% versioned_script 'dist/standalone/loading/index-%v.js' %}"
type="module">
</script>
{% block head %}
{% endblock %}
<meta name="sentry-trace" content="{{ sentry_trace }}">
</head>
<body>
{% block body %}{% endblock %}
{% block scripts %}{% endblock %}
<noscript>
<style>
body {
font-family: var(--ak-font-family-base), sans-serif;
}
</style>
<h1>
{% blocktrans with brand_title=title|default:brand.branding_title %}
JavaScript is required to use {{ brand_title }}
{% endblocktrans %}
</h1>
<p>
{% trans 'Please enable JavaScript in your browser settings and reload the page. If you are using a browser extension that blocks JavaScript, please disable it for this site.' %}
</p>
</noscript>
</body>
</html>

View File

@@ -3,15 +3,22 @@
{% load authentik_core %}
{% block head %}
<script src="{% versioned_script 'dist/admin/AdminInterface-%v.js' %}" type="module"></script>
<meta name="theme-color" content="#18191a" media="(prefers-color-scheme: dark)">
<meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)">
{% include "base/header_js.html" %}
<meta name="theme-color" content="#18191a" media="(prefers-color-scheme: dark)">
<meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)">
{% include "base/header_js.html" %}
<script
data-test-id="entrypoint"
src="{% versioned_script 'dist/admin/AdminInterface-%v.js' %}"
type="module">
</script>
{% endblock %}
{% block body %}
<ak-message-container></ak-message-container>
<ak-interface-admin>
<ak-message-container></ak-message-container>
<ak-interface-admin>
<ak-loading></ak-loading>
</ak-interface-admin>
</ak-interface-admin>
{% endblock %}

View File

@@ -4,18 +4,22 @@
{% load i18n %}
{% block title %}
{{ brand.branding_title }}
{{ brand.branding_title }}
{% endblock %}
{% block card_title %}
{% trans title %}
{% trans title %}
{% endblock %}
{% block card %}
<form method="POST" class="pf-c-form">
<form method="POST" class="pf-c-form">
<p>{% trans message %}</p>
<a id="ak-back-home" href="{% url 'authentik_core:root-redirect' %}" class="pf-c-button pf-m-primary">
{% trans 'Go home' %}
<a
id="ak-back-home"
href="{% url 'authentik_core:root-redirect' %}"
class="pf-c-button pf-m-primary">
{% trans 'Go home' %}
</a>
</form>
</form>
{% endblock %}

View File

@@ -3,15 +3,22 @@
{% load authentik_core %}
{% block head %}
<script src="{% versioned_script 'dist/user/UserInterface-%v.js' %}" type="module"></script>
<meta name="theme-color" content="#1c1e21" media="(prefers-color-scheme: light)">
<meta name="theme-color" content="#1c1e21" media="(prefers-color-scheme: dark)">
{% include "base/header_js.html" %}
<meta name="theme-color" content="#18191a" media="(prefers-color-scheme: dark)">
<meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)">
{% include "base/header_js.html" %}
<script
data-test-id="entrypoint"
src="{% versioned_script 'dist/user/UserInterface-%v.js' %}"
type="module">
</script>
{% endblock %}
{% block body %}
<ak-message-container></ak-message-container>
<ak-interface-user>
<ak-message-container></ak-message-container>
<ak-interface-user>
<ak-loading></ak-loading>
</ak-interface-user>
</ak-interface-user>
{% endblock %}

View File

@@ -4,79 +4,93 @@
{% load i18n %}
{% block head_before %}
<link rel="prefetch" href="{{ request.brand.branding_default_flow_background_url }}" />
<link rel="stylesheet" type="text/css" href="{% static 'dist/patternfly.min.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'dist/theme-dark.css' %}" media="(prefers-color-scheme: dark)">
{% include "base/header_js.html" %}
<link rel="prefetch" href="{{ request.brand.branding_default_flow_background_url }}">
<link rel="stylesheet" type="text/css" href="{% static 'dist/patternfly.min.css' %}">
<link
rel="stylesheet"
type="text/css"
href="{% static 'dist/theme-dark.css' %}"
media="(prefers-color-scheme: dark)"
>
{% include "base/header_js.html" %}
{% endblock %}
{% block head %}
<style>
:root {
--ak-flow-background: url("{{ request.brand.branding_default_flow_background_url }}");
--pf-c-background-image--BackgroundImage: var(--ak-flow-background);
--pf-c-background-image--BackgroundImage-2x: var(--ak-flow-background);
--pf-c-background-image--BackgroundImage--sm: var(--ak-flow-background);
--pf-c-background-image--BackgroundImage--sm-2x: var(--ak-flow-background);
--pf-c-background-image--BackgroundImage--lg: var(--ak-flow-background);
}
/* Form with user */
.form-control-static {
margin-top: var(--pf-global--spacer--sm);
display: flex;
align-items: center;
justify-content: space-between;
}
.form-control-static .avatar {
display: flex;
align-items: center;
}
.form-control-static img {
margin-right: var(--pf-global--spacer--xs);
}
.form-control-static a {
padding-top: var(--pf-global--spacer--xs);
padding-bottom: var(--pf-global--spacer--xs);
line-height: var(--pf-global--spacer--xl);
}
</style>
<style data-test-id="base-full-root-styles">
:root {
--ak-flow-background: url("{{ request.brand.branding_default_flow_background_url }}");
--pf-c-background-image--BackgroundImage: var(--ak-flow-background);
--pf-c-background-image--BackgroundImage-2x: var(--ak-flow-background);
--pf-c-background-image--BackgroundImage--sm: var(--ak-flow-background);
--pf-c-background-image--BackgroundImage--sm-2x: var(--ak-flow-background);
--pf-c-background-image--BackgroundImage--lg: var(--ak-flow-background);
}
/* Form with user */
.form-control-static {
margin-top: var(--pf-global--spacer--sm);
display: flex;
align-items: center;
justify-content: space-between;
}
.form-control-static .avatar {
display: flex;
align-items: center;
}
.form-control-static img {
margin-right: var(--pf-global--spacer--xs);
}
.form-control-static a {
padding-top: var(--pf-global--spacer--xs);
padding-bottom: var(--pf-global--spacer--xs);
line-height: var(--pf-global--spacer--xl);
}
</style>
{% endblock %}
{% block body %}
<div class="pf-c-background-image">
</div>
<ak-message-container></ak-message-container>
<div class="pf-c-login stacked">
<div class="pf-c-background-image"></div>
<ak-message-container></ak-message-container>
<div class="pf-c-login stacked">
<div class="ak-login-container">
<main class="pf-c-login__main">
<div class="pf-c-login__main-header pf-c-brand ak-brand">
<img src="{{ brand.branding_logo_url }}" alt="authentik Logo" />
</div>
<header class="pf-c-login__main-header">
<h1 class="pf-c-title pf-m-3xl">
{% block card_title %}
{% endblock %}
</h1>
</header>
<div class="pf-c-login__main-body">
{% block card %}
{% endblock %}
</div>
</main>
<footer class="pf-c-login__footer">
<ul class="pf-c-list pf-m-inline">
{% for link in footer_links %}
<li>
<a href="{{ link.href }}">{{ link.name }}</a>
</li>
{% endfor %}
<li>
<span>
{% trans 'Powered by authentik' %}
</span>
</li>
</ul>
</footer>
<main class="pf-c-login__main">
<div class="pf-c-login__main-header pf-c-brand ak-brand">
<img src="{{ brand.branding_logo_url }}" alt="authentik Logo">
</div>
<header class="pf-c-login__main-header">
<h1 class="pf-c-title pf-m-3xl">
{% block card_title %}
{% endblock %}
</h1>
</header>
<div class="pf-c-login__main-body">
{% block card %}
{% endblock %}
</div>
</main>
<footer class="pf-c-login__footer">
<ul class="pf-c-list pf-m-inline">
{% for link in footer_links %}
<li>
<a href="{{ link.href }}">{{ link.name }}</a>
</li>
{% endfor %}
<li>
<span>{% trans 'Powered by authentik' %}</span>
</li>
</ul>
</footer>
</div>
</div>
</div>
{% endblock %}

View File

@@ -1,9 +1,17 @@
"""Test API Utils"""
from rest_framework.exceptions import ValidationError
from rest_framework.serializers import (
HyperlinkedModelSerializer,
)
from rest_framework.serializers import (
ModelSerializer as BaseModelSerializer,
)
from rest_framework.test import APITestCase
from authentik.core.api.utils import ModelSerializer as CustomModelSerializer
from authentik.core.api.utils import is_dict
from authentik.lib.utils.reflection import all_subclasses
class TestAPIUtils(APITestCase):
@@ -14,3 +22,14 @@ class TestAPIUtils(APITestCase):
self.assertIsNone(is_dict({}))
with self.assertRaises(ValidationError):
is_dict("foo")
def test_all_serializers_descend_from_custom(self):
"""Test that every serializer we define descends from our own ModelSerializer"""
# Weirdly, there's only one serializer in `rest_framework` which descends from
# ModelSerializer: HyperlinkedModelSerializer
expected = {CustomModelSerializer, HyperlinkedModelSerializer}
actual = set(all_subclasses(BaseModelSerializer)) - set(
all_subclasses(CustomModelSerializer)
)
self.assertEqual(expected, actual)

View File

@@ -4,10 +4,9 @@ from rest_framework.exceptions import PermissionDenied, ValidationError
from rest_framework.fields import CharField, ChoiceField, ListField, SerializerMethodField
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.serializers import ModelSerializer
from structlog.stdlib import get_logger
from authentik.core.api.utils import PassiveSerializer
from authentik.core.api.utils import ModelSerializer, PassiveSerializer
from authentik.enterprise.providers.ssf.models import (
DeliveryMethods,
EventTypes,

View File

@@ -2,11 +2,11 @@
from rest_framework import mixins
from rest_framework.permissions import IsAdminUser
from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import GenericViewSet, ModelViewSet
from structlog.stdlib import get_logger
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import ModelSerializer
from authentik.enterprise.api import EnterpriseRequiredMixin
from authentik.enterprise.stages.authenticator_endpoint_gdtc.models import (
AuthenticatorEndpointGDTCStage,

View File

@@ -2,53 +2,63 @@
{% load i18n %}
{% load authentik_core %}
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>{% block title %}{% trans title|default:brand.branding_title %}{% endblock %}</title>
<link rel="icon" href="{{ brand.branding_favicon_url }}">
<link rel="shortcut icon" href="{{ brand.branding_favicon_url }}">
{% block head_before %}
{% endblock %}
<link rel="stylesheet" type="text/css" href="{% static 'dist/sfe/bootstrap.min.css' %}">
<meta name="sentry-trace" content="{{ sentry_trace }}" />
{% include "base/header_js.html" %}
<style>
html,
body {
height: 100%;
}
body {
background-image: url("{{ flow.background_url }}");
background-repeat: no-repeat;
background-size: cover;
}
.card {
padding: 3rem;
}
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
.form-signin {
max-width: 330px;
padding: 1rem;
}
<title>{% block title %}{% trans title|default:brand.branding_title %}{% endblock %}</title>
.form-signin .form-floating:focus-within {
z-index: 2;
}
.brand-icon {
max-width: 100%;
}
</style>
</head>
<body class="d-flex align-items-center py-4 bg-body-tertiary">
<div class="card m-auto">
<main class="form-signin w-100 m-auto" id="flow-sfe-container">
</main>
<span class="mt-3 mb-0 text-muted text-center">{% trans 'Powered by authentik' %}</span>
</div>
<script src="{% static 'dist/sfe/index.js' %}"></script>
</body>
<link rel="icon" href="{{ brand.branding_favicon_url }}">
<link rel="shortcut icon" href="{{ brand.branding_favicon_url }}">
{% block head_before %}
{% endblock %}
<link rel="stylesheet" type="text/css" href="{% static 'dist/sfe/bootstrap.min.css' %}">
<meta name="sentry-trace" content="{{ sentry_trace }}">
{% include "base/header_js.html" %}
<style>
html,
body {
height: 100%;
}
body {
background-image: url("{{ flow.background_url }}");
background-repeat: no-repeat;
background-size: cover;
}
.card {
padding: 3rem;
}
.form-signin {
max-width: 330px;
padding: 1rem;
}
.form-signin .form-floating:focus-within {
z-index: 2;
}
.brand-icon {
max-width: 100%;
}
</style>
</head>
<body class="d-flex align-items-center py-4 bg-body-tertiary">
<div class="card m-auto">
<main class="form-signin w-100 m-auto" id="flow-sfe-container"></main>
<span class="mt-3 mb-0 text-muted text-center">{% trans 'Powered by authentik' %}</span>
</div>
<script src="{% static 'dist/sfe/index.js' %}"></script>
</body>
</html>

View File

@@ -4,31 +4,45 @@
{% load authentik_core %}
{% block head_before %}
{{ block.super }}
<link rel="prefetch" href="{{ flow.background_url }}" />
{% if flow.compatibility_mode and not inspector %}
<script>ShadyDOM = { force: !navigator.webdriver };</script>
{% endif %}
{% include "base/header_js.html" %}
<script>
window.authentik.flow = {
"layout": "{{ flow.layout }}",
};
</script>
{{ block.super }}
<link rel="prefetch" href="{{ flow.background_url }}">
{% if flow.compatibility_mode and not inspector %}
<script>
window.ShadyDOM = { force: !navigator.webdriver };
</script>
{% endif %}
{% include "base/header_js.html" %}
<script>
window.authentik.flow = {
layout: "{{ flow.layout }}",
};
</script>
<meta name="ak-flow-layout" content="{{ flow.layout }}">
{% endblock %}
{% block head %}
<script src="{% versioned_script 'dist/flow/FlowInterface-%v.js' %}" type="module"></script>
<style>
:root {
--ak-flow-background: url("{{ flow.background_url }}");
}
</style>
<script
data-test-id="entrypoint"
src="{% versioned_script 'dist/flow/FlowInterface-%v.js' %}"
type="module">
</script>
<style data-test-id="flow-root-styles">
:root {
--ak-flow-background: url("{{ flow.background_url }}");
}
</style>
{% endblock %}
{% block body %}
<ak-message-container></ak-message-container>
<ak-flow-executor flowSlug="{{ flow.slug }}">
<ak-message-container></ak-message-container>
<ak-flow-executor flowSlug="{{ flow.slug }}">
<ak-loading></ak-loading>
</ak-flow-executor>
</ak-flow-executor>
{% endblock %}

View File

@@ -356,6 +356,14 @@ def redis_url(db: int) -> str:
def django_db_config(config: ConfigLoader | None = None) -> dict:
if not config:
config = CONFIG
pool_options = False
use_pool = config.get_bool("postgresql.use_pool", False)
if use_pool:
pool_options = config.get_dict_from_b64_json("postgresql.pool_options", True)
if not pool_options:
pool_options = True
db = {
"default": {
"ENGINE": "authentik.root.db",
@@ -369,6 +377,7 @@ def django_db_config(config: ConfigLoader | None = None) -> dict:
"sslrootcert": config.get("postgresql.sslrootcert"),
"sslcert": config.get("postgresql.sslcert"),
"sslkey": config.get("postgresql.sslkey"),
"pool": pool_options,
},
"CONN_MAX_AGE": config.get_optional_int("postgresql.conn_max_age", 0),
"CONN_HEALTH_CHECKS": config.get_bool("postgresql.conn_health_checks", False),

View File

@@ -21,6 +21,7 @@ postgresql:
user: authentik
port: 5432
password: "env://POSTGRES_PASSWORD"
use_pool: False
test:
name: test_authentik
default_schema: public

View File

@@ -217,6 +217,7 @@ class TestConfig(TestCase):
"HOST": "foo",
"NAME": "foo",
"OPTIONS": {
"pool": False,
"sslcert": "foo",
"sslkey": "foo",
"sslmode": "foo",
@@ -267,6 +268,7 @@ class TestConfig(TestCase):
"HOST": "foo",
"NAME": "foo",
"OPTIONS": {
"pool": False,
"sslcert": "foo",
"sslkey": "foo",
"sslmode": "foo",
@@ -285,6 +287,7 @@ class TestConfig(TestCase):
"HOST": "bar",
"NAME": "foo",
"OPTIONS": {
"pool": False,
"sslcert": "foo",
"sslkey": "foo",
"sslmode": "foo",
@@ -333,6 +336,7 @@ class TestConfig(TestCase):
"HOST": "foo",
"NAME": "foo",
"OPTIONS": {
"pool": False,
"sslcert": "foo",
"sslkey": "foo",
"sslmode": "foo",
@@ -351,6 +355,7 @@ class TestConfig(TestCase):
"HOST": "bar",
"NAME": "foo",
"OPTIONS": {
"pool": False,
"sslcert": "foo",
"sslkey": "foo",
"sslmode": "foo",
@@ -394,6 +399,7 @@ class TestConfig(TestCase):
"HOST": "foo",
"NAME": "foo",
"OPTIONS": {
"pool": False,
"sslcert": "foo",
"sslkey": "foo",
"sslmode": "foo",
@@ -412,6 +418,7 @@ class TestConfig(TestCase):
"HOST": "bar",
"NAME": "foo",
"OPTIONS": {
"pool": False,
"sslcert": "foo",
"sslkey": "foo",
"sslmode": "foo",
@@ -451,6 +458,7 @@ class TestConfig(TestCase):
"HOST": "foo",
"NAME": "foo",
"OPTIONS": {
"pool": False,
"sslcert": "foo",
"sslkey": "foo",
"sslmode": "foo",
@@ -469,6 +477,7 @@ class TestConfig(TestCase):
"HOST": "bar",
"NAME": "foo",
"OPTIONS": {
"pool": False,
"sslcert": "bar",
"sslkey": "foo",
"sslmode": "foo",
@@ -484,3 +493,87 @@ class TestConfig(TestCase):
},
},
)
def test_db_pool(self):
"""Test DB Config with pool"""
config = ConfigLoader()
config.set("postgresql.host", "foo")
config.set("postgresql.name", "foo")
config.set("postgresql.user", "foo")
config.set("postgresql.password", "foo")
config.set("postgresql.port", "foo")
config.set("postgresql.test.name", "foo")
config.set("postgresql.use_pool", True)
conf = django_db_config(config)
self.assertEqual(
conf,
{
"default": {
"ENGINE": "authentik.root.db",
"HOST": "foo",
"NAME": "foo",
"OPTIONS": {
"pool": True,
"sslcert": None,
"sslkey": None,
"sslmode": None,
"sslrootcert": None,
},
"PASSWORD": "foo",
"PORT": "foo",
"TEST": {"NAME": "foo"},
"USER": "foo",
"CONN_MAX_AGE": 0,
"CONN_HEALTH_CHECKS": False,
"DISABLE_SERVER_SIDE_CURSORS": False,
}
},
)
def test_db_pool_options(self):
"""Test DB Config with pool"""
config = ConfigLoader()
config.set("postgresql.host", "foo")
config.set("postgresql.name", "foo")
config.set("postgresql.user", "foo")
config.set("postgresql.password", "foo")
config.set("postgresql.port", "foo")
config.set("postgresql.test.name", "foo")
config.set("postgresql.use_pool", True)
config.set(
"postgresql.pool_options",
base64.b64encode(
dumps(
{
"max_size": 15,
}
).encode()
).decode(),
)
conf = django_db_config(config)
self.assertEqual(
conf,
{
"default": {
"ENGINE": "authentik.root.db",
"HOST": "foo",
"NAME": "foo",
"OPTIONS": {
"pool": {
"max_size": 15,
},
"sslcert": None,
"sslkey": None,
"sslmode": None,
"sslrootcert": None,
},
"PASSWORD": "foo",
"PORT": "foo",
"TEST": {"NAME": "foo"},
"USER": "foo",
"CONN_MAX_AGE": 0,
"CONN_HEALTH_CHECKS": False,
"DISABLE_SERVER_SIDE_CURSORS": False,
}
},
)

View File

@@ -66,7 +66,9 @@ class GeoIPPolicy(Policy):
if not static_results and not dynamic_results:
return PolicyResult(True)
passing = any(r.passing for r in static_results) and all(r.passing for r in dynamic_results)
static_passing = any(r.passing for r in static_results) if static_results else True
dynamic_passing = all(r.passing for r in dynamic_results)
passing = static_passing and dynamic_passing
messages = chain(
*[r.messages for r in static_results], *[r.messages for r in dynamic_results]
)
@@ -113,13 +115,19 @@ class GeoIPPolicy(Policy):
to previous authentication requests"""
# Get previous login event and GeoIP data
previous_logins = Event.objects.filter(
action=EventAction.LOGIN, user__pk=request.user.pk, context__geo__isnull=False
action=EventAction.LOGIN,
user__pk=request.user.pk, # context__geo__isnull=False
).order_by("-created")[: self.history_login_count]
_now = now()
geoip_data: GeoIPDict | None = request.context.get("geoip")
if not geoip_data:
return PolicyResult(False)
if not previous_logins.exists():
return PolicyResult(True)
result = False
for previous_login in previous_logins:
if "geo" not in previous_login.context:
continue
previous_login_geoip: GeoIPDict = previous_login.context["geo"]
# Figure out distance
@@ -142,7 +150,8 @@ class GeoIPPolicy(Policy):
(MAX_DISTANCE_HOUR_KM * rel_time_hours) + self.distance_tolerance_km
):
return PolicyResult(False, _("Distance is further than possible."))
return PolicyResult(True)
result = True
return PolicyResult(result)
class Meta(Policy.PolicyMeta):
verbose_name = _("GeoIP Policy")

View File

@@ -163,7 +163,7 @@ class TestGeoIPPolicy(TestCase):
result: PolicyResult = policy.passes(self.request)
self.assertFalse(result.passing)
def test_history_impossible_travel(self):
def test_history_impossible_travel_failing(self):
"""Test history checks"""
Event.objects.create(
action=EventAction.LOGIN,
@@ -181,6 +181,24 @@ class TestGeoIPPolicy(TestCase):
result: PolicyResult = policy.passes(self.request)
self.assertFalse(result.passing)
def test_history_impossible_travel_passing(self):
"""Test history checks"""
Event.objects.create(
action=EventAction.LOGIN,
user=get_user(self.user),
context={
# Random location in Canada
"geo": {"lat": 55.868351, "long": -104.441011},
},
)
# Same location
self.request.context["geoip"] = {"lat": 55.868351, "long": -104.441011}
policy = GeoIPPolicy.objects.create(check_impossible_travel=True)
result: PolicyResult = policy.passes(self.request)
self.assertTrue(result.passing)
def test_history_no_geoip(self):
"""Test history checks (previous login with no geoip data)"""
Event.objects.create(
@@ -195,3 +213,18 @@ class TestGeoIPPolicy(TestCase):
result: PolicyResult = policy.passes(self.request)
self.assertFalse(result.passing)
def test_impossible_travel_no_geoip(self):
"""Test impossible travel checks (previous login with no geoip data)"""
Event.objects.create(
action=EventAction.LOGIN,
user=get_user(self.user),
context={},
)
# Random location in Poland
self.request.context["geoip"] = {"lat": 50.950613, "long": 20.363679}
policy = GeoIPPolicy.objects.create(check_impossible_travel=True)
result: PolicyResult = policy.passes(self.request)
self.assertFalse(result.passing)

View File

@@ -5,21 +5,28 @@
{% block head %}
{{ block.super }}
<script>
let redirecting = false;
const checkAuth = async () => {
if (redirecting) return true;
const url = "{{ check_auth_url }}";
console.debug("authentik/policies/buffer: Checking authentication...");
try {
const result = await fetch(url, {
method: "HEAD",
});
if (result.status >= 400) {
return false
}
console.debug("authentik/policies/buffer: Continuing");
redirecting = true;
if ("{{ auth_req_method }}" === "post") {
document.querySelector("form").submit();
} else {
@@ -29,61 +36,75 @@
return false;
}
};
let timeout = 100;
let offset = 20;
let attempt = 0;
const main = async () => {
attempt += 1;
await checkAuth();
console.debug(`authentik/policies/buffer: Waiting ${timeout}ms...`);
setTimeout(main, timeout);
timeout += (offset * attempt);
if (timeout >= 2000) {
timeout = 2000;
}
}
document.addEventListener("visibilitychange", async () => {
if (document.hidden) return;
console.debug("authentik/policies/buffer: Checking authentication on tab activate...");
await checkAuth();
});
main();
</script>
{% endblock %}
{% block title %}
{% trans 'Waiting for authentication...' %} - {{ brand.branding_title }}
{% trans 'Waiting for authentication...' %} - {{ brand.branding_title }}
{% endblock %}
{% block card_title %}
{% trans 'Waiting for authentication...' %}
{% trans 'Waiting for authentication...' %}
{% endblock %}
{% block card %}
<form class="pf-c-form" method="{{ auth_req_method }}" action="{{ continue_url }}">
{% if auth_req_method == "post" %}
{% for key, value in auth_req_body.items %}
<input type="hidden" name="{{ key }}" value="{{ value }}" />
{% endfor %}
{% endif %}
<div class="pf-c-empty-state">
<div class="pf-c-empty-state__content">
<div class="pf-c-empty-state__icon">
<span class="pf-c-spinner pf-m-xl" role="progressbar">
<span class="pf-c-spinner__clipper"></span>
<span class="pf-c-spinner__lead-ball"></span>
<span class="pf-c-spinner__tail-ball"></span>
</span>
<form class="pf-c-form" method="{{ auth_req_method }}" action="{{ continue_url }}">
{% if auth_req_method == "post" %}
{% for key, value in auth_req_body.items %}
<input type="hidden" name="{{ key }}" value="{{ value }}">
{% endfor %}
{% endif %}
<div class="pf-c-empty-state">
<div class="pf-c-empty-state__content">
<div class="pf-c-empty-state__icon">
<span class="pf-c-spinner pf-m-xl" role="progressbar">
<span class="pf-c-spinner__clipper"></span>
<span class="pf-c-spinner__lead-ball"></span>
<span class="pf-c-spinner__tail-ball"></span>
</span>
</div>
<h1 class="pf-c-title pf-m-lg">
{% trans "You're already authenticating in another tab. This page will refresh once authentication is completed." %}
</h1>
</div>
<h1 class="pf-c-title pf-m-lg">
{% trans "You're already authenticating in another tab. This page will refresh once authentication is completed." %}
</h1>
</div>
</div>
<div class="pf-c-form__group pf-m-action">
<a href="{{ auth_req_url }}" class="pf-c-button pf-m-primary pf-m-block">
{% trans "Authenticate in this tab" %}
</a>
</div>
</form>
<div class="pf-c-form__group pf-m-action">
<a href="{{ auth_req_url }}" class="pf-c-button pf-m-primary pf-m-block">
{% trans "Authenticate in this tab" %}
</a>
</div>
</form>
{% endblock %}

View File

@@ -13,63 +13,69 @@
{% block card %}
<form class="pf-c-form">
{% csrf_token %}
{% if user.is_authenticated %}
<div class="pf-c-form__group">
<div class="form-control-static">
<div class="avatar">
<img class="pf-c-avatar" src="{{ user.avatar }}" alt="{% trans "User's avatar" %}" />
{{ user.username }}
</div>
<div slot="link">
<a href="{{ cancel }}">{% trans "Not you?" %}</a>
</div>
</div>
</div>
{% endif %}
{% csrf_token %}
{% if user.is_authenticated %}
<div class="pf-c-form__group">
<p>
<i class="pf-icon pf-icon-error-circle-o"></i>
{% trans 'Request has been denied.' %}
</p>
{% if error %}
<hr>
<p>
{{ error }}
</p>
{% endif %}
{% if policy_result %}
<hr>
{% if policy_result.messages %}
<em>{% trans 'Messages:' %}</em>
<ul class="pf-c-list">
{% for message in policy_result.messages %}
<li>
{{ message }}
</li>
{% endfor %}
</ul>
{% endif %}
{% if policy_result.source_results %}
<em>{% trans 'Explanation:' %}</em>
<ul class="pf-c-list">
{% for source_result in policy_result.source_results %}
<li>
{% blocktrans with name=source_result.source_binding result=source_result.passing %}
Policy binding '{{ name }}' returned result '{{ result }}'
{% endblocktrans %}
{% if source_result.messages %}
<ul class="pf-c-list">
{% for message in source_result.messages %}
<li>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
</li>
{% endfor %}
</ul>
{% endif %}
{% endif %}
<div class="form-control-static">
<div class="avatar">
<img class="pf-c-avatar" src="{{ user.avatar }}" alt="{% trans "User's avatar" %}">
{{ user.username }}
</div>
<div slot="link">
<a href="{{ cancel }}">{% trans "Not you?" %}</a>
</div>
</div>
</div>
{% endif %}
<div class="pf-c-form__group">
<p>
<i class="pf-icon pf-icon-error-circle-o"></i>
{% trans 'Request has been denied.' %}
</p>
{% if error %}
<hr>
<p>{{ error }}</p>
{% endif %}
{% if policy_result %}
<hr>
{% if policy_result.messages %}
<em>{% trans 'Messages:' %}</em>
<ul class="pf-c-list">
{% for message in policy_result.messages %}
<li>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
{% if policy_result.source_results %}
<em>{% trans 'Explanation:' %}</em>
<ul class="pf-c-list">
{% for source_result in policy_result.source_results %}
<li>
{% blocktrans with name=source_result.source_binding result=source_result.passing %}
Policy binding '{{ name }}' returned result '{{ result }}'
{% endblocktrans %}
{% if source_result.messages %}
<ul class="pf-c-list">
{% for message in source_result.messages %}
<li>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
</li>
{% endfor %}
</ul>
{% endif %}
{% endif %}
</div>
</form>
{% endblock %}

View File

@@ -3,16 +3,22 @@
{% load authentik_core %}
{% block head %}
<script src="{% versioned_script 'dist/rac/index-%v.js' %}" type="module"></script>
<meta name="theme-color" content="#18191a" media="(prefers-color-scheme: dark)">
<meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)">
<link rel="icon" href="{{ tenant.branding_favicon_url }}">
<link rel="shortcut icon" href="{{ tenant.branding_favicon_url }}">
{% include "base/header_js.html" %}
<meta name="theme-color" content="#18191a" media="(prefers-color-scheme: dark)">
<meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)">
<link rel="icon" href="{{ tenant.branding_favicon_url }}">
<link rel="shortcut icon" href="{{ tenant.branding_favicon_url }}">
{% include "base/header_js.html" %}
<script
src="{% versioned_script 'dist/rac/index-%v.js' %}"
type="module">
</script>
{% endblock %}
{% block body %}
<ak-rac token="{{ url_kwargs.token }}" endpointName="{{ token.endpoint.name }}">
<ak-loading></ak-loading>
</ak-rac>
<ak-rac token="{{ url_kwargs.token }}" endpointName="{{ token.endpoint.name }}">
<ak-loading></ak-loading>
</ak-rac>
{% endblock %}

View File

@@ -0,0 +1,41 @@
"""RBAC Initial Permissions"""
from rest_framework.serializers import ListSerializer
from rest_framework.viewsets import ModelViewSet
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import ModelSerializer
from authentik.rbac.api.rbac import PermissionSerializer
from authentik.rbac.models import InitialPermissions
class InitialPermissionsSerializer(ModelSerializer):
"""InitialPermissions serializer"""
permissions_obj = ListSerializer(
child=PermissionSerializer(),
read_only=True,
source="permissions",
required=False,
)
class Meta:
model = InitialPermissions
fields = [
"pk",
"name",
"mode",
"role",
"permissions",
"permissions_obj",
]
class InitialPermissionsViewSet(UsedByMixin, ModelViewSet):
"""InitialPermissions viewset"""
queryset = InitialPermissions.objects.all()
serializer_class = InitialPermissionsSerializer
search_fields = ["name"]
ordering = ["name"]
filterset_fields = ["name"]

View File

@@ -0,0 +1,39 @@
# Generated by Django 5.0.13 on 2025-04-07 13:05
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("auth", "0012_alter_user_first_name_max_length"),
("authentik_rbac", "0004_alter_systempermission_options"),
]
operations = [
migrations.CreateModel(
name="InitialPermissions",
fields=[
(
"id",
models.AutoField(
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
),
),
("name", models.TextField(max_length=150, unique=True)),
("mode", models.CharField(choices=[("user", "User"), ("role", "Role")])),
("permissions", models.ManyToManyField(blank=True, to="auth.permission")),
(
"role",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, to="authentik_rbac.role"
),
),
],
options={
"verbose_name": "Initial Permissions",
"verbose_name_plural": "Initial Permissions",
},
),
]

View File

@@ -3,6 +3,7 @@
from uuid import uuid4
from django.contrib.auth.management import _get_all_permissions
from django.contrib.auth.models import Permission
from django.db import models
from django.db.transaction import atomic
from django.utils.translation import gettext_lazy as _
@@ -75,6 +76,35 @@ class Role(SerializerModel):
]
class InitialPermissionsMode(models.TextChoices):
"""Determines which entity the initial permissions are assigned to."""
USER = "user", _("User")
ROLE = "role", _("Role")
class InitialPermissions(SerializerModel):
"""Assigns permissions for newly created objects."""
name = models.TextField(max_length=150, unique=True)
mode = models.CharField(choices=InitialPermissionsMode.choices)
role = models.ForeignKey(Role, on_delete=models.CASCADE)
permissions = models.ManyToManyField(Permission, blank=True)
@property
def serializer(self) -> type[BaseSerializer]:
from authentik.rbac.api.initial_permissions import InitialPermissionsSerializer
return InitialPermissionsSerializer
def __str__(self) -> str:
return f"Initial Permissions for Role #{self.role_id}, applying to #{self.mode}"
class Meta:
verbose_name = _("Initial Permissions")
verbose_name_plural = _("Initial Permissions")
class SystemPermission(models.Model):
"""System-wide permissions that are not related to any direct
database model"""

View File

@@ -1,9 +1,13 @@
"""RBAC Permissions"""
from django.contrib.contenttypes.models import ContentType
from django.db.models import Model
from guardian.shortcuts import assign_perm
from rest_framework.permissions import BasePermission, DjangoObjectPermissions
from rest_framework.request import Request
from authentik.rbac.models import InitialPermissions, InitialPermissionsMode
class ObjectPermissions(DjangoObjectPermissions):
"""RBAC Permissions"""
@@ -51,3 +55,20 @@ def HasPermission(*perm: str) -> type[BasePermission]:
return bool(request.user and request.user.has_perms(perm))
return checker
# TODO: add `user: User` type annotation without circular dependencies.
# The author of this function isn't proficient/patient enough to do it.
def assign_initial_permissions(user, instance: Model):
# Performance here should not be an issue, but if needed, there are many optimization routes
initial_permissions_list = InitialPermissions.objects.filter(role__group__in=user.groups.all())
for initial_permissions in initial_permissions_list:
for permission in initial_permissions.permissions.all():
if permission.content_type != ContentType.objects.get_for_model(instance):
continue
assign_to = (
user
if initial_permissions.mode == InitialPermissionsMode.USER
else initial_permissions.role.group
)
assign_perm(permission, assign_to, instance)

View File

@@ -0,0 +1,116 @@
"""Test InitialPermissions"""
from django.contrib.auth.models import Permission
from guardian.shortcuts import assign_perm
from rest_framework.reverse import reverse
from rest_framework.test import APITestCase
from authentik.core.models import Group
from authentik.core.tests.utils import create_test_user
from authentik.lib.generators import generate_id
from authentik.rbac.models import InitialPermissions, InitialPermissionsMode, Role
from authentik.stages.dummy.models import DummyStage
class TestInitialPermissions(APITestCase):
"""Test InitialPermissions"""
def setUp(self) -> None:
self.user = create_test_user()
self.same_role_user = create_test_user()
self.different_role_user = create_test_user()
self.role = Role.objects.create(name=generate_id())
self.different_role = Role.objects.create(name=generate_id())
self.group = Group.objects.create(name=generate_id())
self.different_group = Group.objects.create(name=generate_id())
self.group.roles.add(self.role)
self.group.users.add(self.user, self.same_role_user)
self.different_group.roles.add(self.different_role)
self.different_group.users.add(self.different_role_user)
self.ip = InitialPermissions.objects.create(
name=generate_id(), mode=InitialPermissionsMode.USER, role=self.role
)
self.view_role = Permission.objects.filter(codename="view_role").first()
self.ip.permissions.add(self.view_role)
assign_perm("authentik_rbac.add_role", self.user)
self.client.force_login(self.user)
def test_different_role(self):
"""InitialPermissions for different role does nothing"""
self.ip.role = self.different_role
self.ip.save()
self.client.post(reverse("authentik_api:roles-list"), {"name": "test-role"})
role = Role.objects.filter(name="test-role").first()
self.assertFalse(self.user.has_perm("authentik_rbac.view_role", role))
def test_different_model(self):
"""InitialPermissions for different model does nothing"""
assign_perm("authentik_stages_dummy.add_dummystage", self.user)
self.client.post(
reverse("authentik_api:stages-dummy-list"), {"name": "test-stage", "throw-error": False}
)
role = Role.objects.filter(name="test-role").first()
self.assertFalse(self.user.has_perm("authentik_rbac.view_role", role))
stage = DummyStage.objects.filter(name="test-stage").first()
self.assertFalse(self.user.has_perm("authentik_stages_dummy.view_dummystage", stage))
def test_mode_user(self):
"""InitialPermissions adds user permission in user mode"""
self.client.post(reverse("authentik_api:roles-list"), {"name": "test-role"})
role = Role.objects.filter(name="test-role").first()
self.assertTrue(self.user.has_perm("authentik_rbac.view_role", role))
self.assertFalse(self.same_role_user.has_perm("authentik_rbac.view_role", role))
def test_mode_role(self):
"""InitialPermissions adds role permission in role mode"""
self.ip.mode = InitialPermissionsMode.ROLE
self.ip.save()
self.client.post(reverse("authentik_api:roles-list"), {"name": "test-role"})
role = Role.objects.filter(name="test-role").first()
self.assertTrue(self.user.has_perm("authentik_rbac.view_role", role))
self.assertTrue(self.same_role_user.has_perm("authentik_rbac.view_role", role))
def test_many_permissions(self):
"""InitialPermissions can add multiple permissions"""
change_role = Permission.objects.filter(codename="change_role").first()
self.ip.permissions.add(change_role)
self.client.post(reverse("authentik_api:roles-list"), {"name": "test-role"})
role = Role.objects.filter(name="test-role").first()
self.assertTrue(self.user.has_perm("authentik_rbac.view_role", role))
self.assertTrue(self.user.has_perm("authentik_rbac.change_role", role))
def test_permissions_separated_by_role(self):
"""When the triggering user is part of two different roles with InitialPermissions in role
mode, it only adds permissions to the relevant role."""
self.ip.mode = InitialPermissionsMode.ROLE
self.ip.save()
different_ip = InitialPermissions.objects.create(
name=generate_id(), mode=InitialPermissionsMode.ROLE, role=self.different_role
)
change_role = Permission.objects.filter(codename="change_role").first()
different_ip.permissions.add(change_role)
self.different_group.users.add(self.user)
self.client.post(reverse("authentik_api:roles-list"), {"name": "test-role"})
role = Role.objects.filter(name="test-role").first()
self.assertTrue(self.user.has_perm("authentik_rbac.view_role", role))
self.assertTrue(self.same_role_user.has_perm("authentik_rbac.view_role", role))
self.assertFalse(self.different_role_user.has_perm("authentik_rbac.view_role", role))
self.assertTrue(self.user.has_perm("authentik_rbac.change_role", role))
self.assertFalse(self.same_role_user.has_perm("authentik_rbac.change_role", role))
self.assertTrue(self.different_role_user.has_perm("authentik_rbac.change_role", role))

View File

@@ -1,5 +1,6 @@
"""RBAC API urls"""
from authentik.rbac.api.initial_permissions import InitialPermissionsViewSet
from authentik.rbac.api.rbac import RBACPermissionViewSet
from authentik.rbac.api.rbac_assigned_by_roles import RoleAssignedPermissionViewSet
from authentik.rbac.api.rbac_assigned_by_users import UserAssignedPermissionViewSet
@@ -21,5 +22,6 @@ api_urlpatterns = [
("rbac/permissions/users", UserPermissionViewSet, "permissions-users"),
("rbac/permissions/roles", RolePermissionViewSet, "permissions-roles"),
("rbac/permissions", RBACPermissionViewSet),
("rbac/roles", RoleViewSet),
("rbac/roles", RoleViewSet, "roles"),
("rbac/initial_permissions", InitialPermissionsViewSet, "initial-permissions"),
]

View File

@@ -130,6 +130,7 @@ class OAuthSourceSerializer(SourceSerializer):
"oidc_well_known_url",
"oidc_jwks_url",
"oidc_jwks",
"authorization_code_auth_method",
]
extra_kwargs = {
"consumer_secret": {"write_only": True},

View File

@@ -6,11 +6,15 @@ from urllib.parse import parse_qsl
from django.utils.crypto import constant_time_compare, get_random_string
from django.utils.translation import gettext as _
from requests.auth import AuthBase, HTTPBasicAuth
from requests.exceptions import RequestException
from requests.models import Response
from structlog.stdlib import get_logger
from authentik.sources.oauth.clients.base import BaseOAuthClient
from authentik.sources.oauth.models import (
AuthorizationCodeAuthMethod,
)
LOGGER = get_logger()
SESSION_KEY_OAUTH_PKCE = "authentik/sources/oauth/pkce"
@@ -55,6 +59,30 @@ class OAuth2Client(BaseOAuthClient):
"""Get client secret"""
return self.source.consumer_secret
def get_access_token_args(self, callback: str, code: str) -> dict[str, Any]:
args = {
"redirect_uri": callback,
"code": code,
"grant_type": "authorization_code",
}
if SESSION_KEY_OAUTH_PKCE in self.request.session:
args["code_verifier"] = self.request.session[SESSION_KEY_OAUTH_PKCE]
if (
self.source.source_type.authorization_code_auth_method
== AuthorizationCodeAuthMethod.POST_BODY
):
args["client_id"] = self.get_client_id()
args["client_secret"] = self.get_client_secret()
return args
def get_access_token_auth(self) -> AuthBase | None:
if (
self.source.source_type.authorization_code_auth_method
== AuthorizationCodeAuthMethod.BASIC_AUTH
):
return HTTPBasicAuth(self.get_client_id(), self.get_client_secret())
return None
def get_access_token(self, **request_kwargs) -> dict[str, Any] | None:
"""Fetch access token from callback request."""
callback = self.request.build_absolute_uri(self.callback or self.request.path)
@@ -67,13 +95,6 @@ class OAuth2Client(BaseOAuthClient):
error = self.get_request_arg("error", None)
error_desc = self.get_request_arg("error_description", None)
return {"error": error_desc or error or _("No token received.")}
args = {
"redirect_uri": callback,
"code": code,
"grant_type": "authorization_code",
}
if SESSION_KEY_OAUTH_PKCE in self.request.session:
args["code_verifier"] = self.request.session[SESSION_KEY_OAUTH_PKCE]
try:
access_token_url = self.source.source_type.access_token_url or ""
if self.source.source_type.urls_customizable and self.source.access_token_url:
@@ -81,8 +102,8 @@ class OAuth2Client(BaseOAuthClient):
response = self.do_request(
"post",
access_token_url,
auth=(self.get_client_id(), self.get_client_secret()),
data=args,
auth=self.get_access_token_auth(),
data=self.get_access_token_args(callback, code),
headers=self._default_headers,
**request_kwargs,
)

View File

@@ -0,0 +1,25 @@
# Generated by Django 5.0.14 on 2025-04-11 18:09
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_sources_oauth", "0009_migrate_useroauthsourceconnection_identifier"),
]
operations = [
migrations.AddField(
model_name="oauthsource",
name="authorization_code_auth_method",
field=models.TextField(
choices=[
("basic_auth", "HTTP Basic Authentication"),
("post_body", "Include the client ID and secret as request parameters"),
],
default="basic_auth",
help_text="How to perform authentication during an authorization_code token request flow",
),
),
]

View File

@@ -21,6 +21,11 @@ if TYPE_CHECKING:
from authentik.sources.oauth.types.registry import SourceType
class AuthorizationCodeAuthMethod(models.TextChoices):
BASIC_AUTH = "basic_auth", _("HTTP Basic Authentication")
POST_BODY = "post_body", _("Include the client ID and secret as request parameters")
class OAuthSource(NonCreatableType, Source):
"""Login using a Generic OAuth provider."""
@@ -61,6 +66,14 @@ class OAuthSource(NonCreatableType, Source):
oidc_jwks_url = models.TextField(default="", blank=True)
oidc_jwks = models.JSONField(default=dict, blank=True)
authorization_code_auth_method = models.TextField(
choices=AuthorizationCodeAuthMethod.choices,
default=AuthorizationCodeAuthMethod.BASIC_AUTH,
help_text=_(
"How to perform authentication during an authorization_code token request flow"
),
)
@property
def source_type(self) -> type["SourceType"]:
"""Return the provider instance for this source"""

View File

@@ -0,0 +1,69 @@
from django.test import RequestFactory, TestCase
from guardian.shortcuts import get_anonymous_user
from authentik.lib.generators import generate_id
from authentik.sources.oauth.clients.oauth2 import OAuth2Client
from authentik.sources.oauth.models import AuthorizationCodeAuthMethod, OAuthSource
from authentik.sources.oauth.types.oidc import OpenIDConnectClient
class TestOAuthClient(TestCase):
"""OAuth Source tests"""
def setUp(self):
self.source = OAuthSource.objects.create(
name="test",
slug="test",
provider_type="openidconnect",
authorization_url="",
profile_url="",
consumer_key=generate_id(),
)
self.factory = RequestFactory()
def test_client_post_body_auth(self):
"""Test login_challenge"""
self.source.provider_type = "apple"
self.source.save()
request = self.factory.get("/")
request.session = {}
request.user = get_anonymous_user()
client = OAuth2Client(self.source, request)
self.assertIsNone(client.get_access_token_auth())
args = client.get_access_token_args("", "")
self.assertIn("client_id", args)
self.assertIn("client_secret", args)
def test_client_basic_auth(self):
"""Test login_challenge"""
self.source.provider_type = "reddit"
self.source.save()
request = self.factory.get("/")
request.session = {}
request.user = get_anonymous_user()
client = OAuth2Client(self.source, request)
self.assertIsNotNone(client.get_access_token_auth())
args = client.get_access_token_args("", "")
self.assertNotIn("client_id", args)
self.assertNotIn("client_secret", args)
def test_client_openid_auth(self):
"""Test login_challenge"""
request = self.factory.get("/")
request.session = {}
request.user = get_anonymous_user()
client = OpenIDConnectClient(self.source, request)
self.assertIsNotNone(client.get_access_token_auth())
args = client.get_access_token_args("", "")
self.assertNotIn("client_id", args)
self.assertNotIn("client_secret", args)
self.source.authorization_code_auth_method = AuthorizationCodeAuthMethod.POST_BODY
self.source.save()
client = OpenIDConnectClient(self.source, request)
self.assertIsNone(client.get_access_token_auth())
args = client.get_access_token_args("", "")
self.assertIn("client_id", args)
self.assertIn("client_secret", args)

View File

@@ -11,7 +11,7 @@ from structlog.stdlib import get_logger
from authentik.flows.challenge import Challenge, ChallengeResponse
from authentik.sources.oauth.clients.oauth2 import OAuth2Client
from authentik.sources.oauth.models import OAuthSource
from authentik.sources.oauth.models import AuthorizationCodeAuthMethod, OAuthSource
from authentik.sources.oauth.types.registry import SourceType, registry
from authentik.sources.oauth.views.callback import OAuthCallback
from authentik.sources.oauth.views.redirect import OAuthRedirect
@@ -105,6 +105,8 @@ class AppleType(SourceType):
access_token_url = "https://appleid.apple.com/auth/token" # nosec
profile_url = ""
authorization_code_auth_method = AuthorizationCodeAuthMethod.POST_BODY
def login_challenge(self, source: OAuthSource, request: HttpRequest) -> Challenge:
"""Pre-general all the things required for the JS SDK"""
apple_client = AppleOAuthClient(

View File

@@ -6,6 +6,7 @@ from requests import RequestException
from structlog.stdlib import get_logger
from authentik.sources.oauth.clients.oauth2 import UserprofileHeaderAuthClient
from authentik.sources.oauth.models import AuthorizationCodeAuthMethod
from authentik.sources.oauth.types.oidc import OpenIDConnectOAuth2Callback
from authentik.sources.oauth.types.registry import SourceType, registry
from authentik.sources.oauth.views.redirect import OAuthRedirect
@@ -77,6 +78,8 @@ class AzureADType(SourceType):
)
oidc_jwks_url = "https://login.microsoftonline.com/common/discovery/keys"
authorization_code_auth_method = AuthorizationCodeAuthMethod.POST_BODY
def get_base_user_properties(self, info: dict[str, Any], **kwargs) -> dict[str, Any]:
mail = info.get("mail", None) or info.get("otherMails", [None])[0]
# Format group info

View File

@@ -2,8 +2,8 @@
from typing import Any
from authentik.sources.oauth.models import AuthorizationCodeAuthMethod
from authentik.sources.oauth.types.registry import SourceType, registry
from authentik.sources.oauth.views.callback import OAuthCallback
from authentik.sources.oauth.views.redirect import OAuthRedirect
@@ -16,15 +16,10 @@ class FacebookOAuthRedirect(OAuthRedirect):
}
class FacebookOAuth2Callback(OAuthCallback):
"""Facebook OAuth2 Callback"""
@registry.register()
class FacebookType(SourceType):
"""Facebook Type definition"""
callback_view = FacebookOAuth2Callback
redirect_view = FacebookOAuthRedirect
verbose_name = "Facebook"
name = "facebook"
@@ -33,6 +28,8 @@ class FacebookType(SourceType):
access_token_url = "https://graph.facebook.com/v7.0/oauth/access_token" # nosec
profile_url = "https://graph.facebook.com/v7.0/me?fields=id,name,email"
authorization_code_auth_method = AuthorizationCodeAuthMethod.POST_BODY
def get_base_user_properties(self, info: dict[str, Any], **kwargs) -> dict[str, Any]:
return {
"username": info.get("name"),

View File

@@ -5,7 +5,7 @@ from typing import Any
from requests.exceptions import RequestException
from authentik.sources.oauth.clients.oauth2 import OAuth2Client
from authentik.sources.oauth.models import OAuthSource
from authentik.sources.oauth.models import AuthorizationCodeAuthMethod, OAuthSource
from authentik.sources.oauth.types.registry import SourceType, registry
from authentik.sources.oauth.views.callback import OAuthCallback
from authentik.sources.oauth.views.redirect import OAuthRedirect
@@ -63,6 +63,8 @@ class GitHubType(SourceType):
)
oidc_jwks_url = "https://token.actions.githubusercontent.com/.well-known/jwks"
authorization_code_auth_method = AuthorizationCodeAuthMethod.POST_BODY
def get_base_user_properties(
self,
source: OAuthSource,

View File

@@ -7,9 +7,8 @@ and https://docs.gitlab.com/ee/integration/openid_connect_provider.html
from typing import Any
from authentik.sources.oauth.models import OAuthSource
from authentik.sources.oauth.models import AuthorizationCodeAuthMethod, OAuthSource
from authentik.sources.oauth.types.registry import SourceType, registry
from authentik.sources.oauth.views.callback import OAuthCallback
from authentik.sources.oauth.views.redirect import OAuthRedirect
@@ -22,15 +21,10 @@ class GitLabOAuthRedirect(OAuthRedirect):
}
class GitLabOAuthCallback(OAuthCallback):
"""GitLab OAuth2 Callback"""
@registry.register()
class GitLabType(SourceType):
"""GitLab Type definition"""
callback_view = GitLabOAuthCallback
redirect_view = GitLabOAuthRedirect
verbose_name = "GitLab"
name = "gitlab"
@@ -43,6 +37,8 @@ class GitLabType(SourceType):
oidc_well_known_url = "https://gitlab.com/.well-known/openid-configuration"
oidc_jwks_url = "https://gitlab.com/oauth/discovery/keys"
authorization_code_auth_method = AuthorizationCodeAuthMethod.POST_BODY
def get_base_user_properties(self, info: dict[str, Any], **kwargs) -> dict[str, Any]:
return {
"username": info.get("preferred_username"),

View File

@@ -2,8 +2,8 @@
from typing import Any
from authentik.sources.oauth.models import AuthorizationCodeAuthMethod
from authentik.sources.oauth.types.registry import SourceType, registry
from authentik.sources.oauth.views.callback import OAuthCallback
from authentik.sources.oauth.views.redirect import OAuthRedirect
@@ -16,15 +16,10 @@ class GoogleOAuthRedirect(OAuthRedirect):
}
class GoogleOAuth2Callback(OAuthCallback):
"""Google OAuth2 Callback"""
@registry.register()
class GoogleType(SourceType):
"""Google Type definition"""
callback_view = GoogleOAuth2Callback
redirect_view = GoogleOAuthRedirect
verbose_name = "Google"
name = "google"
@@ -35,6 +30,8 @@ class GoogleType(SourceType):
oidc_well_known_url = "https://accounts.google.com/.well-known/openid-configuration"
oidc_jwks_url = "https://www.googleapis.com/oauth2/v3/certs"
authorization_code_auth_method = AuthorizationCodeAuthMethod.POST_BODY
def get_base_user_properties(self, info: dict[str, Any], **kwargs) -> dict[str, Any]:
return {
"email": info.get("email"),

View File

@@ -6,6 +6,7 @@ from requests.exceptions import RequestException
from structlog.stdlib import get_logger
from authentik.sources.oauth.clients.oauth2 import OAuth2Client
from authentik.sources.oauth.models import AuthorizationCodeAuthMethod
from authentik.sources.oauth.types.registry import SourceType, registry
from authentik.sources.oauth.views.callback import OAuthCallback
from authentik.sources.oauth.views.redirect import OAuthRedirect
@@ -59,6 +60,8 @@ class MailcowType(SourceType):
urls_customizable = True
authorization_code_auth_method = AuthorizationCodeAuthMethod.POST_BODY
def get_base_user_properties(self, info: dict[str, Any], **kwargs) -> dict[str, Any]:
return {
"username": info.get("full_name"),

View File

@@ -2,8 +2,10 @@
from typing import Any
from requests.auth import AuthBase, HTTPBasicAuth
from authentik.sources.oauth.clients.oauth2 import UserprofileHeaderAuthClient
from authentik.sources.oauth.models import OAuthSource
from authentik.sources.oauth.models import AuthorizationCodeAuthMethod, OAuthSource
from authentik.sources.oauth.types.registry import SourceType, registry
from authentik.sources.oauth.views.callback import OAuthCallback
from authentik.sources.oauth.views.redirect import OAuthRedirect
@@ -18,10 +20,27 @@ class OpenIDConnectOAuthRedirect(OAuthRedirect):
}
class OpenIDConnectClient(UserprofileHeaderAuthClient):
def get_access_token_args(self, callback: str, code: str) -> dict[str, Any]:
args = super().get_access_token_args(callback, code)
if self.source.authorization_code_auth_method == AuthorizationCodeAuthMethod.POST_BODY:
args["client_id"] = self.get_client_id()
args["client_secret"] = self.get_client_secret()
else:
args.pop("client_id", None)
args.pop("client_secret", None)
return args
def get_access_token_auth(self) -> AuthBase | None:
if self.source.authorization_code_auth_method == AuthorizationCodeAuthMethod.BASIC_AUTH:
return HTTPBasicAuth(self.get_client_id(), self.get_client_secret())
return None
class OpenIDConnectOAuth2Callback(OAuthCallback):
"""OpenIDConnect OAuth2 Callback"""
client_class = UserprofileHeaderAuthClient
client_class = OpenIDConnectClient
def get_user_id(self, info: dict[str, str]) -> str:
return info.get("sub", None)

View File

@@ -2,7 +2,6 @@
from typing import Any
from authentik.sources.oauth.clients.oauth2 import UserprofileHeaderAuthClient
from authentik.sources.oauth.models import OAuthSource
from authentik.sources.oauth.types.oidc import OpenIDConnectOAuth2Callback
from authentik.sources.oauth.types.registry import SourceType, registry
@@ -18,20 +17,11 @@ class OktaOAuthRedirect(OAuthRedirect):
}
class OktaOAuth2Callback(OpenIDConnectOAuth2Callback):
"""Okta OAuth2 Callback"""
# Okta has the same quirk as azure and throws an error if the access token
# is set via query parameter, so we reuse the azure client
# see https://github.com/goauthentik/authentik/issues/1910
client_class = UserprofileHeaderAuthClient
@registry.register()
class OktaType(SourceType):
"""Okta Type definition"""
callback_view = OktaOAuth2Callback
callback_view = OpenIDConnectOAuth2Callback
redirect_view = OktaOAuthRedirect
verbose_name = "Okta"
name = "okta"

View File

@@ -3,7 +3,7 @@
from typing import Any
from authentik.sources.oauth.clients.oauth2 import UserprofileHeaderAuthClient
from authentik.sources.oauth.models import OAuthSource
from authentik.sources.oauth.models import AuthorizationCodeAuthMethod, OAuthSource
from authentik.sources.oauth.types.registry import SourceType, registry
from authentik.sources.oauth.views.callback import OAuthCallback
from authentik.sources.oauth.views.redirect import OAuthRedirect
@@ -41,6 +41,8 @@ class PatreonType(SourceType):
access_token_url = "https://www.patreon.com/api/oauth2/token" # nosec
profile_url = "https://www.patreon.com/api/oauth2/api/current_user"
authorization_code_auth_method = AuthorizationCodeAuthMethod.POST_BODY
def get_base_user_properties(self, info: dict[str, Any], **kwargs) -> dict[str, Any]:
return {
"username": info.get("data", {}).get("attributes", {}).get("vanity"),

View File

@@ -2,8 +2,6 @@
from typing import Any
from requests.auth import HTTPBasicAuth
from authentik.sources.oauth.clients.oauth2 import UserprofileHeaderAuthClient
from authentik.sources.oauth.types.registry import SourceType, registry
from authentik.sources.oauth.views.callback import OAuthCallback
@@ -20,21 +18,10 @@ class RedditOAuthRedirect(OAuthRedirect):
}
class RedditOAuth2Client(UserprofileHeaderAuthClient):
"""Reddit OAuth2 Client"""
def get_access_token(self, **request_kwargs):
"Fetch access token from callback request."
request_kwargs["auth"] = HTTPBasicAuth(
self.source.consumer_key, self.source.consumer_secret
)
return super().get_access_token(**request_kwargs)
class RedditOAuth2Callback(OAuthCallback):
"""Reddit OAuth2 Callback"""
client_class = RedditOAuth2Client
client_class = UserprofileHeaderAuthClient
@registry.register()

View File

@@ -10,7 +10,7 @@ from django.urls.base import reverse
from structlog.stdlib import get_logger
from authentik.flows.challenge import Challenge, RedirectChallenge
from authentik.sources.oauth.models import OAuthSource
from authentik.sources.oauth.models import AuthorizationCodeAuthMethod, OAuthSource
from authentik.sources.oauth.views.callback import OAuthCallback
from authentik.sources.oauth.views.redirect import OAuthRedirect
@@ -41,6 +41,10 @@ class SourceType:
oidc_well_known_url: str | None = None
oidc_jwks_url: str | None = None
authorization_code_auth_method: AuthorizationCodeAuthMethod = (
AuthorizationCodeAuthMethod.BASIC_AUTH
)
def icon_url(self) -> str:
"""Get Icon URL for login"""
return static(f"authentik/sources/{self.name}.svg")

View File

@@ -4,6 +4,7 @@ from json import dumps
from typing import Any
from authentik.sources.oauth.clients.oauth2 import UserprofileHeaderAuthClient
from authentik.sources.oauth.models import AuthorizationCodeAuthMethod
from authentik.sources.oauth.types.oidc import OpenIDConnectOAuth2Callback
from authentik.sources.oauth.types.registry import SourceType, registry
from authentik.sources.oauth.views.redirect import OAuthRedirect
@@ -47,6 +48,8 @@ class TwitchType(SourceType):
access_token_url = "https://id.twitch.tv/oauth2/token" # nosec
profile_url = "https://id.twitch.tv/oauth2/userinfo"
authorization_code_auth_method = AuthorizationCodeAuthMethod.POST_BODY
def get_base_user_properties(self, info: dict[str, Any], **kwargs) -> dict[str, Any]:
return {
"username": info.get("preferred_username"),

View File

@@ -12,23 +12,6 @@ from authentik.sources.oauth.views.callback import OAuthCallback
from authentik.sources.oauth.views.redirect import OAuthRedirect
class TwitterClient(UserprofileHeaderAuthClient):
"""Twitter has similar quirks to Azure AD, and additionally requires Basic auth on
the access token endpoint for some reason."""
# Twitter has the same quirk as azure and throws an error if the access token
# is set via query parameter, so we reuse the azure client
# see https://github.com/goauthentik/authentik/issues/1910
def get_access_token(self, **request_kwargs) -> dict[str, Any] | None:
return super().get_access_token(
auth=(
self.source.consumer_key,
self.source.consumer_secret,
)
)
class TwitterOAuthRedirect(OAuthRedirect):
"""Twitter OAuth2 Redirect"""
@@ -44,7 +27,7 @@ class TwitterOAuthRedirect(OAuthRedirect):
class TwitterOAuthCallback(OAuthCallback):
"""Twitter OAuth2 Callback"""
client_class = TwitterClient
client_class = UserprofileHeaderAuthClient
def get_user_id(self, info: dict[str, str]) -> str:
return info.get("data", {}).get("id", "")

View File

@@ -4,41 +4,43 @@
{% load humanize %}
{% block content %}
<tr>
<td align="center">
<h1>
{% blocktrans with username=user.username %}
Hi {{ username }},
{% endblocktrans %}
</h1>
</td>
</tr>
<tr>
<td align="center">
<table border="0">
<tr>
<td align="center" style="max-width: 300px; padding: 20px 0; color: #212124;">
{% blocktrans %}
Email MFA code.
{% endblocktrans %}
</td>
</tr>
<tr>
<td align="center" class="btn btn-primary">
{{ token }}
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td align="center">
<h1>
{% blocktrans with username=user.username %}
Hi {{ username }},
{% endblocktrans %}
</h1>
</td>
</tr>
<tr>
<td align="center">
<table border="0">
<tr>
<td align="center" style="max-width: 300px; padding: 20px 0; color: #212124;">
{% blocktrans %}
Email MFA code.
{% endblocktrans %}
</td>
</tr>
<tr>
<td align="center" class="btn btn-primary">
{{ token }}
</td>
</tr>
</table>
</td>
</tr>
{% endblock %}
{% block sub_content %}
<tr>
<td style="padding: 20px; font-size: 12px; color: #212124;" align="center">
{% blocktrans with expires=expires|timeuntil %}
If you did not request this code, please ignore this email. The code above is valid for {{ expires }}.
{% endblocktrans %}
</td>
</tr>
<tr>
<td style="padding: 20px; font-size: 12px; color: #212124;" align="center">
{% blocktrans with expires=expires|timeuntil %}
If you did not request this code, please ignore this email. The code above is valid for {{ expires }}.
{% endblocktrans %}
</td>
</tr>
{% endblock %}

File diff suppressed because one or more lines are too long

View File

@@ -2,4 +2,4 @@
from authentik.stages.dummy.api import DummyStageViewSet
api_urlpatterns = [("stages/dummy", DummyStageViewSet)]
api_urlpatterns = [("stages/dummy", DummyStageViewSet, "stages-dummy")]

View File

@@ -4,38 +4,47 @@
{% load i18n %}
{% block content %}
<tr>
<td align="center">
<h1>
{% trans 'Welcome!' %}
</h1>
</td>
</tr>
<tr>
<td align="center">
<table border="0">
<tr>
<td align="center" style="max-width: 300px; padding: 20px 0; color: #212124;">
{% trans "We're excited to have you get started. First, you need to confirm your account. Just press the button below."%}
</td>
</tr>
<tr>
<td align="center" class="btn btn-primary">
<a id="confirm" href="{{ url }}" rel="noopener noreferrer" target="_blank">{% trans 'Confirm Account' %}</a>
</td>
</tr>
</table>
</td>
</tr>
<td>
<tr>
<td align="center">
<h1>
{% trans 'Welcome!' %}
</h1>
</td>
</tr>
<tr>
<td align="center">
<table border="0">
<tr>
<td align="center" style="max-width: 300px; padding: 20px 0; color: #212124;">
{% trans "We're excited to have you get started. First, you need to confirm your account. Just press the button below."%}
</td>
</tr>
<tr>
<td align="center" class="btn btn-primary">
<a
id="confirm"
href="{{ url }}"
rel="noopener noreferrer"
target="_blank">
{% trans 'Confirm Account' %}
</a>
</td>
</tr>
</table>
</td>
</tr>
<td>
{% endblock %}
{% block sub_content %}
<tr>
<td style="padding: 20px; font-size: 12px; color: #212124;word-break: break-all; overflow-wrap: break-word;" align="center">
{% blocktrans with url=url %}
If that doesn't work, copy and paste the following link in your browser: {{ url }}
{% endblocktrans %}
</td>
</tr>
<tr>
<td style="padding: 20px; font-size: 12px; color: #212124;word-break: break-all; overflow-wrap: break-word;" align="center">
{% blocktrans with url=url %}
If that doesn't work, copy and paste the following link in your browser: {{ url }}
{% endblocktrans %}
</td>
</tr>
{% endblock %}

View File

@@ -1,5 +1,6 @@
{% load authentik_stages_email %}
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtm=l">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
@@ -99,11 +100,13 @@
<img src="{% block logo_url %}cid:logo.png{% endblock %}" border="0=" alt="authentik logo" class="flexibleImage logo">
</td>
</tr>
{% block content %}
{% endblock %}
</table>
</td>
</tr>
<tr>
<td>
<table border="0" style="margin-top: 10px;" width="100%">
@@ -118,6 +121,7 @@
</table>
</td>
</tr>
<tr>
<td align="center">
Powered by <a rel="noopener noreferrer" target="_blank" href="https://goauthentik.io?utm_source=authentik&utm_medium=email">authentik</a>.

View File

@@ -3,50 +3,52 @@
{% load i18n %}
{% block content %}
<tr>
<td align="center">
<h1>
{{ title }}
</h1>
</td>
</tr>
<tr>
<td align="center">
<table border="0">
<tr>
<td align="center" style="max-width: 300px; padding: 20px 0; color: #212124;">
{{ body }}
</td>
</tr>
{% if key_value %}
<tr>
<td>
<table class="properties-table" width="100%">
<tbody>
{% for key, value in key_value.items %}
<tr>
<td class="td-right">{{ key }}</td>
<td class="td-left">{{ value }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</td>
</tr>
{% endif %}
</table>
</td>
</tr>
<tr>
<td align="center">
<h1>
{{ title }}
</h1>
</td>
</tr>
<tr>
<td align="center">
<table border="0">
<tr>
<td align="center" style="max-width: 300px; padding: 20px 0; color: #212124;">
{{ body }}
</td>
</tr>
{% if key_value %}
<tr>
<td>
<table class="properties-table" width="100%">
<tbody>
{% for key, value in key_value.items %}
<tr>
<td class="td-right">{{ key }}</td>
<td class="td-left">{{ value }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</td>
</tr>
{% endif %}
</table>
</td>
</tr>
{% endblock %}
{% block sub_content %}
{% if source %}
<tr>
<td style="padding: 20px; font-size: 12px; color: #212124;" align="center">
{% blocktranslate with name=source.from %}
This email was sent from the notification transport <code>{{ name }}</code>.
{% endblocktranslate %}
</td>
</tr>
{% endif %}
{% if source %}
<tr>
<td style="padding: 20px; font-size: 12px; color: #212124;" align="center">
{% blocktranslate with name=source.from %}
This email was sent from the notification transport <code>{{ name }}</code>.
{% endblocktranslate %}
</td>
</tr>
{% endif %}
{% endblock %}

View File

@@ -4,41 +4,49 @@
{% load humanize %}
{% block content %}
<tr>
<td align="center">
<h1>
{% blocktrans with username=user.username %}
Hi {{ username }},
{% endblocktrans %}
</h1>
</td>
</tr>
<tr>
<td align="center">
<table border="0">
<tr>
<td align="center" style="max-width: 300px; padding: 20px 0; color: #212124;">
{% blocktrans %}
You recently requested to change your password for your authentik account. Use the button below to set a new password.
{% endblocktrans %}
</td>
</tr>
<tr>
<td align="center" class="btn btn-primary">
<a id="confirm" href="{{ url }}" rel="noopener noreferrer" target="_blank">{% trans 'Reset Password' %}</a>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td align="center">
<h1>
{% blocktrans with username=user.username %}
Hi {{ username }},
{% endblocktrans %}
</h1>
</td>
</tr>
<tr>
<td align="center">
<table border="0">
<tr>
<td align="center" style="max-width: 300px; padding: 20px 0; color: #212124;">
{% blocktrans %}
You recently requested to change your password for your authentik account. Use the button below to set a new password.
{% endblocktrans %}
</td>
</tr>
<tr>
<td align="center" class="btn btn-primary">
<a
id="confirm"
href="{{ url }}"
rel="noopener noreferrer"
target="_blank">
{% trans 'Reset Password' %}
</a>
</td>
</tr>
</table>
</td>
</tr>
{% endblock %}
{% block sub_content %}
<tr>
<td style="padding: 20px; font-size: 12px; color: #212124;" align="center">
{% blocktrans with expires=expires|naturaltime %}
If you did not request a password change, please ignore this email. The link above is valid for {{ expires }}.
{% endblocktrans %}
</td>
</tr>
<tr>
<td style="padding: 20px; font-size: 12px; color: #212124;" align="center">
{% blocktrans with expires=expires|naturaltime %}
If you did not request a password change, please ignore this email. The link above is valid for {{ expires }}.
{% endblocktrans %}
</td>
</tr>
{% endblock %}

View File

@@ -4,22 +4,23 @@
{% load i18n %}
{% block content %}
<tr>
<tr>
<td class="alert alert-brand">
{% trans 'authentik Test-Email' %}
{% trans 'authentik Test-Email' %}
</td>
</tr>
<tr>
</tr>
<tr>
<td class="content-wrap">
<table width="100%" cellpadding="0" cellspacing="0">
<tr>
<td class="content-block">
{% blocktrans %}
This is a test email to inform you, that you've successfully configured authentik emails.
{% endblocktrans %}
</td>
</tr>
</table>
<table width="100%" cellpadding="0" cellspacing="0">
<tr>
<td class="content-block">
{% blocktrans %}
This is a test email to inform you, that you've successfully configured authentik emails.
{% endblocktrans %}
</td>
</tr>
</table>
</td>
</tr>
</tr>
{% endblock %}

View File

@@ -4,11 +4,12 @@ from drf_spectacular.utils import extend_schema
from rest_framework.decorators import action
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.serializers import CharField, ModelSerializer
from rest_framework.serializers import CharField
from rest_framework.validators import UniqueValidator
from rest_framework.viewsets import ModelViewSet
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import ModelSerializer
from authentik.core.expression.exceptions import PropertyMappingExpressionException
from authentik.flows.api.stages import StageSerializer
from authentik.flows.challenge import HttpChallengeResponse

View File

@@ -15,12 +15,12 @@ from rest_framework.filters import OrderingFilter, SearchFilter
from rest_framework.permissions import BasePermission
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.serializers import DateTimeField, ModelSerializer
from rest_framework.serializers import DateTimeField
from rest_framework.views import View
from rest_framework.viewsets import ModelViewSet
from authentik.api.authentication import validate_auth
from authentik.core.api.utils import PassiveSerializer
from authentik.core.api.utils import ModelSerializer, PassiveSerializer
from authentik.core.models import User
from authentik.lib.config import CONFIG
from authentik.recovery.lib import create_admin_group, create_recovery_token

View File

@@ -1201,6 +1201,46 @@
}
}
},
{
"type": "object",
"required": [
"model",
"identifiers"
],
"properties": {
"model": {
"const": "authentik_rbac.initialpermissions"
},
"id": {
"type": "string"
},
"state": {
"type": "string",
"enum": [
"absent",
"present",
"created",
"must_created"
],
"default": "present"
},
"conditions": {
"type": "array",
"items": {
"type": "boolean"
}
},
"permissions": {
"$ref": "#/$defs/model_authentik_rbac.initialpermissions_permissions"
},
"attrs": {
"$ref": "#/$defs/model_authentik_rbac.initialpermissions"
},
"identifiers": {
"$ref": "#/$defs/model_authentik_rbac.initialpermissions"
}
}
},
{
"type": "object",
"required": [
@@ -4828,6 +4868,7 @@
"authentik_providers_scim.scimprovider",
"authentik_providers_scim.scimmapping",
"authentik_rbac.role",
"authentik_rbac.initialpermissions",
"authentik_sources_kerberos.kerberossource",
"authentik_sources_kerberos.kerberossourcepropertymapping",
"authentik_sources_kerberos.userkerberossourceconnection",
@@ -7169,12 +7210,16 @@
"authentik_providers_ssf.view_stream",
"authentik_providers_ssf.view_streamevent",
"authentik_rbac.access_admin_interface",
"authentik_rbac.add_initialpermissions",
"authentik_rbac.add_role",
"authentik_rbac.assign_role_permissions",
"authentik_rbac.change_initialpermissions",
"authentik_rbac.change_role",
"authentik_rbac.delete_initialpermissions",
"authentik_rbac.delete_role",
"authentik_rbac.edit_system_settings",
"authentik_rbac.unassign_role_permissions",
"authentik_rbac.view_initialpermissions",
"authentik_rbac.view_role",
"authentik_rbac.view_system_info",
"authentik_rbac.view_system_settings",
@@ -7461,6 +7506,64 @@
}
}
},
"model_authentik_rbac.initialpermissions": {
"type": "object",
"properties": {
"name": {
"type": "string",
"maxLength": 150,
"minLength": 1,
"title": "Name"
},
"mode": {
"type": "string",
"enum": [
"user",
"role"
],
"title": "Mode"
},
"role": {
"type": "string",
"format": "uuid",
"title": "Role"
},
"permissions": {
"type": "array",
"items": {
"type": "integer"
},
"title": "Permissions"
}
},
"required": []
},
"model_authentik_rbac.initialpermissions_permissions": {
"type": "array",
"items": {
"type": "object",
"required": [
"permission"
],
"properties": {
"permission": {
"type": "string",
"enum": [
"add_initialpermissions",
"change_initialpermissions",
"delete_initialpermissions",
"view_initialpermissions"
]
},
"user": {
"type": "integer"
},
"role": {
"type": "string"
}
}
}
},
"model_authentik_sources_kerberos.kerberossource": {
"type": "object",
"properties": {
@@ -8333,6 +8436,15 @@
"type": "object",
"additionalProperties": true,
"title": "Oidc jwks"
},
"authorization_code_auth_method": {
"type": "string",
"enum": [
"basic_auth",
"post_body"
],
"title": "Authorization code auth method",
"description": "How to perform authentication during an authorization_code token request flow"
}
},
"required": []
@@ -13793,12 +13905,16 @@
"authentik_providers_ssf.view_stream",
"authentik_providers_ssf.view_streamevent",
"authentik_rbac.access_admin_interface",
"authentik_rbac.add_initialpermissions",
"authentik_rbac.add_role",
"authentik_rbac.assign_role_permissions",
"authentik_rbac.change_initialpermissions",
"authentik_rbac.change_role",
"authentik_rbac.delete_initialpermissions",
"authentik_rbac.delete_role",
"authentik_rbac.edit_system_settings",
"authentik_rbac.unassign_role_permissions",
"authentik_rbac.view_initialpermissions",
"authentik_rbac.view_role",
"authentik_rbac.view_system_info",
"authentik_rbac.view_system_settings",

2
go.mod
View File

@@ -27,7 +27,7 @@ require (
github.com/spf13/cobra v1.9.1
github.com/stretchr/testify v1.10.0
github.com/wwt/guac v1.3.2
goauthentik.io/api/v3 v3.2025024.4
goauthentik.io/api/v3 v3.2025024.6
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab
golang.org/x/oauth2 v0.29.0
golang.org/x/sync v0.13.0

4
go.sum
View File

@@ -300,8 +300,8 @@ go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
goauthentik.io/api/v3 v3.2025024.4 h1:fD4K6YcCTdwtkqKbYBdJk3POHVzw+LDdJdZSbOAKbX4=
goauthentik.io/api/v3 v3.2025024.4/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
goauthentik.io/api/v3 v3.2025024.6 h1:3mmZY7E0EM/RR8uMF17mxa7368ZgZEIq/FjlCLJ9+lA=
goauthentik.io/api/v3 v3.2025024.6/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=

View File

@@ -1,59 +1,70 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>{{.Title}}</title>
<link rel="shortcut icon" type="image/png" href="/outpost.goauthentik.io/static/dist/assets/icons/icon.png">
<link rel="stylesheet" type="text/css" href="/outpost.goauthentik.io/static/dist/patternfly.min.css">
<link rel="stylesheet" type="text/css" href="/outpost.goauthentik.io/static/dist/authentik.css">
<link rel="prefetch" href="/outpost.goauthentik.io/static/dist/assets/images/flow_background.jpg" />
<style>
.pf-c-background-image::before {
--ak-flow-background: url("/outpost.goauthentik.io/static/dist/assets/images/flow_background.jpg");
}
:root {
--ak-flow-background: url("/outpost.goauthentik.io/static/dist/assets/images/flow_background.jpg");
--pf-c-background-image--BackgroundImage: var(--ak-flow-background);
--pf-c-background-image--BackgroundImage-2x: var(--ak-flow-background);
--pf-c-background-image--BackgroundImage--sm: var(--ak-flow-background);
--pf-c-background-image--BackgroundImage--sm-2x: var(--ak-flow-background);
--pf-c-background-image--BackgroundImage--lg: var(--ak-flow-background);
}
</style>
</head>
<body>
<div class="pf-c-background-image">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>{{.Title}}</title>
<link rel="shortcut icon" type="image/png" href="/outpost.goauthentik.io/static/dist/assets/icons/icon.png">
<link rel="stylesheet" type="text/css" href="/outpost.goauthentik.io/static/dist/patternfly.min.css">
<link rel="stylesheet" type="text/css" href="/outpost.goauthentik.io/static/dist/authentik.css">
<link rel="prefetch" href="/outpost.goauthentik.io/static/dist/assets/images/flow_background.jpg">
<style>
.pf-c-background-image::before {
--ak-flow-background: url("/outpost.goauthentik.io/static/dist/assets/images/flow_background.jpg");
}
:root {
--ak-flow-background: url("/outpost.goauthentik.io/static/dist/assets/images/flow_background.jpg");
--pf-c-background-image--BackgroundImage: var(--ak-flow-background);
--pf-c-background-image--BackgroundImage-2x: var(--ak-flow-background);
--pf-c-background-image--BackgroundImage--sm: var(--ak-flow-background);
--pf-c-background-image--BackgroundImage--sm-2x: var(--ak-flow-background);
--pf-c-background-image--BackgroundImage--lg: var(--ak-flow-background);
}
</style>
</head>
<body>
<div class="pf-c-background-image"></div>
<div class="pf-c-login stacked">
<div class="ak-login-container">
<main class="pf-c-login__main">
<div class="pf-c-login__main-header pf-c-brand ak-brand">
<img src="/outpost.goauthentik.io/static/dist/assets/icons/icon_left_brand.svg" alt="authentik Logo">
</div>
<div class="pf-c-login stacked">
<div class="ak-login-container">
<main class="pf-c-login__main">
<div class="pf-c-login__main-header pf-c-brand ak-brand">
<img src="/outpost.goauthentik.io/static/dist/assets/icons/icon_left_brand.svg" alt="authentik Logo" />
</div>
<header class="pf-c-login__main-header">
<h1 class="pf-c-title pf-m-3xl">
{{ .Title }}
</h1>
</header>
<div class="pf-c-login__main-body">
{{ .Message }}
</div>
<div class="pf-c-login__main-body">
<a href="/" class="pf-c-button pf-m-primary pf-m-block">Go to home</a>
</div>
</main>
<footer class="pf-c-login__footer">
<ul class="pf-c-list pf-m-inline">
<li>
<span>
Powered by authentik
</span>
</li>
</ul>
</footer>
</div>
<header class="pf-c-login__main-header">
<h1 class="pf-c-title pf-m-3xl">
{{ .Title }}
</h1>
</header>
<div class="pf-c-login__main-body">
{{ .Message }}
</div>
</body>
<div class="pf-c-login__main-body">
<a href="/" class="pf-c-button pf-m-primary pf-m-block">Go to home</a>
</div>
</main>
<footer class="pf-c-login__footer">
<ul class="pf-c-list pf-m-inline">
<li>
<span>
Powered by authentik
</span>
</li>
</ul>
</footer>
</div>
</div>
</body>
</html>

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-04-14 00:11+0000\n"
"POT-Creation-Date: 2025-04-15 00:11+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"
@@ -169,6 +169,7 @@ msgid "User's display name."
msgstr ""
#: authentik/core/models.py authentik/providers/oauth2/models.py
#: authentik/rbac/models.py
msgid "User"
msgstr ""
@@ -1984,6 +1985,10 @@ msgstr ""
msgid "Roles"
msgstr ""
#: authentik/rbac/models.py
msgid "Initial Permissions"
msgstr ""
#: authentik/rbac/models.py
msgid "System permission"
msgstr ""
@@ -3468,6 +3473,14 @@ msgid ""
"seconds=2)."
msgstr ""
#: authentik/tenants/models.py
msgid "Reputation cannot decrease lower than this value. Zero or negative."
msgstr ""
#: authentik/tenants/models.py
msgid "Reputation cannot increase higher than this value. Zero or positive."
msgstr ""
#: authentik/tenants/models.py
msgid "The option configures the footer links on the flow executor pages."
msgstr ""

View File

@@ -9,9 +9,9 @@
# Kyllian Delaye-Maillot, 2023
# Manuel Viens, 2023
# Mordecai, 2023
# nerdinator <florian.dupret@gmail.com>, 2024
# Charles Leclerc, 2025
# Tina, 2025
# nerdinator <florian.dupret@gmail.com>, 2025
# Marc Schmitt, 2025
#
#, fuzzy
@@ -19,7 +19,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-04-11 00:10+0000\n"
"POT-Creation-Date: 2025-04-15 00:11+0000\n"
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
"Last-Translator: Marc Schmitt, 2025\n"
"Language-Team: French (https://app.transifex.com/authentik/teams/119923/fr/)\n"
@@ -194,6 +194,7 @@ msgid "User's display name."
msgstr "Nom d'affichage de l'utilisateur"
#: authentik/core/models.py authentik/providers/oauth2/models.py
#: authentik/rbac/models.py
msgid "User"
msgstr "Utilisateur"
@@ -382,6 +383,18 @@ msgstr "Mappage de propriété"
msgid "Property Mappings"
msgstr "Mappages de propriété"
#: authentik/core/models.py
msgid "session data"
msgstr "Données de session"
#: authentik/core/models.py
msgid "Session"
msgstr "Session"
#: authentik/core/models.py
msgid "Sessions"
msgstr "Sessions"
#: authentik/core/models.py
msgid "Authenticated Session"
msgstr "Session Authentifiée"
@@ -2197,6 +2210,10 @@ msgstr "Rôle"
msgid "Roles"
msgstr "Rôles"
#: authentik/rbac/models.py
msgid "Initial Permissions"
msgstr "Permissions initiales"
#: authentik/rbac/models.py
msgid "System permission"
msgstr "Permission système"
@@ -2447,6 +2464,9 @@ msgid ""
"attribute. This allows nested group resolution on systems like FreeIPA and "
"Active Directory"
msgstr ""
"Recherche de l'appartenance aux groupes basée sur un attribut utilisateur "
"plutôt que sur un attribut de groupe. Cela permet la résolution des groupes "
"imbriqués sur des systèmes tels que FreeIPA et Active Directory."
#: authentik/sources/ldap/models.py
msgid "LDAP Source"
@@ -2464,6 +2484,22 @@ msgstr "Mappage de propriété source LDAP"
msgid "LDAP Source Property Mappings"
msgstr "Mappages de propriété source LDAP"
#: authentik/sources/ldap/models.py
msgid "User LDAP Source Connection"
msgstr "Connexion de l'utilisateur à la source LDAP"
#: authentik/sources/ldap/models.py
msgid "User LDAP Source Connections"
msgstr "Connexions de l'utilisateur à la source LDAP"
#: authentik/sources/ldap/models.py
msgid "Group LDAP Source Connection"
msgstr "Connexion du groupe à la source LDAP"
#: authentik/sources/ldap/models.py
msgid "Group LDAP Source Connections"
msgstr "Connexions du groupe à la source LDAP"
#: authentik/sources/ldap/signals.py
msgid "Password does not match Active Directory Complexity."
msgstr "Le mot de passe ne correspond pas à la complexité d'Active Directory."
@@ -3817,6 +3853,17 @@ msgstr ""
"Les évènements seront supprimés après cet interval. (Format : "
"weeks=3;days=2;hours=3,seconds=2)"
#: authentik/tenants/models.py
msgid "Reputation cannot decrease lower than this value. Zero or negative."
msgstr ""
"La réputation ne peut pas descendre en dessous de cette valeur. Zéro ou "
"négatif."
#: authentik/tenants/models.py
msgid "Reputation cannot increase higher than this value. Zero or positive."
msgstr ""
"La réputation ne peut pas monter au dessus de cette valeur. Zéro ou positif."
#: authentik/tenants/models.py
msgid "The option configures the footer links on the flow executor pages."
msgstr ""

Binary file not shown.

Binary file not shown.

View File

@@ -15,7 +15,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-04-11 00:10+0000\n"
"POT-Creation-Date: 2025-04-15 00:11+0000\n"
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
"Last-Translator: deluxghost, 2025\n"
"Language-Team: Chinese Simplified (https://app.transifex.com/authentik/teams/119923/zh-Hans/)\n"
@@ -179,6 +179,7 @@ msgid "User's display name."
msgstr "用户的显示名称。"
#: authentik/core/models.py authentik/providers/oauth2/models.py
#: authentik/rbac/models.py
msgid "User"
msgstr "用户"
@@ -345,6 +346,18 @@ msgstr "属性映射"
msgid "Property Mappings"
msgstr "属性映射"
#: authentik/core/models.py
msgid "session data"
msgstr "会话数据"
#: authentik/core/models.py
msgid "Session"
msgstr "会话"
#: authentik/core/models.py
msgid "Sessions"
msgstr "会话"
#: authentik/core/models.py
msgid "Authenticated Session"
msgstr "已认证会话"
@@ -1243,11 +1256,11 @@ msgstr "正在等待身份验证…"
msgid ""
"You're already authenticating in another tab. This page will refresh once "
"authentication is completed."
msgstr ""
msgstr "您正在另一个标签页中验证身份。身份验证完成后,此页面会刷新。"
#: authentik/policies/templates/policies/buffer.html
msgid "Authenticate in this tab"
msgstr ""
msgstr "在此标签页中验证身份"
#: authentik/policies/templates/policies/denied.html
msgid "Permission denied"
@@ -1999,6 +2012,10 @@ msgstr "角色"
msgid "Roles"
msgstr "角色"
#: authentik/rbac/models.py
msgid "Initial Permissions"
msgstr "初始权限"
#: authentik/rbac/models.py
msgid "System permission"
msgstr "系统权限"
@@ -2227,7 +2244,7 @@ msgid ""
"Lookup group membership based on a user attribute instead of a group "
"attribute. This allows nested group resolution on systems like FreeIPA and "
"Active Directory"
msgstr ""
msgstr "基于用户属性而非组属性查询组成员身份。这允许在 FreeIPA 或 Active Directory 等系统上支持嵌套组决策"
#: authentik/sources/ldap/models.py
msgid "LDAP Source"
@@ -2245,6 +2262,22 @@ msgstr "LDAP 源属性映射"
msgid "LDAP Source Property Mappings"
msgstr "LDAP 源属性映射"
#: authentik/sources/ldap/models.py
msgid "User LDAP Source Connection"
msgstr "用户 LDAP 源连接"
#: authentik/sources/ldap/models.py
msgid "User LDAP Source Connections"
msgstr "用户 LDAP 源连接"
#: authentik/sources/ldap/models.py
msgid "Group LDAP Source Connection"
msgstr "组 LDAP 源连接"
#: authentik/sources/ldap/models.py
msgid "Group LDAP Source Connections"
msgstr "组 LDAP 源连接"
#: authentik/sources/ldap/signals.py
msgid "Password does not match Active Directory Complexity."
msgstr "密码与 Active Directory 复杂度不匹配。"
@@ -3505,6 +3538,14 @@ msgid ""
"weeks=3;days=2;hours=3,seconds=2)."
msgstr "事件会在多久后被删除。格式weeks=3;days=2;hours=3,seconds=2。"
#: authentik/tenants/models.py
msgid "Reputation cannot decrease lower than this value. Zero or negative."
msgstr "信誉无法降低到此值以下。可为零或负数。"
#: authentik/tenants/models.py
msgid "Reputation cannot increase higher than this value. Zero or positive."
msgstr "信誉无法提高到此值以上。可为零或正数。"
#: authentik/tenants/models.py
msgid "The option configures the footer links on the flow executor pages."
msgstr "此选项配置流程执行器页面上的页脚链接。"

View File

@@ -14,7 +14,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-04-11 00:10+0000\n"
"POT-Creation-Date: 2025-04-15 00:11+0000\n"
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
"Last-Translator: deluxghost, 2025\n"
"Language-Team: Chinese (China) (https://app.transifex.com/authentik/teams/119923/zh_CN/)\n"
@@ -178,6 +178,7 @@ msgid "User's display name."
msgstr "用户的显示名称。"
#: authentik/core/models.py authentik/providers/oauth2/models.py
#: authentik/rbac/models.py
msgid "User"
msgstr "用户"
@@ -344,6 +345,18 @@ msgstr "属性映射"
msgid "Property Mappings"
msgstr "属性映射"
#: authentik/core/models.py
msgid "session data"
msgstr "会话数据"
#: authentik/core/models.py
msgid "Session"
msgstr "会话"
#: authentik/core/models.py
msgid "Sessions"
msgstr "会话"
#: authentik/core/models.py
msgid "Authenticated Session"
msgstr "已认证会话"
@@ -1998,6 +2011,10 @@ msgstr "角色"
msgid "Roles"
msgstr "角色"
#: authentik/rbac/models.py
msgid "Initial Permissions"
msgstr "初始权限"
#: authentik/rbac/models.py
msgid "System permission"
msgstr "系统权限"
@@ -2226,7 +2243,7 @@ msgid ""
"Lookup group membership based on a user attribute instead of a group "
"attribute. This allows nested group resolution on systems like FreeIPA and "
"Active Directory"
msgstr ""
msgstr "基于用户属性而非组属性查询组成员身份。这允许在 FreeIPA 或 Active Directory 等系统上支持嵌套组决策"
#: authentik/sources/ldap/models.py
msgid "LDAP Source"
@@ -2244,6 +2261,22 @@ msgstr "LDAP 源属性映射"
msgid "LDAP Source Property Mappings"
msgstr "LDAP 源属性映射"
#: authentik/sources/ldap/models.py
msgid "User LDAP Source Connection"
msgstr "用户 LDAP 源连接"
#: authentik/sources/ldap/models.py
msgid "User LDAP Source Connections"
msgstr "用户 LDAP 源连接"
#: authentik/sources/ldap/models.py
msgid "Group LDAP Source Connection"
msgstr "组 LDAP 源连接"
#: authentik/sources/ldap/models.py
msgid "Group LDAP Source Connections"
msgstr "组 LDAP 源连接"
#: authentik/sources/ldap/signals.py
msgid "Password does not match Active Directory Complexity."
msgstr "密码与 Active Directory 复杂度不匹配。"
@@ -3504,6 +3537,14 @@ msgid ""
"weeks=3;days=2;hours=3,seconds=2)."
msgstr "事件会在多久后被删除。格式weeks=3;days=2;hours=3,seconds=2。"
#: authentik/tenants/models.py
msgid "Reputation cannot decrease lower than this value. Zero or negative."
msgstr "信誉无法降低到此值以下。可为零或负数。"
#: authentik/tenants/models.py
msgid "Reputation cannot increase higher than this value. Zero or positive."
msgstr "信誉无法提高到此值以上。可为零或正数。"
#: authentik/tenants/models.py
msgid "The option configures the footer links on the flow executor pages."
msgstr "此选项配置流程执行器页面上的页脚链接。"

View File

@@ -47,7 +47,7 @@ dependencies = [
"opencontainers",
"packaging",
"paramiko",
"psycopg[c]",
"psycopg[c, pool]",
"pydantic",
"pydantic-scim",
"pyjwt",

View File

@@ -24209,6 +24209,270 @@ paths:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
/rbac/initial_permissions/:
get:
operationId: rbac_initial_permissions_list
description: InitialPermissions viewset
parameters:
- in: query
name: name
schema:
type: string
- name: ordering
required: false
in: query
description: Which field to use when ordering the results.
schema:
type: string
- name: page
required: false
in: query
description: A page number within the paginated result set.
schema:
type: integer
- name: page_size
required: false
in: query
description: Number of results to return per page.
schema:
type: integer
- name: search
required: false
in: query
description: A search term.
schema:
type: string
tags:
- rbac
security:
- authentik: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/PaginatedInitialPermissionsList'
description: ''
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
description: ''
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
post:
operationId: rbac_initial_permissions_create
description: InitialPermissions viewset
tags:
- rbac
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/InitialPermissionsRequest'
required: true
security:
- authentik: []
responses:
'201':
content:
application/json:
schema:
$ref: '#/components/schemas/InitialPermissions'
description: ''
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
description: ''
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
/rbac/initial_permissions/{id}/:
get:
operationId: rbac_initial_permissions_retrieve
description: InitialPermissions viewset
parameters:
- in: path
name: id
schema:
type: integer
description: A unique integer value identifying this Initial Permissions.
required: true
tags:
- rbac
security:
- authentik: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/InitialPermissions'
description: ''
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
description: ''
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
put:
operationId: rbac_initial_permissions_update
description: InitialPermissions viewset
parameters:
- in: path
name: id
schema:
type: integer
description: A unique integer value identifying this Initial Permissions.
required: true
tags:
- rbac
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/InitialPermissionsRequest'
required: true
security:
- authentik: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/InitialPermissions'
description: ''
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
description: ''
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
patch:
operationId: rbac_initial_permissions_partial_update
description: InitialPermissions viewset
parameters:
- in: path
name: id
schema:
type: integer
description: A unique integer value identifying this Initial Permissions.
required: true
tags:
- rbac
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/PatchedInitialPermissionsRequest'
security:
- authentik: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/InitialPermissions'
description: ''
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
description: ''
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
delete:
operationId: rbac_initial_permissions_destroy
description: InitialPermissions viewset
parameters:
- in: path
name: id
schema:
type: integer
description: A unique integer value identifying this Initial Permissions.
required: true
tags:
- rbac
security:
- authentik: []
responses:
'204':
description: No response body
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
description: ''
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
/rbac/initial_permissions/{id}/used_by/:
get:
operationId: rbac_initial_permissions_used_by_list
description: Get a list of all objects that use this object
parameters:
- in: path
name: id
schema:
type: integer
description: A unique integer value identifying this Initial Permissions.
required: true
tags:
- rbac
security:
- authentik: []
responses:
'200':
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/UsedBy'
description: ''
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
description: ''
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
/rbac/permissions/:
get:
operationId: rbac_permissions_list
@@ -24370,6 +24634,7 @@ paths:
- authentik_providers_scim.scimmapping
- authentik_providers_scim.scimprovider
- authentik_providers_ssf.ssfprovider
- authentik_rbac.initialpermissions
- authentik_rbac.role
- authentik_sources_kerberos.groupkerberossourceconnection
- authentik_sources_kerberos.kerberossource
@@ -24616,6 +24881,7 @@ paths:
- authentik_providers_scim.scimmapping
- authentik_providers_scim.scimprovider
- authentik_providers_ssf.ssfprovider
- authentik_rbac.initialpermissions
- authentik_rbac.role
- authentik_sources_kerberos.groupkerberossourceconnection
- authentik_sources_kerberos.kerberossource
@@ -41844,6 +42110,11 @@ components:
format: uuid
required:
- name
AuthorizationCodeAuthMethodEnum:
enum:
- basic_auth
- post_body
type: string
AutoSubmitChallengeResponseRequest:
type: object
description: Pseudo class for autosubmit response
@@ -45974,6 +46245,63 @@ components:
minLength: 1
required:
- reason
InitialPermissions:
type: object
description: InitialPermissions serializer
properties:
pk:
type: integer
readOnly: true
title: ID
name:
type: string
maxLength: 150
mode:
$ref: '#/components/schemas/InitialPermissionsModeEnum'
role:
type: string
format: uuid
permissions:
type: array
items:
type: integer
permissions_obj:
type: array
items:
$ref: '#/components/schemas/Permission'
readOnly: true
required:
- mode
- name
- permissions_obj
- pk
- role
InitialPermissionsModeEnum:
enum:
- user
- role
type: string
InitialPermissionsRequest:
type: object
description: InitialPermissions serializer
properties:
name:
type: string
minLength: 1
maxLength: 150
mode:
$ref: '#/components/schemas/InitialPermissionsModeEnum'
role:
type: string
format: uuid
permissions:
type: array
items:
type: integer
required:
- mode
- name
- role
InstallID:
type: object
properties:
@@ -47662,6 +47990,7 @@ components:
- authentik_providers_scim.scimprovider
- authentik_providers_scim.scimmapping
- authentik_rbac.role
- authentik_rbac.initialpermissions
- authentik_sources_kerberos.kerberossource
- authentik_sources_kerberos.kerberossourcepropertymapping
- authentik_sources_kerberos.userkerberossourceconnection
@@ -48418,6 +48747,11 @@ components:
oidc_jwks_url:
type: string
oidc_jwks: {}
authorization_code_auth_method:
allOf:
- $ref: '#/components/schemas/AuthorizationCodeAuthMethodEnum'
description: How to perform authentication during an authorization_code
token request flow
required:
- callback_url
- component
@@ -48587,6 +48921,11 @@ components:
oidc_jwks_url:
type: string
oidc_jwks: {}
authorization_code_auth_method:
allOf:
- $ref: '#/components/schemas/AuthorizationCodeAuthMethodEnum'
description: How to perform authentication during an authorization_code
token request flow
required:
- consumer_key
- consumer_secret
@@ -49390,6 +49729,18 @@ components:
required:
- pagination
- results
PaginatedInitialPermissionsList:
type: object
properties:
pagination:
$ref: '#/components/schemas/Pagination'
results:
type: array
items:
$ref: '#/components/schemas/InitialPermissions'
required:
- pagination
- results
PaginatedInvitationList:
type: object
properties:
@@ -51939,6 +52290,23 @@ components:
type: boolean
description: When enabled, the stage will succeed and continue even when
incorrect user info is entered.
PatchedInitialPermissionsRequest:
type: object
description: InitialPermissions serializer
properties:
name:
type: string
minLength: 1
maxLength: 150
mode:
$ref: '#/components/schemas/InitialPermissionsModeEnum'
role:
type: string
format: uuid
permissions:
type: array
items:
type: integer
PatchedInvitationRequest:
type: object
description: Invitation Serializer
@@ -52656,6 +53024,11 @@ components:
oidc_jwks_url:
type: string
oidc_jwks: {}
authorization_code_auth_method:
allOf:
- $ref: '#/components/schemas/AuthorizationCodeAuthMethodEnum'
description: How to perform authentication during an authorization_code
token request flow
PatchedOutpostRequest:
type: object
description: Outpost Serializer
@@ -54104,6 +54477,21 @@ components:
type: string
required:
- id
PermissionRequest:
type: object
description: Global permission
properties:
name:
type: string
minLength: 1
maxLength: 255
codename:
type: string
minLength: 1
maxLength: 100
required:
- codename
- name
PlexAuthenticationChallenge:
type: object
description: Challenge shown to the user in identification stage

79
uv.lock generated
View File

@@ -210,7 +210,7 @@ dependencies = [
{ name = "opencontainers" },
{ name = "packaging" },
{ name = "paramiko" },
{ name = "psycopg", extra = ["c"] },
{ name = "psycopg", extra = ["c", "pool"] },
{ name = "pydantic" },
{ name = "pydantic-scim" },
{ name = "pyjwt" },
@@ -308,7 +308,7 @@ requires-dist = [
{ name = "opencontainers", git = "https://github.com/BeryJu/oci-python?rev=c791b19056769cd67957322806809ab70f5bead8" },
{ name = "packaging" },
{ name = "paramiko" },
{ name = "psycopg", extras = ["c"] },
{ name = "psycopg", extras = ["c", "pool"] },
{ name = "pydantic" },
{ name = "pydantic-scim" },
{ name = "pyjwt" },
@@ -558,30 +558,30 @@ wheels = [
[[package]]
name = "boto3"
version = "1.37.33"
version = "1.37.34"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "botocore" },
{ name = "jmespath" },
{ name = "s3transfer" },
]
sdist = { url = "https://files.pythonhosted.org/packages/89/74/001695948752bc1b5357677eb2635e059f464b22c3eb5f9411ec4e8c48a3/boto3-1.37.33.tar.gz", hash = "sha256:4390317a1578af73f1514651bd180ba25802dcbe0a23deafa13851d54d3c3203", size = 111676 }
sdist = { url = "https://files.pythonhosted.org/packages/39/5d/6b1ca20ba4da350799509a69f2d295ae11d5ec08a98e82f74b5708a8180c/boto3-1.37.34.tar.gz", hash = "sha256:94ca07328474db3fa605eb99b011512caa73f7161740d365a1f00cfebfb6dd90", size = 111701 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/1f/e7/e660fac728570c926c4a12fa1ae8bffde7300d4817942bbd7871a6ebd4e2/boto3-1.37.33-py3-none-any.whl", hash = "sha256:7b1b1bc69762975824e5a5d570880abebf634f7594f88b3dc175e8800f35be1a", size = 139920 },
{ url = "https://files.pythonhosted.org/packages/cb/2e/ad43d1e87d46d11dcf4104f97b9a7f6beb38a52a0e752edfadf3eb8b6e38/boto3-1.37.34-py3-none-any.whl", hash = "sha256:586bfa72a00601c04067f9adcbb08ecaf63b05b7d731103f33cb2ce0d6950b1b", size = 139920 },
]
[[package]]
name = "botocore"
version = "1.37.33"
version = "1.37.34"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "jmespath" },
{ name = "python-dateutil" },
{ name = "urllib3" },
]
sdist = { url = "https://files.pythonhosted.org/packages/f4/1d/0c539ae261d2f8fe8b47c358b369ec58645bf0ea96b78825365e48675b67/botocore-1.37.33.tar.gz", hash = "sha256:09b213b0d0500040f85c7daee912ea767c724e43ed61909e624c803ff6925222", size = 13817305 }
sdist = { url = "https://files.pythonhosted.org/packages/ca/60/9ec251a0e2d3994f3eac8bd9741576757c3aad189abbdec8fab6011f5a1a/botocore-1.37.34.tar.gz", hash = "sha256:2909b6dbf9c90347c71a6fa0364acee522d6a7664f13d6f7996c9dd1b1f46fac", size = 13817141 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/21/93/425fb149fb969f07804f60cb1931d8aab197eb5f45dce821cbbbffc49207/botocore-1.37.33-py3-none-any.whl", hash = "sha256:4a167dfecae51e9140de24067de1c339acde5ade3dad524a4600ac2c72055e23", size = 13482312 },
{ url = "https://files.pythonhosted.org/packages/e8/51/19fff717cc5000708c4ce3d081bb0e63ca117c6823975b33101d52fdd9f5/botocore-1.37.34-py3-none-any.whl", hash = "sha256:bd9af0db1097befd2028ba8525e32cacc04f26ccb9dbd5d48d6ecd05bc16c27a", size = 13483679 },
]
[[package]]
@@ -1370,16 +1370,16 @@ wheels = [
[[package]]
name = "google-auth"
version = "2.38.0"
version = "2.39.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "cachetools" },
{ name = "pyasn1-modules" },
{ name = "rsa" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c6/eb/d504ba1daf190af6b204a9d4714d457462b486043744901a6eeea711f913/google_auth-2.38.0.tar.gz", hash = "sha256:8285113607d3b80a3f1543b75962447ba8a09fe85783432a784fdeef6ac094c4", size = 270866 }
sdist = { url = "https://files.pythonhosted.org/packages/cb/8e/8f45c9a32f73e786e954b8f9761c61422955d23c45d1e8c347f9b4b59e8e/google_auth-2.39.0.tar.gz", hash = "sha256:73222d43cdc35a3aeacbfdcaf73142a97839f10de930550d89ebfe1d0a00cde7", size = 274834 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/9d/47/603554949a37bca5b7f894d51896a9c534b9eab808e2520a748e081669d0/google_auth-2.38.0-py2.py3-none-any.whl", hash = "sha256:e7dae6694313f434a2727bf2906f27ad259bae090d7aa896590d86feec3d9d4a", size = 210770 },
{ url = "https://files.pythonhosted.org/packages/ce/12/ad37a1ef86006d0a0117fc06a4a00bd461c775356b534b425f00dde208ea/google_auth-2.39.0-py2.py3-none-any.whl", hash = "sha256:0150b6711e97fb9f52fe599f55648950cc4540015565d8fbb31be2ad6e1548a2", size = 212319 },
]
[[package]]
@@ -2009,7 +2009,7 @@ wheels = [
[[package]]
name = "msgraph-sdk"
version = "1.27.0"
version = "1.28.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "azure-identity" },
@@ -2019,9 +2019,9 @@ dependencies = [
{ name = "microsoft-kiota-serialization-text" },
{ name = "msgraph-core" },
]
sdist = { url = "https://files.pythonhosted.org/packages/a5/5d/678e6e95151ebc012a8e00164ea85c3c638c3e9d42baf76bb0efbde4fce2/msgraph_sdk-1.27.0.tar.gz", hash = "sha256:92c3d168a2842eec631c772c2825864aa53ca54ea417935a6a9d2d1e50ede6f5", size = 6121021 }
sdist = { url = "https://files.pythonhosted.org/packages/b9/41/40bb3c630ca026182aefd79a9862ef4a1917b1161c83690c858d714788f5/msgraph_sdk-1.28.0.tar.gz", hash = "sha256:b2d64b7bd711ad285fc2c090dd524853a026848732e1c83874fe34561805350d", size = 6121069 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/0b/ec/a2a7bda2e5828b16cc561aee996b2ee28724e5921bf571ae1846bafde805/msgraph_sdk-1.27.0-py3-none-any.whl", hash = "sha256:d5e91ef46fc7b95ae8e003cc3a27793075f4efeabd65611ccb9dad6976f76e99", size = 25091359 },
{ url = "https://files.pythonhosted.org/packages/a8/58/d8e9593ea81779d503831b5b06c8d9881d5affefe3df99ca20112c969e6f/msgraph_sdk-1.28.0-py3-none-any.whl", hash = "sha256:bd33b186371dfa8ed6375dfda92eef0931485633e69b06c001ce3c2fd3658f18", size = 25091309 },
]
[[package]]
@@ -2084,42 +2084,42 @@ source = { git = "https://github.com/BeryJu/oci-python?rev=c791b19056769cd679573
[[package]]
name = "opentelemetry-api"
version = "1.32.0"
version = "1.32.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "deprecated" },
{ name = "importlib-metadata" },
]
sdist = { url = "https://files.pythonhosted.org/packages/7b/34/e701d77900123af17a11dbaf0c9f527fa7ef94b8f02b2c55bed94477890a/opentelemetry_api-1.32.0.tar.gz", hash = "sha256:2623280c916f9b19cad0aa4280cb171265f19fd2909b0d47e4f06f7c83b02cb5", size = 64134 }
sdist = { url = "https://files.pythonhosted.org/packages/42/40/2359245cd33641c2736a0136a50813352d72f3fc209de28fb226950db4a1/opentelemetry_api-1.32.1.tar.gz", hash = "sha256:a5be71591694a4d9195caf6776b055aa702e964d961051a0715d05f8632c32fb", size = 64138 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/fe/e8/d05fd19c1c7e7e230ab44c366791179fd64c843bc587c257a56e853893c5/opentelemetry_api-1.32.0-py3-none-any.whl", hash = "sha256:15df743c765078611f376037b0d9111ec5c1febf2ec9440cdd919370faa1ce55", size = 65285 },
{ url = "https://files.pythonhosted.org/packages/12/f2/89ea3361a305466bc6460a532188830351220b5f0851a5fa133155c16eca/opentelemetry_api-1.32.1-py3-none-any.whl", hash = "sha256:bbd19f14ab9f15f0e85e43e6a958aa4cb1f36870ee62b7fd205783a112012724", size = 65287 },
]
[[package]]
name = "opentelemetry-sdk"
version = "1.32.0"
version = "1.32.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "opentelemetry-api" },
{ name = "opentelemetry-semantic-conventions" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/e8/0c/842aed73035cab0302ec70057f3180f4f023974d74bd9764ef3046f358fb/opentelemetry_sdk-1.32.0.tar.gz", hash = "sha256:5ff07fb371d1ab1189fa7047702e2e888b5403c5efcbb18083cae0d5aa5f58d2", size = 161043 }
sdist = { url = "https://files.pythonhosted.org/packages/a3/65/2069caef9257fae234ca0040d945c741aa7afbd83a7298ee70fc0bc6b6f4/opentelemetry_sdk-1.32.1.tar.gz", hash = "sha256:8ef373d490961848f525255a42b193430a0637e064dd132fd2a014d94792a092", size = 161044 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ee/6a/b8cb562234bd94bcf12ad3058ef7f31319b94a8df65130ce9cc2ff3c8d55/opentelemetry_sdk-1.32.0-py3-none-any.whl", hash = "sha256:ed252d035c22a15536c1f603ca089298daab60850fc2f5ddfa95d95cc1c043ea", size = 118990 },
{ url = "https://files.pythonhosted.org/packages/dc/00/d3976cdcb98027aaf16f1e980e54935eb820872792f0eaedd4fd7abb5964/opentelemetry_sdk-1.32.1-py3-none-any.whl", hash = "sha256:bba37b70a08038613247bc42beee5a81b0ddca422c7d7f1b097b32bf1c7e2f17", size = 118989 },
]
[[package]]
name = "opentelemetry-semantic-conventions"
version = "0.53b0"
version = "0.53b1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "deprecated" },
{ name = "opentelemetry-api" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c2/c4/213d23239df175b420b74c6e25899c482701e6614822dc51f8c20dae7e2d/opentelemetry_semantic_conventions-0.53b0.tar.gz", hash = "sha256:05b7908e1da62d72f9bf717ed25c72f566fe005a2dd260c61b11e025f2552cf6", size = 114343 }
sdist = { url = "https://files.pythonhosted.org/packages/5e/b6/3c56e22e9b51bcb89edab30d54830958f049760bbd9ab0a759cece7bca88/opentelemetry_semantic_conventions-0.53b1.tar.gz", hash = "sha256:4c5a6fede9de61211b2e9fc1e02e8acacce882204cd770177342b6a3be682992", size = 114350 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/7c/23/0bef11f394f828f910f32567d057f097dbaba23edf33114018a380a0d0bf/opentelemetry_semantic_conventions-0.53b0-py3-none-any.whl", hash = "sha256:561da89f766ab51615c0e72b12329e0a1bc16945dbd62c8646ffc74e36a1edff", size = 188441 },
{ url = "https://files.pythonhosted.org/packages/27/6b/a8fb94760ef8da5ec283e488eb43235eac3ae7514385a51b6accf881e671/opentelemetry_semantic_conventions-0.53b1-py3-none-any.whl", hash = "sha256:21df3ed13f035f8f3ea42d07cbebae37020367a53b47f1ebee3b10a381a00208", size = 188443 },
]
[[package]]
@@ -2243,14 +2243,14 @@ wheels = [
[[package]]
name = "prompt-toolkit"
version = "3.0.50"
version = "3.0.51"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "wcwidth" },
]
sdist = { url = "https://files.pythonhosted.org/packages/a1/e1/bd15cb8ffdcfeeb2bdc215de3c3cffca11408d829e4b8416dcfe71ba8854/prompt_toolkit-3.0.50.tar.gz", hash = "sha256:544748f3860a2623ca5cd6d2795e7a14f3d0e1c3c9728359013f79877fc89bab", size = 429087 }
sdist = { url = "https://files.pythonhosted.org/packages/bb/6e/9d084c929dfe9e3bfe0c6a47e31f78a25c54627d64a66e884a8bf5474f1c/prompt_toolkit-3.0.51.tar.gz", hash = "sha256:931a162e3b27fc90c86f1b48bb1fb2c528c2761475e57c9c06de13311c7b54ed", size = 428940 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e4/ea/d836f008d33151c7a1f62caf3d8dd782e4d15f6a43897f64480c2b8de2ad/prompt_toolkit-3.0.50-py3-none-any.whl", hash = "sha256:9b6427eb19e479d98acff65196a307c555eb567989e6d88ebbb1b509d9779198", size = 387816 },
{ url = "https://files.pythonhosted.org/packages/ce/4f/5249960887b1fbe561d9ff265496d170b55a735b76724f10ef19f9e40716/prompt_toolkit-3.0.51-py3-none-any.whl", hash = "sha256:52742911fde84e2d423e2f9a4cf1de7d7ac4e51958f648d9540e0fb8db077b07", size = 387810 },
]
[[package]]
@@ -2321,6 +2321,9 @@ wheels = [
c = [
{ name = "psycopg-c", marker = "implementation_name != 'pypy'" },
]
pool = [
{ name = "psycopg-pool" },
]
[[package]]
name = "psycopg-c"
@@ -2328,6 +2331,18 @@ version = "3.2.6"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/2f/f1/367a2429af2b97f6a46dc116206cd3b1cf668fca7ff3c22b979ea0686427/psycopg_c-3.2.6.tar.gz", hash = "sha256:b5fd4ce70f82766a122ca5076a36c4d5818eaa9df9bf76870bc83a064ffaed3a", size = 609304 }
[[package]]
name = "psycopg-pool"
version = "3.2.6"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/cf/13/1e7850bb2c69a63267c3dbf37387d3f71a00fd0e2fa55c5db14d64ba1af4/psycopg_pool-3.2.6.tar.gz", hash = "sha256:0f92a7817719517212fbfe2fd58b8c35c1850cdd2a80d36b581ba2085d9148e5", size = 29770 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/47/fd/4feb52a55c1a4bd748f2acaed1903ab54a723c47f6d0242780f4d97104d4/psycopg_pool-3.2.6-py3-none-any.whl", hash = "sha256:5887318a9f6af906d041a0b1dc1c60f8f0dda8340c2572b74e10907b51ed5da7", size = 38252 },
]
[[package]]
name = "publication"
version = "0.0.3"
@@ -2744,14 +2759,14 @@ wheels = [
[[package]]
name = "rsa"
version = "4.9"
version = "4.9.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pyasn1" },
]
sdist = { url = "https://files.pythonhosted.org/packages/aa/65/7d973b89c4d2351d7fb232c2e452547ddfa243e93131e7cfa766da627b52/rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21", size = 29711 }
sdist = { url = "https://files.pythonhosted.org/packages/da/8a/22b7beea3ee0d44b1916c0c1cb0ee3af23b700b6da9f04991899d0c555d4/rsa-4.9.1.tar.gz", hash = "sha256:e7bdbfdb5497da4c07dfd35530e1a902659db6ff241e39d9953cad06ebd0ae75", size = 29034 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/49/97/fa78e3d2f65c02c8e1268b9aba606569fe97f6c8f7c2d74394553347c145/rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7", size = 34315 },
{ url = "https://files.pythonhosted.org/packages/64/8d/0133e4eb4beed9e425d9a98ed6e081a55d195481b7632472be1af08d2f6b/rsa-4.9.1-py3-none-any.whl", hash = "sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762", size = 34696 },
]
[[package]]
@@ -2822,15 +2837,15 @@ wheels = [
[[package]]
name = "sentry-sdk"
version = "2.25.1"
version = "2.26.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "certifi" },
{ name = "urllib3" },
]
sdist = { url = "https://files.pythonhosted.org/packages/85/2f/a0f732270cc7c1834f5ec45539aec87c360d5483a8bd788217a9102ccfbd/sentry_sdk-2.25.1.tar.gz", hash = "sha256:f9041b7054a7cf12d41eadabe6458ce7c6d6eea7a97cfe1b760b6692e9562cf0", size = 322190 }
sdist = { url = "https://files.pythonhosted.org/packages/85/26/099631caa51abffb1fd9e08c2138bc6681d3f288a5936c2fc4e054729611/sentry_sdk-2.26.1.tar.gz", hash = "sha256:759e019c41551a21519a95e6cef6d91fb4af1054761923dadaee2e6eca9c02c7", size = 323099 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/96/b6/84049ab0967affbc7cc7590d86ae0170c1b494edb69df8786707100420e5/sentry_sdk-2.25.1-py2.py3-none-any.whl", hash = "sha256:60b016d0772789454dc55a284a6a44212044d4a16d9f8448725effee97aaf7f6", size = 339851 },
{ url = "https://files.pythonhosted.org/packages/23/32/0a30b4fafdb3d26d133f99bb566aaa6000004ee7f2c4b72aafea9237ab7e/sentry_sdk-2.26.1-py2.py3-none-any.whl", hash = "sha256:e99390e3f217d13ddcbaeaed08789f1ca614d663b345b9da42e35ad6b60d696a", size = 340558 },
]
[[package]]

1138
web/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,14 @@
{
"name": "@goauthentik/web",
"version": "0.0.0",
"overrides": {
"rapidoc": {
"@apitools/openapi-parser@": "0.0.37"
},
"chromedriver": {
"axios": "^1.8.4"
}
},
"dependencies": {
"@codemirror/lang-css": "^6.3.1",
"@codemirror/lang-html": "^6.4.9",
@@ -12,7 +20,7 @@
"@floating-ui/dom": "^1.6.11",
"@formatjs/intl-listformat": "^7.5.7",
"@fortawesome/fontawesome-free": "^6.6.0",
"@goauthentik/api": "^2025.2.4-1744288676",
"@goauthentik/api": "^2025.2.4-1744640358",
"@lit-labs/ssr": "^3.2.2",
"@lit/context": "^1.1.2",
"@lit/localize": "^0.12.2",
@@ -42,7 +50,7 @@
"lit": "^3.2.0",
"md-front-matter": "^1.0.4",
"mermaid": "^11.4.1",
"rapidoc": "^9.3.7",
"rapidoc": "^9.3.8",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"rehype-highlight": "^7.0.2",

View File

@@ -131,6 +131,7 @@ export class AkAdminSidebar extends WithCapabilitiesConfig(WithVersion(AKElement
["/identity/users", msg("Users"), [`^/identity/users/(?<id>${ID_REGEX})$`]],
["/identity/groups", msg("Groups"), [`^/identity/groups/(?<id>${UUID_REGEX})$`]],
["/identity/roles", msg("Roles"), [`^/identity/roles/(?<id>${UUID_REGEX})$`]],
["/identity/initial-permissions", msg("Initial Permissions"), [`^/identity/initial-permissions/(?<id>${ID_REGEX})$`]],
["/core/sources", msg("Federation and Social login"), [`^/core/sources/(?<slug>${SLUG_REGEX})$`]],
["/core/tokens", msg("Tokens and App passwords")],
["/flow/stages/invitations", msg("Invitations")]]],

View File

@@ -84,6 +84,10 @@ export const ROUTES: Route[] = [
await import("@goauthentik/admin/roles/RoleListPage");
return html`<ak-role-list></ak-role-list>`;
}),
new Route(new RegExp("^/identity/initial-permissions$"), async () => {
await import("@goauthentik/admin/rbac/InitialPermissionsListPage");
return html`<ak-initial-permissions-list></ak-initial-permissions-list>`;
}),
new Route(new RegExp(`^/identity/roles/(?<id>${UUID_REGEX})$`), async (args) => {
await import("@goauthentik/admin/roles/RoleViewPage");
return html`<ak-role-view roleId=${args.id}></ak-role-view>`;

View File

@@ -0,0 +1,150 @@
import { InitialPermissionsModeToLabel } from "@goauthentik/admin/rbac/utils";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import "@goauthentik/elements/ak-dual-select/ak-dual-select-provider";
import { DataProvision, DualSelectPair } from "@goauthentik/elements/ak-dual-select/types";
import "@goauthentik/elements/chips/Chip";
import "@goauthentik/elements/chips/ChipGroup";
import "@goauthentik/elements/forms/HorizontalFormElement";
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
import "@goauthentik/elements/forms/SearchSelect";
import { msg } from "@lit/localize";
import { TemplateResult, html } from "lit";
import { customElement } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
import {
InitialPermissions,
InitialPermissionsModeEnum,
Permission,
RbacApi,
RbacRolesListRequest,
Role,
} from "@goauthentik/api";
export function rbacPermissionPair(item: Permission): DualSelectPair {
return [item.id.toString(), html`<div class="selection-main">${item.name}</div>`, item.name];
}
@customElement("ak-initial-permissions-form")
export class InitialPermissionsForm extends ModelForm<InitialPermissions, string> {
loadInstance(pk: string): Promise<InitialPermissions> {
return new RbacApi(DEFAULT_CONFIG).rbacInitialPermissionsRetrieve({
id: Number(pk),
});
}
getSuccessMessage(): string {
return this.instance
? msg("Successfully updated initial permissions.")
: msg("Successfully created initial permissions.");
}
async send(data: InitialPermissions): Promise<InitialPermissions> {
if (this.instance?.pk) {
return new RbacApi(DEFAULT_CONFIG).rbacInitialPermissionsPartialUpdate({
id: this.instance.pk,
patchedInitialPermissionsRequest: data,
});
} else {
return new RbacApi(DEFAULT_CONFIG).rbacInitialPermissionsCreate({
initialPermissionsRequest: data,
});
}
}
renderForm(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
<ak-form-element-horizontal label=${msg("Name")} required name="name">
<input
type="text"
value="${ifDefined(this.instance?.name)}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${msg("Role")} required name="role">
<ak-search-select
.fetchObjects=${async (query?: string): Promise<Role[]> => {
const args: RbacRolesListRequest = {
ordering: "name",
};
if (query !== undefined) {
args.search = query;
}
const users = await new RbacApi(DEFAULT_CONFIG).rbacRolesList(args);
return users.results;
}}
.renderElement=${(role: Role): string => {
return role.name;
}}
.renderDescription=${(role: Role): TemplateResult => {
return html`${role.name}`;
}}
.value=${(role: Role | undefined): string | undefined => {
return role?.pk;
}}
.selected=${(role: Role): boolean => {
return this.instance?.role === role.pk;
}}
>
</ak-search-select>
<p class="pf-c-form__helper-text">
${msg(
"When a user with the selected Role creates an object, the Initial Permissions will be applied to that object.",
)}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${msg("Mode")} required name="mode">
<select class="pf-c-form-control">
<option
value=${InitialPermissionsModeEnum.User}
?selected=${this.instance?.mode === InitialPermissionsModeEnum.User}
>
${InitialPermissionsModeToLabel(InitialPermissionsModeEnum.User)}
</option>
<option
value=${InitialPermissionsModeEnum.Role}
?selected=${this.instance?.mode === InitialPermissionsModeEnum.Role}
>
${InitialPermissionsModeToLabel(InitialPermissionsModeEnum.Role)}
</option>
</select>
<p class="pf-c-form__helper-text">
${msg(
"The Initial Permissions can either be placed on the User creating the object, or the Role selected in the previous field.",
)}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${msg("Permissions")} name="permissions">
<ak-dual-select-provider
.provider=${(page: number, search?: string): Promise<DataProvision> => {
return new RbacApi(DEFAULT_CONFIG)
.rbacPermissionsList({
page: page,
search: search,
})
.then((results) => {
return {
pagination: results.pagination,
options: results.results.map(rbacPermissionPair),
};
});
}}
.selected=${(this.instance?.permissionsObj ?? []).map(rbacPermissionPair)}
available-label="${msg("Available Permissions")}"
selected-label="${msg("Selected Permissions")}"
></ak-dual-select-provider>
<p class="pf-c-form__helper-text">
${msg("Permissions to grant when a new object is created.")}
</p>
</ak-form-element-horizontal>
</form>`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ak-initial-permissions-form": InitialPermissionsForm;
}
}

View File

@@ -0,0 +1,115 @@
import "@goauthentik/admin/rbac/InitialPermissionsForm";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import "@goauthentik/elements/buttons/SpinnerButton";
import "@goauthentik/elements/forms/DeleteBulkForm";
import "@goauthentik/elements/forms/ModalForm";
import { PaginatedResponse } from "@goauthentik/elements/table/Table";
import { TableColumn } from "@goauthentik/elements/table/Table";
import { TablePage } from "@goauthentik/elements/table/TablePage";
import "@patternfly/elements/pf-tooltip/pf-tooltip.js";
import { msg } from "@lit/localize";
import { TemplateResult, html } from "lit";
import { customElement, property } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
import { InitialPermissions, RbacApi } from "@goauthentik/api";
@customElement("ak-initial-permissions-list")
export class InitialPermissionsListPage extends TablePage<InitialPermissions> {
checkbox = true;
clearOnRefresh = true;
searchEnabled(): boolean {
return true;
}
pageTitle(): string {
return msg("Initial Permissions");
}
pageDescription(): string {
return msg("Set initial permissions for newly created objects.");
}
pageIcon(): string {
return "fa fa-lock";
}
@property()
order = "name";
async apiEndpoint(): Promise<PaginatedResponse<InitialPermissions>> {
return new RbacApi(DEFAULT_CONFIG).rbacInitialPermissionsList(
await this.defaultEndpointConfig(),
);
}
columns(): TableColumn[] {
return [new TableColumn(msg("Name"), "name"), new TableColumn(msg("Actions"))];
}
renderToolbarSelected(): TemplateResult {
const disabled = this.selectedElements.length < 1;
return html`<ak-forms-delete-bulk
objectLabel=${msg("Initial Permissions")}
.objects=${this.selectedElements}
.usedBy=${(item: InitialPermissions) => {
return new RbacApi(DEFAULT_CONFIG).rbacInitialPermissionsUsedByList({
id: item.pk,
});
}}
.delete=${(item: InitialPermissions) => {
return new RbacApi(DEFAULT_CONFIG).rbacInitialPermissionsDestroy({
id: item.pk,
});
}}
>
<button ?disabled=${disabled} slot="trigger" class="pf-c-button pf-m-danger">
${msg("Delete")}
</button>
</ak-forms-delete-bulk>`;
}
render(): TemplateResult {
return html`<ak-page-header
icon=${this.pageIcon()}
header=${this.pageTitle()}
description=${ifDefined(this.pageDescription())}
>
</ak-page-header>
<section class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-c-card">${this.renderTable()}</div>
</section>`;
}
row(item: InitialPermissions): TemplateResult[] {
return [
html`${item.name}`,
html`<ak-forms-modal>
<span slot="submit"> ${msg("Update")} </span>
<span slot="header"> ${msg("Update Initial Permissions")} </span>
<ak-initial-permissions-form slot="form" .instancePk=${item.pk}>
</ak-initial-permissions-form>
<button slot="trigger" class="pf-c-button pf-m-plain">
<pf-tooltip position="top" content=${msg("Edit")}>
<i class="fas fa-edit"></i>
</pf-tooltip>
</button>
</ak-forms-modal>`,
];
}
renderObjectCreate(): TemplateResult {
return html`
<ak-forms-modal>
<span slot="submit"> ${msg("Create")} </span>
<span slot="header"> ${msg("Create Initial Permissions")} </span>
<ak-initial-permissions-form slot="form"> </ak-initial-permissions-form>
<button slot="trigger" class="pf-c-button pf-m-primary">${msg("Create")}</button>
</ak-forms-modal>
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"initial-permissions-list": InitialPermissionsListPage;
}
}

View File

@@ -0,0 +1,14 @@
import { msg } from "@lit/localize";
import { InitialPermissionsModeEnum } from "@goauthentik/api";
export function InitialPermissionsModeToLabel(mode: InitialPermissionsModeEnum): string {
switch (mode) {
case InitialPermissionsModeEnum.User:
return msg("User");
case InitialPermissionsModeEnum.Role:
return msg("Role");
case InitialPermissionsModeEnum.UnknownDefaultOpenApi:
return msg("Unknown Initial Permissions mode");
}
}

View File

@@ -7,6 +7,7 @@ import {
} from "@goauthentik/admin/sources/oauth/utils";
import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config";
import { first } from "@goauthentik/common/utils";
import "@goauthentik/components/ak-radio-input";
import "@goauthentik/elements/CodeMirror";
import { CodeMirrorMode } from "@goauthentik/elements/CodeMirror";
import {
@@ -16,6 +17,7 @@ import {
import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js";
import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement";
import "@goauthentik/elements/forms/Radio";
import "@goauthentik/elements/forms/SearchSelect";
import { msg } from "@lit/localize";
@@ -24,6 +26,7 @@ import { customElement, property, state } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
import {
AuthorizationCodeAuthMethodEnum,
FlowsInstancesListDesignationEnum,
GroupMatchingModeEnum,
OAuthSource,
@@ -36,6 +39,18 @@ import {
import { propertyMappingsProvider, propertyMappingsSelector } from "./OAuthSourceFormHelpers.js";
const authorizationCodeAuthMethodOptions = [
{
label: msg("HTTP Basic Auth"),
value: AuthorizationCodeAuthMethodEnum.BasicAuth,
default: true,
},
{
label: msg("Include the client ID and secret as request parameters"),
value: AuthorizationCodeAuthMethodEnum.PostBody,
},
];
@customElement("ak-source-oauth-form")
export class OAuthSourceForm extends WithCapabilitiesConfig(BaseSourceForm<OAuthSource>) {
async loadInstance(pk: string): Promise<OAuthSource> {
@@ -240,6 +255,19 @@ export class OAuthSourceForm extends WithCapabilitiesConfig(BaseSourceForm<OAuth
<p class="pf-c-form__helper-text">${msg("Raw JWKS data.")}</p>
</ak-form-element-horizontal>`
: html``}
${this.providerType.name === ProviderTypeEnum.Openidconnect
? html`<ak-radio-input
label=${msg("Authorization code authentication method")}
name="authorizationCodeAuthMethod"
required
.options=${authorizationCodeAuthMethodOptions}
.value=${this.instance?.authorizationCodeAuthMethod}
help=${msg(
"How to perform authentication during an authorization_code token request flow",
)}
>
</ak-radio-input>`
: html``}
</div>
</ak-form-group>`;
}

View File

@@ -1,4 +1,5 @@
import {
CSRFHeaderName,
CSRFMiddleware,
EventMiddleware,
LoggingMiddleware,
@@ -8,6 +9,10 @@ import { globalAK } from "@goauthentik/common/global";
import { Config, Configuration, CoreApi, CurrentBrand, RootApi } from "@goauthentik/api";
// HACK: Workaround for ESBuild not being able to hoist import statement across entrypoints.
// This can be removed after ESBuild uses a single build context for all entrypoints.
export { CSRFHeaderName };
let globalConfigPromise: Promise<Config> | undefined = Promise.resolve(globalAK().config);
export function config(): Promise<Config> {
if (!globalConfigPromise) {

View File

@@ -1,4 +1,7 @@
import { CSRFHeaderName } from "@goauthentik/common/api/middleware";
// sort-imports-ignore
import "rapidoc";
import { CSRFHeaderName } from "@goauthentik/common/api/config";
import { EVENT_THEME_CHANGE } from "@goauthentik/common/constants";
import { globalAK } from "@goauthentik/common/global";
import { first, getCookie } from "@goauthentik/common/utils";
@@ -6,7 +9,6 @@ import { Interface } from "@goauthentik/elements/Interface";
import "@goauthentik/elements/ak-locale-context";
import { DefaultBrand } from "@goauthentik/elements/sidebar/SidebarBrand";
import { themeImage } from "@goauthentik/elements/utils/images";
import "rapidoc";
import { msg } from "@lit/localize";
import { CSSResult, TemplateResult, css, html } from "lit";

View File

@@ -9086,6 +9086,54 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit>
<trans-unit id="s17359123e1f24504">
<source>Field which contains DNs of groups the user is a member of. This field is used to lookup groups from users, e.g. 'memberOf'. To lookup nested groups in an Active Directory environment use 'memberOf:1.2.840.113556.1.4.1941:'.</source>
</trans-unit>
<trans-unit id="s891cd64acabf23bf">
<source>Initial Permissions</source>
</trans-unit>
<trans-unit id="sedb57bf4b42a8e40">
<source>Unknown Initial Permissions mode</source>
</trans-unit>
<trans-unit id="s6ea6a64acb45dfdf">
<source>Successfully updated initial permissions.</source>
</trans-unit>
<trans-unit id="sfddf7896ab5938b6">
<source>Successfully created initial permissions.</source>
</trans-unit>
<trans-unit id="s5c5f240cbb6d0bae">
<source>When a user with the selected Role creates an object, the Initial Permissions will be applied to that object.</source>
</trans-unit>
<trans-unit id="sbf27294eef56ac81">
<source>The Initial Permissions can either be placed on the User creating the object, or the Role selected in the previous field.</source>
</trans-unit>
<trans-unit id="s93f04537efda1b24">
<source>Available Permissions</source>
</trans-unit>
<trans-unit id="s297bc57f9e494470">
<source>Selected Permissions</source>
</trans-unit>
<trans-unit id="s0ea71d53764d781c">
<source>Permissions to grant when a new object is created.</source>
</trans-unit>
<trans-unit id="s06fc21a40f5d7de1">
<source>Set initial permissions for newly created objects.</source>
</trans-unit>
<trans-unit id="sc7104a4d0fc35c7c">
<source>Update Initial Permissions</source>
</trans-unit>
<trans-unit id="s83fa005829a65be9">
<source>Create Initial Permissions</source>
</trans-unit>
<trans-unit id="se37ac6cf9c72f21a">
<source>Reputation: lower limit</source>
</trans-unit>
<trans-unit id="sa634ffa797037aac">
<source>Reputation cannot decrease lower than this value. Zero or negative.</source>
</trans-unit>
<trans-unit id="s862986ce8e70edd7">
<source>Reputation: upper limit</source>
</trans-unit>
<trans-unit id="sdd04913b3b46cf30">
<source>Reputation cannot increase higher than this value. Zero or positive.</source>
</trans-unit>
</body>
</file>

View File

@@ -7619,6 +7619,54 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit>
<trans-unit id="s17359123e1f24504">
<source>Field which contains DNs of groups the user is a member of. This field is used to lookup groups from users, e.g. 'memberOf'. To lookup nested groups in an Active Directory environment use 'memberOf:1.2.840.113556.1.4.1941:'.</source>
</trans-unit>
<trans-unit id="s891cd64acabf23bf">
<source>Initial Permissions</source>
</trans-unit>
<trans-unit id="sedb57bf4b42a8e40">
<source>Unknown Initial Permissions mode</source>
</trans-unit>
<trans-unit id="s6ea6a64acb45dfdf">
<source>Successfully updated initial permissions.</source>
</trans-unit>
<trans-unit id="sfddf7896ab5938b6">
<source>Successfully created initial permissions.</source>
</trans-unit>
<trans-unit id="s5c5f240cbb6d0bae">
<source>When a user with the selected Role creates an object, the Initial Permissions will be applied to that object.</source>
</trans-unit>
<trans-unit id="sbf27294eef56ac81">
<source>The Initial Permissions can either be placed on the User creating the object, or the Role selected in the previous field.</source>
</trans-unit>
<trans-unit id="s93f04537efda1b24">
<source>Available Permissions</source>
</trans-unit>
<trans-unit id="s297bc57f9e494470">
<source>Selected Permissions</source>
</trans-unit>
<trans-unit id="s0ea71d53764d781c">
<source>Permissions to grant when a new object is created.</source>
</trans-unit>
<trans-unit id="s06fc21a40f5d7de1">
<source>Set initial permissions for newly created objects.</source>
</trans-unit>
<trans-unit id="sc7104a4d0fc35c7c">
<source>Update Initial Permissions</source>
</trans-unit>
<trans-unit id="s83fa005829a65be9">
<source>Create Initial Permissions</source>
</trans-unit>
<trans-unit id="se37ac6cf9c72f21a">
<source>Reputation: lower limit</source>
</trans-unit>
<trans-unit id="sa634ffa797037aac">
<source>Reputation cannot decrease lower than this value. Zero or negative.</source>
</trans-unit>
<trans-unit id="s862986ce8e70edd7">
<source>Reputation: upper limit</source>
</trans-unit>
<trans-unit id="sdd04913b3b46cf30">
<source>Reputation cannot increase higher than this value. Zero or positive.</source>
</trans-unit>
</body>
</file>

View File

@@ -9178,6 +9178,54 @@ Las vinculaciones a grupos o usuarios se comparan con el usuario del evento.</ta
</trans-unit>
<trans-unit id="s17359123e1f24504">
<source>Field which contains DNs of groups the user is a member of. This field is used to lookup groups from users, e.g. 'memberOf'. To lookup nested groups in an Active Directory environment use 'memberOf:1.2.840.113556.1.4.1941:'.</source>
</trans-unit>
<trans-unit id="s891cd64acabf23bf">
<source>Initial Permissions</source>
</trans-unit>
<trans-unit id="sedb57bf4b42a8e40">
<source>Unknown Initial Permissions mode</source>
</trans-unit>
<trans-unit id="s6ea6a64acb45dfdf">
<source>Successfully updated initial permissions.</source>
</trans-unit>
<trans-unit id="sfddf7896ab5938b6">
<source>Successfully created initial permissions.</source>
</trans-unit>
<trans-unit id="s5c5f240cbb6d0bae">
<source>When a user with the selected Role creates an object, the Initial Permissions will be applied to that object.</source>
</trans-unit>
<trans-unit id="sbf27294eef56ac81">
<source>The Initial Permissions can either be placed on the User creating the object, or the Role selected in the previous field.</source>
</trans-unit>
<trans-unit id="s93f04537efda1b24">
<source>Available Permissions</source>
</trans-unit>
<trans-unit id="s297bc57f9e494470">
<source>Selected Permissions</source>
</trans-unit>
<trans-unit id="s0ea71d53764d781c">
<source>Permissions to grant when a new object is created.</source>
</trans-unit>
<trans-unit id="s06fc21a40f5d7de1">
<source>Set initial permissions for newly created objects.</source>
</trans-unit>
<trans-unit id="sc7104a4d0fc35c7c">
<source>Update Initial Permissions</source>
</trans-unit>
<trans-unit id="s83fa005829a65be9">
<source>Create Initial Permissions</source>
</trans-unit>
<trans-unit id="se37ac6cf9c72f21a">
<source>Reputation: lower limit</source>
</trans-unit>
<trans-unit id="sa634ffa797037aac">
<source>Reputation cannot decrease lower than this value. Zero or negative.</source>
</trans-unit>
<trans-unit id="s862986ce8e70edd7">
<source>Reputation: upper limit</source>
</trans-unit>
<trans-unit id="sdd04913b3b46cf30">
<source>Reputation cannot increase higher than this value. Zero or positive.</source>
</trans-unit>
</body>
</file>

View File

@@ -9696,12 +9696,79 @@ Les liaisons avec les groupes/utilisateurs sont vérifiées par rapport à l'uti
</trans-unit>
<trans-unit id="s783964a224796865">
<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,...'. When selecting 'Lookup using a user attribute', this should be a user attribute, otherwise a group attribute.</source>
<target>Champ qui contient les membres d'un groupe. Si vous utilisez le champ "memberUid", la valeur est censée contenir un nom distinctif relatif, par exemple 'memberUid=un-utilisateur' au lieu de 'memberUid=cn=un-utilisateur,ou=groups,...'. Lorsque "Recherche avec un attribut utilisateur" est sélectionné, cet attribut doit être un attribut utilisateur, sinon un attribut de groupe.</target>
</trans-unit>
<trans-unit id="s1d47b4f61ca53e8e">
<source>Lookup using user attribute</source>
<target>Recherche avec un attribut utilisateur</target>
</trans-unit>
<trans-unit id="s17359123e1f24504">
<source>Field which contains DNs of groups the user is a member of. This field is used to lookup groups from users, e.g. 'memberOf'. To lookup nested groups in an Active Directory environment use 'memberOf:1.2.840.113556.1.4.1941:'.</source>
<target>Champ contenant les DN des groupes dont l'utilisateur est membre. Ce champ est utilisé pour rechercher les groupes d'un utilisateur, par exemple 'memberOf'. Pour rechercher les groupes imbriqués dans un environnement Active Directory, utilisez 'memberOf:1.2.840.113556.1.4.1941:'.</target>
</trans-unit>
<trans-unit id="s891cd64acabf23bf">
<source>Initial Permissions</source>
<target>Permissions initiales</target>
</trans-unit>
<trans-unit id="sedb57bf4b42a8e40">
<source>Unknown Initial Permissions mode</source>
<target>Mode de permissions initiales inconnu</target>
</trans-unit>
<trans-unit id="s6ea6a64acb45dfdf">
<source>Successfully updated initial permissions.</source>
<target>Permissions initiales mises à jour avec succès.</target>
</trans-unit>
<trans-unit id="sfddf7896ab5938b6">
<source>Successfully created initial permissions.</source>
<target>Permissions initiales créées avec succès.</target>
</trans-unit>
<trans-unit id="s5c5f240cbb6d0bae">
<source>When a user with the selected Role creates an object, the Initial Permissions will be applied to that object.</source>
<target>Lorsqu'un utilisateur avec le rôle sélectionné crée un objet, les permissions initiales seront appliquées à cet objet,</target>
</trans-unit>
<trans-unit id="sbf27294eef56ac81">
<source>The Initial Permissions can either be placed on the User creating the object, or the Role selected in the previous field.</source>
<target>Les permissions initiales peuvent soit être placées sur l'utilisateur créant l'objet, ou sur le rôle sélectionné au champ précédent.</target>
</trans-unit>
<trans-unit id="s93f04537efda1b24">
<source>Available Permissions</source>
<target>Permissions disponibles</target>
</trans-unit>
<trans-unit id="s297bc57f9e494470">
<source>Selected Permissions</source>
<target>Permissions sélectionnées</target>
</trans-unit>
<trans-unit id="s0ea71d53764d781c">
<source>Permissions to grant when a new object is created.</source>
<target>Permissions à attribuer lorsqu'un nouvel objet est créé.</target>
</trans-unit>
<trans-unit id="s06fc21a40f5d7de1">
<source>Set initial permissions for newly created objects.</source>
<target>Définir des permissions initiales pour les objets nouvellement créés.</target>
</trans-unit>
<trans-unit id="sc7104a4d0fc35c7c">
<source>Update Initial Permissions</source>
<target>Mettre à jour les permissions initiales</target>
</trans-unit>
<trans-unit id="s83fa005829a65be9">
<source>Create Initial Permissions</source>
<target>Créer des permissions initiales</target>
</trans-unit>
<trans-unit id="se37ac6cf9c72f21a">
<source>Reputation: lower limit</source>
<target>Réputation : limite inférieure</target>
</trans-unit>
<trans-unit id="sa634ffa797037aac">
<source>Reputation cannot decrease lower than this value. Zero or negative.</source>
<target>La réputation ne peut pas descendre en dessous de cette valeur. Zéro ou négatif.</target>
</trans-unit>
<trans-unit id="s862986ce8e70edd7">
<source>Reputation: upper limit</source>
<target>Réputation : limite supérieure</target>
</trans-unit>
<trans-unit id="sdd04913b3b46cf30">
<source>Reputation cannot increase higher than this value. Zero or positive.</source>
<target>La réputation ne peut pas monter au dessus de cette valeur. Zéro ou positif.</target>
</trans-unit>
</body>
</file>

View File

@@ -9703,6 +9703,54 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit>
<trans-unit id="s17359123e1f24504">
<source>Field which contains DNs of groups the user is a member of. This field is used to lookup groups from users, e.g. 'memberOf'. To lookup nested groups in an Active Directory environment use 'memberOf:1.2.840.113556.1.4.1941:'.</source>
</trans-unit>
<trans-unit id="s891cd64acabf23bf">
<source>Initial Permissions</source>
</trans-unit>
<trans-unit id="sedb57bf4b42a8e40">
<source>Unknown Initial Permissions mode</source>
</trans-unit>
<trans-unit id="s6ea6a64acb45dfdf">
<source>Successfully updated initial permissions.</source>
</trans-unit>
<trans-unit id="sfddf7896ab5938b6">
<source>Successfully created initial permissions.</source>
</trans-unit>
<trans-unit id="s5c5f240cbb6d0bae">
<source>When a user with the selected Role creates an object, the Initial Permissions will be applied to that object.</source>
</trans-unit>
<trans-unit id="sbf27294eef56ac81">
<source>The Initial Permissions can either be placed on the User creating the object, or the Role selected in the previous field.</source>
</trans-unit>
<trans-unit id="s93f04537efda1b24">
<source>Available Permissions</source>
</trans-unit>
<trans-unit id="s297bc57f9e494470">
<source>Selected Permissions</source>
</trans-unit>
<trans-unit id="s0ea71d53764d781c">
<source>Permissions to grant when a new object is created.</source>
</trans-unit>
<trans-unit id="s06fc21a40f5d7de1">
<source>Set initial permissions for newly created objects.</source>
</trans-unit>
<trans-unit id="sc7104a4d0fc35c7c">
<source>Update Initial Permissions</source>
</trans-unit>
<trans-unit id="s83fa005829a65be9">
<source>Create Initial Permissions</source>
</trans-unit>
<trans-unit id="se37ac6cf9c72f21a">
<source>Reputation: lower limit</source>
</trans-unit>
<trans-unit id="sa634ffa797037aac">
<source>Reputation cannot decrease lower than this value. Zero or negative.</source>
</trans-unit>
<trans-unit id="s862986ce8e70edd7">
<source>Reputation: upper limit</source>
</trans-unit>
<trans-unit id="sdd04913b3b46cf30">
<source>Reputation cannot increase higher than this value. Zero or positive.</source>
</trans-unit>
</body>
</file>

View File

@@ -9086,6 +9086,54 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit>
<trans-unit id="s17359123e1f24504">
<source>Field which contains DNs of groups the user is a member of. This field is used to lookup groups from users, e.g. 'memberOf'. To lookup nested groups in an Active Directory environment use 'memberOf:1.2.840.113556.1.4.1941:'.</source>
</trans-unit>
<trans-unit id="s891cd64acabf23bf">
<source>Initial Permissions</source>
</trans-unit>
<trans-unit id="sedb57bf4b42a8e40">
<source>Unknown Initial Permissions mode</source>
</trans-unit>
<trans-unit id="s6ea6a64acb45dfdf">
<source>Successfully updated initial permissions.</source>
</trans-unit>
<trans-unit id="sfddf7896ab5938b6">
<source>Successfully created initial permissions.</source>
</trans-unit>
<trans-unit id="s5c5f240cbb6d0bae">
<source>When a user with the selected Role creates an object, the Initial Permissions will be applied to that object.</source>
</trans-unit>
<trans-unit id="sbf27294eef56ac81">
<source>The Initial Permissions can either be placed on the User creating the object, or the Role selected in the previous field.</source>
</trans-unit>
<trans-unit id="s93f04537efda1b24">
<source>Available Permissions</source>
</trans-unit>
<trans-unit id="s297bc57f9e494470">
<source>Selected Permissions</source>
</trans-unit>
<trans-unit id="s0ea71d53764d781c">
<source>Permissions to grant when a new object is created.</source>
</trans-unit>
<trans-unit id="s06fc21a40f5d7de1">
<source>Set initial permissions for newly created objects.</source>
</trans-unit>
<trans-unit id="sc7104a4d0fc35c7c">
<source>Update Initial Permissions</source>
</trans-unit>
<trans-unit id="s83fa005829a65be9">
<source>Create Initial Permissions</source>
</trans-unit>
<trans-unit id="se37ac6cf9c72f21a">
<source>Reputation: lower limit</source>
</trans-unit>
<trans-unit id="sa634ffa797037aac">
<source>Reputation cannot decrease lower than this value. Zero or negative.</source>
</trans-unit>
<trans-unit id="s862986ce8e70edd7">
<source>Reputation: upper limit</source>
</trans-unit>
<trans-unit id="sdd04913b3b46cf30">
<source>Reputation cannot increase higher than this value. Zero or positive.</source>
</trans-unit>
</body>
</file>

View File

@@ -8988,6 +8988,54 @@ Bindingen naar groepen/gebruikers worden gecontroleerd tegen de gebruiker van de
</trans-unit>
<trans-unit id="s17359123e1f24504">
<source>Field which contains DNs of groups the user is a member of. This field is used to lookup groups from users, e.g. 'memberOf'. To lookup nested groups in an Active Directory environment use 'memberOf:1.2.840.113556.1.4.1941:'.</source>
</trans-unit>
<trans-unit id="s891cd64acabf23bf">
<source>Initial Permissions</source>
</trans-unit>
<trans-unit id="sedb57bf4b42a8e40">
<source>Unknown Initial Permissions mode</source>
</trans-unit>
<trans-unit id="s6ea6a64acb45dfdf">
<source>Successfully updated initial permissions.</source>
</trans-unit>
<trans-unit id="sfddf7896ab5938b6">
<source>Successfully created initial permissions.</source>
</trans-unit>
<trans-unit id="s5c5f240cbb6d0bae">
<source>When a user with the selected Role creates an object, the Initial Permissions will be applied to that object.</source>
</trans-unit>
<trans-unit id="sbf27294eef56ac81">
<source>The Initial Permissions can either be placed on the User creating the object, or the Role selected in the previous field.</source>
</trans-unit>
<trans-unit id="s93f04537efda1b24">
<source>Available Permissions</source>
</trans-unit>
<trans-unit id="s297bc57f9e494470">
<source>Selected Permissions</source>
</trans-unit>
<trans-unit id="s0ea71d53764d781c">
<source>Permissions to grant when a new object is created.</source>
</trans-unit>
<trans-unit id="s06fc21a40f5d7de1">
<source>Set initial permissions for newly created objects.</source>
</trans-unit>
<trans-unit id="sc7104a4d0fc35c7c">
<source>Update Initial Permissions</source>
</trans-unit>
<trans-unit id="s83fa005829a65be9">
<source>Create Initial Permissions</source>
</trans-unit>
<trans-unit id="se37ac6cf9c72f21a">
<source>Reputation: lower limit</source>
</trans-unit>
<trans-unit id="sa634ffa797037aac">
<source>Reputation cannot decrease lower than this value. Zero or negative.</source>
</trans-unit>
<trans-unit id="s862986ce8e70edd7">
<source>Reputation: upper limit</source>
</trans-unit>
<trans-unit id="sdd04913b3b46cf30">
<source>Reputation cannot increase higher than this value. Zero or positive.</source>
</trans-unit>
</body>
</file>

View File

@@ -9413,6 +9413,54 @@ Powiązania z grupami/użytkownikami są sprawdzane względem użytkownika zdarz
</trans-unit>
<trans-unit id="s17359123e1f24504">
<source>Field which contains DNs of groups the user is a member of. This field is used to lookup groups from users, e.g. 'memberOf'. To lookup nested groups in an Active Directory environment use 'memberOf:1.2.840.113556.1.4.1941:'.</source>
</trans-unit>
<trans-unit id="s891cd64acabf23bf">
<source>Initial Permissions</source>
</trans-unit>
<trans-unit id="sedb57bf4b42a8e40">
<source>Unknown Initial Permissions mode</source>
</trans-unit>
<trans-unit id="s6ea6a64acb45dfdf">
<source>Successfully updated initial permissions.</source>
</trans-unit>
<trans-unit id="sfddf7896ab5938b6">
<source>Successfully created initial permissions.</source>
</trans-unit>
<trans-unit id="s5c5f240cbb6d0bae">
<source>When a user with the selected Role creates an object, the Initial Permissions will be applied to that object.</source>
</trans-unit>
<trans-unit id="sbf27294eef56ac81">
<source>The Initial Permissions can either be placed on the User creating the object, or the Role selected in the previous field.</source>
</trans-unit>
<trans-unit id="s93f04537efda1b24">
<source>Available Permissions</source>
</trans-unit>
<trans-unit id="s297bc57f9e494470">
<source>Selected Permissions</source>
</trans-unit>
<trans-unit id="s0ea71d53764d781c">
<source>Permissions to grant when a new object is created.</source>
</trans-unit>
<trans-unit id="s06fc21a40f5d7de1">
<source>Set initial permissions for newly created objects.</source>
</trans-unit>
<trans-unit id="sc7104a4d0fc35c7c">
<source>Update Initial Permissions</source>
</trans-unit>
<trans-unit id="s83fa005829a65be9">
<source>Create Initial Permissions</source>
</trans-unit>
<trans-unit id="se37ac6cf9c72f21a">
<source>Reputation: lower limit</source>
</trans-unit>
<trans-unit id="sa634ffa797037aac">
<source>Reputation cannot decrease lower than this value. Zero or negative.</source>
</trans-unit>
<trans-unit id="s862986ce8e70edd7">
<source>Reputation: upper limit</source>
</trans-unit>
<trans-unit id="sdd04913b3b46cf30">
<source>Reputation cannot increase higher than this value. Zero or positive.</source>
</trans-unit>
</body>
</file>

View File

@@ -9421,4 +9421,52 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="s17359123e1f24504">
<source>Field which contains DNs of groups the user is a member of. This field is used to lookup groups from users, e.g. 'memberOf'. To lookup nested groups in an Active Directory environment use 'memberOf:1.2.840.113556.1.4.1941:'.</source>
</trans-unit>
<trans-unit id="s891cd64acabf23bf">
<source>Initial Permissions</source>
</trans-unit>
<trans-unit id="sedb57bf4b42a8e40">
<source>Unknown Initial Permissions mode</source>
</trans-unit>
<trans-unit id="s6ea6a64acb45dfdf">
<source>Successfully updated initial permissions.</source>
</trans-unit>
<trans-unit id="sfddf7896ab5938b6">
<source>Successfully created initial permissions.</source>
</trans-unit>
<trans-unit id="s5c5f240cbb6d0bae">
<source>When a user with the selected Role creates an object, the Initial Permissions will be applied to that object.</source>
</trans-unit>
<trans-unit id="sbf27294eef56ac81">
<source>The Initial Permissions can either be placed on the User creating the object, or the Role selected in the previous field.</source>
</trans-unit>
<trans-unit id="s93f04537efda1b24">
<source>Available Permissions</source>
</trans-unit>
<trans-unit id="s297bc57f9e494470">
<source>Selected Permissions</source>
</trans-unit>
<trans-unit id="s0ea71d53764d781c">
<source>Permissions to grant when a new object is created.</source>
</trans-unit>
<trans-unit id="s06fc21a40f5d7de1">
<source>Set initial permissions for newly created objects.</source>
</trans-unit>
<trans-unit id="sc7104a4d0fc35c7c">
<source>Update Initial Permissions</source>
</trans-unit>
<trans-unit id="s83fa005829a65be9">
<source>Create Initial Permissions</source>
</trans-unit>
<trans-unit id="se37ac6cf9c72f21a">
<source>Reputation: lower limit</source>
</trans-unit>
<trans-unit id="sa634ffa797037aac">
<source>Reputation cannot decrease lower than this value. Zero or negative.</source>
</trans-unit>
<trans-unit id="s862986ce8e70edd7">
<source>Reputation: upper limit</source>
</trans-unit>
<trans-unit id="sdd04913b3b46cf30">
<source>Reputation cannot increase higher than this value. Zero or positive.</source>
</trans-unit>
</body></file></xliff>

View File

@@ -9506,6 +9506,54 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit>
<trans-unit id="s17359123e1f24504">
<source>Field which contains DNs of groups the user is a member of. This field is used to lookup groups from users, e.g. 'memberOf'. To lookup nested groups in an Active Directory environment use 'memberOf:1.2.840.113556.1.4.1941:'.</source>
</trans-unit>
<trans-unit id="s891cd64acabf23bf">
<source>Initial Permissions</source>
</trans-unit>
<trans-unit id="sedb57bf4b42a8e40">
<source>Unknown Initial Permissions mode</source>
</trans-unit>
<trans-unit id="s6ea6a64acb45dfdf">
<source>Successfully updated initial permissions.</source>
</trans-unit>
<trans-unit id="sfddf7896ab5938b6">
<source>Successfully created initial permissions.</source>
</trans-unit>
<trans-unit id="s5c5f240cbb6d0bae">
<source>When a user with the selected Role creates an object, the Initial Permissions will be applied to that object.</source>
</trans-unit>
<trans-unit id="sbf27294eef56ac81">
<source>The Initial Permissions can either be placed on the User creating the object, or the Role selected in the previous field.</source>
</trans-unit>
<trans-unit id="s93f04537efda1b24">
<source>Available Permissions</source>
</trans-unit>
<trans-unit id="s297bc57f9e494470">
<source>Selected Permissions</source>
</trans-unit>
<trans-unit id="s0ea71d53764d781c">
<source>Permissions to grant when a new object is created.</source>
</trans-unit>
<trans-unit id="s06fc21a40f5d7de1">
<source>Set initial permissions for newly created objects.</source>
</trans-unit>
<trans-unit id="sc7104a4d0fc35c7c">
<source>Update Initial Permissions</source>
</trans-unit>
<trans-unit id="s83fa005829a65be9">
<source>Create Initial Permissions</source>
</trans-unit>
<trans-unit id="se37ac6cf9c72f21a">
<source>Reputation: lower limit</source>
</trans-unit>
<trans-unit id="sa634ffa797037aac">
<source>Reputation cannot decrease lower than this value. Zero or negative.</source>
</trans-unit>
<trans-unit id="s862986ce8e70edd7">
<source>Reputation: upper limit</source>
</trans-unit>
<trans-unit id="sdd04913b3b46cf30">
<source>Reputation cannot increase higher than this value. Zero or positive.</source>
</trans-unit>
</body>
</file>

View File

@@ -9477,6 +9477,54 @@ Gruplara/kullanıcılara yapılan bağlamalar, etkinliğin kullanıcısına kar
</trans-unit>
<trans-unit id="s17359123e1f24504">
<source>Field which contains DNs of groups the user is a member of. This field is used to lookup groups from users, e.g. 'memberOf'. To lookup nested groups in an Active Directory environment use 'memberOf:1.2.840.113556.1.4.1941:'.</source>
</trans-unit>
<trans-unit id="s891cd64acabf23bf">
<source>Initial Permissions</source>
</trans-unit>
<trans-unit id="sedb57bf4b42a8e40">
<source>Unknown Initial Permissions mode</source>
</trans-unit>
<trans-unit id="s6ea6a64acb45dfdf">
<source>Successfully updated initial permissions.</source>
</trans-unit>
<trans-unit id="sfddf7896ab5938b6">
<source>Successfully created initial permissions.</source>
</trans-unit>
<trans-unit id="s5c5f240cbb6d0bae">
<source>When a user with the selected Role creates an object, the Initial Permissions will be applied to that object.</source>
</trans-unit>
<trans-unit id="sbf27294eef56ac81">
<source>The Initial Permissions can either be placed on the User creating the object, or the Role selected in the previous field.</source>
</trans-unit>
<trans-unit id="s93f04537efda1b24">
<source>Available Permissions</source>
</trans-unit>
<trans-unit id="s297bc57f9e494470">
<source>Selected Permissions</source>
</trans-unit>
<trans-unit id="s0ea71d53764d781c">
<source>Permissions to grant when a new object is created.</source>
</trans-unit>
<trans-unit id="s06fc21a40f5d7de1">
<source>Set initial permissions for newly created objects.</source>
</trans-unit>
<trans-unit id="sc7104a4d0fc35c7c">
<source>Update Initial Permissions</source>
</trans-unit>
<trans-unit id="s83fa005829a65be9">
<source>Create Initial Permissions</source>
</trans-unit>
<trans-unit id="se37ac6cf9c72f21a">
<source>Reputation: lower limit</source>
</trans-unit>
<trans-unit id="sa634ffa797037aac">
<source>Reputation cannot decrease lower than this value. Zero or negative.</source>
</trans-unit>
<trans-unit id="s862986ce8e70edd7">
<source>Reputation: upper limit</source>
</trans-unit>
<trans-unit id="sdd04913b3b46cf30">
<source>Reputation cannot increase higher than this value. Zero or positive.</source>
</trans-unit>
</body>
</file>

View File

@@ -6227,6 +6227,54 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="s17359123e1f24504">
<source>Field which contains DNs of groups the user is a member of. This field is used to lookup groups from users, e.g. 'memberOf'. To lookup nested groups in an Active Directory environment use 'memberOf:1.2.840.113556.1.4.1941:'.</source>
</trans-unit>
<trans-unit id="s891cd64acabf23bf">
<source>Initial Permissions</source>
</trans-unit>
<trans-unit id="sedb57bf4b42a8e40">
<source>Unknown Initial Permissions mode</source>
</trans-unit>
<trans-unit id="s6ea6a64acb45dfdf">
<source>Successfully updated initial permissions.</source>
</trans-unit>
<trans-unit id="sfddf7896ab5938b6">
<source>Successfully created initial permissions.</source>
</trans-unit>
<trans-unit id="s5c5f240cbb6d0bae">
<source>When a user with the selected Role creates an object, the Initial Permissions will be applied to that object.</source>
</trans-unit>
<trans-unit id="sbf27294eef56ac81">
<source>The Initial Permissions can either be placed on the User creating the object, or the Role selected in the previous field.</source>
</trans-unit>
<trans-unit id="s93f04537efda1b24">
<source>Available Permissions</source>
</trans-unit>
<trans-unit id="s297bc57f9e494470">
<source>Selected Permissions</source>
</trans-unit>
<trans-unit id="s0ea71d53764d781c">
<source>Permissions to grant when a new object is created.</source>
</trans-unit>
<trans-unit id="s06fc21a40f5d7de1">
<source>Set initial permissions for newly created objects.</source>
</trans-unit>
<trans-unit id="sc7104a4d0fc35c7c">
<source>Update Initial Permissions</source>
</trans-unit>
<trans-unit id="s83fa005829a65be9">
<source>Create Initial Permissions</source>
</trans-unit>
<trans-unit id="se37ac6cf9c72f21a">
<source>Reputation: lower limit</source>
</trans-unit>
<trans-unit id="sa634ffa797037aac">
<source>Reputation cannot decrease lower than this value. Zero or negative.</source>
</trans-unit>
<trans-unit id="s862986ce8e70edd7">
<source>Reputation: upper limit</source>
</trans-unit>
<trans-unit id="sdd04913b3b46cf30">
<source>Reputation cannot increase higher than this value. Zero or positive.</source>
</trans-unit>
</body>
</file>
</xliff>

View File

@@ -9697,12 +9697,79 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit>
<trans-unit id="s783964a224796865">
<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,...'. When selecting 'Lookup using a user attribute', this should be a user attribute, otherwise a group attribute.</source>
<target>包含组成员的字段。请注意,如果使用 "memberUid" 字段,则假定该值包含相对可分辨名称。例如,'memberUid=some-user' 而不是 'memberUid=cn=some-user,ou=groups,...'。当选中“使用用户属性查询”时,此配置应该为用户属性,否则为组属性。</target>
</trans-unit>
<trans-unit id="s1d47b4f61ca53e8e">
<source>Lookup using user attribute</source>
<target>使用用户属性查询</target>
</trans-unit>
<trans-unit id="s17359123e1f24504">
<source>Field which contains DNs of groups the user is a member of. This field is used to lookup groups from users, e.g. 'memberOf'. To lookup nested groups in an Active Directory environment use 'memberOf:1.2.840.113556.1.4.1941:'.</source>
<target>包含用户所属组 DN 的字段。此字段用于从用户查询组,例如 'memberOf'。要在 Active Directory 环境中查询嵌套组,则使用 'memberOf:1.2.840.113556.1.4.1941:'。</target>
</trans-unit>
<trans-unit id="s891cd64acabf23bf">
<source>Initial Permissions</source>
<target>初始权限</target>
</trans-unit>
<trans-unit id="sedb57bf4b42a8e40">
<source>Unknown Initial Permissions mode</source>
<target>未知初始权限模式</target>
</trans-unit>
<trans-unit id="s6ea6a64acb45dfdf">
<source>Successfully updated initial permissions.</source>
<target>已成功更新初始权限。</target>
</trans-unit>
<trans-unit id="sfddf7896ab5938b6">
<source>Successfully created initial permissions.</source>
<target>已成功创建初始权限。</target>
</trans-unit>
<trans-unit id="s5c5f240cbb6d0bae">
<source>When a user with the selected Role creates an object, the Initial Permissions will be applied to that object.</source>
<target>当所选角色的用户创建对象时,初始权限会应用于该对象。</target>
</trans-unit>
<trans-unit id="sbf27294eef56ac81">
<source>The Initial Permissions can either be placed on the User creating the object, or the Role selected in the previous field.</source>
<target>初始权限既可以属于创建对象的用户,又可以属于上一个字段中设置的角色。</target>
</trans-unit>
<trans-unit id="s93f04537efda1b24">
<source>Available Permissions</source>
<target>可用权限</target>
</trans-unit>
<trans-unit id="s297bc57f9e494470">
<source>Selected Permissions</source>
<target>已选权限</target>
</trans-unit>
<trans-unit id="s0ea71d53764d781c">
<source>Permissions to grant when a new object is created.</source>
<target>创建新对象时授予的权限。</target>
</trans-unit>
<trans-unit id="s06fc21a40f5d7de1">
<source>Set initial permissions for newly created objects.</source>
<target>为新创建的对象设置初始权限。</target>
</trans-unit>
<trans-unit id="sc7104a4d0fc35c7c">
<source>Update Initial Permissions</source>
<target>更新初始权限</target>
</trans-unit>
<trans-unit id="s83fa005829a65be9">
<source>Create Initial Permissions</source>
<target>创建初始权限</target>
</trans-unit>
<trans-unit id="se37ac6cf9c72f21a">
<source>Reputation: lower limit</source>
<target>信誉:下限值</target>
</trans-unit>
<trans-unit id="sa634ffa797037aac">
<source>Reputation cannot decrease lower than this value. Zero or negative.</source>
<target>信誉无法降低到此值以下。可为零或负数。</target>
</trans-unit>
<trans-unit id="s862986ce8e70edd7">
<source>Reputation: upper limit</source>
<target>信誉:上限值</target>
</trans-unit>
<trans-unit id="sdd04913b3b46cf30">
<source>Reputation cannot increase higher than this value. Zero or positive.</source>
<target>信誉无法提高到此值以上。可为零或正数。</target>
</trans-unit>
</body>
</file>

View File

@@ -7319,6 +7319,54 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit>
<trans-unit id="s17359123e1f24504">
<source>Field which contains DNs of groups the user is a member of. This field is used to lookup groups from users, e.g. 'memberOf'. To lookup nested groups in an Active Directory environment use 'memberOf:1.2.840.113556.1.4.1941:'.</source>
</trans-unit>
<trans-unit id="s891cd64acabf23bf">
<source>Initial Permissions</source>
</trans-unit>
<trans-unit id="sedb57bf4b42a8e40">
<source>Unknown Initial Permissions mode</source>
</trans-unit>
<trans-unit id="s6ea6a64acb45dfdf">
<source>Successfully updated initial permissions.</source>
</trans-unit>
<trans-unit id="sfddf7896ab5938b6">
<source>Successfully created initial permissions.</source>
</trans-unit>
<trans-unit id="s5c5f240cbb6d0bae">
<source>When a user with the selected Role creates an object, the Initial Permissions will be applied to that object.</source>
</trans-unit>
<trans-unit id="sbf27294eef56ac81">
<source>The Initial Permissions can either be placed on the User creating the object, or the Role selected in the previous field.</source>
</trans-unit>
<trans-unit id="s93f04537efda1b24">
<source>Available Permissions</source>
</trans-unit>
<trans-unit id="s297bc57f9e494470">
<source>Selected Permissions</source>
</trans-unit>
<trans-unit id="s0ea71d53764d781c">
<source>Permissions to grant when a new object is created.</source>
</trans-unit>
<trans-unit id="s06fc21a40f5d7de1">
<source>Set initial permissions for newly created objects.</source>
</trans-unit>
<trans-unit id="sc7104a4d0fc35c7c">
<source>Update Initial Permissions</source>
</trans-unit>
<trans-unit id="s83fa005829a65be9">
<source>Create Initial Permissions</source>
</trans-unit>
<trans-unit id="se37ac6cf9c72f21a">
<source>Reputation: lower limit</source>
</trans-unit>
<trans-unit id="sa634ffa797037aac">
<source>Reputation cannot decrease lower than this value. Zero or negative.</source>
</trans-unit>
<trans-unit id="s862986ce8e70edd7">
<source>Reputation: upper limit</source>
</trans-unit>
<trans-unit id="sdd04913b3b46cf30">
<source>Reputation cannot increase higher than this value. Zero or positive.</source>
</trans-unit>
</body>
</file>

View File

@@ -9697,12 +9697,79 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit>
<trans-unit id="s783964a224796865">
<source>Field which contains members of a group. Note that if using the &quot;memberUid&quot; field, the value is assumed to contain a relative distinguished name. e.g. 'memberUid=some-user' instead of 'memberUid=cn=some-user,ou=groups,...'. When selecting 'Lookup using a user attribute', this should be a user attribute, otherwise a group attribute.</source>
<target>包含组成员的字段。请注意,如果使用 &quot;memberUid&quot; 字段,则假定该值包含相对可分辨名称。例如,'memberUid=some-user' 而不是 'memberUid=cn=some-user,ou=groups,...'。当选中“使用用户属性查询”时,此配置应该为用户属性,否则为组属性。</target>
</trans-unit>
<trans-unit id="s1d47b4f61ca53e8e">
<source>Lookup using user attribute</source>
<target>使用用户属性查询</target>
</trans-unit>
<trans-unit id="s17359123e1f24504">
<source>Field which contains DNs of groups the user is a member of. This field is used to lookup groups from users, e.g. 'memberOf'. To lookup nested groups in an Active Directory environment use 'memberOf:1.2.840.113556.1.4.1941:'.</source>
<target>包含用户所属组 DN 的字段。此字段用于从用户查询组,例如 'memberOf'。要在 Active Directory 环境中查询嵌套组,则使用 'memberOf:1.2.840.113556.1.4.1941:'。</target>
</trans-unit>
<trans-unit id="s891cd64acabf23bf">
<source>Initial Permissions</source>
<target>初始权限</target>
</trans-unit>
<trans-unit id="sedb57bf4b42a8e40">
<source>Unknown Initial Permissions mode</source>
<target>未知初始权限模式</target>
</trans-unit>
<trans-unit id="s6ea6a64acb45dfdf">
<source>Successfully updated initial permissions.</source>
<target>已成功更新初始权限。</target>
</trans-unit>
<trans-unit id="sfddf7896ab5938b6">
<source>Successfully created initial permissions.</source>
<target>已成功创建初始权限。</target>
</trans-unit>
<trans-unit id="s5c5f240cbb6d0bae">
<source>When a user with the selected Role creates an object, the Initial Permissions will be applied to that object.</source>
<target>当所选角色的用户创建对象时,初始权限会应用于该对象。</target>
</trans-unit>
<trans-unit id="sbf27294eef56ac81">
<source>The Initial Permissions can either be placed on the User creating the object, or the Role selected in the previous field.</source>
<target>初始权限既可以属于创建对象的用户,又可以属于上一个字段中设置的角色。</target>
</trans-unit>
<trans-unit id="s93f04537efda1b24">
<source>Available Permissions</source>
<target>可用权限</target>
</trans-unit>
<trans-unit id="s297bc57f9e494470">
<source>Selected Permissions</source>
<target>已选权限</target>
</trans-unit>
<trans-unit id="s0ea71d53764d781c">
<source>Permissions to grant when a new object is created.</source>
<target>创建新对象时授予的权限。</target>
</trans-unit>
<trans-unit id="s06fc21a40f5d7de1">
<source>Set initial permissions for newly created objects.</source>
<target>为新创建的对象设置初始权限。</target>
</trans-unit>
<trans-unit id="sc7104a4d0fc35c7c">
<source>Update Initial Permissions</source>
<target>更新初始权限</target>
</trans-unit>
<trans-unit id="s83fa005829a65be9">
<source>Create Initial Permissions</source>
<target>创建初始权限</target>
</trans-unit>
<trans-unit id="se37ac6cf9c72f21a">
<source>Reputation: lower limit</source>
<target>信誉:下限值</target>
</trans-unit>
<trans-unit id="sa634ffa797037aac">
<source>Reputation cannot decrease lower than this value. Zero or negative.</source>
<target>信誉无法降低到此值以下。可为零或负数。</target>
</trans-unit>
<trans-unit id="s862986ce8e70edd7">
<source>Reputation: upper limit</source>
<target>信誉:上限值</target>
</trans-unit>
<trans-unit id="sdd04913b3b46cf30">
<source>Reputation cannot increase higher than this value. Zero or positive.</source>
<target>信誉无法提高到此值以上。可为零或正数。</target>
</trans-unit>
</body>
</file>

View File

@@ -9063,6 +9063,54 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit>
<trans-unit id="s17359123e1f24504">
<source>Field which contains DNs of groups the user is a member of. This field is used to lookup groups from users, e.g. 'memberOf'. To lookup nested groups in an Active Directory environment use 'memberOf:1.2.840.113556.1.4.1941:'.</source>
</trans-unit>
<trans-unit id="s891cd64acabf23bf">
<source>Initial Permissions</source>
</trans-unit>
<trans-unit id="sedb57bf4b42a8e40">
<source>Unknown Initial Permissions mode</source>
</trans-unit>
<trans-unit id="s6ea6a64acb45dfdf">
<source>Successfully updated initial permissions.</source>
</trans-unit>
<trans-unit id="sfddf7896ab5938b6">
<source>Successfully created initial permissions.</source>
</trans-unit>
<trans-unit id="s5c5f240cbb6d0bae">
<source>When a user with the selected Role creates an object, the Initial Permissions will be applied to that object.</source>
</trans-unit>
<trans-unit id="sbf27294eef56ac81">
<source>The Initial Permissions can either be placed on the User creating the object, or the Role selected in the previous field.</source>
</trans-unit>
<trans-unit id="s93f04537efda1b24">
<source>Available Permissions</source>
</trans-unit>
<trans-unit id="s297bc57f9e494470">
<source>Selected Permissions</source>
</trans-unit>
<trans-unit id="s0ea71d53764d781c">
<source>Permissions to grant when a new object is created.</source>
</trans-unit>
<trans-unit id="s06fc21a40f5d7de1">
<source>Set initial permissions for newly created objects.</source>
</trans-unit>
<trans-unit id="sc7104a4d0fc35c7c">
<source>Update Initial Permissions</source>
</trans-unit>
<trans-unit id="s83fa005829a65be9">
<source>Create Initial Permissions</source>
</trans-unit>
<trans-unit id="se37ac6cf9c72f21a">
<source>Reputation: lower limit</source>
</trans-unit>
<trans-unit id="sa634ffa797037aac">
<source>Reputation cannot decrease lower than this value. Zero or negative.</source>
</trans-unit>
<trans-unit id="s862986ce8e70edd7">
<source>Reputation: upper limit</source>
</trans-unit>
<trans-unit id="sdd04913b3b46cf30">
<source>Reputation cannot increase higher than this value. Zero or positive.</source>
</trans-unit>
</body>
</file>

View File

@@ -8,7 +8,7 @@ authentik provides several [standard policy types](./index.md#standard-policies)
We also document how to use a policy to [whitelist email domains](./expression/whitelist_email.md) and to [ensure unique email addresses](./expression/unique_email.md).
To learn more see also [bindings](../../add-secure-apps/flows-stages/bindings/index.md) and how to [bind policy bindings to a new application when yo create the application](../../add-secure-apps/applications/manage_apps.mdx#instructions) (for example, to configure application-specific access).
To learn more see also [bindings](../../add-secure-apps/flows-stages/bindings/index.md) and how to [bind policy bindings to a new application when the application is created](../../add-secure-apps/applications/manage_apps.mdx#instructions) (for example, to configure application-specific access).
## Create a policy

View File

@@ -19,6 +19,8 @@ Previously, sessions were stored by default in the cache. Now, they are stored i
## New features
### Postgres pool
## Upgrading
This release does not introduce any new requirements. You can follow the upgrade instructions below; for more detailed information about upgrading authentik, refer to our [Upgrade documentation](../../install-config/upgrade.mdx).

View File

@@ -37,7 +37,7 @@ To support the integration of Actual Budget with authentik, you need to create a
- **Choose a Provider type**: select **OAuth2/OpenID Connect** as the provider type.
- **Configure the Provider**: provide a name (or accept the auto-provided name), the authorization flow to use for this provider, and the following required configurations.
- Note the **Client ID**,**Client Secret**, and **slug** values because they will be required later.
- Set a `Strict` redirect URI to <kbd>https://<em>actual.company</em>/openid/callback/</kbd>.
- Set a `Strict` redirect URI to <kbd>https://<em>actual.company</em>/openid/callback</kbd>.
- Select any available signing key. Actual Budget only supports the RS256 algorithm. Be aware of this when choosing a signing key.
- **Configure Bindings** _(optional)_: you can create a [binding](/docs/add-secure-apps/flows-stages/bindings/) (policy, group, or user) to manage the listing and access to applications on a user's **My applications** page.

View File

@@ -18,10 +18,7 @@ The following placeholders are used in this guide:
- `example.com` is the default E-mail address configured in Google workspace.
:::note
This documentation lists only the settings that you need to change from their default values.
Be aware that any changes other than those explicitly mentioned in this guide could cause issues accessing your application.
This documentation lists only the settings that you need to change from their default values. Be aware that any changes other than those explicitly mentioned in this guide could cause issues accessing your application.
:::
## authentik Configuration