enterprise: initial enterprise (#5721)
* initial Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add user type Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add external users Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add ui, add more logic, add public JWT validation key Signed-off-by: Jens Langhammer <jens@goauthentik.io> * revert to not use install_id as session jwt signing key Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix more Signed-off-by: Jens Langhammer <jens@goauthentik.io> * switch to PKI Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add more licensing stuff Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add install ID to form Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix bugs Signed-off-by: Jens Langhammer <jens@goauthentik.io> * start adding tests Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fixes Signed-off-by: Jens Langhammer <jens@goauthentik.io> * use x5c correctly Signed-off-by: Jens Langhammer <jens@goauthentik.io> * license checks Signed-off-by: Jens Langhammer <jens@goauthentik.io> * use production CA Signed-off-by: Jens Langhammer <jens@goauthentik.io> * more Signed-off-by: Jens Langhammer <jens@goauthentik.io> * more UI stuff Signed-off-by: Jens Langhammer <jens@goauthentik.io> * rename to summary Signed-off-by: Jens Langhammer <jens@goauthentik.io> * update locale, improve ui Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add direct button Signed-off-by: Jens Langhammer <jens@goauthentik.io> * update link Signed-off-by: Jens Langhammer <jens@goauthentik.io> * format and such Signed-off-by: Jens Langhammer <jens@goauthentik.io> * remove old attributes from ldap Signed-off-by: Jens Langhammer <jens@goauthentik.io> * remove is_enterprise_licensed Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix admin interface styling issue Signed-off-by: Jens Langhammer <jens@goauthentik.io> * Update authentik/core/models.py Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com> Signed-off-by: Jens L. <jens@beryju.org> * fix default case Signed-off-by: Jens Langhammer <jens@goauthentik.io> --------- Signed-off-by: Jens Langhammer <jens@goauthentik.io> Signed-off-by: Jens L. <jens@beryju.org> Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
This commit is contained in:
parent
cf799fca03
commit
41af486006
|
@ -204,3 +204,4 @@ data/
|
||||||
|
|
||||||
# Local Netlify folder
|
# Local Netlify folder
|
||||||
.netlify
|
.netlify
|
||||||
|
.ruff_cache
|
||||||
|
|
|
@ -9,7 +9,7 @@ from rest_framework.exceptions import AuthenticationFailed
|
||||||
|
|
||||||
from authentik.api.authentication import bearer_auth
|
from authentik.api.authentication import bearer_auth
|
||||||
from authentik.blueprints.tests import reconcile_app
|
from authentik.blueprints.tests import reconcile_app
|
||||||
from authentik.core.models import USER_ATTRIBUTE_SA, Token, TokenIntents
|
from authentik.core.models import Token, TokenIntents, User, UserTypes
|
||||||
from authentik.core.tests.utils import create_test_admin_user, create_test_flow
|
from authentik.core.tests.utils import create_test_admin_user, create_test_flow
|
||||||
from authentik.lib.generators import generate_id
|
from authentik.lib.generators import generate_id
|
||||||
from authentik.providers.oauth2.constants import SCOPE_AUTHENTIK_API
|
from authentik.providers.oauth2.constants import SCOPE_AUTHENTIK_API
|
||||||
|
@ -57,8 +57,8 @@ class TestAPIAuth(TestCase):
|
||||||
@reconcile_app("authentik_outposts")
|
@reconcile_app("authentik_outposts")
|
||||||
def test_managed_outpost_success(self):
|
def test_managed_outpost_success(self):
|
||||||
"""Test managed outpost"""
|
"""Test managed outpost"""
|
||||||
user = bearer_auth(f"Bearer {settings.SECRET_KEY}".encode())
|
user: User = bearer_auth(f"Bearer {settings.SECRET_KEY}".encode())
|
||||||
self.assertEqual(user.attributes[USER_ATTRIBUTE_SA], True)
|
self.assertEqual(user.type, UserTypes.INTERNAL_SERVICE_ACCOUNT)
|
||||||
|
|
||||||
def test_jwt_valid(self):
|
def test_jwt_valid(self):
|
||||||
"""Test valid JWT"""
|
"""Test valid JWT"""
|
||||||
|
|
|
@ -3,6 +3,7 @@ from pathlib import Path
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.dispatch import Signal
|
||||||
from drf_spectacular.utils import extend_schema
|
from drf_spectacular.utils import extend_schema
|
||||||
from rest_framework.fields import (
|
from rest_framework.fields import (
|
||||||
BooleanField,
|
BooleanField,
|
||||||
|
@ -21,6 +22,8 @@ from authentik.core.api.utils import PassiveSerializer
|
||||||
from authentik.events.geo import GEOIP_READER
|
from authentik.events.geo import GEOIP_READER
|
||||||
from authentik.lib.config import CONFIG
|
from authentik.lib.config import CONFIG
|
||||||
|
|
||||||
|
capabilities = Signal()
|
||||||
|
|
||||||
|
|
||||||
class Capabilities(models.TextChoices):
|
class Capabilities(models.TextChoices):
|
||||||
"""Define capabilities which influence which APIs can/should be used"""
|
"""Define capabilities which influence which APIs can/should be used"""
|
||||||
|
@ -73,6 +76,9 @@ class ConfigView(APIView):
|
||||||
caps.append(Capabilities.CAN_DEBUG)
|
caps.append(Capabilities.CAN_DEBUG)
|
||||||
if "authentik.enterprise" in settings.INSTALLED_APPS:
|
if "authentik.enterprise" in settings.INSTALLED_APPS:
|
||||||
caps.append(Capabilities.IS_ENTERPRISE)
|
caps.append(Capabilities.IS_ENTERPRISE)
|
||||||
|
for _, result in capabilities.send(sender=self):
|
||||||
|
if result:
|
||||||
|
caps.append(result)
|
||||||
return caps
|
return caps
|
||||||
|
|
||||||
def get_config(self) -> ConfigSerializer:
|
def get_config(self) -> ConfigSerializer:
|
||||||
|
|
|
@ -59,7 +59,6 @@ from authentik.core.middleware import (
|
||||||
SESSION_KEY_IMPERSONATE_USER,
|
SESSION_KEY_IMPERSONATE_USER,
|
||||||
)
|
)
|
||||||
from authentik.core.models import (
|
from authentik.core.models import (
|
||||||
USER_ATTRIBUTE_SA,
|
|
||||||
USER_ATTRIBUTE_TOKEN_EXPIRING,
|
USER_ATTRIBUTE_TOKEN_EXPIRING,
|
||||||
USER_PATH_SERVICE_ACCOUNT,
|
USER_PATH_SERVICE_ACCOUNT,
|
||||||
AuthenticatedSession,
|
AuthenticatedSession,
|
||||||
|
@ -67,6 +66,7 @@ from authentik.core.models import (
|
||||||
Token,
|
Token,
|
||||||
TokenIntents,
|
TokenIntents,
|
||||||
User,
|
User,
|
||||||
|
UserTypes,
|
||||||
)
|
)
|
||||||
from authentik.events.models import Event, EventAction
|
from authentik.events.models import Event, EventAction
|
||||||
from authentik.flows.exceptions import FlowNonApplicableException
|
from authentik.flows.exceptions import FlowNonApplicableException
|
||||||
|
@ -147,6 +147,18 @@ class UserSerializer(ModelSerializer):
|
||||||
raise ValidationError(_("No empty segments in user path allowed."))
|
raise ValidationError(_("No empty segments in user path allowed."))
|
||||||
return path
|
return path
|
||||||
|
|
||||||
|
def validate_type(self, user_type: str) -> str:
|
||||||
|
"""Validate user type, internal_service_account is an internal value"""
|
||||||
|
if (
|
||||||
|
self.instance
|
||||||
|
and self.instance.type == UserTypes.INTERNAL_SERVICE_ACCOUNT
|
||||||
|
and user_type != UserTypes.INTERNAL_SERVICE_ACCOUNT.value
|
||||||
|
):
|
||||||
|
raise ValidationError("Can't change internal service account to other user type.")
|
||||||
|
if not self.instance and user_type == UserTypes.INTERNAL_SERVICE_ACCOUNT.value:
|
||||||
|
raise ValidationError("Setting a user to internal service account is not allowed.")
|
||||||
|
return user_type
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = User
|
model = User
|
||||||
fields = [
|
fields = [
|
||||||
|
@ -163,6 +175,7 @@ class UserSerializer(ModelSerializer):
|
||||||
"attributes",
|
"attributes",
|
||||||
"uid",
|
"uid",
|
||||||
"path",
|
"path",
|
||||||
|
"type",
|
||||||
]
|
]
|
||||||
extra_kwargs = {
|
extra_kwargs = {
|
||||||
"name": {"allow_blank": True},
|
"name": {"allow_blank": True},
|
||||||
|
@ -211,6 +224,7 @@ class UserSelfSerializer(ModelSerializer):
|
||||||
"avatar",
|
"avatar",
|
||||||
"uid",
|
"uid",
|
||||||
"settings",
|
"settings",
|
||||||
|
"type",
|
||||||
]
|
]
|
||||||
extra_kwargs = {
|
extra_kwargs = {
|
||||||
"is_active": {"read_only": True},
|
"is_active": {"read_only": True},
|
||||||
|
@ -329,6 +343,7 @@ class UsersFilter(FilterSet):
|
||||||
"attributes",
|
"attributes",
|
||||||
"groups_by_name",
|
"groups_by_name",
|
||||||
"groups_by_pk",
|
"groups_by_pk",
|
||||||
|
"type",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -421,7 +436,8 @@ class UserViewSet(UsedByMixin, ModelViewSet):
|
||||||
user: User = User.objects.create(
|
user: User = User.objects.create(
|
||||||
username=username,
|
username=username,
|
||||||
name=username,
|
name=username,
|
||||||
attributes={USER_ATTRIBUTE_SA: True, USER_ATTRIBUTE_TOKEN_EXPIRING: expiring},
|
type=UserTypes.SERVICE_ACCOUNT,
|
||||||
|
attributes={USER_ATTRIBUTE_TOKEN_EXPIRING: expiring},
|
||||||
path=USER_PATH_SERVICE_ACCOUNT,
|
path=USER_PATH_SERVICE_ACCOUNT,
|
||||||
)
|
)
|
||||||
user.set_unusable_password()
|
user.set_unusable_password()
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
# Generated by Django 4.1.7 on 2023-05-21 11:44
|
||||||
|
|
||||||
|
from django.apps.registry import Apps
|
||||||
|
from django.db import migrations, models
|
||||||
|
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_user_type(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||||
|
db_alias = schema_editor.connection.alias
|
||||||
|
User = apps.get_model("authentik_core", "User")
|
||||||
|
|
||||||
|
from authentik.core.models import UserTypes
|
||||||
|
|
||||||
|
for user in User.objects.using(db_alias).all():
|
||||||
|
user.type = UserTypes.DEFAULT
|
||||||
|
if "goauthentik.io/user/service-account" in user.attributes:
|
||||||
|
user.type = UserTypes.SERVICE_ACCOUNT
|
||||||
|
if "goauthentik.io/user/override-ips" in user.attributes:
|
||||||
|
user.type = UserTypes.INTERNAL_SERVICE_ACCOUNT
|
||||||
|
user.save()
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("authentik_core", "0029_provider_backchannel_applications_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="user",
|
||||||
|
name="type",
|
||||||
|
field=models.TextField(
|
||||||
|
choices=[
|
||||||
|
("default", "Default"),
|
||||||
|
("external", "External"),
|
||||||
|
("service_account", "Service Account"),
|
||||||
|
("internal_service_account", "Internal Service Account"),
|
||||||
|
],
|
||||||
|
default="default",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.RunPython(migrate_user_type),
|
||||||
|
]
|
|
@ -36,7 +36,6 @@ from authentik.root.install_id import get_install_id
|
||||||
|
|
||||||
LOGGER = get_logger()
|
LOGGER = get_logger()
|
||||||
USER_ATTRIBUTE_DEBUG = "goauthentik.io/user/debug"
|
USER_ATTRIBUTE_DEBUG = "goauthentik.io/user/debug"
|
||||||
USER_ATTRIBUTE_SA = "goauthentik.io/user/service-account"
|
|
||||||
USER_ATTRIBUTE_GENERATED = "goauthentik.io/user/generated"
|
USER_ATTRIBUTE_GENERATED = "goauthentik.io/user/generated"
|
||||||
USER_ATTRIBUTE_EXPIRES = "goauthentik.io/user/expires"
|
USER_ATTRIBUTE_EXPIRES = "goauthentik.io/user/expires"
|
||||||
USER_ATTRIBUTE_DELETE_ON_LOGOUT = "goauthentik.io/user/delete-on-logout"
|
USER_ATTRIBUTE_DELETE_ON_LOGOUT = "goauthentik.io/user/delete-on-logout"
|
||||||
|
@ -45,8 +44,6 @@ USER_ATTRIBUTE_TOKEN_EXPIRING = "goauthentik.io/user/token-expires" # nosec
|
||||||
USER_ATTRIBUTE_CHANGE_USERNAME = "goauthentik.io/user/can-change-username"
|
USER_ATTRIBUTE_CHANGE_USERNAME = "goauthentik.io/user/can-change-username"
|
||||||
USER_ATTRIBUTE_CHANGE_NAME = "goauthentik.io/user/can-change-name"
|
USER_ATTRIBUTE_CHANGE_NAME = "goauthentik.io/user/can-change-name"
|
||||||
USER_ATTRIBUTE_CHANGE_EMAIL = "goauthentik.io/user/can-change-email"
|
USER_ATTRIBUTE_CHANGE_EMAIL = "goauthentik.io/user/can-change-email"
|
||||||
USER_ATTRIBUTE_CAN_OVERRIDE_IP = "goauthentik.io/user/override-ips"
|
|
||||||
|
|
||||||
USER_PATH_SYSTEM_PREFIX = "goauthentik.io"
|
USER_PATH_SYSTEM_PREFIX = "goauthentik.io"
|
||||||
USER_PATH_SERVICE_ACCOUNT = USER_PATH_SYSTEM_PREFIX + "/service-accounts"
|
USER_PATH_SERVICE_ACCOUNT = USER_PATH_SYSTEM_PREFIX + "/service-accounts"
|
||||||
|
|
||||||
|
@ -66,6 +63,21 @@ def default_token_key():
|
||||||
return generate_id(int(CONFIG.y("default_token_length")))
|
return generate_id(int(CONFIG.y("default_token_length")))
|
||||||
|
|
||||||
|
|
||||||
|
class UserTypes(models.TextChoices):
|
||||||
|
"""User types, both for grouping, licensing and permissions in the case
|
||||||
|
of the internal_service_account"""
|
||||||
|
|
||||||
|
DEFAULT = "default"
|
||||||
|
EXTERNAL = "external"
|
||||||
|
|
||||||
|
# User-created service accounts
|
||||||
|
SERVICE_ACCOUNT = "service_account"
|
||||||
|
|
||||||
|
# Special user type for internally managed and created service
|
||||||
|
# accounts, such as outpost users
|
||||||
|
INTERNAL_SERVICE_ACCOUNT = "internal_service_account"
|
||||||
|
|
||||||
|
|
||||||
class Group(SerializerModel):
|
class Group(SerializerModel):
|
||||||
"""Custom Group model which supports a basic hierarchy"""
|
"""Custom Group model which supports a basic hierarchy"""
|
||||||
|
|
||||||
|
@ -149,6 +161,7 @@ class User(SerializerModel, GuardianUserMixin, AbstractUser):
|
||||||
uuid = models.UUIDField(default=uuid4, editable=False, unique=True)
|
uuid = models.UUIDField(default=uuid4, editable=False, unique=True)
|
||||||
name = models.TextField(help_text=_("User's display name."))
|
name = models.TextField(help_text=_("User's display name."))
|
||||||
path = models.TextField(default="users")
|
path = models.TextField(default="users")
|
||||||
|
type = models.TextField(choices=UserTypes.choices, default=UserTypes.DEFAULT)
|
||||||
|
|
||||||
sources = models.ManyToManyField("Source", through="UserSourceConnection")
|
sources = models.ManyToManyField("Source", through="UserSourceConnection")
|
||||||
ak_groups = models.ManyToManyField("Group", related_name="users")
|
ak_groups = models.ManyToManyField("Group", related_name="users")
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
"""authentik core signals"""
|
"""authentik core signals"""
|
||||||
from typing import TYPE_CHECKING
|
|
||||||
|
|
||||||
from django.contrib.auth.signals import user_logged_in, user_logged_out
|
from django.contrib.auth.signals import user_logged_in, user_logged_out
|
||||||
from django.contrib.sessions.backends.cache import KEY_PREFIX
|
from django.contrib.sessions.backends.cache import KEY_PREFIX
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
|
@ -10,16 +8,13 @@ from django.db.models.signals import post_save, pre_delete, pre_save
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from django.http.request import HttpRequest
|
from django.http.request import HttpRequest
|
||||||
|
|
||||||
from authentik.core.models import Application, AuthenticatedSession, BackchannelProvider
|
from authentik.core.models import Application, AuthenticatedSession, BackchannelProvider, User
|
||||||
|
|
||||||
# Arguments: user: User, password: str
|
# Arguments: user: User, password: str
|
||||||
password_changed = Signal()
|
password_changed = Signal()
|
||||||
# Arguments: credentials: dict[str, any], request: HttpRequest, stage: Stage
|
# Arguments: credentials: dict[str, any], request: HttpRequest, stage: Stage
|
||||||
login_failed = Signal()
|
login_failed = Signal()
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from authentik.core.models import User
|
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=Application)
|
@receiver(post_save, sender=Application)
|
||||||
def post_save_application(sender: type[Model], instance, created: bool, **_):
|
def post_save_application(sender: type[Model], instance, created: bool, **_):
|
||||||
|
@ -35,7 +30,7 @@ def post_save_application(sender: type[Model], instance, created: bool, **_):
|
||||||
|
|
||||||
|
|
||||||
@receiver(user_logged_in)
|
@receiver(user_logged_in)
|
||||||
def user_logged_in_session(sender, request: HttpRequest, user: "User", **_):
|
def user_logged_in_session(sender, request: HttpRequest, user: User, **_):
|
||||||
"""Create an AuthenticatedSession from request"""
|
"""Create an AuthenticatedSession from request"""
|
||||||
|
|
||||||
session = AuthenticatedSession.from_request(request, user)
|
session = AuthenticatedSession.from_request(request, user)
|
||||||
|
@ -44,7 +39,7 @@ def user_logged_in_session(sender, request: HttpRequest, user: "User", **_):
|
||||||
|
|
||||||
|
|
||||||
@receiver(user_logged_out)
|
@receiver(user_logged_out)
|
||||||
def user_logged_out_session(sender, request: HttpRequest, user: "User", **_):
|
def user_logged_out_session(sender, request: HttpRequest, user: User, **_):
|
||||||
"""Delete AuthenticatedSession if it exists"""
|
"""Delete AuthenticatedSession if it exists"""
|
||||||
AuthenticatedSession.objects.filter(session_key=request.session.session_key).delete()
|
AuthenticatedSession.objects.filter(session_key=request.session.session_key).delete()
|
||||||
|
|
||||||
|
|
|
@ -8,11 +8,11 @@ from django.urls.base import reverse
|
||||||
from rest_framework.test import APITestCase
|
from rest_framework.test import APITestCase
|
||||||
|
|
||||||
from authentik.core.models import (
|
from authentik.core.models import (
|
||||||
USER_ATTRIBUTE_SA,
|
|
||||||
USER_ATTRIBUTE_TOKEN_EXPIRING,
|
USER_ATTRIBUTE_TOKEN_EXPIRING,
|
||||||
AuthenticatedSession,
|
AuthenticatedSession,
|
||||||
Token,
|
Token,
|
||||||
User,
|
User,
|
||||||
|
UserTypes,
|
||||||
)
|
)
|
||||||
from authentik.core.tests.utils import create_test_admin_user, create_test_flow, create_test_tenant
|
from authentik.core.tests.utils import create_test_admin_user, create_test_flow, create_test_tenant
|
||||||
from authentik.flows.models import FlowDesignation
|
from authentik.flows.models import FlowDesignation
|
||||||
|
@ -141,7 +141,8 @@ class TestUsersAPI(APITestCase):
|
||||||
|
|
||||||
user_filter = User.objects.filter(
|
user_filter = User.objects.filter(
|
||||||
username="test-sa",
|
username="test-sa",
|
||||||
attributes={USER_ATTRIBUTE_TOKEN_EXPIRING: True, USER_ATTRIBUTE_SA: True},
|
type=UserTypes.SERVICE_ACCOUNT,
|
||||||
|
attributes={USER_ATTRIBUTE_TOKEN_EXPIRING: True},
|
||||||
)
|
)
|
||||||
self.assertTrue(user_filter.exists())
|
self.assertTrue(user_filter.exists())
|
||||||
user: User = user_filter.first()
|
user: User = user_filter.first()
|
||||||
|
@ -166,7 +167,8 @@ class TestUsersAPI(APITestCase):
|
||||||
|
|
||||||
user_filter = User.objects.filter(
|
user_filter = User.objects.filter(
|
||||||
username="test-sa",
|
username="test-sa",
|
||||||
attributes={USER_ATTRIBUTE_TOKEN_EXPIRING: False, USER_ATTRIBUTE_SA: True},
|
type=UserTypes.SERVICE_ACCOUNT,
|
||||||
|
attributes={USER_ATTRIBUTE_TOKEN_EXPIRING: False},
|
||||||
)
|
)
|
||||||
self.assertTrue(user_filter.exists())
|
self.assertTrue(user_filter.exists())
|
||||||
user: User = user_filter.first()
|
user: User = user_filter.first()
|
||||||
|
@ -192,7 +194,8 @@ class TestUsersAPI(APITestCase):
|
||||||
|
|
||||||
user_filter = User.objects.filter(
|
user_filter = User.objects.filter(
|
||||||
username="test-sa",
|
username="test-sa",
|
||||||
attributes={USER_ATTRIBUTE_TOKEN_EXPIRING: True, USER_ATTRIBUTE_SA: True},
|
type=UserTypes.SERVICE_ACCOUNT,
|
||||||
|
attributes={USER_ATTRIBUTE_TOKEN_EXPIRING: True},
|
||||||
)
|
)
|
||||||
self.assertTrue(user_filter.exists())
|
self.assertTrue(user_filter.exists())
|
||||||
user: User = user_filter.first()
|
user: User = user_filter.first()
|
||||||
|
@ -218,7 +221,8 @@ class TestUsersAPI(APITestCase):
|
||||||
|
|
||||||
user_filter = User.objects.filter(
|
user_filter = User.objects.filter(
|
||||||
username="test-sa",
|
username="test-sa",
|
||||||
attributes={USER_ATTRIBUTE_TOKEN_EXPIRING: True, USER_ATTRIBUTE_SA: True},
|
type=UserTypes.SERVICE_ACCOUNT,
|
||||||
|
attributes={USER_ATTRIBUTE_TOKEN_EXPIRING: True},
|
||||||
)
|
)
|
||||||
self.assertTrue(user_filter.exists())
|
self.assertTrue(user_filter.exists())
|
||||||
user: User = user_filter.first()
|
user: User = user_filter.first()
|
||||||
|
|
|
@ -0,0 +1,150 @@
|
||||||
|
"""Enterprise API Views"""
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
from django.utils.timezone import now
|
||||||
|
from drf_spectacular.types import OpenApiTypes
|
||||||
|
from drf_spectacular.utils import extend_schema, inline_serializer
|
||||||
|
from rest_framework.decorators import action
|
||||||
|
from rest_framework.fields import BooleanField, CharField, DateTimeField, IntegerField
|
||||||
|
from rest_framework.permissions import IsAdminUser, IsAuthenticated
|
||||||
|
from rest_framework.request import Request
|
||||||
|
from rest_framework.response import Response
|
||||||
|
from rest_framework.serializers import ModelSerializer
|
||||||
|
from rest_framework.viewsets import ModelViewSet
|
||||||
|
|
||||||
|
from authentik.api.decorators import permission_required
|
||||||
|
from authentik.core.api.used_by import UsedByMixin
|
||||||
|
from authentik.core.api.utils import PassiveSerializer
|
||||||
|
from authentik.core.models import User, UserTypes
|
||||||
|
from authentik.enterprise.models import License, LicenseKey
|
||||||
|
from authentik.root.install_id import get_install_id
|
||||||
|
|
||||||
|
|
||||||
|
class LicenseSerializer(ModelSerializer):
|
||||||
|
"""License Serializer"""
|
||||||
|
|
||||||
|
def validate_key(self, key: str) -> str:
|
||||||
|
"""Validate the license key (install_id and signature)"""
|
||||||
|
LicenseKey.validate(key)
|
||||||
|
return key
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = License
|
||||||
|
fields = [
|
||||||
|
"license_uuid",
|
||||||
|
"name",
|
||||||
|
"key",
|
||||||
|
"expiry",
|
||||||
|
"users",
|
||||||
|
"external_users",
|
||||||
|
]
|
||||||
|
extra_kwargs = {
|
||||||
|
"name": {"read_only": True},
|
||||||
|
"expiry": {"read_only": True},
|
||||||
|
"users": {"read_only": True},
|
||||||
|
"external_users": {"read_only": True},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class LicenseSummary(PassiveSerializer):
|
||||||
|
"""Serializer for license status"""
|
||||||
|
|
||||||
|
users = IntegerField(required=True)
|
||||||
|
external_users = IntegerField(required=True)
|
||||||
|
valid = BooleanField()
|
||||||
|
show_admin_warning = BooleanField()
|
||||||
|
show_user_warning = BooleanField()
|
||||||
|
read_only = BooleanField()
|
||||||
|
latest_valid = DateTimeField()
|
||||||
|
has_license = BooleanField()
|
||||||
|
|
||||||
|
|
||||||
|
class LicenseForecastSerializer(PassiveSerializer):
|
||||||
|
"""Serializer for license forecast"""
|
||||||
|
|
||||||
|
users = IntegerField(required=True)
|
||||||
|
external_users = IntegerField(required=True)
|
||||||
|
|
||||||
|
|
||||||
|
class LicenseViewSet(UsedByMixin, ModelViewSet):
|
||||||
|
"""License Viewset"""
|
||||||
|
|
||||||
|
queryset = License.objects.all()
|
||||||
|
serializer_class = LicenseSerializer
|
||||||
|
search_fields = ["name"]
|
||||||
|
ordering = ["name"]
|
||||||
|
filterset_fields = ["name"]
|
||||||
|
|
||||||
|
@permission_required(None, ["authentik_enterprise.view_license"])
|
||||||
|
@extend_schema(
|
||||||
|
request=OpenApiTypes.NONE,
|
||||||
|
responses={
|
||||||
|
200: inline_serializer("InstallIDSerializer", {"install_id": CharField(required=True)}),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
@action(detail=False, methods=["GET"], permission_classes=[IsAdminUser])
|
||||||
|
def get_install_id(self, request: Request) -> Response:
|
||||||
|
"""Get install_id"""
|
||||||
|
return Response(
|
||||||
|
data={
|
||||||
|
"install_id": get_install_id(),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
@extend_schema(
|
||||||
|
request=OpenApiTypes.NONE,
|
||||||
|
responses={
|
||||||
|
200: LicenseSummary(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
@action(detail=False, methods=["GET"], permission_classes=[IsAuthenticated])
|
||||||
|
def summary(self, request: Request) -> Response:
|
||||||
|
"""Get the total license status"""
|
||||||
|
total = LicenseKey.get_total()
|
||||||
|
last_valid = LicenseKey.last_valid_date()
|
||||||
|
# TODO: move this to a different place?
|
||||||
|
show_admin_warning = last_valid < now() - timedelta(weeks=2)
|
||||||
|
show_user_warning = last_valid < now() - timedelta(weeks=4)
|
||||||
|
read_only = last_valid < now() - timedelta(weeks=6)
|
||||||
|
latest_valid = datetime.fromtimestamp(total.exp)
|
||||||
|
response = LicenseSummary(
|
||||||
|
data={
|
||||||
|
"users": total.users,
|
||||||
|
"external_users": total.external_users,
|
||||||
|
"valid": total.is_valid(),
|
||||||
|
"show_admin_warning": show_admin_warning,
|
||||||
|
"show_user_warning": show_user_warning,
|
||||||
|
"read_only": read_only,
|
||||||
|
"latest_valid": latest_valid,
|
||||||
|
"has_license": License.objects.all().count() > 0,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
response.is_valid(raise_exception=True)
|
||||||
|
return Response(response.data)
|
||||||
|
|
||||||
|
@permission_required(None, ["authentik_enterprise.view_license"])
|
||||||
|
@extend_schema(
|
||||||
|
request=OpenApiTypes.NONE,
|
||||||
|
responses={
|
||||||
|
200: LicenseForecastSerializer(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
@action(detail=False, methods=["GET"])
|
||||||
|
def forecast(self, request: Request) -> Response:
|
||||||
|
"""Forecast how many users will be required in a year"""
|
||||||
|
last_month = now() - timedelta(days=30)
|
||||||
|
# Forecast for default users
|
||||||
|
users_in_last_month = User.objects.filter(
|
||||||
|
type=UserTypes.DEFAULT, date_joined__gte=last_month
|
||||||
|
).count()
|
||||||
|
# Forecast for external users
|
||||||
|
external_in_last_month = LicenseKey.get_external_user_count()
|
||||||
|
forecast_for_months = 12
|
||||||
|
response = LicenseForecastSerializer(
|
||||||
|
data={
|
||||||
|
"users": users_in_last_month * forecast_for_months,
|
||||||
|
"external_users": external_in_last_month * forecast_for_months,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
response.is_valid(raise_exception=True)
|
||||||
|
return Response(response.data)
|
|
@ -9,3 +9,7 @@ class AuthentikEnterpriseConfig(ManagedAppConfig):
|
||||||
label = "authentik_enterprise"
|
label = "authentik_enterprise"
|
||||||
verbose_name = "authentik Enterprise"
|
verbose_name = "authentik Enterprise"
|
||||||
default = True
|
default = True
|
||||||
|
|
||||||
|
def reconcile_load_enterprise_signals(self):
|
||||||
|
"""Load enterprise signals"""
|
||||||
|
self.import_module("authentik.enterprise.signals")
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
# Generated by Django 4.1.10 on 2023-07-06 12:51
|
||||||
|
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
import authentik.enterprise.models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = []
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="License",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"license_uuid",
|
||||||
|
models.UUIDField(
|
||||||
|
default=uuid.uuid4, editable=False, primary_key=True, serialize=False
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("key", models.TextField(unique=True)),
|
||||||
|
("name", models.TextField()),
|
||||||
|
("expiry", models.DateTimeField()),
|
||||||
|
("users", models.BigIntegerField()),
|
||||||
|
("external_users", models.BigIntegerField()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="LicenseUsage",
|
||||||
|
fields=[
|
||||||
|
("expiring", models.BooleanField(default=True)),
|
||||||
|
("expires", models.DateTimeField(default=authentik.enterprise.models.usage_expiry)),
|
||||||
|
(
|
||||||
|
"usage_uuid",
|
||||||
|
models.UUIDField(
|
||||||
|
default=uuid.uuid4, editable=False, primary_key=True, serialize=False
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("user_count", models.BigIntegerField()),
|
||||||
|
("external_user_count", models.BigIntegerField()),
|
||||||
|
("within_limits", models.BooleanField()),
|
||||||
|
("record_date", models.DateTimeField(auto_now_add=True)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"abstract": False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,185 @@
|
||||||
|
"""Enterprise models"""
|
||||||
|
from base64 import b64decode
|
||||||
|
from binascii import Error
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from enum import Enum
|
||||||
|
from functools import lru_cache
|
||||||
|
from time import mktime
|
||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
|
from cryptography.exceptions import InvalidSignature
|
||||||
|
from cryptography.x509 import Certificate, load_pem_x509_certificate
|
||||||
|
from dacite import from_dict
|
||||||
|
from django.db import models
|
||||||
|
from django.db.models.query import QuerySet
|
||||||
|
from django.utils.timezone import now
|
||||||
|
from guardian.shortcuts import get_anonymous_user
|
||||||
|
from jwt import PyJWTError, decode, get_unverified_header
|
||||||
|
from rest_framework.exceptions import ValidationError
|
||||||
|
|
||||||
|
from authentik.core.models import ExpiringModel, User, UserTypes
|
||||||
|
from authentik.root.install_id import get_install_id
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache()
|
||||||
|
def get_licensing_key() -> Certificate:
|
||||||
|
"""Get Root CA PEM"""
|
||||||
|
with open("authentik/enterprise/public.pem", "rb") as _key:
|
||||||
|
return load_pem_x509_certificate(_key.read())
|
||||||
|
|
||||||
|
|
||||||
|
def get_license_aud() -> str:
|
||||||
|
"""Get the JWT audience field"""
|
||||||
|
return f"enterprise.goauthentik.io/license/{get_install_id()}"
|
||||||
|
|
||||||
|
|
||||||
|
class LicenseFlags(Enum):
|
||||||
|
"""License flags"""
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class LicenseKey:
|
||||||
|
"""License JWT claims"""
|
||||||
|
|
||||||
|
aud: str
|
||||||
|
exp: int
|
||||||
|
|
||||||
|
name: str
|
||||||
|
users: int
|
||||||
|
external_users: int
|
||||||
|
flags: list[LicenseFlags] = field(default_factory=list)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def validate(jwt: str) -> "LicenseKey":
|
||||||
|
"""Validate the license from a given JWT"""
|
||||||
|
try:
|
||||||
|
headers = get_unverified_header(jwt)
|
||||||
|
except PyJWTError:
|
||||||
|
raise ValidationError("Unable to verify license")
|
||||||
|
x5c: list[str] = headers.get("x5c", [])
|
||||||
|
if len(x5c) < 1:
|
||||||
|
raise ValidationError("Unable to verify license")
|
||||||
|
try:
|
||||||
|
our_cert = load_pem_x509_certificate(b64decode(x5c[0]))
|
||||||
|
intermediate = load_pem_x509_certificate(b64decode(x5c[1]))
|
||||||
|
our_cert.verify_directly_issued_by(intermediate)
|
||||||
|
intermediate.verify_directly_issued_by(get_licensing_key())
|
||||||
|
except (InvalidSignature, TypeError, ValueError, Error):
|
||||||
|
raise ValidationError("Unable to verify license")
|
||||||
|
try:
|
||||||
|
body = from_dict(
|
||||||
|
LicenseKey,
|
||||||
|
decode(
|
||||||
|
jwt,
|
||||||
|
our_cert.public_key(),
|
||||||
|
algorithms=["ES521"],
|
||||||
|
audience=get_license_aud(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
except PyJWTError:
|
||||||
|
raise ValidationError("Unable to verify license")
|
||||||
|
return body
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_total() -> "LicenseKey":
|
||||||
|
"""Get a summarized version of all (not expired) licenses"""
|
||||||
|
active_licenses = License.objects.filter(expiry__gte=now())
|
||||||
|
total = LicenseKey(get_license_aud(), 0, "Summarized license", 0, 0)
|
||||||
|
for lic in active_licenses:
|
||||||
|
total.users += lic.users
|
||||||
|
total.external_users += lic.external_users
|
||||||
|
exp_ts = int(mktime(lic.expiry.timetuple()))
|
||||||
|
if total.exp == 0:
|
||||||
|
total.exp = exp_ts
|
||||||
|
if exp_ts <= total.exp:
|
||||||
|
total.exp = exp_ts
|
||||||
|
total.flags.extend(lic.status.flags)
|
||||||
|
return total
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def base_user_qs() -> QuerySet:
|
||||||
|
"""Base query set for all users"""
|
||||||
|
return User.objects.all().exclude(pk=get_anonymous_user().pk)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_default_user_count():
|
||||||
|
"""Get current default user count"""
|
||||||
|
return LicenseKey.base_user_qs().filter(type=UserTypes.DEFAULT).count()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_external_user_count():
|
||||||
|
"""Get current external user count"""
|
||||||
|
# Count since start of the month
|
||||||
|
last_month = now().replace(day=1)
|
||||||
|
return (
|
||||||
|
LicenseKey.base_user_qs()
|
||||||
|
.filter(type=UserTypes.EXTERNAL, last_login__gte=last_month)
|
||||||
|
.count()
|
||||||
|
)
|
||||||
|
|
||||||
|
def is_valid(self) -> bool:
|
||||||
|
"""Check if the given license body covers all users
|
||||||
|
|
||||||
|
Only checks the current count, no historical data is checked"""
|
||||||
|
default_users = self.get_default_user_count()
|
||||||
|
if default_users > self.users:
|
||||||
|
return False
|
||||||
|
active_users = self.get_external_user_count()
|
||||||
|
if active_users > self.external_users:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def record_usage(self):
|
||||||
|
"""Capture the current validity status and metrics and save them"""
|
||||||
|
LicenseUsage.objects.create(
|
||||||
|
user_count=self.get_default_user_count(),
|
||||||
|
external_user_count=self.get_external_user_count(),
|
||||||
|
within_limits=self.is_valid(),
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def last_valid_date() -> datetime:
|
||||||
|
"""Get the last date the license was valid"""
|
||||||
|
usage: LicenseUsage = (
|
||||||
|
LicenseUsage.filter_not_expired(within_limits=True).order_by("-record_date").first()
|
||||||
|
)
|
||||||
|
if not usage:
|
||||||
|
return now()
|
||||||
|
return usage.record_date
|
||||||
|
|
||||||
|
|
||||||
|
class License(models.Model):
|
||||||
|
"""An authentik enterprise license"""
|
||||||
|
|
||||||
|
license_uuid = models.UUIDField(primary_key=True, editable=False, default=uuid4)
|
||||||
|
key = models.TextField(unique=True)
|
||||||
|
|
||||||
|
name = models.TextField()
|
||||||
|
expiry = models.DateTimeField()
|
||||||
|
users = models.BigIntegerField()
|
||||||
|
external_users = models.BigIntegerField()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def status(self) -> LicenseKey:
|
||||||
|
"""Get parsed license status"""
|
||||||
|
return LicenseKey.validate(self.key)
|
||||||
|
|
||||||
|
|
||||||
|
def usage_expiry():
|
||||||
|
"""Keep license usage records for 3 months"""
|
||||||
|
return now() + timedelta(days=30 * 3)
|
||||||
|
|
||||||
|
|
||||||
|
class LicenseUsage(ExpiringModel):
|
||||||
|
"""a single license usage record"""
|
||||||
|
|
||||||
|
expires = models.DateTimeField(default=usage_expiry)
|
||||||
|
|
||||||
|
usage_uuid = models.UUIDField(primary_key=True, editable=False, default=uuid4)
|
||||||
|
|
||||||
|
user_count = models.BigIntegerField()
|
||||||
|
external_user_count = models.BigIntegerField()
|
||||||
|
within_limits = models.BooleanField()
|
||||||
|
|
||||||
|
record_date = models.DateTimeField(auto_now_add=True)
|
|
@ -0,0 +1,46 @@
|
||||||
|
"""Enterprise license policies"""
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from rest_framework.serializers import BaseSerializer
|
||||||
|
|
||||||
|
from authentik.core.models import User, UserTypes
|
||||||
|
from authentik.enterprise.models import LicenseKey
|
||||||
|
from authentik.policies.models import Policy
|
||||||
|
from authentik.policies.types import PolicyRequest, PolicyResult
|
||||||
|
from authentik.policies.views import PolicyAccessView
|
||||||
|
|
||||||
|
|
||||||
|
class EnterprisePolicy(Policy):
|
||||||
|
"""Check that a user is correctly licensed for the request"""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def component(self) -> str:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def serializer(self) -> type[BaseSerializer]:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def passes(self, request: PolicyRequest) -> PolicyResult:
|
||||||
|
if not LicenseKey.get_total().is_valid():
|
||||||
|
return PolicyResult(False)
|
||||||
|
if request.user.type != UserTypes.DEFAULT:
|
||||||
|
return PolicyResult(False)
|
||||||
|
return PolicyResult(True)
|
||||||
|
|
||||||
|
|
||||||
|
class EnterprisePolicyAccessView(PolicyAccessView):
|
||||||
|
"""PolicyAccessView which also checks enterprise licensing"""
|
||||||
|
|
||||||
|
def user_has_access(self, user: Optional[User] = None) -> PolicyResult:
|
||||||
|
user = user or self.request.user
|
||||||
|
request = PolicyRequest(user)
|
||||||
|
request.http_request = self.request
|
||||||
|
result = super().user_has_access(user)
|
||||||
|
enterprise_result = EnterprisePolicy().passes(request)
|
||||||
|
if not enterprise_result.passing:
|
||||||
|
return enterprise_result
|
||||||
|
return result
|
||||||
|
|
||||||
|
def resolve_provider_application(self):
|
||||||
|
raise NotImplementedError
|
|
@ -0,0 +1,26 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIEdzCCA/6gAwIBAgIUQrj1jxn4q/BB38B2SwTrvGyrZLMwCgYIKoZIzj0EAwMw
|
||||||
|
ge8xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1T
|
||||||
|
YW4gRnJhbmNpc2NvMSQwIgYDVQQJExs1NDggTWFya2V0IFN0cmVldCBQbWIgNzAx
|
||||||
|
NDgxDjAMBgNVBBETBTk0MTA0MSAwHgYDVQQKExdBdXRoZW50aWsgU2VjdXJpdHkg
|
||||||
|
SW5jLjEcMBoGA1UECxMTRW50ZXJwcmlzZSBMaWNlbnNlczE9MDsGA1UEAxM0QXV0
|
||||||
|
aGVudGlrIFNlY3VyaXR5IEluYy4gRW50ZXJwcmlzZSBMaWNlbnNpbmcgUm9vdCBY
|
||||||
|
MTAgFw0yMzA3MDQxNzQ3NDBaGA8yMTIzMDYxMDE3NDgxMFowge8xCzAJBgNVBAYT
|
||||||
|
AlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1TYW4gRnJhbmNpc2Nv
|
||||||
|
MSQwIgYDVQQJExs1NDggTWFya2V0IFN0cmVldCBQbWIgNzAxNDgxDjAMBgNVBBET
|
||||||
|
BTk0MTA0MSAwHgYDVQQKExdBdXRoZW50aWsgU2VjdXJpdHkgSW5jLjEcMBoGA1UE
|
||||||
|
CxMTRW50ZXJwcmlzZSBMaWNlbnNlczE9MDsGA1UEAxM0QXV0aGVudGlrIFNlY3Vy
|
||||||
|
aXR5IEluYy4gRW50ZXJwcmlzZSBMaWNlbnNpbmcgUm9vdCBYMTB2MBAGByqGSM49
|
||||||
|
AgEGBSuBBAAiA2IABNbPJH6nDbSshpDsDHBRL0UcZVXWCK30txqcMKU+YFmLB6iR
|
||||||
|
PJiHjHA8Z+5aP4eNH6onA5xqykQf65tvbFBA1LB/6HqMArU/tYVVQx4+o9hRBxF5
|
||||||
|
RrzXucUg2br+RX8aa6OCAVUwggFRMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8E
|
||||||
|
BTADAQH/MB0GA1UdDgQWBBRHpR3/ptPgN0yHVfUjyJOEmsPZqTAfBgNVHSMEGDAW
|
||||||
|
gBRHpR3/ptPgN0yHVfUjyJOEmsPZqTCBoAYIKwYBBQUHAQEEgZMwgZAwRwYIKwYB
|
||||||
|
BQUHMAGGO2h0dHBzOi8vdmF1bHQuY3VzdG9tZXJzLmdvYXV0aGVudGlrLmlvL3Yx
|
||||||
|
L2xpY2Vuc2luZy1jYS9vY3NwMEUGCCsGAQUFBzAChjlodHRwczovL3ZhdWx0LmN1
|
||||||
|
c3RvbWVycy5nb2F1dGhlbnRpay5pby92MS9saWNlbnNpbmctY2EvY2EwSwYDVR0f
|
||||||
|
BEQwQjBAoD6gPIY6aHR0cHM6Ly92YXVsdC5jdXN0b21lcnMuZ29hdXRoZW50aWsu
|
||||||
|
aW8vdjEvbGljZW5zaW5nLWNhL2NybDAKBggqhkjOPQQDAwNnADBkAjB0+YA1yjEO
|
||||||
|
g43CCYUJXz9m9CNIkjOPUI0jO4UtvSj8j067TKRbX6IL/29HxPtQoYACME8eZHBJ
|
||||||
|
Ljcog0oeBgjr4wK8bobgknr5wrm70rrNNpbSAjDvTvXMQeAShGgsftEquQ==
|
||||||
|
-----END CERTIFICATE-----
|
|
@ -1 +1,12 @@
|
||||||
"""Enterprise additional settings"""
|
"""Enterprise additional settings"""
|
||||||
|
from celery.schedules import crontab
|
||||||
|
|
||||||
|
from authentik.lib.utils.time import fqdn_rand
|
||||||
|
|
||||||
|
CELERY_BEAT_SCHEDULE = {
|
||||||
|
"enterprise_calculate_license": {
|
||||||
|
"task": "authentik.enterprise.tasks.calculate_license",
|
||||||
|
"schedule": crontab(minute=fqdn_rand("calculate_license"), hour="*/8"),
|
||||||
|
"options": {"queue": "authentik_scheduled"},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
"""Enterprise signals"""
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from django.db.models.signals import pre_save
|
||||||
|
from django.dispatch import receiver
|
||||||
|
from django.utils.timezone import get_current_timezone
|
||||||
|
|
||||||
|
from authentik.enterprise.models import License
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(pre_save, sender=License)
|
||||||
|
def pre_save_license(sender: type[License], instance: License, **_):
|
||||||
|
"""Extract data from license jwt and save it into model"""
|
||||||
|
status = instance.status
|
||||||
|
instance.name = status.name
|
||||||
|
instance.users = status.users
|
||||||
|
instance.external_users = status.external_users
|
||||||
|
instance.expiry = datetime.fromtimestamp(status.exp, tz=get_current_timezone())
|
|
@ -0,0 +1,10 @@
|
||||||
|
"""Enterprise tasks"""
|
||||||
|
from authentik.enterprise.models import LicenseKey
|
||||||
|
from authentik.root.celery import CELERY_APP
|
||||||
|
|
||||||
|
|
||||||
|
@CELERY_APP.task()
|
||||||
|
def calculate_license():
|
||||||
|
"""Calculate licensing status"""
|
||||||
|
total = LicenseKey.get_total()
|
||||||
|
total.record_usage()
|
|
@ -0,0 +1,64 @@
|
||||||
|
"""Enterprise license tests"""
|
||||||
|
from datetime import timedelta
|
||||||
|
from time import mktime
|
||||||
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
|
from django.test import TestCase
|
||||||
|
from django.utils.timezone import now
|
||||||
|
from rest_framework.exceptions import ValidationError
|
||||||
|
|
||||||
|
from authentik.enterprise.models import License, LicenseKey
|
||||||
|
from authentik.lib.generators import generate_id
|
||||||
|
|
||||||
|
_exp = int(mktime((now() + timedelta(days=3000)).timetuple()))
|
||||||
|
|
||||||
|
|
||||||
|
class TestEnterpriseLicense(TestCase):
|
||||||
|
"""Enterprise license tests"""
|
||||||
|
|
||||||
|
@patch(
|
||||||
|
"authentik.enterprise.models.LicenseKey.validate",
|
||||||
|
MagicMock(
|
||||||
|
return_value=LicenseKey(
|
||||||
|
aud="",
|
||||||
|
exp=_exp,
|
||||||
|
name=generate_id(),
|
||||||
|
users=100,
|
||||||
|
external_users=100,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
def test_valid(self):
|
||||||
|
"""Check license verification"""
|
||||||
|
lic = License.objects.create(key=generate_id())
|
||||||
|
self.assertTrue(lic.status.is_valid())
|
||||||
|
self.assertEqual(lic.users, 100)
|
||||||
|
|
||||||
|
def test_invalid(self):
|
||||||
|
"""Test invalid license"""
|
||||||
|
with self.assertRaises(ValidationError):
|
||||||
|
License.objects.create(key=generate_id())
|
||||||
|
|
||||||
|
@patch(
|
||||||
|
"authentik.enterprise.models.LicenseKey.validate",
|
||||||
|
MagicMock(
|
||||||
|
return_value=LicenseKey(
|
||||||
|
aud="",
|
||||||
|
exp=_exp,
|
||||||
|
name=generate_id(),
|
||||||
|
users=100,
|
||||||
|
external_users=100,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
def test_valid_multiple(self):
|
||||||
|
"""Check license verification"""
|
||||||
|
lic = License.objects.create(key=generate_id())
|
||||||
|
self.assertTrue(lic.status.is_valid())
|
||||||
|
lic2 = License.objects.create(key=generate_id())
|
||||||
|
self.assertTrue(lic2.status.is_valid())
|
||||||
|
total = LicenseKey.get_total()
|
||||||
|
self.assertEqual(total.users, 200)
|
||||||
|
self.assertEqual(total.external_users, 200)
|
||||||
|
self.assertEqual(total.exp, _exp)
|
||||||
|
self.assertTrue(total.is_valid())
|
|
@ -0,0 +1,7 @@
|
||||||
|
"""API URLs"""
|
||||||
|
|
||||||
|
from authentik.enterprise.api import LicenseViewSet
|
||||||
|
|
||||||
|
api_urlpatterns = [
|
||||||
|
("enterprise/license", LicenseViewSet),
|
||||||
|
]
|
|
@ -1,7 +1,7 @@
|
||||||
"""Test HTTP Helpers"""
|
"""Test HTTP Helpers"""
|
||||||
from django.test import RequestFactory, TestCase
|
from django.test import RequestFactory, TestCase
|
||||||
|
|
||||||
from authentik.core.models import USER_ATTRIBUTE_CAN_OVERRIDE_IP, Token, TokenIntents
|
from authentik.core.models import Token, TokenIntents, UserTypes
|
||||||
from authentik.core.tests.utils import create_test_admin_user
|
from authentik.core.tests.utils import create_test_admin_user
|
||||||
from authentik.lib.utils.http import OUTPOST_REMOTE_IP_HEADER, OUTPOST_TOKEN_HEADER, get_client_ip
|
from authentik.lib.utils.http import OUTPOST_REMOTE_IP_HEADER, OUTPOST_TOKEN_HEADER, get_client_ip
|
||||||
from authentik.lib.views import bad_request_message
|
from authentik.lib.views import bad_request_message
|
||||||
|
@ -53,7 +53,7 @@ class TestHTTP(TestCase):
|
||||||
)
|
)
|
||||||
self.assertEqual(get_client_ip(request), "127.0.0.1")
|
self.assertEqual(get_client_ip(request), "127.0.0.1")
|
||||||
# Valid
|
# Valid
|
||||||
self.user.attributes[USER_ATTRIBUTE_CAN_OVERRIDE_IP] = True
|
self.user.type = UserTypes.INTERNAL_SERVICE_ACCOUNT
|
||||||
self.user.save()
|
self.user.save()
|
||||||
request = self.factory.get(
|
request = self.factory.get(
|
||||||
"/",
|
"/",
|
||||||
|
|
|
@ -33,9 +33,8 @@ def _get_client_ip_from_meta(meta: dict[str, Any]) -> str:
|
||||||
|
|
||||||
def _get_outpost_override_ip(request: HttpRequest) -> Optional[str]:
|
def _get_outpost_override_ip(request: HttpRequest) -> Optional[str]:
|
||||||
"""Get the actual remote IP when set by an outpost. Only
|
"""Get the actual remote IP when set by an outpost. Only
|
||||||
allowed when the request is authenticated, by a user with USER_ATTRIBUTE_CAN_OVERRIDE_IP set
|
allowed when the request is authenticated, by an outpost internal service account"""
|
||||||
to outpost"""
|
from authentik.core.models import Token, TokenIntents, UserTypes
|
||||||
from authentik.core.models import USER_ATTRIBUTE_CAN_OVERRIDE_IP, Token, TokenIntents
|
|
||||||
|
|
||||||
if OUTPOST_REMOTE_IP_HEADER not in request.META or OUTPOST_TOKEN_HEADER not in request.META:
|
if OUTPOST_REMOTE_IP_HEADER not in request.META or OUTPOST_TOKEN_HEADER not in request.META:
|
||||||
return None
|
return None
|
||||||
|
@ -51,7 +50,7 @@ def _get_outpost_override_ip(request: HttpRequest) -> Optional[str]:
|
||||||
LOGGER.warning("Attempted remote-ip override without token", fake_ip=fake_ip)
|
LOGGER.warning("Attempted remote-ip override without token", fake_ip=fake_ip)
|
||||||
return None
|
return None
|
||||||
user = token.user
|
user = token.user
|
||||||
if not user.group_attributes(request).get(USER_ATTRIBUTE_CAN_OVERRIDE_IP, False):
|
if user.type != UserTypes.INTERNAL_SERVICE_ACCOUNT:
|
||||||
LOGGER.warning(
|
LOGGER.warning(
|
||||||
"Remote-IP override: user doesn't have permission",
|
"Remote-IP override: user doesn't have permission",
|
||||||
user=user,
|
user=user,
|
||||||
|
|
|
@ -20,13 +20,12 @@ from structlog.stdlib import get_logger
|
||||||
from authentik import __version__, get_build_hash
|
from authentik import __version__, get_build_hash
|
||||||
from authentik.blueprints.models import ManagedModel
|
from authentik.blueprints.models import ManagedModel
|
||||||
from authentik.core.models import (
|
from authentik.core.models import (
|
||||||
USER_ATTRIBUTE_CAN_OVERRIDE_IP,
|
|
||||||
USER_ATTRIBUTE_SA,
|
|
||||||
USER_PATH_SYSTEM_PREFIX,
|
USER_PATH_SYSTEM_PREFIX,
|
||||||
Provider,
|
Provider,
|
||||||
Token,
|
Token,
|
||||||
TokenIntents,
|
TokenIntents,
|
||||||
User,
|
User,
|
||||||
|
UserTypes,
|
||||||
)
|
)
|
||||||
from authentik.crypto.models import CertificateKeyPair
|
from authentik.crypto.models import CertificateKeyPair
|
||||||
from authentik.events.models import Event, EventAction
|
from authentik.events.models import Event, EventAction
|
||||||
|
@ -346,8 +345,7 @@ class Outpost(SerializerModel, ManagedModel):
|
||||||
user: User = User.objects.create(username=self.user_identifier)
|
user: User = User.objects.create(username=self.user_identifier)
|
||||||
user.set_unusable_password()
|
user.set_unusable_password()
|
||||||
user_created = True
|
user_created = True
|
||||||
user.attributes[USER_ATTRIBUTE_SA] = True
|
user.type = UserTypes.INTERNAL_SERVICE_ACCOUNT
|
||||||
user.attributes[USER_ATTRIBUTE_CAN_OVERRIDE_IP] = True
|
|
||||||
user.name = f"Outpost {self.name} Service-Account"
|
user.name = f"Outpost {self.name} Service-Account"
|
||||||
user.path = USER_PATH_OUTPOSTS
|
user.path = USER_PATH_OUTPOSTS
|
||||||
user.save()
|
user.save()
|
||||||
|
|
|
@ -64,7 +64,7 @@ class PolicyEngine:
|
||||||
self.use_cache = True
|
self.use_cache = True
|
||||||
self.__expected_result_count = 0
|
self.__expected_result_count = 0
|
||||||
|
|
||||||
def _iter_bindings(self) -> Iterator[PolicyBinding]:
|
def iterate_bindings(self) -> Iterator[PolicyBinding]:
|
||||||
"""Make sure all Policies are their respective classes"""
|
"""Make sure all Policies are their respective classes"""
|
||||||
return (
|
return (
|
||||||
PolicyBinding.objects.filter(target=self.__pbm, enabled=True)
|
PolicyBinding.objects.filter(target=self.__pbm, enabled=True)
|
||||||
|
@ -88,7 +88,7 @@ class PolicyEngine:
|
||||||
span: Span
|
span: Span
|
||||||
span.set_data("pbm", self.__pbm)
|
span.set_data("pbm", self.__pbm)
|
||||||
span.set_data("request", self.request)
|
span.set_data("request", self.request)
|
||||||
for binding in self._iter_bindings():
|
for binding in self.iterate_bindings():
|
||||||
self.__expected_result_count += 1
|
self.__expected_result_count += 1
|
||||||
|
|
||||||
self._check_policy_type(binding)
|
self._check_policy_type(binding)
|
||||||
|
|
|
@ -6,7 +6,7 @@ from django.urls import reverse
|
||||||
from jwt import decode
|
from jwt import decode
|
||||||
|
|
||||||
from authentik.blueprints.tests import apply_blueprint
|
from authentik.blueprints.tests import apply_blueprint
|
||||||
from authentik.core.models import USER_ATTRIBUTE_SA, Application, Group, Token, TokenIntents
|
from authentik.core.models import Application, Group, Token, TokenIntents, UserTypes
|
||||||
from authentik.core.tests.utils import create_test_admin_user, create_test_cert, create_test_flow
|
from authentik.core.tests.utils import create_test_admin_user, create_test_cert, create_test_flow
|
||||||
from authentik.policies.models import PolicyBinding
|
from authentik.policies.models import PolicyBinding
|
||||||
from authentik.providers.oauth2.constants import (
|
from authentik.providers.oauth2.constants import (
|
||||||
|
@ -37,7 +37,7 @@ class TestTokenClientCredentials(OAuthTestCase):
|
||||||
self.provider.property_mappings.set(ScopeMapping.objects.all())
|
self.provider.property_mappings.set(ScopeMapping.objects.all())
|
||||||
self.app = Application.objects.create(name="test", slug="test", provider=self.provider)
|
self.app = Application.objects.create(name="test", slug="test", provider=self.provider)
|
||||||
self.user = create_test_admin_user("sa")
|
self.user = create_test_admin_user("sa")
|
||||||
self.user.attributes[USER_ATTRIBUTE_SA] = True
|
self.user.type = UserTypes.SERVICE_ACCOUNT
|
||||||
self.user.save()
|
self.user.save()
|
||||||
self.token = Token.objects.create(
|
self.token = Token.objects.create(
|
||||||
identifier="sa-token",
|
identifier="sa-token",
|
||||||
|
|
|
@ -1,17 +1,11 @@
|
||||||
"""SCIM Provider models"""
|
"""SCIM Provider models"""
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import Q, QuerySet
|
from django.db.models import QuerySet
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from guardian.shortcuts import get_anonymous_user
|
from guardian.shortcuts import get_anonymous_user
|
||||||
from rest_framework.serializers import Serializer
|
from rest_framework.serializers import Serializer
|
||||||
|
|
||||||
from authentik.core.models import (
|
from authentik.core.models import BackchannelProvider, Group, PropertyMapping, User, UserTypes
|
||||||
USER_ATTRIBUTE_SA,
|
|
||||||
BackchannelProvider,
|
|
||||||
Group,
|
|
||||||
PropertyMapping,
|
|
||||||
User,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class SCIMProvider(BackchannelProvider):
|
class SCIMProvider(BackchannelProvider):
|
||||||
|
@ -38,17 +32,8 @@ class SCIMProvider(BackchannelProvider):
|
||||||
according to the provider's settings"""
|
according to the provider's settings"""
|
||||||
base = User.objects.all().exclude(pk=get_anonymous_user().pk)
|
base = User.objects.all().exclude(pk=get_anonymous_user().pk)
|
||||||
if self.exclude_users_service_account:
|
if self.exclude_users_service_account:
|
||||||
base = base.filter(
|
base = base.exclude(type=UserTypes.SERVICE_ACCOUNT).exclude(
|
||||||
Q(
|
type=UserTypes.INTERNAL_SERVICE_ACCOUNT
|
||||||
**{
|
|
||||||
f"attributes__{USER_ATTRIBUTE_SA}__isnull": True,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
| Q(
|
|
||||||
**{
|
|
||||||
f"attributes__{USER_ATTRIBUTE_SA}": False,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
if self.filter_group:
|
if self.filter_group:
|
||||||
base = base.filter(ak_groups__in=[self.filter_group])
|
base = base.filter(ak_groups__in=[self.filter_group])
|
||||||
|
|
|
@ -146,6 +146,7 @@ SPECTACULAR_SETTINGS = {
|
||||||
"PromptTypeEnum": "authentik.stages.prompt.models.FieldTypes",
|
"PromptTypeEnum": "authentik.stages.prompt.models.FieldTypes",
|
||||||
"LDAPAPIAccessMode": "authentik.providers.ldap.models.APIAccessMode",
|
"LDAPAPIAccessMode": "authentik.providers.ldap.models.APIAccessMode",
|
||||||
"UserVerificationEnum": "authentik.stages.authenticator_webauthn.models.UserVerification",
|
"UserVerificationEnum": "authentik.stages.authenticator_webauthn.models.UserVerification",
|
||||||
|
"UserTypeEnum": "authentik.core.models.UserTypes",
|
||||||
},
|
},
|
||||||
"ENUM_ADD_EXPLICIT_BLANK_NULL_CHOICE": False,
|
"ENUM_ADD_EXPLICIT_BLANK_NULL_CHOICE": False,
|
||||||
"POSTPROCESSING_HOOKS": [
|
"POSTPROCESSING_HOOKS": [
|
||||||
|
|
|
@ -179,7 +179,7 @@ class ListPolicyEngine(PolicyEngine):
|
||||||
self.__list = policies
|
self.__list = policies
|
||||||
self.use_cache = False
|
self.use_cache = False
|
||||||
|
|
||||||
def _iter_bindings(self) -> Iterator[PolicyBinding]:
|
def iterate_bindings(self) -> Iterator[PolicyBinding]:
|
||||||
for policy in self.__list:
|
for policy in self.__list:
|
||||||
yield PolicyBinding(
|
yield PolicyBinding(
|
||||||
policy=policy,
|
policy=policy,
|
||||||
|
|
|
@ -3980,6 +3980,16 @@
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"minLength": 1,
|
"minLength": 1,
|
||||||
"title": "Path"
|
"title": "Path"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"default",
|
||||||
|
"external",
|
||||||
|
"service_account",
|
||||||
|
"internal_service_account"
|
||||||
|
],
|
||||||
|
"title": "Type"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
|
@ -4171,6 +4181,16 @@
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"minLength": 1,
|
"minLength": 1,
|
||||||
"title": "Path"
|
"title": "Path"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"default",
|
||||||
|
"external",
|
||||||
|
"service_account",
|
||||||
|
"internal_service_account"
|
||||||
|
],
|
||||||
|
"title": "Type"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
|
@ -4366,6 +4386,16 @@
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"minLength": 1,
|
"minLength": 1,
|
||||||
"title": "Path"
|
"title": "Path"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"default",
|
||||||
|
"external",
|
||||||
|
"service_account",
|
||||||
|
"internal_service_account"
|
||||||
|
],
|
||||||
|
"title": "Type"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
|
@ -6522,6 +6552,16 @@
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"minLength": 1,
|
"minLength": 1,
|
||||||
"title": "Path"
|
"title": "Path"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"default",
|
||||||
|
"external",
|
||||||
|
"service_account",
|
||||||
|
"internal_service_account"
|
||||||
|
],
|
||||||
|
"title": "Type"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
|
@ -7257,6 +7297,16 @@
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"minLength": 1,
|
"minLength": 1,
|
||||||
"title": "Path"
|
"title": "Path"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"default",
|
||||||
|
"external",
|
||||||
|
"service_account",
|
||||||
|
"internal_service_account"
|
||||||
|
],
|
||||||
|
"title": "Type"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
|
@ -8334,6 +8384,16 @@
|
||||||
"minLength": 1,
|
"minLength": 1,
|
||||||
"title": "Path"
|
"title": "Path"
|
||||||
},
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"default",
|
||||||
|
"external",
|
||||||
|
"service_account",
|
||||||
|
"internal_service_account"
|
||||||
|
],
|
||||||
|
"title": "Type"
|
||||||
|
},
|
||||||
"password": {
|
"password": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"minLength": 1,
|
"minLength": 1,
|
||||||
|
|
|
@ -41,7 +41,7 @@ func (ds *DirectSearcher) SearchSubschema(req *search.Request) (ldap.ServerSearc
|
||||||
// Custom attributes
|
// Custom attributes
|
||||||
// Temporarily use 1.3.6.1.4.1.26027.1.1 as a base
|
// Temporarily use 1.3.6.1.4.1.26027.1.1 as a base
|
||||||
// https://docs.oracle.com/cd/E19450-01/820-6169/working-with-object-identifiers.html#obtaining-a-base-oid
|
// https://docs.oracle.com/cd/E19450-01/820-6169/working-with-object-identifiers.html#obtaining-a-base-oid
|
||||||
"( 1.3.6.1.4.1.26027.1.1.1 NAME 'goauthentik.io/ldap/user' SUP organizationalPerson STRUCTURAL MAY ( ak-active $ sAMAccountName $ goauthentikio-user-sources $ goauthentik.io/user/sources $ goauthentik.io/ldap/active $ goauthentik.io/ldap/superuser $ goauthentikio-user-override-ips $ goauthentikio-user-service-account ) )",
|
"( 1.3.6.1.4.1.26027.1.1.1 NAME 'goauthentik.io/ldap/user' SUP organizationalPerson STRUCTURAL MAY ( ak-active $ sAMAccountName $ goauthentikio-user-sources $ goauthentik.io/user/sources $ goauthentik.io/ldap/active $ goauthentik.io/ldap/superuser ) )",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -85,8 +85,6 @@ func (ds *DirectSearcher) SearchSubschema(req *search.Request) (ldap.ServerSearc
|
||||||
// https://docs.oracle.com/cd/E19450-01/820-6169/working-with-object-identifiers.html#obtaining-a-base-oid
|
// https://docs.oracle.com/cd/E19450-01/820-6169/working-with-object-identifiers.html#obtaining-a-base-oid
|
||||||
"( 1.3.6.1.4.1.26027.1.1.2 NAME ( 'goauthentik.io/ldap/superuser' 'ak-superuser' ) SYNTAX '1.3.6.1.4.1.1466.115.121.1.7' SINGLE-VALUE )",
|
"( 1.3.6.1.4.1.26027.1.1.2 NAME ( 'goauthentik.io/ldap/superuser' 'ak-superuser' ) SYNTAX '1.3.6.1.4.1.1466.115.121.1.7' SINGLE-VALUE )",
|
||||||
"( 1.3.6.1.4.1.26027.1.1.3 NAME ( 'goauthentik.io/ldap/active' 'ak-active' ) SYNTAX '1.3.6.1.4.1.1466.115.121.1.7' SINGLE-VALUE )",
|
"( 1.3.6.1.4.1.26027.1.1.3 NAME ( 'goauthentik.io/ldap/active' 'ak-active' ) SYNTAX '1.3.6.1.4.1.1466.115.121.1.7' SINGLE-VALUE )",
|
||||||
"( 1.3.6.1.4.1.26027.1.1.4 NAME 'goauthentikio-user-override-ips' SYNTAX '1.3.6.1.4.1.1466.115.121.1.7' SINGLE-VALUE )",
|
|
||||||
"( 1.3.6.1.4.1.26027.1.1.5 NAME 'goauthentikio-user-service-account' SYNTAX '1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE' )",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -8,7 +8,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2023-07-11 18:34+0000\n"
|
"POT-Creation-Date: 2023-07-16 13:59+0000\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
|
@ -90,133 +90,133 @@ msgstr ""
|
||||||
msgid "No empty segments in user path allowed."
|
msgid "No empty segments in user path allowed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/core/models.py:74
|
#: authentik/core/models.py:86
|
||||||
msgid "name"
|
msgid "name"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/core/models.py:76
|
#: authentik/core/models.py:88
|
||||||
msgid "Users added to this group will be superusers."
|
msgid "Users added to this group will be superusers."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/core/models.py:150
|
#: authentik/core/models.py:162
|
||||||
msgid "User's display name."
|
msgid "User's display name."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/core/models.py:243 authentik/providers/oauth2/models.py:294
|
#: authentik/core/models.py:256 authentik/providers/oauth2/models.py:294
|
||||||
msgid "User"
|
msgid "User"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/core/models.py:244
|
#: authentik/core/models.py:257
|
||||||
msgid "Users"
|
msgid "Users"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/core/models.py:257
|
#: authentik/core/models.py:270
|
||||||
msgid ""
|
msgid ""
|
||||||
"Flow used for authentication when the associated application is accessed by "
|
"Flow used for authentication when the associated application is accessed by "
|
||||||
"an un-authenticated user."
|
"an un-authenticated user."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/core/models.py:267
|
#: authentik/core/models.py:280
|
||||||
msgid "Flow used when authorizing this provider."
|
msgid "Flow used when authorizing this provider."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/core/models.py:279
|
#: authentik/core/models.py:292
|
||||||
msgid ""
|
msgid ""
|
||||||
"Accessed from applications; optional backchannel providers for protocols "
|
"Accessed from applications; optional backchannel providers for protocols "
|
||||||
"like LDAP and SCIM."
|
"like LDAP and SCIM."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/core/models.py:334
|
#: authentik/core/models.py:347
|
||||||
msgid "Application's display Name."
|
msgid "Application's display Name."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/core/models.py:335
|
#: authentik/core/models.py:348
|
||||||
msgid "Internal application name, used in URLs."
|
msgid "Internal application name, used in URLs."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/core/models.py:347
|
#: authentik/core/models.py:360
|
||||||
msgid "Open launch URL in a new browser tab or window."
|
msgid "Open launch URL in a new browser tab or window."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/core/models.py:411
|
#: authentik/core/models.py:424
|
||||||
msgid "Application"
|
msgid "Application"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/core/models.py:412
|
#: authentik/core/models.py:425
|
||||||
msgid "Applications"
|
msgid "Applications"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/core/models.py:418
|
#: authentik/core/models.py:431
|
||||||
msgid "Use the source-specific identifier"
|
msgid "Use the source-specific identifier"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/core/models.py:420
|
#: authentik/core/models.py:433
|
||||||
msgid ""
|
msgid ""
|
||||||
"Link to a user with identical email address. Can have security implications "
|
"Link to a user with identical email address. Can have security implications "
|
||||||
"when a source doesn't validate email addresses."
|
"when a source doesn't validate email addresses."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/core/models.py:424
|
#: authentik/core/models.py:437
|
||||||
msgid ""
|
msgid ""
|
||||||
"Use the user's email address, but deny enrollment when the email address "
|
"Use the user's email address, but deny enrollment when the email address "
|
||||||
"already exists."
|
"already exists."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/core/models.py:427
|
#: authentik/core/models.py:440
|
||||||
msgid ""
|
msgid ""
|
||||||
"Link to a user with identical username. Can have security implications when "
|
"Link to a user with identical username. Can have security implications when "
|
||||||
"a username is used with another source."
|
"a username is used with another source."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/core/models.py:431
|
#: authentik/core/models.py:444
|
||||||
msgid ""
|
msgid ""
|
||||||
"Use the user's username, but deny enrollment when the username already "
|
"Use the user's username, but deny enrollment when the username already "
|
||||||
"exists."
|
"exists."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/core/models.py:438
|
#: authentik/core/models.py:451
|
||||||
msgid "Source's display Name."
|
msgid "Source's display Name."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/core/models.py:439
|
#: authentik/core/models.py:452
|
||||||
msgid "Internal source name, used in URLs."
|
msgid "Internal source name, used in URLs."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/core/models.py:458
|
#: authentik/core/models.py:471
|
||||||
msgid "Flow to use when authenticating existing users."
|
msgid "Flow to use when authenticating existing users."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/core/models.py:467
|
#: authentik/core/models.py:480
|
||||||
msgid "Flow to use when enrolling new users."
|
msgid "Flow to use when enrolling new users."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/core/models.py:475
|
#: authentik/core/models.py:488
|
||||||
msgid ""
|
msgid ""
|
||||||
"How the source determines if an existing user should be authenticated or a "
|
"How the source determines if an existing user should be authenticated or a "
|
||||||
"new user enrolled."
|
"new user enrolled."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/core/models.py:647
|
#: authentik/core/models.py:660
|
||||||
msgid "Token"
|
msgid "Token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/core/models.py:648
|
#: authentik/core/models.py:661
|
||||||
msgid "Tokens"
|
msgid "Tokens"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/core/models.py:689
|
#: authentik/core/models.py:702
|
||||||
msgid "Property Mapping"
|
msgid "Property Mapping"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/core/models.py:690
|
#: authentik/core/models.py:703
|
||||||
msgid "Property Mappings"
|
msgid "Property Mappings"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/core/models.py:725
|
#: authentik/core/models.py:738
|
||||||
msgid "Authenticated Session"
|
msgid "Authenticated Session"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/core/models.py:726
|
#: authentik/core/models.py:739
|
||||||
msgid "Authenticated Sessions"
|
msgid "Authenticated Sessions"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -585,65 +585,65 @@ msgstr ""
|
||||||
msgid "Invalid kubeconfig"
|
msgid "Invalid kubeconfig"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/outposts/models.py:122
|
#: authentik/outposts/models.py:121
|
||||||
msgid ""
|
msgid ""
|
||||||
"If enabled, use the local connection. Required Docker socket/Kubernetes "
|
"If enabled, use the local connection. Required Docker socket/Kubernetes "
|
||||||
"Integration"
|
"Integration"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/outposts/models.py:152
|
#: authentik/outposts/models.py:151
|
||||||
msgid "Outpost Service-Connection"
|
msgid "Outpost Service-Connection"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/outposts/models.py:153
|
#: authentik/outposts/models.py:152
|
||||||
msgid "Outpost Service-Connections"
|
msgid "Outpost Service-Connections"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/outposts/models.py:161
|
#: authentik/outposts/models.py:160
|
||||||
msgid ""
|
msgid ""
|
||||||
"Can be in the format of 'unix://<path>' when connecting to a local docker "
|
"Can be in the format of 'unix://<path>' when connecting to a local docker "
|
||||||
"daemon, or 'https://<hostname>:2376' when connecting to a remote system."
|
"daemon, or 'https://<hostname>:2376' when connecting to a remote system."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/outposts/models.py:173
|
#: authentik/outposts/models.py:172
|
||||||
msgid ""
|
msgid ""
|
||||||
"CA which the endpoint's Certificate is verified against. Can be left empty "
|
"CA which the endpoint's Certificate is verified against. Can be left empty "
|
||||||
"for no validation."
|
"for no validation."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/outposts/models.py:185
|
#: authentik/outposts/models.py:184
|
||||||
msgid ""
|
msgid ""
|
||||||
"Certificate/Key used for authentication. Can be left empty for no "
|
"Certificate/Key used for authentication. Can be left empty for no "
|
||||||
"authentication."
|
"authentication."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/outposts/models.py:203
|
#: authentik/outposts/models.py:202
|
||||||
msgid "Docker Service-Connection"
|
msgid "Docker Service-Connection"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/outposts/models.py:204
|
#: authentik/outposts/models.py:203
|
||||||
msgid "Docker Service-Connections"
|
msgid "Docker Service-Connections"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/outposts/models.py:212
|
#: authentik/outposts/models.py:211
|
||||||
msgid ""
|
msgid ""
|
||||||
"Paste your kubeconfig here. authentik will automatically use the currently "
|
"Paste your kubeconfig here. authentik will automatically use the currently "
|
||||||
"selected context."
|
"selected context."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/outposts/models.py:218
|
#: authentik/outposts/models.py:217
|
||||||
msgid "Verify SSL Certificates of the Kubernetes API endpoint"
|
msgid "Verify SSL Certificates of the Kubernetes API endpoint"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/outposts/models.py:235
|
#: authentik/outposts/models.py:234
|
||||||
msgid "Kubernetes Service-Connection"
|
msgid "Kubernetes Service-Connection"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/outposts/models.py:236
|
#: authentik/outposts/models.py:235
|
||||||
msgid "Kubernetes Service-Connections"
|
msgid "Kubernetes Service-Connections"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/outposts/models.py:252
|
#: authentik/outposts/models.py:251
|
||||||
msgid ""
|
msgid ""
|
||||||
"Select Service-Connection authentik should use to manage this outpost. Leave "
|
"Select Service-Connection authentik should use to manage this outpost. Leave "
|
||||||
"empty if authentik should not handle the deployment."
|
"empty if authentik should not handle the deployment."
|
||||||
|
@ -1373,31 +1373,31 @@ msgstr ""
|
||||||
msgid "SAML Property Mappings"
|
msgid "SAML Property Mappings"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/providers/scim/models.py:26
|
#: authentik/providers/scim/models.py:20
|
||||||
msgid "Base URL to SCIM requests, usually ends in /v2"
|
msgid "Base URL to SCIM requests, usually ends in /v2"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/providers/scim/models.py:27
|
#: authentik/providers/scim/models.py:21
|
||||||
msgid "Authentication token"
|
msgid "Authentication token"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/providers/scim/models.py:33 authentik/sources/ldap/models.py:94
|
#: authentik/providers/scim/models.py:27 authentik/sources/ldap/models.py:94
|
||||||
msgid "Property mappings used for group creation/updating."
|
msgid "Property mappings used for group creation/updating."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/providers/scim/models.py:75
|
#: authentik/providers/scim/models.py:60
|
||||||
msgid "SCIM Provider"
|
msgid "SCIM Provider"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/providers/scim/models.py:76
|
#: authentik/providers/scim/models.py:61
|
||||||
msgid "SCIM Providers"
|
msgid "SCIM Providers"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/providers/scim/models.py:96
|
#: authentik/providers/scim/models.py:81
|
||||||
msgid "SCIM Mapping"
|
msgid "SCIM Mapping"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: authentik/providers/scim/models.py:97
|
#: authentik/providers/scim/models.py:82
|
||||||
msgid "SCIM Mappings"
|
msgid "SCIM Mappings"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
514
schema.yml
514
schema.yml
|
@ -4604,6 +4604,20 @@ paths:
|
||||||
description: A search term.
|
description: A search term.
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
|
- in: query
|
||||||
|
name: type
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- default
|
||||||
|
- external
|
||||||
|
- internal_service_account
|
||||||
|
- service_account
|
||||||
|
description: |-
|
||||||
|
* `default` - Default
|
||||||
|
* `external` - External
|
||||||
|
* `service_account` - Service Account
|
||||||
|
* `internal_service_account` - Internal Service Account
|
||||||
- in: query
|
- in: query
|
||||||
name: username
|
name: username
|
||||||
schema:
|
schema:
|
||||||
|
@ -4612,6 +4626,7 @@ paths:
|
||||||
name: uuid
|
name: uuid
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
|
format: uuid
|
||||||
tags:
|
tags:
|
||||||
- core
|
- core
|
||||||
security:
|
security:
|
||||||
|
@ -5527,6 +5542,356 @@ paths:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/GenericError'
|
$ref: '#/components/schemas/GenericError'
|
||||||
description: ''
|
description: ''
|
||||||
|
/enterprise/license/:
|
||||||
|
get:
|
||||||
|
operationId: enterprise_license_list
|
||||||
|
description: License 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:
|
||||||
|
- enterprise
|
||||||
|
security:
|
||||||
|
- authentik: []
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/PaginatedLicenseList'
|
||||||
|
description: ''
|
||||||
|
'400':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ValidationError'
|
||||||
|
description: ''
|
||||||
|
'403':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/GenericError'
|
||||||
|
description: ''
|
||||||
|
post:
|
||||||
|
operationId: enterprise_license_create
|
||||||
|
description: License Viewset
|
||||||
|
tags:
|
||||||
|
- enterprise
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/LicenseRequest'
|
||||||
|
required: true
|
||||||
|
security:
|
||||||
|
- authentik: []
|
||||||
|
responses:
|
||||||
|
'201':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/License'
|
||||||
|
description: ''
|
||||||
|
'400':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ValidationError'
|
||||||
|
description: ''
|
||||||
|
'403':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/GenericError'
|
||||||
|
description: ''
|
||||||
|
/enterprise/license/{license_uuid}/:
|
||||||
|
get:
|
||||||
|
operationId: enterprise_license_retrieve
|
||||||
|
description: License Viewset
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: license_uuid
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
description: A UUID string identifying this license.
|
||||||
|
required: true
|
||||||
|
tags:
|
||||||
|
- enterprise
|
||||||
|
security:
|
||||||
|
- authentik: []
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/License'
|
||||||
|
description: ''
|
||||||
|
'400':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ValidationError'
|
||||||
|
description: ''
|
||||||
|
'403':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/GenericError'
|
||||||
|
description: ''
|
||||||
|
put:
|
||||||
|
operationId: enterprise_license_update
|
||||||
|
description: License Viewset
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: license_uuid
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
description: A UUID string identifying this license.
|
||||||
|
required: true
|
||||||
|
tags:
|
||||||
|
- enterprise
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/LicenseRequest'
|
||||||
|
required: true
|
||||||
|
security:
|
||||||
|
- authentik: []
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/License'
|
||||||
|
description: ''
|
||||||
|
'400':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ValidationError'
|
||||||
|
description: ''
|
||||||
|
'403':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/GenericError'
|
||||||
|
description: ''
|
||||||
|
patch:
|
||||||
|
operationId: enterprise_license_partial_update
|
||||||
|
description: License Viewset
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: license_uuid
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
description: A UUID string identifying this license.
|
||||||
|
required: true
|
||||||
|
tags:
|
||||||
|
- enterprise
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/PatchedLicenseRequest'
|
||||||
|
security:
|
||||||
|
- authentik: []
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/License'
|
||||||
|
description: ''
|
||||||
|
'400':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ValidationError'
|
||||||
|
description: ''
|
||||||
|
'403':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/GenericError'
|
||||||
|
description: ''
|
||||||
|
delete:
|
||||||
|
operationId: enterprise_license_destroy
|
||||||
|
description: License Viewset
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: license_uuid
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
description: A UUID string identifying this license.
|
||||||
|
required: true
|
||||||
|
tags:
|
||||||
|
- enterprise
|
||||||
|
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: ''
|
||||||
|
/enterprise/license/{license_uuid}/used_by/:
|
||||||
|
get:
|
||||||
|
operationId: enterprise_license_used_by_list
|
||||||
|
description: Get a list of all objects that use this object
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: license_uuid
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
description: A UUID string identifying this license.
|
||||||
|
required: true
|
||||||
|
tags:
|
||||||
|
- enterprise
|
||||||
|
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: ''
|
||||||
|
/enterprise/license/forecast/:
|
||||||
|
get:
|
||||||
|
operationId: enterprise_license_forecast_retrieve
|
||||||
|
description: Forecast how many users will be required in a year
|
||||||
|
tags:
|
||||||
|
- enterprise
|
||||||
|
security:
|
||||||
|
- authentik: []
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/LicenseForecast'
|
||||||
|
description: ''
|
||||||
|
'400':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ValidationError'
|
||||||
|
description: ''
|
||||||
|
'403':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/GenericError'
|
||||||
|
description: ''
|
||||||
|
/enterprise/license/get_install_id/:
|
||||||
|
get:
|
||||||
|
operationId: enterprise_license_get_install_id_retrieve
|
||||||
|
description: Get install_id
|
||||||
|
tags:
|
||||||
|
- enterprise
|
||||||
|
security:
|
||||||
|
- authentik: []
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/InstallID'
|
||||||
|
description: ''
|
||||||
|
'400':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ValidationError'
|
||||||
|
description: ''
|
||||||
|
'403':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/GenericError'
|
||||||
|
description: ''
|
||||||
|
/enterprise/license/summary/:
|
||||||
|
get:
|
||||||
|
operationId: enterprise_license_summary_retrieve
|
||||||
|
description: Get the total license status
|
||||||
|
tags:
|
||||||
|
- enterprise
|
||||||
|
security:
|
||||||
|
- authentik: []
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/LicenseSummary'
|
||||||
|
description: ''
|
||||||
|
'400':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ValidationError'
|
||||||
|
description: ''
|
||||||
|
'403':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/GenericError'
|
||||||
|
description: ''
|
||||||
/events/events/:
|
/events/events/:
|
||||||
get:
|
get:
|
||||||
operationId: events_events_list
|
operationId: events_events_list
|
||||||
|
@ -30468,6 +30833,13 @@ components:
|
||||||
type: boolean
|
type: boolean
|
||||||
required:
|
required:
|
||||||
- name
|
- name
|
||||||
|
InstallID:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
install_id:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- install_id
|
||||||
IntentEnum:
|
IntentEnum:
|
||||||
enum:
|
enum:
|
||||||
- verification
|
- verification
|
||||||
|
@ -31336,6 +31708,86 @@ components:
|
||||||
* `content_right` - CONTENT_RIGHT
|
* `content_right` - CONTENT_RIGHT
|
||||||
* `sidebar_left` - SIDEBAR_LEFT
|
* `sidebar_left` - SIDEBAR_LEFT
|
||||||
* `sidebar_right` - SIDEBAR_RIGHT
|
* `sidebar_right` - SIDEBAR_RIGHT
|
||||||
|
License:
|
||||||
|
type: object
|
||||||
|
description: License Serializer
|
||||||
|
properties:
|
||||||
|
license_uuid:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
readOnly: true
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
readOnly: true
|
||||||
|
key:
|
||||||
|
type: string
|
||||||
|
expiry:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
readOnly: true
|
||||||
|
users:
|
||||||
|
type: integer
|
||||||
|
readOnly: true
|
||||||
|
external_users:
|
||||||
|
type: integer
|
||||||
|
readOnly: true
|
||||||
|
required:
|
||||||
|
- expiry
|
||||||
|
- external_users
|
||||||
|
- key
|
||||||
|
- license_uuid
|
||||||
|
- name
|
||||||
|
- users
|
||||||
|
LicenseForecast:
|
||||||
|
type: object
|
||||||
|
description: Serializer for license forecast
|
||||||
|
properties:
|
||||||
|
users:
|
||||||
|
type: integer
|
||||||
|
external_users:
|
||||||
|
type: integer
|
||||||
|
required:
|
||||||
|
- external_users
|
||||||
|
- users
|
||||||
|
LicenseRequest:
|
||||||
|
type: object
|
||||||
|
description: License Serializer
|
||||||
|
properties:
|
||||||
|
key:
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
required:
|
||||||
|
- key
|
||||||
|
LicenseSummary:
|
||||||
|
type: object
|
||||||
|
description: Serializer for license status
|
||||||
|
properties:
|
||||||
|
users:
|
||||||
|
type: integer
|
||||||
|
external_users:
|
||||||
|
type: integer
|
||||||
|
valid:
|
||||||
|
type: boolean
|
||||||
|
show_admin_warning:
|
||||||
|
type: boolean
|
||||||
|
show_user_warning:
|
||||||
|
type: boolean
|
||||||
|
read_only:
|
||||||
|
type: boolean
|
||||||
|
latest_valid:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
has_license:
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- external_users
|
||||||
|
- has_license
|
||||||
|
- latest_valid
|
||||||
|
- read_only
|
||||||
|
- show_admin_warning
|
||||||
|
- show_user_warning
|
||||||
|
- users
|
||||||
|
- valid
|
||||||
Link:
|
Link:
|
||||||
type: object
|
type: object
|
||||||
description: Returns a single link
|
description: Returns a single link
|
||||||
|
@ -33686,6 +34138,41 @@ components:
|
||||||
required:
|
required:
|
||||||
- pagination
|
- pagination
|
||||||
- results
|
- results
|
||||||
|
PaginatedLicenseList:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
pagination:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
next:
|
||||||
|
type: number
|
||||||
|
previous:
|
||||||
|
type: number
|
||||||
|
count:
|
||||||
|
type: number
|
||||||
|
current:
|
||||||
|
type: number
|
||||||
|
total_pages:
|
||||||
|
type: number
|
||||||
|
start_index:
|
||||||
|
type: number
|
||||||
|
end_index:
|
||||||
|
type: number
|
||||||
|
required:
|
||||||
|
- next
|
||||||
|
- previous
|
||||||
|
- count
|
||||||
|
- current
|
||||||
|
- total_pages
|
||||||
|
- start_index
|
||||||
|
- end_index
|
||||||
|
results:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/License'
|
||||||
|
required:
|
||||||
|
- pagination
|
||||||
|
- results
|
||||||
PaginatedNotificationList:
|
PaginatedNotificationList:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
@ -36850,6 +37337,13 @@ components:
|
||||||
type: string
|
type: string
|
||||||
format: uuid
|
format: uuid
|
||||||
description: Property mappings used for group creation/updating.
|
description: Property mappings used for group creation/updating.
|
||||||
|
PatchedLicenseRequest:
|
||||||
|
type: object
|
||||||
|
description: License Serializer
|
||||||
|
properties:
|
||||||
|
key:
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
PatchedNotificationRequest:
|
PatchedNotificationRequest:
|
||||||
type: object
|
type: object
|
||||||
description: Notification Serializer
|
description: Notification Serializer
|
||||||
|
@ -38033,6 +38527,8 @@ components:
|
||||||
path:
|
path:
|
||||||
type: string
|
type: string
|
||||||
minLength: 1
|
minLength: 1
|
||||||
|
type:
|
||||||
|
$ref: '#/components/schemas/UserTypeEnum'
|
||||||
PatchedUserSAMLSourceConnectionRequest:
|
PatchedUserSAMLSourceConnectionRequest:
|
||||||
type: object
|
type: object
|
||||||
description: SAML Source Serializer
|
description: SAML Source Serializer
|
||||||
|
@ -41430,6 +41926,8 @@ components:
|
||||||
readOnly: true
|
readOnly: true
|
||||||
path:
|
path:
|
||||||
type: string
|
type: string
|
||||||
|
type:
|
||||||
|
$ref: '#/components/schemas/UserTypeEnum'
|
||||||
required:
|
required:
|
||||||
- avatar
|
- avatar
|
||||||
- groups_obj
|
- groups_obj
|
||||||
|
@ -41888,6 +42386,8 @@ components:
|
||||||
path:
|
path:
|
||||||
type: string
|
type: string
|
||||||
minLength: 1
|
minLength: 1
|
||||||
|
type:
|
||||||
|
$ref: '#/components/schemas/UserTypeEnum'
|
||||||
required:
|
required:
|
||||||
- name
|
- name
|
||||||
- username
|
- username
|
||||||
|
@ -41971,6 +42471,8 @@ components:
|
||||||
additionalProperties: {}
|
additionalProperties: {}
|
||||||
description: Get user settings with tenant and group settings applied
|
description: Get user settings with tenant and group settings applied
|
||||||
readOnly: true
|
readOnly: true
|
||||||
|
type:
|
||||||
|
$ref: '#/components/schemas/UserTypeEnum'
|
||||||
required:
|
required:
|
||||||
- avatar
|
- avatar
|
||||||
- groups
|
- groups
|
||||||
|
@ -42071,6 +42573,18 @@ components:
|
||||||
- pk
|
- pk
|
||||||
- source
|
- source
|
||||||
- user
|
- user
|
||||||
|
UserTypeEnum:
|
||||||
|
enum:
|
||||||
|
- default
|
||||||
|
- external
|
||||||
|
- service_account
|
||||||
|
- internal_service_account
|
||||||
|
type: string
|
||||||
|
description: |-
|
||||||
|
* `default` - Default
|
||||||
|
* `external` - External
|
||||||
|
* `service_account` - Service Account
|
||||||
|
* `internal_service_account` - Internal Service Account
|
||||||
UserVerificationEnum:
|
UserVerificationEnum:
|
||||||
enum:
|
enum:
|
||||||
- required
|
- required
|
||||||
|
|
|
@ -256,8 +256,6 @@ class TestProviderLDAP(SeleniumTestCase):
|
||||||
"homeDirectory": f"/home/{o_user.username}",
|
"homeDirectory": f"/home/{o_user.username}",
|
||||||
"ak-active": True,
|
"ak-active": True,
|
||||||
"ak-superuser": False,
|
"ak-superuser": False,
|
||||||
"goauthentikio-user-override-ips": True,
|
|
||||||
"goauthentikio-user-service-account": True,
|
|
||||||
},
|
},
|
||||||
"type": "searchResEntry",
|
"type": "searchResEntry",
|
||||||
},
|
},
|
||||||
|
@ -284,8 +282,6 @@ class TestProviderLDAP(SeleniumTestCase):
|
||||||
"homeDirectory": f"/home/{embedded_account.username}",
|
"homeDirectory": f"/home/{embedded_account.username}",
|
||||||
"ak-active": True,
|
"ak-active": True,
|
||||||
"ak-superuser": False,
|
"ak-superuser": False,
|
||||||
"goauthentikio-user-override-ips": True,
|
|
||||||
"goauthentikio-user-service-account": True,
|
|
||||||
},
|
},
|
||||||
"type": "searchResEntry",
|
"type": "searchResEntry",
|
||||||
},
|
},
|
||||||
|
|
|
@ -8,3 +8,4 @@ coverage
|
||||||
poly.ts
|
poly.ts
|
||||||
src/locale-codes.ts
|
src/locale-codes.ts
|
||||||
src/locales/
|
src/locales/
|
||||||
|
storybook-static/
|
||||||
|
|
|
@ -11,6 +11,7 @@ import { me } from "@goauthentik/common/users";
|
||||||
import { WebsocketClient } from "@goauthentik/common/ws";
|
import { WebsocketClient } from "@goauthentik/common/ws";
|
||||||
import { Interface } from "@goauthentik/elements/Base";
|
import { Interface } from "@goauthentik/elements/Base";
|
||||||
import "@goauthentik/elements/ak-locale-context";
|
import "@goauthentik/elements/ak-locale-context";
|
||||||
|
import "@goauthentik/elements/enterprise/EnterpriseStatusBanner";
|
||||||
import "@goauthentik/elements/messages/MessageContainer";
|
import "@goauthentik/elements/messages/MessageContainer";
|
||||||
import "@goauthentik/elements/messages/MessageContainer";
|
import "@goauthentik/elements/messages/MessageContainer";
|
||||||
import "@goauthentik/elements/notifications/APIDrawer";
|
import "@goauthentik/elements/notifications/APIDrawer";
|
||||||
|
@ -30,7 +31,14 @@ import PFDrawer from "@patternfly/patternfly/components/Drawer/drawer.css";
|
||||||
import PFPage from "@patternfly/patternfly/components/Page/page.css";
|
import PFPage from "@patternfly/patternfly/components/Page/page.css";
|
||||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||||
|
|
||||||
import { AdminApi, CoreApi, SessionUser, UiThemeEnum, Version } from "@goauthentik/api";
|
import {
|
||||||
|
AdminApi,
|
||||||
|
CapabilitiesEnum,
|
||||||
|
CoreApi,
|
||||||
|
SessionUser,
|
||||||
|
UiThemeEnum,
|
||||||
|
Version,
|
||||||
|
} from "@goauthentik/api";
|
||||||
|
|
||||||
@customElement("ak-interface-admin")
|
@customElement("ak-interface-admin")
|
||||||
export class AdminInterface extends Interface {
|
export class AdminInterface extends Interface {
|
||||||
|
@ -67,7 +75,17 @@ export class AdminInterface extends Interface {
|
||||||
.display-none {
|
.display-none {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
:host {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
ak-locale-context {
|
||||||
|
display: flex;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
.pf-c-page {
|
.pf-c-page {
|
||||||
|
flex-grow: 1;
|
||||||
background-color: var(--pf-c-page--BackgroundColor) !important;
|
background-color: var(--pf-c-page--BackgroundColor) !important;
|
||||||
}
|
}
|
||||||
/* Global page background colour */
|
/* Global page background colour */
|
||||||
|
@ -113,7 +131,8 @@ export class AdminInterface extends Interface {
|
||||||
|
|
||||||
render(): TemplateResult {
|
render(): TemplateResult {
|
||||||
return html` <ak-locale-context
|
return html` <ak-locale-context
|
||||||
><div class="pf-c-page">
|
><ak-enterprise-status interface="admin"></ak-enterprise-status>
|
||||||
|
<div class="pf-c-page">
|
||||||
<ak-sidebar
|
<ak-sidebar
|
||||||
class="pf-c-page__sidebar ${this.sidebarOpen
|
class="pf-c-page__sidebar ${this.sidebarOpen
|
||||||
? "pf-m-expanded"
|
? "pf-m-expanded"
|
||||||
|
@ -308,6 +327,16 @@ export class AdminInterface extends Interface {
|
||||||
<span slot="label">${msg("Outpost Integrations")}</span>
|
<span slot="label">${msg("Outpost Integrations")}</span>
|
||||||
</ak-sidebar-item>
|
</ak-sidebar-item>
|
||||||
</ak-sidebar-item>
|
</ak-sidebar-item>
|
||||||
|
${this.config?.capabilities.includes(CapabilitiesEnum.IsEnterprise)
|
||||||
|
? html`
|
||||||
|
<ak-sidebar-item>
|
||||||
|
<span slot="label">${msg("Enterprise")}</span>
|
||||||
|
<ak-sidebar-item path="/enterprise/licenses">
|
||||||
|
<span slot="label">${msg("Licenses")}</span>
|
||||||
|
</ak-sidebar-item>
|
||||||
|
</ak-sidebar-item>
|
||||||
|
`
|
||||||
|
: html``}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -136,4 +136,8 @@ export const ROUTES: Route[] = [
|
||||||
await import("@goauthentik/admin/DebugPage");
|
await import("@goauthentik/admin/DebugPage");
|
||||||
return html`<ak-admin-debug-page></ak-admin-debug-page>`;
|
return html`<ak-admin-debug-page></ak-admin-debug-page>`;
|
||||||
}),
|
}),
|
||||||
|
new Route(new RegExp("^/enterprise/licenses$"), async () => {
|
||||||
|
await import("@goauthentik/admin/enterprise/EnterpriseLicenseListPage");
|
||||||
|
return html`<ak-enterprise-license-list></ak-enterprise-license-list>`;
|
||||||
|
}),
|
||||||
];
|
];
|
||||||
|
|
|
@ -26,6 +26,7 @@ export class SystemStatusCard extends AdminStatusCard<System> {
|
||||||
// First install, ensure the embedded outpost host is set
|
// First install, ensure the embedded outpost host is set
|
||||||
// also run when outpost host does not contain http
|
// also run when outpost host does not contain http
|
||||||
// (yes it's called host and requires a URL, i know)
|
// (yes it's called host and requires a URL, i know)
|
||||||
|
// TODO: Improve this in OOB flow
|
||||||
await this.setOutpostHost();
|
await this.setOutpostHost();
|
||||||
status = await new AdminApi(DEFAULT_CONFIG).adminSystemRetrieve();
|
status = await new AdminApi(DEFAULT_CONFIG).adminSystemRetrieve();
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
|
import "@goauthentik/elements/CodeMirror";
|
||||||
|
import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||||
|
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
|
||||||
|
|
||||||
|
import { msg } from "@lit/localize";
|
||||||
|
import { TemplateResult, html } from "lit";
|
||||||
|
import { customElement, state } from "lit/decorators.js";
|
||||||
|
|
||||||
|
import { EnterpriseApi, License } from "@goauthentik/api";
|
||||||
|
|
||||||
|
@customElement("ak-enterprise-license-form")
|
||||||
|
export class EnterpriseLicenseForm extends ModelForm<License, string> {
|
||||||
|
@state()
|
||||||
|
installID?: string;
|
||||||
|
|
||||||
|
loadInstance(pk: string): Promise<License> {
|
||||||
|
return new EnterpriseApi(DEFAULT_CONFIG).enterpriseLicenseRetrieve({
|
||||||
|
licenseUuid: pk,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getSuccessMessage(): string {
|
||||||
|
if (this.instance) {
|
||||||
|
return msg("Successfully updated license.");
|
||||||
|
} else {
|
||||||
|
return msg("Successfully created license.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async load(): Promise<void> {
|
||||||
|
this.installID = (
|
||||||
|
await new EnterpriseApi(DEFAULT_CONFIG).enterpriseLicenseGetInstallIdRetrieve()
|
||||||
|
).installId;
|
||||||
|
}
|
||||||
|
|
||||||
|
async send(data: License): Promise<License> {
|
||||||
|
if (this.instance) {
|
||||||
|
return new EnterpriseApi(DEFAULT_CONFIG).enterpriseLicensePartialUpdate({
|
||||||
|
licenseUuid: this.instance.licenseUuid || "",
|
||||||
|
patchedLicenseRequest: data,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return new EnterpriseApi(DEFAULT_CONFIG).enterpriseLicenseCreate({
|
||||||
|
licenseRequest: data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderForm(): TemplateResult {
|
||||||
|
return html`<form class="pf-c-form pf-m-horizontal">
|
||||||
|
<ak-form-element-horizontal label=${msg("Install ID")}>
|
||||||
|
<input class="pf-c-form-control" readonly type="text" value="${this.installID}" />
|
||||||
|
</ak-form-element-horizontal>
|
||||||
|
<ak-form-element-horizontal
|
||||||
|
name="key"
|
||||||
|
?writeOnly=${this.instance !== undefined}
|
||||||
|
label=${msg("License key")}
|
||||||
|
>
|
||||||
|
<textarea class="pf-c-form-control"></textarea>
|
||||||
|
</ak-form-element-horizontal>
|
||||||
|
</form>`;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,222 @@
|
||||||
|
import "@goauthentik/admin/enterprise/EnterpriseLicenseForm";
|
||||||
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
|
import { uiConfig } from "@goauthentik/common/ui/config";
|
||||||
|
import { PFColor } from "@goauthentik/elements/Label";
|
||||||
|
import "@goauthentik/elements/Spinner";
|
||||||
|
import "@goauthentik/elements/buttons/SpinnerButton";
|
||||||
|
import "@goauthentik/elements/cards/AggregateCard";
|
||||||
|
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 { msg } from "@lit/localize";
|
||||||
|
import { CSSResult, TemplateResult, css, html } from "lit";
|
||||||
|
import { customElement, property, state } from "lit/decorators.js";
|
||||||
|
|
||||||
|
import PFBanner from "@patternfly/patternfly/components/Banner/banner.css";
|
||||||
|
import PFButton from "@patternfly/patternfly/components/Button/button.css";
|
||||||
|
import PFCard from "@patternfly/patternfly/components/Card/card.css";
|
||||||
|
import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css";
|
||||||
|
import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css";
|
||||||
|
import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css";
|
||||||
|
|
||||||
|
import { EnterpriseApi, License, LicenseForecast, LicenseSummary } from "@goauthentik/api";
|
||||||
|
|
||||||
|
@customElement("ak-enterprise-license-list")
|
||||||
|
export class EnterpriseLicenseListPage extends TablePage<License> {
|
||||||
|
checkbox = true;
|
||||||
|
|
||||||
|
searchEnabled(): boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
pageTitle(): string {
|
||||||
|
return msg("Licenses");
|
||||||
|
}
|
||||||
|
pageDescription(): string {
|
||||||
|
return msg("Manage enterprise licenses");
|
||||||
|
}
|
||||||
|
pageIcon(): string {
|
||||||
|
return "pf-icon pf-icon-key";
|
||||||
|
}
|
||||||
|
|
||||||
|
@property()
|
||||||
|
order = "name";
|
||||||
|
|
||||||
|
@state()
|
||||||
|
forecast?: LicenseForecast;
|
||||||
|
|
||||||
|
@state()
|
||||||
|
summary?: LicenseSummary;
|
||||||
|
|
||||||
|
@state()
|
||||||
|
installID?: string;
|
||||||
|
|
||||||
|
static get styles(): CSSResult[] {
|
||||||
|
return super.styles.concat(
|
||||||
|
PFDescriptionList,
|
||||||
|
PFGrid,
|
||||||
|
PFBanner,
|
||||||
|
PFFormControl,
|
||||||
|
PFButton,
|
||||||
|
PFCard,
|
||||||
|
css`
|
||||||
|
.pf-m-no-padding-bottom {
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async apiEndpoint(page: number): Promise<PaginatedResponse<License>> {
|
||||||
|
this.forecast = await new EnterpriseApi(DEFAULT_CONFIG).enterpriseLicenseForecastRetrieve();
|
||||||
|
this.summary = await new EnterpriseApi(DEFAULT_CONFIG).enterpriseLicenseSummaryRetrieve();
|
||||||
|
this.installID = (
|
||||||
|
await new EnterpriseApi(DEFAULT_CONFIG).enterpriseLicenseGetInstallIdRetrieve()
|
||||||
|
).installId;
|
||||||
|
return new EnterpriseApi(DEFAULT_CONFIG).enterpriseLicenseList({
|
||||||
|
ordering: this.order,
|
||||||
|
page: page,
|
||||||
|
pageSize: (await uiConfig()).pagination.perPage,
|
||||||
|
search: this.search || "",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
columns(): TableColumn[] {
|
||||||
|
return [
|
||||||
|
new TableColumn(msg("Name"), "name"),
|
||||||
|
new TableColumn(msg("Users")),
|
||||||
|
new TableColumn(msg("Expiry date")),
|
||||||
|
new TableColumn(msg("Actions")),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
renderToolbarSelected(): TemplateResult {
|
||||||
|
const disabled = this.selectedElements.length < 1;
|
||||||
|
return html`<ak-forms-delete-bulk
|
||||||
|
objectLabel=${msg("License(s)")}
|
||||||
|
.objects=${this.selectedElements}
|
||||||
|
.metadata=${(item: License) => {
|
||||||
|
return [
|
||||||
|
{ key: msg("Name"), value: item.name },
|
||||||
|
{ key: msg("Expiry"), value: item.expiry?.toLocaleString() },
|
||||||
|
];
|
||||||
|
}}
|
||||||
|
.usedBy=${(item: License) => {
|
||||||
|
return new EnterpriseApi(DEFAULT_CONFIG).enterpriseLicenseUsedByList({
|
||||||
|
licenseUuid: item.licenseUuid,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
.delete=${(item: License) => {
|
||||||
|
return new EnterpriseApi(DEFAULT_CONFIG).enterpriseLicenseDestroy({
|
||||||
|
licenseUuid: item.licenseUuid,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<button ?disabled=${disabled} slot="trigger" class="pf-c-button pf-m-danger">
|
||||||
|
${msg("Delete")}
|
||||||
|
</button>
|
||||||
|
</ak-forms-delete-bulk>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderSectionBefore(): TemplateResult {
|
||||||
|
return html`
|
||||||
|
<div class="pf-c-banner pf-m-info">
|
||||||
|
${msg("Enterprise is in preview.")}
|
||||||
|
<a href="mailto:hello@goauthentik.io">${msg("Send us feedback!")}</a>
|
||||||
|
</div>
|
||||||
|
<section class="pf-c-page__main-section pf-m-no-padding-bottom">
|
||||||
|
<div
|
||||||
|
class="pf-l-grid pf-m-gutter pf-m-all-6-col-on-sm pf-m-all-4-col-on-md pf-m-all-3-col-on-lg pf-m-all-3-col-on-xl"
|
||||||
|
>
|
||||||
|
<div class="pf-l-grid__item pf-c-card">
|
||||||
|
<div class="pf-c-card__title">${msg("How to get a license")}</div>
|
||||||
|
<div class="pf-c-card__body">
|
||||||
|
${this.installID
|
||||||
|
? html` <a
|
||||||
|
target="_blank"
|
||||||
|
href=${`https://customers.goauthentik.io/from_authentik/purchase/?install_id=${this.installID}`}
|
||||||
|
class="pf-c-button pf-m-primary pf-m-block"
|
||||||
|
>${msg("Go to the customer portal")}</a
|
||||||
|
>`
|
||||||
|
: html`<ak-spinner></ak-spinner>`}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="pf-l-grid__item pf-c-card">
|
||||||
|
<ak-aggregate-card
|
||||||
|
icon="pf-icon pf-icon-user"
|
||||||
|
header=${msg("Forecasted default users")}
|
||||||
|
subtext=${msg("Estimated user count one year from now")}
|
||||||
|
>
|
||||||
|
${this.forecast?.users}
|
||||||
|
</ak-aggregate-card>
|
||||||
|
</div>
|
||||||
|
<div class="pf-l-grid__item pf-c-card">
|
||||||
|
<ak-aggregate-card
|
||||||
|
icon="pf-icon pf-icon-user"
|
||||||
|
header=${msg("Forecasted external users")}
|
||||||
|
subtext=${msg("Estimated external user count one year from now")}
|
||||||
|
>
|
||||||
|
${this.forecast?.externalUsers}
|
||||||
|
</ak-aggregate-card>
|
||||||
|
</div>
|
||||||
|
<div class="pf-l-grid__item pf-c-card">
|
||||||
|
<ak-aggregate-card
|
||||||
|
icon="pf-icon pf-icon-user"
|
||||||
|
header=${msg("Expiry")}
|
||||||
|
subtext=${msg("Cumulative license expiry")}
|
||||||
|
>
|
||||||
|
${this.summary?.hasLicense
|
||||||
|
? this.summary.latestValid.toLocaleString()
|
||||||
|
: "-"}
|
||||||
|
</ak-aggregate-card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
row(item: License): TemplateResult[] {
|
||||||
|
let color = PFColor.Green;
|
||||||
|
if (item.expiry) {
|
||||||
|
const now = new Date();
|
||||||
|
const inAMonth = new Date();
|
||||||
|
inAMonth.setDate(inAMonth.getDate() + 30);
|
||||||
|
if (item.expiry <= inAMonth) {
|
||||||
|
color = PFColor.Orange;
|
||||||
|
}
|
||||||
|
if (item.expiry <= now) {
|
||||||
|
color = PFColor.Red;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
html`<div>${item.name}</div>`,
|
||||||
|
html`<div>
|
||||||
|
<small>0 / ${item.users}</small>
|
||||||
|
<small>0 / ${item.externalUsers}</small>
|
||||||
|
</div>`,
|
||||||
|
html`<ak-label color=${color}> ${item.expiry?.toLocaleString()} </ak-label>`,
|
||||||
|
html`<ak-forms-modal>
|
||||||
|
<span slot="submit"> ${msg("Update")} </span>
|
||||||
|
<span slot="header"> ${msg("Update License")} </span>
|
||||||
|
<ak-enterprise-license-form slot="form" .instancePk=${item.licenseUuid}>
|
||||||
|
</ak-enterprise-license-form>
|
||||||
|
<button slot="trigger" class="pf-c-button pf-m-plain">
|
||||||
|
<i class="fas fa-edit"></i>
|
||||||
|
</button>
|
||||||
|
</ak-forms-modal>`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
renderObjectCreate(): TemplateResult {
|
||||||
|
return html`
|
||||||
|
<ak-forms-modal>
|
||||||
|
<span slot="submit"> ${msg("Create")} </span>
|
||||||
|
<span slot="header"> ${msg("Create License")} </span>
|
||||||
|
<ak-enterprise-license-form slot="form"> </ak-enterprise-license-form>
|
||||||
|
<button slot="trigger" class="pf-c-button pf-m-primary">${msg("Create")}</button>
|
||||||
|
</ak-forms-modal>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +1,11 @@
|
||||||
import "@goauthentik/admin/users/GroupSelectModal";
|
import "@goauthentik/admin/users/GroupSelectModal";
|
||||||
|
import { UserTypeEnum } from "@goauthentik/api/dist/models/UserTypeEnum";
|
||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
import { first } from "@goauthentik/common/utils";
|
import { first } from "@goauthentik/common/utils";
|
||||||
import "@goauthentik/elements/CodeMirror";
|
import "@goauthentik/elements/CodeMirror";
|
||||||
import "@goauthentik/elements/forms/HorizontalFormElement";
|
import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||||
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
|
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
|
||||||
|
import "@goauthentik/elements/forms/Radio";
|
||||||
import YAML from "yaml";
|
import YAML from "yaml";
|
||||||
|
|
||||||
import { msg } from "@lit/localize";
|
import { msg } from "@lit/localize";
|
||||||
|
@ -75,6 +77,31 @@ export class UserForm extends ModelForm<User, number> {
|
||||||
/>
|
/>
|
||||||
<p class="pf-c-form__helper-text">${msg("User's display name.")}</p>
|
<p class="pf-c-form__helper-text">${msg("User's display name.")}</p>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
|
<ak-form-element-horizontal label=${msg("User type")} ?required=${true} name="type">
|
||||||
|
<ak-radio
|
||||||
|
.options=${[
|
||||||
|
// TODO: Add better copy
|
||||||
|
{
|
||||||
|
label: "Default",
|
||||||
|
value: UserTypeEnum.Default,
|
||||||
|
default: true,
|
||||||
|
description: html`${msg("Default user")}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "External",
|
||||||
|
value: UserTypeEnum.External,
|
||||||
|
description: html`${msg("External user")}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Service account",
|
||||||
|
value: UserTypeEnum.ServiceAccount,
|
||||||
|
description: html`${msg("Service account")}`,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
.value=${this.instance?.type}
|
||||||
|
>
|
||||||
|
</ak-radio>
|
||||||
|
</ak-form-element-horizontal>
|
||||||
<ak-form-element-horizontal label=${msg("Email")} name="email">
|
<ak-form-element-horizontal label=${msg("Email")} name="email">
|
||||||
<input
|
<input
|
||||||
type="email"
|
type="email"
|
||||||
|
|
|
@ -19,6 +19,9 @@ export class AggregateCard extends AKElement {
|
||||||
@property()
|
@property()
|
||||||
headerLink?: string;
|
headerLink?: string;
|
||||||
|
|
||||||
|
@property()
|
||||||
|
subtext?: string;
|
||||||
|
|
||||||
@property({ type: Boolean })
|
@property({ type: Boolean })
|
||||||
isCenter = true;
|
isCenter = true;
|
||||||
|
|
||||||
|
@ -79,6 +82,7 @@ export class AggregateCard extends AKElement {
|
||||||
</div>
|
</div>
|
||||||
<div class="pf-c-card__body ${this.isCenter ? "center-value" : ""}">
|
<div class="pf-c-card__body ${this.isCenter ? "center-value" : ""}">
|
||||||
${this.renderInner()}
|
${this.renderInner()}
|
||||||
|
${this.subtext ? html`<p class="subtext">${this.subtext}</p>` : html``}
|
||||||
</div>
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
|
import { AKElement } from "@goauthentik/elements/Base";
|
||||||
|
|
||||||
|
import { msg } from "@lit/localize";
|
||||||
|
import { CSSResult, TemplateResult, html } from "lit";
|
||||||
|
import { customElement, property, state } from "lit/decorators.js";
|
||||||
|
|
||||||
|
import PFBanner from "@patternfly/patternfly/components/Banner/banner.css";
|
||||||
|
|
||||||
|
import { EnterpriseApi, LicenseSummary } from "@goauthentik/api";
|
||||||
|
|
||||||
|
@customElement("ak-enterprise-status")
|
||||||
|
export class EnterpriseStatusBanner extends AKElement {
|
||||||
|
@state()
|
||||||
|
summary?: LicenseSummary;
|
||||||
|
|
||||||
|
@property()
|
||||||
|
interface: "admin" | "user" | "" = "";
|
||||||
|
|
||||||
|
static get styles(): CSSResult[] {
|
||||||
|
return [PFBanner];
|
||||||
|
}
|
||||||
|
|
||||||
|
firstUpdated(): void {
|
||||||
|
new EnterpriseApi(DEFAULT_CONFIG).enterpriseLicenseSummaryRetrieve().then((b) => {
|
||||||
|
this.summary = b;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
renderBanner(): TemplateResult {
|
||||||
|
return html`<div class="pf-c-banner ${this.summary?.readOnly ? "pf-m-red" : "pf-m-orange"}">
|
||||||
|
${msg("Warning: The current user count has exceeded the configured licenses.")}
|
||||||
|
<a href="/if/admin/#/enterprise/licenses"> ${msg("Click here for more info.")} </a>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
render(): TemplateResult {
|
||||||
|
switch (this.interface.toLowerCase()) {
|
||||||
|
case "admin":
|
||||||
|
if (this.summary?.showAdminWarning || this.summary?.readOnly) {
|
||||||
|
return this.renderBanner();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "user":
|
||||||
|
if (this.summary?.showUserWarning || this.summary?.readOnly) {
|
||||||
|
return this.renderBanner();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return html``;
|
||||||
|
}
|
||||||
|
}
|
|
@ -28,6 +28,16 @@ export abstract class TablePage<T> extends Table<T> {
|
||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Optionally render section above the table
|
||||||
|
renderSectionBefore(): TemplateResult {
|
||||||
|
return html``;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optionally render section below the table
|
||||||
|
renderSectionAfter(): TemplateResult {
|
||||||
|
return html``;
|
||||||
|
}
|
||||||
|
|
||||||
renderEmpty(inner?: TemplateResult): TemplateResult {
|
renderEmpty(inner?: TemplateResult): TemplateResult {
|
||||||
return super.renderEmpty(html`
|
return super.renderEmpty(html`
|
||||||
${inner
|
${inner
|
||||||
|
@ -75,6 +85,7 @@ export abstract class TablePage<T> extends Table<T> {
|
||||||
description=${ifDefined(this.pageDescription())}
|
description=${ifDefined(this.pageDescription())}
|
||||||
>
|
>
|
||||||
</ak-page-header>
|
</ak-page-header>
|
||||||
|
${this.renderSectionBefore()}
|
||||||
<section class="pf-c-page__main-section pf-m-no-padding-mobile">
|
<section class="pf-c-page__main-section pf-m-no-padding-mobile">
|
||||||
<div class="pf-c-sidebar pf-m-gutter">
|
<div class="pf-c-sidebar pf-m-gutter">
|
||||||
<div class="pf-c-sidebar__main">
|
<div class="pf-c-sidebar__main">
|
||||||
|
@ -85,6 +96,7 @@ export abstract class TablePage<T> extends Table<T> {
|
||||||
${this.renderSidebarAfter()}
|
${this.renderSidebarAfter()}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>`;
|
</section>
|
||||||
|
${this.renderSectionAfter()}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,8 @@ import { first } from "@goauthentik/common/utils";
|
||||||
import { WebsocketClient } from "@goauthentik/common/ws";
|
import { WebsocketClient } from "@goauthentik/common/ws";
|
||||||
import { Interface } from "@goauthentik/elements/Base";
|
import { Interface } from "@goauthentik/elements/Base";
|
||||||
import "@goauthentik/elements/ak-locale-context";
|
import "@goauthentik/elements/ak-locale-context";
|
||||||
|
import "@goauthentik/elements/buttons/ActionButton";
|
||||||
|
import "@goauthentik/elements/enterprise/EnterpriseStatusBanner";
|
||||||
import "@goauthentik/elements/messages/MessageContainer";
|
import "@goauthentik/elements/messages/MessageContainer";
|
||||||
import "@goauthentik/elements/notifications/APIDrawer";
|
import "@goauthentik/elements/notifications/APIDrawer";
|
||||||
import "@goauthentik/elements/notifications/NotificationDrawer";
|
import "@goauthentik/elements/notifications/NotificationDrawer";
|
||||||
|
@ -35,7 +37,7 @@ import PFPage from "@patternfly/patternfly/components/Page/page.css";
|
||||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||||
import PFDisplay from "@patternfly/patternfly/utilities/Display/display.css";
|
import PFDisplay from "@patternfly/patternfly/utilities/Display/display.css";
|
||||||
|
|
||||||
import { EventsApi, SessionUser } from "@goauthentik/api";
|
import { CoreApi, EventsApi, SessionUser } from "@goauthentik/api";
|
||||||
|
|
||||||
@customElement("ak-interface-user")
|
@customElement("ak-interface-user")
|
||||||
export class UserInterface extends Interface {
|
export class UserInterface extends Interface {
|
||||||
|
@ -148,6 +150,7 @@ export class UserInterface extends Interface {
|
||||||
userDisplay = this.me.user.username;
|
userDisplay = this.me.user.username;
|
||||||
}
|
}
|
||||||
return html` <ak-locale-context>
|
return html` <ak-locale-context>
|
||||||
|
<ak-enterprise-status interface="user"></ak-enterprise-status>
|
||||||
<div class="pf-c-page">
|
<div class="pf-c-page">
|
||||||
<div class="background-wrapper" style="${this.uiConfig.theme.background}"></div>
|
<div class="background-wrapper" style="${this.uiConfig.theme.background}"></div>
|
||||||
<header class="pf-c-page__header">
|
<header class="pf-c-page__header">
|
||||||
|
@ -243,16 +246,21 @@ export class UserInterface extends Interface {
|
||||||
: html``}
|
: html``}
|
||||||
</div>
|
</div>
|
||||||
${this.me.original
|
${this.me.original
|
||||||
? html`<div class="pf-c-page__header-tools">
|
? html`
|
||||||
|
<div class="pf-c-page__header-tools">
|
||||||
<div class="pf-c-page__header-tools-group">
|
<div class="pf-c-page__header-tools-group">
|
||||||
<a
|
<ak-action-button
|
||||||
class="pf-c-button pf-m-warning pf-m-small"
|
class="pf-m-warning pf-m-small"
|
||||||
href=${`/-/impersonation/end/?back=${encodeURIComponent(
|
.apiRequest=${() => {
|
||||||
`${window.location.pathname}#${window.location.hash}`,
|
return new CoreApi(DEFAULT_CONFIG)
|
||||||
)}`}
|
.coreUsersImpersonateEndRetrieve()
|
||||||
|
.then(() => {
|
||||||
|
window.location.reload();
|
||||||
|
});
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
${msg("Stop impersonation")}
|
${msg("Stop impersonation")}
|
||||||
</a>
|
</ak-action-button>
|
||||||
</div>
|
</div>
|
||||||
</div>`
|
</div>`
|
||||||
: html``}
|
: html``}
|
||||||
|
|
|
@ -5747,6 +5747,84 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s1889ba2eaeec2f1e">
|
<trans-unit id="s1889ba2eaeec2f1e">
|
||||||
<source>When enabled, code-based multi-factor authentication can be used by appending a semicolon and the TOTP code to the password. This should only be enabled if all users that will bind to this provider have a TOTP device configured, as otherwise a password may incorrectly be rejected if it contains a semicolon.</source>
|
<source>When enabled, code-based multi-factor authentication can be used by appending a semicolon and the TOTP code to the password. This should only be enabled if all users that will bind to this provider have a TOTP device configured, as otherwise a password may incorrectly be rejected if it contains a semicolon.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s9f9492d30a96b9c6">
|
||||||
|
<source>User type</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s0b9a40b7b2853c7d">
|
||||||
|
<source>Default user</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s35b9fa270f45b391">
|
||||||
|
<source>External user</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s1a635369edaf4dc3">
|
||||||
|
<source>Service account</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s0e427111d750cc02">
|
||||||
|
<source>Successfully updated license.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s06ae64e621f302eb">
|
||||||
|
<source>Successfully created license.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s2905c425adae99bd">
|
||||||
|
<source>Install ID</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sb18ec434a8a3aafb">
|
||||||
|
<source>License key</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s2e109263b73c12d5">
|
||||||
|
<source>Licenses</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sf8f9f3032e891e16">
|
||||||
|
<source>TODO Copy</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sd49099e9522635f4">
|
||||||
|
<source>License(s)</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s3be1d90ffa46b7f1">
|
||||||
|
<source>Enterprise is in preview.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s34dca481f039c226">
|
||||||
|
<source>How to get a license</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s948364901c166232">
|
||||||
|
<source>Copy the installation ID</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s75c167446b237e0f">
|
||||||
|
<source>Then open the customer portal</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s9748dd3bd53d27a4">
|
||||||
|
<source>Forecasted default users</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s6b18f594d94c2374">
|
||||||
|
<source>Estimated user count one year from now</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s69f246d164be88d0">
|
||||||
|
<source>Forecasted external users</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s878fc2eaf94642db">
|
||||||
|
<source>Estimated external user count one year from now</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sd22bd01bdf28c548">
|
||||||
|
<source>Cumulative license expiry</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sdeb6cee42435dd07">
|
||||||
|
<source>Update License</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s99afa741c259d70e">
|
||||||
|
<source>Create License</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s7df5b92a3f93544f">
|
||||||
|
<source>Warning: The current user count has exceeded the configured licenses.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s0141f42936495787">
|
||||||
|
<source>Click here for more info.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s7be2df39f727faa2">
|
||||||
|
<source>Enterprise</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s9ce7cc01fb9b5b53">
|
||||||
|
<source>Manage enterprise licenses</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
|
|
|
@ -6063,6 +6063,84 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s1889ba2eaeec2f1e">
|
<trans-unit id="s1889ba2eaeec2f1e">
|
||||||
<source>When enabled, code-based multi-factor authentication can be used by appending a semicolon and the TOTP code to the password. This should only be enabled if all users that will bind to this provider have a TOTP device configured, as otherwise a password may incorrectly be rejected if it contains a semicolon.</source>
|
<source>When enabled, code-based multi-factor authentication can be used by appending a semicolon and the TOTP code to the password. This should only be enabled if all users that will bind to this provider have a TOTP device configured, as otherwise a password may incorrectly be rejected if it contains a semicolon.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s9f9492d30a96b9c6">
|
||||||
|
<source>User type</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s0b9a40b7b2853c7d">
|
||||||
|
<source>Default user</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s35b9fa270f45b391">
|
||||||
|
<source>External user</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s1a635369edaf4dc3">
|
||||||
|
<source>Service account</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s0e427111d750cc02">
|
||||||
|
<source>Successfully updated license.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s06ae64e621f302eb">
|
||||||
|
<source>Successfully created license.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s2905c425adae99bd">
|
||||||
|
<source>Install ID</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sb18ec434a8a3aafb">
|
||||||
|
<source>License key</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s2e109263b73c12d5">
|
||||||
|
<source>Licenses</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sf8f9f3032e891e16">
|
||||||
|
<source>TODO Copy</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sd49099e9522635f4">
|
||||||
|
<source>License(s)</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s3be1d90ffa46b7f1">
|
||||||
|
<source>Enterprise is in preview.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s34dca481f039c226">
|
||||||
|
<source>How to get a license</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s948364901c166232">
|
||||||
|
<source>Copy the installation ID</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s75c167446b237e0f">
|
||||||
|
<source>Then open the customer portal</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s9748dd3bd53d27a4">
|
||||||
|
<source>Forecasted default users</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s6b18f594d94c2374">
|
||||||
|
<source>Estimated user count one year from now</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s69f246d164be88d0">
|
||||||
|
<source>Forecasted external users</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s878fc2eaf94642db">
|
||||||
|
<source>Estimated external user count one year from now</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sd22bd01bdf28c548">
|
||||||
|
<source>Cumulative license expiry</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sdeb6cee42435dd07">
|
||||||
|
<source>Update License</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s99afa741c259d70e">
|
||||||
|
<source>Create License</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s7df5b92a3f93544f">
|
||||||
|
<source>Warning: The current user count has exceeded the configured licenses.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s0141f42936495787">
|
||||||
|
<source>Click here for more info.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s7be2df39f727faa2">
|
||||||
|
<source>Enterprise</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s9ce7cc01fb9b5b53">
|
||||||
|
<source>Manage enterprise licenses</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
|
|
|
@ -5655,6 +5655,84 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s1889ba2eaeec2f1e">
|
<trans-unit id="s1889ba2eaeec2f1e">
|
||||||
<source>When enabled, code-based multi-factor authentication can be used by appending a semicolon and the TOTP code to the password. This should only be enabled if all users that will bind to this provider have a TOTP device configured, as otherwise a password may incorrectly be rejected if it contains a semicolon.</source>
|
<source>When enabled, code-based multi-factor authentication can be used by appending a semicolon and the TOTP code to the password. This should only be enabled if all users that will bind to this provider have a TOTP device configured, as otherwise a password may incorrectly be rejected if it contains a semicolon.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s9f9492d30a96b9c6">
|
||||||
|
<source>User type</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s0b9a40b7b2853c7d">
|
||||||
|
<source>Default user</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s35b9fa270f45b391">
|
||||||
|
<source>External user</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s1a635369edaf4dc3">
|
||||||
|
<source>Service account</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s0e427111d750cc02">
|
||||||
|
<source>Successfully updated license.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s06ae64e621f302eb">
|
||||||
|
<source>Successfully created license.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s2905c425adae99bd">
|
||||||
|
<source>Install ID</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sb18ec434a8a3aafb">
|
||||||
|
<source>License key</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s2e109263b73c12d5">
|
||||||
|
<source>Licenses</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sf8f9f3032e891e16">
|
||||||
|
<source>TODO Copy</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sd49099e9522635f4">
|
||||||
|
<source>License(s)</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s3be1d90ffa46b7f1">
|
||||||
|
<source>Enterprise is in preview.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s34dca481f039c226">
|
||||||
|
<source>How to get a license</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s948364901c166232">
|
||||||
|
<source>Copy the installation ID</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s75c167446b237e0f">
|
||||||
|
<source>Then open the customer portal</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s9748dd3bd53d27a4">
|
||||||
|
<source>Forecasted default users</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s6b18f594d94c2374">
|
||||||
|
<source>Estimated user count one year from now</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s69f246d164be88d0">
|
||||||
|
<source>Forecasted external users</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s878fc2eaf94642db">
|
||||||
|
<source>Estimated external user count one year from now</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sd22bd01bdf28c548">
|
||||||
|
<source>Cumulative license expiry</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sdeb6cee42435dd07">
|
||||||
|
<source>Update License</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s99afa741c259d70e">
|
||||||
|
<source>Create License</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s7df5b92a3f93544f">
|
||||||
|
<source>Warning: The current user count has exceeded the configured licenses.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s0141f42936495787">
|
||||||
|
<source>Click here for more info.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s7be2df39f727faa2">
|
||||||
|
<source>Enterprise</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s9ce7cc01fb9b5b53">
|
||||||
|
<source>Manage enterprise licenses</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
|
|
|
@ -5762,6 +5762,84 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s1889ba2eaeec2f1e">
|
<trans-unit id="s1889ba2eaeec2f1e">
|
||||||
<source>When enabled, code-based multi-factor authentication can be used by appending a semicolon and the TOTP code to the password. This should only be enabled if all users that will bind to this provider have a TOTP device configured, as otherwise a password may incorrectly be rejected if it contains a semicolon.</source>
|
<source>When enabled, code-based multi-factor authentication can be used by appending a semicolon and the TOTP code to the password. This should only be enabled if all users that will bind to this provider have a TOTP device configured, as otherwise a password may incorrectly be rejected if it contains a semicolon.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s9f9492d30a96b9c6">
|
||||||
|
<source>User type</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s0b9a40b7b2853c7d">
|
||||||
|
<source>Default user</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s35b9fa270f45b391">
|
||||||
|
<source>External user</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s1a635369edaf4dc3">
|
||||||
|
<source>Service account</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s0e427111d750cc02">
|
||||||
|
<source>Successfully updated license.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s06ae64e621f302eb">
|
||||||
|
<source>Successfully created license.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s2905c425adae99bd">
|
||||||
|
<source>Install ID</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sb18ec434a8a3aafb">
|
||||||
|
<source>License key</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s2e109263b73c12d5">
|
||||||
|
<source>Licenses</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sf8f9f3032e891e16">
|
||||||
|
<source>TODO Copy</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sd49099e9522635f4">
|
||||||
|
<source>License(s)</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s3be1d90ffa46b7f1">
|
||||||
|
<source>Enterprise is in preview.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s34dca481f039c226">
|
||||||
|
<source>How to get a license</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s948364901c166232">
|
||||||
|
<source>Copy the installation ID</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s75c167446b237e0f">
|
||||||
|
<source>Then open the customer portal</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s9748dd3bd53d27a4">
|
||||||
|
<source>Forecasted default users</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s6b18f594d94c2374">
|
||||||
|
<source>Estimated user count one year from now</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s69f246d164be88d0">
|
||||||
|
<source>Forecasted external users</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s878fc2eaf94642db">
|
||||||
|
<source>Estimated external user count one year from now</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sd22bd01bdf28c548">
|
||||||
|
<source>Cumulative license expiry</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sdeb6cee42435dd07">
|
||||||
|
<source>Update License</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s99afa741c259d70e">
|
||||||
|
<source>Create License</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s7df5b92a3f93544f">
|
||||||
|
<source>Warning: The current user count has exceeded the configured licenses.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s0141f42936495787">
|
||||||
|
<source>Click here for more info.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s7be2df39f727faa2">
|
||||||
|
<source>Enterprise</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s9ce7cc01fb9b5b53">
|
||||||
|
<source>Manage enterprise licenses</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
|
|
|
@ -5894,6 +5894,84 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s1889ba2eaeec2f1e">
|
<trans-unit id="s1889ba2eaeec2f1e">
|
||||||
<source>When enabled, code-based multi-factor authentication can be used by appending a semicolon and the TOTP code to the password. This should only be enabled if all users that will bind to this provider have a TOTP device configured, as otherwise a password may incorrectly be rejected if it contains a semicolon.</source>
|
<source>When enabled, code-based multi-factor authentication can be used by appending a semicolon and the TOTP code to the password. This should only be enabled if all users that will bind to this provider have a TOTP device configured, as otherwise a password may incorrectly be rejected if it contains a semicolon.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s9f9492d30a96b9c6">
|
||||||
|
<source>User type</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s0b9a40b7b2853c7d">
|
||||||
|
<source>Default user</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s35b9fa270f45b391">
|
||||||
|
<source>External user</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s1a635369edaf4dc3">
|
||||||
|
<source>Service account</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s0e427111d750cc02">
|
||||||
|
<source>Successfully updated license.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s06ae64e621f302eb">
|
||||||
|
<source>Successfully created license.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s2905c425adae99bd">
|
||||||
|
<source>Install ID</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sb18ec434a8a3aafb">
|
||||||
|
<source>License key</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s2e109263b73c12d5">
|
||||||
|
<source>Licenses</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sf8f9f3032e891e16">
|
||||||
|
<source>TODO Copy</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sd49099e9522635f4">
|
||||||
|
<source>License(s)</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s3be1d90ffa46b7f1">
|
||||||
|
<source>Enterprise is in preview.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s34dca481f039c226">
|
||||||
|
<source>How to get a license</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s948364901c166232">
|
||||||
|
<source>Copy the installation ID</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s75c167446b237e0f">
|
||||||
|
<source>Then open the customer portal</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s9748dd3bd53d27a4">
|
||||||
|
<source>Forecasted default users</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s6b18f594d94c2374">
|
||||||
|
<source>Estimated user count one year from now</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s69f246d164be88d0">
|
||||||
|
<source>Forecasted external users</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s878fc2eaf94642db">
|
||||||
|
<source>Estimated external user count one year from now</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sd22bd01bdf28c548">
|
||||||
|
<source>Cumulative license expiry</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sdeb6cee42435dd07">
|
||||||
|
<source>Update License</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s99afa741c259d70e">
|
||||||
|
<source>Create License</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s7df5b92a3f93544f">
|
||||||
|
<source>Warning: The current user count has exceeded the configured licenses.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s0141f42936495787">
|
||||||
|
<source>Click here for more info.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s7be2df39f727faa2">
|
||||||
|
<source>Enterprise</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s9ce7cc01fb9b5b53">
|
||||||
|
<source>Manage enterprise licenses</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
|
|
|
@ -5998,6 +5998,84 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s1889ba2eaeec2f1e">
|
<trans-unit id="s1889ba2eaeec2f1e">
|
||||||
<source>When enabled, code-based multi-factor authentication can be used by appending a semicolon and the TOTP code to the password. This should only be enabled if all users that will bind to this provider have a TOTP device configured, as otherwise a password may incorrectly be rejected if it contains a semicolon.</source>
|
<source>When enabled, code-based multi-factor authentication can be used by appending a semicolon and the TOTP code to the password. This should only be enabled if all users that will bind to this provider have a TOTP device configured, as otherwise a password may incorrectly be rejected if it contains a semicolon.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s9f9492d30a96b9c6">
|
||||||
|
<source>User type</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s0b9a40b7b2853c7d">
|
||||||
|
<source>Default user</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s35b9fa270f45b391">
|
||||||
|
<source>External user</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s1a635369edaf4dc3">
|
||||||
|
<source>Service account</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s0e427111d750cc02">
|
||||||
|
<source>Successfully updated license.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s06ae64e621f302eb">
|
||||||
|
<source>Successfully created license.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s2905c425adae99bd">
|
||||||
|
<source>Install ID</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sb18ec434a8a3aafb">
|
||||||
|
<source>License key</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s2e109263b73c12d5">
|
||||||
|
<source>Licenses</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sf8f9f3032e891e16">
|
||||||
|
<source>TODO Copy</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sd49099e9522635f4">
|
||||||
|
<source>License(s)</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s3be1d90ffa46b7f1">
|
||||||
|
<source>Enterprise is in preview.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s34dca481f039c226">
|
||||||
|
<source>How to get a license</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s948364901c166232">
|
||||||
|
<source>Copy the installation ID</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s75c167446b237e0f">
|
||||||
|
<source>Then open the customer portal</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s9748dd3bd53d27a4">
|
||||||
|
<source>Forecasted default users</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s6b18f594d94c2374">
|
||||||
|
<source>Estimated user count one year from now</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s69f246d164be88d0">
|
||||||
|
<source>Forecasted external users</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s878fc2eaf94642db">
|
||||||
|
<source>Estimated external user count one year from now</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sd22bd01bdf28c548">
|
||||||
|
<source>Cumulative license expiry</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sdeb6cee42435dd07">
|
||||||
|
<source>Update License</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s99afa741c259d70e">
|
||||||
|
<source>Create License</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s7df5b92a3f93544f">
|
||||||
|
<source>Warning: The current user count has exceeded the configured licenses.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s0141f42936495787">
|
||||||
|
<source>Click here for more info.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s7be2df39f727faa2">
|
||||||
|
<source>Enterprise</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s9ce7cc01fb9b5b53">
|
||||||
|
<source>Manage enterprise licenses</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
|
|
|
@ -5645,6 +5645,84 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s1889ba2eaeec2f1e">
|
<trans-unit id="s1889ba2eaeec2f1e">
|
||||||
<source>When enabled, code-based multi-factor authentication can be used by appending a semicolon and the TOTP code to the password. This should only be enabled if all users that will bind to this provider have a TOTP device configured, as otherwise a password may incorrectly be rejected if it contains a semicolon.</source>
|
<source>When enabled, code-based multi-factor authentication can be used by appending a semicolon and the TOTP code to the password. This should only be enabled if all users that will bind to this provider have a TOTP device configured, as otherwise a password may incorrectly be rejected if it contains a semicolon.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s9f9492d30a96b9c6">
|
||||||
|
<source>User type</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s0b9a40b7b2853c7d">
|
||||||
|
<source>Default user</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s35b9fa270f45b391">
|
||||||
|
<source>External user</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s1a635369edaf4dc3">
|
||||||
|
<source>Service account</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s0e427111d750cc02">
|
||||||
|
<source>Successfully updated license.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s06ae64e621f302eb">
|
||||||
|
<source>Successfully created license.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s2905c425adae99bd">
|
||||||
|
<source>Install ID</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sb18ec434a8a3aafb">
|
||||||
|
<source>License key</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s2e109263b73c12d5">
|
||||||
|
<source>Licenses</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sf8f9f3032e891e16">
|
||||||
|
<source>TODO Copy</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sd49099e9522635f4">
|
||||||
|
<source>License(s)</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s3be1d90ffa46b7f1">
|
||||||
|
<source>Enterprise is in preview.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s34dca481f039c226">
|
||||||
|
<source>How to get a license</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s948364901c166232">
|
||||||
|
<source>Copy the installation ID</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s75c167446b237e0f">
|
||||||
|
<source>Then open the customer portal</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s9748dd3bd53d27a4">
|
||||||
|
<source>Forecasted default users</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s6b18f594d94c2374">
|
||||||
|
<source>Estimated user count one year from now</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s69f246d164be88d0">
|
||||||
|
<source>Forecasted external users</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s878fc2eaf94642db">
|
||||||
|
<source>Estimated external user count one year from now</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sd22bd01bdf28c548">
|
||||||
|
<source>Cumulative license expiry</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sdeb6cee42435dd07">
|
||||||
|
<source>Update License</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s99afa741c259d70e">
|
||||||
|
<source>Create License</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s7df5b92a3f93544f">
|
||||||
|
<source>Warning: The current user count has exceeded the configured licenses.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s0141f42936495787">
|
||||||
|
<source>Click here for more info.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s7be2df39f727faa2">
|
||||||
|
<source>Enterprise</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s9ce7cc01fb9b5b53">
|
||||||
|
<source>Manage enterprise licenses</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
|
|
|
@ -7580,6 +7580,84 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||||
<trans-unit id="s1889ba2eaeec2f1e">
|
<trans-unit id="s1889ba2eaeec2f1e">
|
||||||
<source>When enabled, code-based multi-factor authentication can be used by appending a semicolon and the TOTP code to the password. This should only be enabled if all users that will bind to this provider have a TOTP device configured, as otherwise a password may incorrectly be rejected if it contains a semicolon.</source>
|
<source>When enabled, code-based multi-factor authentication can be used by appending a semicolon and the TOTP code to the password. This should only be enabled if all users that will bind to this provider have a TOTP device configured, as otherwise a password may incorrectly be rejected if it contains a semicolon.</source>
|
||||||
<target>启用时,可以通过在密码后添加分号和 TOTP 代码来使用基于代码的多因素身份验证。仅在所有绑定到此提供程序的用户都已配置 TOTP 设备的情况下才应该启用,否则密码可能会因为包含分号而被错误地拒绝。</target>
|
<target>启用时,可以通过在密码后添加分号和 TOTP 代码来使用基于代码的多因素身份验证。仅在所有绑定到此提供程序的用户都已配置 TOTP 设备的情况下才应该启用,否则密码可能会因为包含分号而被错误地拒绝。</target>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s9f9492d30a96b9c6">
|
||||||
|
<source>User type</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s0b9a40b7b2853c7d">
|
||||||
|
<source>Default user</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s35b9fa270f45b391">
|
||||||
|
<source>External user</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s1a635369edaf4dc3">
|
||||||
|
<source>Service account</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s0e427111d750cc02">
|
||||||
|
<source>Successfully updated license.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s06ae64e621f302eb">
|
||||||
|
<source>Successfully created license.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s2905c425adae99bd">
|
||||||
|
<source>Install ID</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sb18ec434a8a3aafb">
|
||||||
|
<source>License key</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s2e109263b73c12d5">
|
||||||
|
<source>Licenses</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sf8f9f3032e891e16">
|
||||||
|
<source>TODO Copy</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sd49099e9522635f4">
|
||||||
|
<source>License(s)</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s3be1d90ffa46b7f1">
|
||||||
|
<source>Enterprise is in preview.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s34dca481f039c226">
|
||||||
|
<source>How to get a license</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s948364901c166232">
|
||||||
|
<source>Copy the installation ID</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s75c167446b237e0f">
|
||||||
|
<source>Then open the customer portal</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s9748dd3bd53d27a4">
|
||||||
|
<source>Forecasted default users</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s6b18f594d94c2374">
|
||||||
|
<source>Estimated user count one year from now</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s69f246d164be88d0">
|
||||||
|
<source>Forecasted external users</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s878fc2eaf94642db">
|
||||||
|
<source>Estimated external user count one year from now</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sd22bd01bdf28c548">
|
||||||
|
<source>Cumulative license expiry</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sdeb6cee42435dd07">
|
||||||
|
<source>Update License</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s99afa741c259d70e">
|
||||||
|
<source>Create License</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s7df5b92a3f93544f">
|
||||||
|
<source>Warning: The current user count has exceeded the configured licenses.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s0141f42936495787">
|
||||||
|
<source>Click here for more info.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s7be2df39f727faa2">
|
||||||
|
<source>Enterprise</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s9ce7cc01fb9b5b53">
|
||||||
|
<source>Manage enterprise licenses</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
|
|
|
@ -5700,6 +5700,84 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s1889ba2eaeec2f1e">
|
<trans-unit id="s1889ba2eaeec2f1e">
|
||||||
<source>When enabled, code-based multi-factor authentication can be used by appending a semicolon and the TOTP code to the password. This should only be enabled if all users that will bind to this provider have a TOTP device configured, as otherwise a password may incorrectly be rejected if it contains a semicolon.</source>
|
<source>When enabled, code-based multi-factor authentication can be used by appending a semicolon and the TOTP code to the password. This should only be enabled if all users that will bind to this provider have a TOTP device configured, as otherwise a password may incorrectly be rejected if it contains a semicolon.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s9f9492d30a96b9c6">
|
||||||
|
<source>User type</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s0b9a40b7b2853c7d">
|
||||||
|
<source>Default user</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s35b9fa270f45b391">
|
||||||
|
<source>External user</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s1a635369edaf4dc3">
|
||||||
|
<source>Service account</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s0e427111d750cc02">
|
||||||
|
<source>Successfully updated license.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s06ae64e621f302eb">
|
||||||
|
<source>Successfully created license.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s2905c425adae99bd">
|
||||||
|
<source>Install ID</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sb18ec434a8a3aafb">
|
||||||
|
<source>License key</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s2e109263b73c12d5">
|
||||||
|
<source>Licenses</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sf8f9f3032e891e16">
|
||||||
|
<source>TODO Copy</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sd49099e9522635f4">
|
||||||
|
<source>License(s)</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s3be1d90ffa46b7f1">
|
||||||
|
<source>Enterprise is in preview.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s34dca481f039c226">
|
||||||
|
<source>How to get a license</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s948364901c166232">
|
||||||
|
<source>Copy the installation ID</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s75c167446b237e0f">
|
||||||
|
<source>Then open the customer portal</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s9748dd3bd53d27a4">
|
||||||
|
<source>Forecasted default users</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s6b18f594d94c2374">
|
||||||
|
<source>Estimated user count one year from now</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s69f246d164be88d0">
|
||||||
|
<source>Forecasted external users</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s878fc2eaf94642db">
|
||||||
|
<source>Estimated external user count one year from now</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sd22bd01bdf28c548">
|
||||||
|
<source>Cumulative license expiry</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sdeb6cee42435dd07">
|
||||||
|
<source>Update License</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s99afa741c259d70e">
|
||||||
|
<source>Create License</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s7df5b92a3f93544f">
|
||||||
|
<source>Warning: The current user count has exceeded the configured licenses.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s0141f42936495787">
|
||||||
|
<source>Click here for more info.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s7be2df39f727faa2">
|
||||||
|
<source>Enterprise</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s9ce7cc01fb9b5b53">
|
||||||
|
<source>Manage enterprise licenses</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
|
|
|
@ -5699,6 +5699,84 @@ Bindings to groups/users are checked against the user of the event.</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s1889ba2eaeec2f1e">
|
<trans-unit id="s1889ba2eaeec2f1e">
|
||||||
<source>When enabled, code-based multi-factor authentication can be used by appending a semicolon and the TOTP code to the password. This should only be enabled if all users that will bind to this provider have a TOTP device configured, as otherwise a password may incorrectly be rejected if it contains a semicolon.</source>
|
<source>When enabled, code-based multi-factor authentication can be used by appending a semicolon and the TOTP code to the password. This should only be enabled if all users that will bind to this provider have a TOTP device configured, as otherwise a password may incorrectly be rejected if it contains a semicolon.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s9f9492d30a96b9c6">
|
||||||
|
<source>User type</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s0b9a40b7b2853c7d">
|
||||||
|
<source>Default user</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s35b9fa270f45b391">
|
||||||
|
<source>External user</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s1a635369edaf4dc3">
|
||||||
|
<source>Service account</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s0e427111d750cc02">
|
||||||
|
<source>Successfully updated license.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s06ae64e621f302eb">
|
||||||
|
<source>Successfully created license.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s2905c425adae99bd">
|
||||||
|
<source>Install ID</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sb18ec434a8a3aafb">
|
||||||
|
<source>License key</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s2e109263b73c12d5">
|
||||||
|
<source>Licenses</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sf8f9f3032e891e16">
|
||||||
|
<source>TODO Copy</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sd49099e9522635f4">
|
||||||
|
<source>License(s)</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s3be1d90ffa46b7f1">
|
||||||
|
<source>Enterprise is in preview.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s34dca481f039c226">
|
||||||
|
<source>How to get a license</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s948364901c166232">
|
||||||
|
<source>Copy the installation ID</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s75c167446b237e0f">
|
||||||
|
<source>Then open the customer portal</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s9748dd3bd53d27a4">
|
||||||
|
<source>Forecasted default users</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s6b18f594d94c2374">
|
||||||
|
<source>Estimated user count one year from now</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s69f246d164be88d0">
|
||||||
|
<source>Forecasted external users</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s878fc2eaf94642db">
|
||||||
|
<source>Estimated external user count one year from now</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sd22bd01bdf28c548">
|
||||||
|
<source>Cumulative license expiry</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sdeb6cee42435dd07">
|
||||||
|
<source>Update License</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s99afa741c259d70e">
|
||||||
|
<source>Create License</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s7df5b92a3f93544f">
|
||||||
|
<source>Warning: The current user count has exceeded the configured licenses.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s0141f42936495787">
|
||||||
|
<source>Click here for more info.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s7be2df39f727faa2">
|
||||||
|
<source>Enterprise</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s9ce7cc01fb9b5b53">
|
||||||
|
<source>Manage enterprise licenses</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
|
|
Reference in New Issue