Compare commits
34 Commits
version/20
...
version-20
Author | SHA1 | Date | |
---|---|---|---|
5a7508d2e0 | |||
9c31ea1aa6 | |||
18211a2033 | |||
b4cfc56e5e | |||
8e797fa76b | |||
1b91543add | |||
1cd59be8dc | |||
66bb68a747 | |||
aa4f7fb2b6 | |||
4f1c11c5ef | |||
add7a80fdc | |||
aac91c2e9d | |||
85e86351cd | |||
d767504474 | |||
f84cd6208c | |||
1ec540ea9a | |||
29fe731bbf | |||
26e66969c9 | |||
fe629f8b51 | |||
72b7642c5a | |||
a97f842112 | |||
16e6e4c3b7 | |||
dc0d715885 | |||
7ecd57ecff | |||
0cb4d64b57 | |||
a4fd58a0db | |||
fb6e8ca1eb | |||
4c41948e75 | |||
a5c8caf909 | |||
970655ab21 | |||
c8c7202c61 | |||
a3981dd3cd | |||
affafc31cf | |||
602aed674b |
@ -1,5 +1,5 @@
|
|||||||
[bumpversion]
|
[bumpversion]
|
||||||
current_version = 2021.7.1
|
current_version = 2021.7.3
|
||||||
tag = True
|
tag = True
|
||||||
commit = True
|
commit = True
|
||||||
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-?(?P<release>.*)
|
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-?(?P<release>.*)
|
||||||
|
22
.github/workflows/release.yml
vendored
22
.github/workflows/release.yml
vendored
@ -33,14 +33,14 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
push: ${{ github.event_name == 'release' }}
|
push: ${{ github.event_name == 'release' }}
|
||||||
tags: |
|
tags: |
|
||||||
beryju/authentik:2021.7.1,
|
beryju/authentik:2021.7.3,
|
||||||
beryju/authentik:latest,
|
beryju/authentik:latest,
|
||||||
ghcr.io/goauthentik/server:2021.7.1,
|
ghcr.io/goauthentik/server:2021.7.3,
|
||||||
ghcr.io/goauthentik/server:latest
|
ghcr.io/goauthentik/server:latest
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
context: .
|
context: .
|
||||||
- name: Building Docker Image (stable)
|
- name: Building Docker Image (stable)
|
||||||
if: ${{ github.event_name == 'release' && !contains('2021.7.1', 'rc') }}
|
if: ${{ github.event_name == 'release' && !contains('2021.7.3', 'rc') }}
|
||||||
run: |
|
run: |
|
||||||
docker pull beryju/authentik:latest
|
docker pull beryju/authentik:latest
|
||||||
docker tag beryju/authentik:latest beryju/authentik:stable
|
docker tag beryju/authentik:latest beryju/authentik:stable
|
||||||
@ -75,14 +75,14 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
push: ${{ github.event_name == 'release' }}
|
push: ${{ github.event_name == 'release' }}
|
||||||
tags: |
|
tags: |
|
||||||
beryju/authentik-proxy:2021.7.1,
|
beryju/authentik-proxy:2021.7.3,
|
||||||
beryju/authentik-proxy:latest,
|
beryju/authentik-proxy:latest,
|
||||||
ghcr.io/goauthentik/proxy:2021.7.1,
|
ghcr.io/goauthentik/proxy:2021.7.3,
|
||||||
ghcr.io/goauthentik/proxy:latest
|
ghcr.io/goauthentik/proxy:latest
|
||||||
file: proxy.Dockerfile
|
file: proxy.Dockerfile
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
- name: Building Docker Image (stable)
|
- name: Building Docker Image (stable)
|
||||||
if: ${{ github.event_name == 'release' && !contains('2021.7.1', 'rc') }}
|
if: ${{ github.event_name == 'release' && !contains('2021.7.3', 'rc') }}
|
||||||
run: |
|
run: |
|
||||||
docker pull beryju/authentik-proxy:latest
|
docker pull beryju/authentik-proxy:latest
|
||||||
docker tag beryju/authentik-proxy:latest beryju/authentik-proxy:stable
|
docker tag beryju/authentik-proxy:latest beryju/authentik-proxy:stable
|
||||||
@ -117,14 +117,14 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
push: ${{ github.event_name == 'release' }}
|
push: ${{ github.event_name == 'release' }}
|
||||||
tags: |
|
tags: |
|
||||||
beryju/authentik-ldap:2021.7.1,
|
beryju/authentik-ldap:2021.7.3,
|
||||||
beryju/authentik-ldap:latest,
|
beryju/authentik-ldap:latest,
|
||||||
ghcr.io/goauthentik/ldap:2021.7.1,
|
ghcr.io/goauthentik/ldap:2021.7.3,
|
||||||
ghcr.io/goauthentik/ldap:latest
|
ghcr.io/goauthentik/ldap:latest
|
||||||
file: ldap.Dockerfile
|
file: ldap.Dockerfile
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
- name: Building Docker Image (stable)
|
- name: Building Docker Image (stable)
|
||||||
if: ${{ github.event_name == 'release' && !contains('2021.7.1', 'rc') }}
|
if: ${{ github.event_name == 'release' && !contains('2021.7.3', 'rc') }}
|
||||||
run: |
|
run: |
|
||||||
docker pull beryju/authentik-ldap:latest
|
docker pull beryju/authentik-ldap:latest
|
||||||
docker tag beryju/authentik-ldap:latest beryju/authentik-ldap:stable
|
docker tag beryju/authentik-ldap:latest beryju/authentik-ldap:stable
|
||||||
@ -176,7 +176,7 @@ jobs:
|
|||||||
SENTRY_PROJECT: authentik
|
SENTRY_PROJECT: authentik
|
||||||
SENTRY_URL: https://sentry.beryju.org
|
SENTRY_URL: https://sentry.beryju.org
|
||||||
with:
|
with:
|
||||||
version: authentik@2021.7.1
|
version: authentik@2021.7.3
|
||||||
environment: beryjuorg-prod
|
environment: beryjuorg-prod
|
||||||
sourcemaps: './web/dist'
|
sourcemaps: './web/dist'
|
||||||
url_prefix: /static/dist
|
url_prefix: '~/static/dist'
|
||||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -200,4 +200,4 @@ media/
|
|||||||
*mmdb
|
*mmdb
|
||||||
|
|
||||||
.idea/
|
.idea/
|
||||||
api/
|
/api/
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
"""authentik"""
|
"""authentik"""
|
||||||
__version__ = "2021.7.1"
|
__version__ = "2021.7.3"
|
||||||
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"
|
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"
|
||||||
|
@ -48,7 +48,7 @@ class TokenViewSet(UsedByMixin, ModelViewSet):
|
|||||||
"""Token Viewset"""
|
"""Token Viewset"""
|
||||||
|
|
||||||
lookup_field = "identifier"
|
lookup_field = "identifier"
|
||||||
queryset = Token.filter_not_expired()
|
queryset = Token.objects.all()
|
||||||
serializer_class = TokenSerializer
|
serializer_class = TokenSerializer
|
||||||
search_fields = [
|
search_fields = [
|
||||||
"identifier",
|
"identifier",
|
||||||
|
@ -10,6 +10,7 @@ from drf_spectacular.utils import extend_schema, extend_schema_field
|
|||||||
from guardian.utils import get_anonymous_user
|
from guardian.utils import get_anonymous_user
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
from rest_framework.fields import CharField, JSONField, SerializerMethodField
|
from rest_framework.fields import CharField, JSONField, SerializerMethodField
|
||||||
|
from rest_framework.permissions import IsAuthenticated
|
||||||
from rest_framework.request import Request
|
from rest_framework.request import Request
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.serializers import (
|
from rest_framework.serializers import (
|
||||||
@ -62,12 +63,40 @@ class UserSerializer(ModelSerializer):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class UserSelfSerializer(ModelSerializer):
|
||||||
|
"""User Serializer for information a user can retrieve about themselves and
|
||||||
|
update about themselves"""
|
||||||
|
|
||||||
|
is_superuser = BooleanField(read_only=True)
|
||||||
|
avatar = CharField(read_only=True)
|
||||||
|
groups = ListSerializer(child=GroupSerializer(), read_only=True, source="ak_groups")
|
||||||
|
uid = CharField(read_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
|
||||||
|
model = User
|
||||||
|
fields = [
|
||||||
|
"pk",
|
||||||
|
"username",
|
||||||
|
"name",
|
||||||
|
"is_active",
|
||||||
|
"is_superuser",
|
||||||
|
"groups",
|
||||||
|
"email",
|
||||||
|
"avatar",
|
||||||
|
"uid",
|
||||||
|
]
|
||||||
|
extra_kwargs = {
|
||||||
|
"is_active": {"read_only": True},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class SessionUserSerializer(PassiveSerializer):
|
class SessionUserSerializer(PassiveSerializer):
|
||||||
"""Response for the /user/me endpoint, returns the currently active user (as `user` property)
|
"""Response for the /user/me endpoint, returns the currently active user (as `user` property)
|
||||||
and, if this user is being impersonated, the original user in the `original` property."""
|
and, if this user is being impersonated, the original user in the `original` property."""
|
||||||
|
|
||||||
user = UserSerializer()
|
user = UserSelfSerializer()
|
||||||
original = UserSerializer(required=False)
|
original = UserSelfSerializer(required=False)
|
||||||
|
|
||||||
|
|
||||||
class UserMetricsSerializer(PassiveSerializer):
|
class UserMetricsSerializer(PassiveSerializer):
|
||||||
@ -158,12 +187,36 @@ class UserViewSet(UsedByMixin, ModelViewSet):
|
|||||||
data={"user": UserSerializer(request.user).data}
|
data={"user": UserSerializer(request.user).data}
|
||||||
)
|
)
|
||||||
if SESSION_IMPERSONATE_USER in request._request.session:
|
if SESSION_IMPERSONATE_USER in request._request.session:
|
||||||
serializer.initial_data["original"] = UserSerializer(
|
serializer.initial_data["original"] = UserSelfSerializer(
|
||||||
request._request.session[SESSION_IMPERSONATE_ORIGINAL_USER]
|
request._request.session[SESSION_IMPERSONATE_ORIGINAL_USER]
|
||||||
).data
|
).data
|
||||||
serializer.is_valid()
|
serializer.is_valid()
|
||||||
return Response(serializer.data)
|
return Response(serializer.data)
|
||||||
|
|
||||||
|
@extend_schema(
|
||||||
|
request=UserSelfSerializer, responses={200: SessionUserSerializer(many=False)}
|
||||||
|
)
|
||||||
|
@action(
|
||||||
|
methods=["PUT"],
|
||||||
|
detail=False,
|
||||||
|
pagination_class=None,
|
||||||
|
filter_backends=[],
|
||||||
|
permission_classes=[IsAuthenticated],
|
||||||
|
)
|
||||||
|
def update_self(self, request: Request) -> Response:
|
||||||
|
"""Allow users to change information on their own profile"""
|
||||||
|
data = UserSelfSerializer(
|
||||||
|
instance=User.objects.get(pk=request.user.pk), data=request.data
|
||||||
|
)
|
||||||
|
if not data.is_valid():
|
||||||
|
return Response(data.errors)
|
||||||
|
new_user = data.save()
|
||||||
|
# If we're impersonating, we need to update that user object
|
||||||
|
# since it caches the full object
|
||||||
|
if SESSION_IMPERSONATE_USER in request.session:
|
||||||
|
request.session[SESSION_IMPERSONATE_USER] = new_user
|
||||||
|
return self.me(request)
|
||||||
|
|
||||||
@permission_required("authentik_core.view_user", ["authentik_events.view_event"])
|
@permission_required("authentik_core.view_user", ["authentik_events.view_event"])
|
||||||
@extend_schema(responses={200: UserMetricsSerializer(many=False)})
|
@extend_schema(responses={200: UserMetricsSerializer(many=False)})
|
||||||
@action(detail=True, pagination_class=None, filter_backends=[])
|
@action(detail=True, pagination_class=None, filter_backends=[])
|
||||||
|
@ -438,6 +438,7 @@ class Token(ManagedModel, ExpiringModel):
|
|||||||
from authentik.events.models import Event, EventAction
|
from authentik.events.models import Event, EventAction
|
||||||
|
|
||||||
self.key = default_token_key()
|
self.key = default_token_key()
|
||||||
|
self.expires = default_token_duration()
|
||||||
self.save(*args, **kwargs)
|
self.save(*args, **kwargs)
|
||||||
Event.new(
|
Event.new(
|
||||||
action=EventAction.SECRET_ROTATE,
|
action=EventAction.SECRET_ROTATE,
|
||||||
|
@ -114,7 +114,7 @@ class MonitoredTask(Task):
|
|||||||
# For tasks that should only be listed if they failed, set this to False
|
# For tasks that should only be listed if they failed, set this to False
|
||||||
save_on_success: bool
|
save_on_success: bool
|
||||||
|
|
||||||
_result: TaskResult
|
_result: Optional[TaskResult]
|
||||||
|
|
||||||
_uid: Optional[str]
|
_uid: Optional[str]
|
||||||
|
|
||||||
@ -122,7 +122,7 @@ class MonitoredTask(Task):
|
|||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.save_on_success = True
|
self.save_on_success = True
|
||||||
self._uid = None
|
self._uid = None
|
||||||
self._result = TaskResult(status=TaskResultStatus.ERROR, messages=[])
|
self._result = None
|
||||||
self.result_timeout_hours = 6
|
self.result_timeout_hours = 6
|
||||||
self.start = default_timer()
|
self.start = default_timer()
|
||||||
|
|
||||||
@ -138,25 +138,30 @@ class MonitoredTask(Task):
|
|||||||
def after_return(
|
def after_return(
|
||||||
self, status, retval, task_id, args: list[Any], kwargs: dict[str, Any], einfo
|
self, status, retval, task_id, args: list[Any], kwargs: dict[str, Any], einfo
|
||||||
):
|
):
|
||||||
if not self._result.uid:
|
if self._result:
|
||||||
self._result.uid = self._uid
|
if not self._result.uid:
|
||||||
if self.save_on_success:
|
self._result.uid = self._uid
|
||||||
TaskInfo(
|
if self.save_on_success:
|
||||||
task_name=self.__name__,
|
TaskInfo(
|
||||||
task_description=self.__doc__,
|
task_name=self.__name__,
|
||||||
start_timestamp=self.start,
|
task_description=self.__doc__,
|
||||||
finish_timestamp=default_timer(),
|
start_timestamp=self.start,
|
||||||
finish_time=datetime.now(),
|
finish_timestamp=default_timer(),
|
||||||
result=self._result,
|
finish_time=datetime.now(),
|
||||||
task_call_module=self.__module__,
|
result=self._result,
|
||||||
task_call_func=self.__name__,
|
task_call_module=self.__module__,
|
||||||
task_call_args=args,
|
task_call_func=self.__name__,
|
||||||
task_call_kwargs=kwargs,
|
task_call_args=args,
|
||||||
).save(self.result_timeout_hours)
|
task_call_kwargs=kwargs,
|
||||||
|
).save(self.result_timeout_hours)
|
||||||
return super().after_return(status, retval, task_id, args, kwargs, einfo=einfo)
|
return super().after_return(status, retval, task_id, args, kwargs, einfo=einfo)
|
||||||
|
|
||||||
# pylint: disable=too-many-arguments
|
# pylint: disable=too-many-arguments
|
||||||
def on_failure(self, exc, task_id, args, kwargs, einfo):
|
def on_failure(self, exc, task_id, args, kwargs, einfo):
|
||||||
|
if not self._result:
|
||||||
|
self._result = TaskResult(
|
||||||
|
status=TaskResultStatus.ERROR, messages=[str(exc)]
|
||||||
|
)
|
||||||
if not self._result.uid:
|
if not self._result.uid:
|
||||||
self._result.uid = self._uid
|
self._result.uid = self._uid
|
||||||
TaskInfo(
|
TaskInfo(
|
||||||
|
@ -26,7 +26,7 @@ from sentry_sdk import capture_exception
|
|||||||
from structlog.stdlib import BoundLogger, get_logger
|
from structlog.stdlib import BoundLogger, get_logger
|
||||||
|
|
||||||
from authentik.core.models import USER_ATTRIBUTE_DEBUG
|
from authentik.core.models import USER_ATTRIBUTE_DEBUG
|
||||||
from authentik.events.models import cleanse_dict
|
from authentik.events.models import Event, EventAction, cleanse_dict
|
||||||
from authentik.flows.challenge import (
|
from authentik.flows.challenge import (
|
||||||
AccessDeniedChallenge,
|
AccessDeniedChallenge,
|
||||||
Challenge,
|
Challenge,
|
||||||
@ -52,6 +52,7 @@ from authentik.flows.planner import (
|
|||||||
FlowPlanner,
|
FlowPlanner,
|
||||||
)
|
)
|
||||||
from authentik.lib.sentry import SentryIgnoredException
|
from authentik.lib.sentry import SentryIgnoredException
|
||||||
|
from authentik.lib.utils.errors import exception_to_string
|
||||||
from authentik.lib.utils.reflection import all_subclasses, class_to_path
|
from authentik.lib.utils.reflection import all_subclasses, class_to_path
|
||||||
from authentik.lib.utils.urls import is_url_absolute, redirect_with_qs
|
from authentik.lib.utils.urls import is_url_absolute, redirect_with_qs
|
||||||
from authentik.tenants.models import Tenant
|
from authentik.tenants.models import Tenant
|
||||||
@ -203,6 +204,18 @@ class FlowExecutorView(APIView):
|
|||||||
except InvalidStageError as exc:
|
except InvalidStageError as exc:
|
||||||
return self.stage_invalid(str(exc))
|
return self.stage_invalid(str(exc))
|
||||||
|
|
||||||
|
def handle_exception(self, exc: Exception) -> HttpResponse:
|
||||||
|
"""Handle exception in stage execution"""
|
||||||
|
if settings.DEBUG or settings.TEST:
|
||||||
|
raise exc
|
||||||
|
capture_exception(exc)
|
||||||
|
self._logger.warning(exc)
|
||||||
|
Event.new(
|
||||||
|
action=EventAction.SYSTEM_EXCEPTION,
|
||||||
|
message=exception_to_string(exc),
|
||||||
|
).from_http(self.request)
|
||||||
|
return to_stage_response(self.request, FlowErrorResponse(self.request, exc))
|
||||||
|
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
responses={
|
responses={
|
||||||
200: PolymorphicProxySerializer(
|
200: PolymorphicProxySerializer(
|
||||||
@ -237,11 +250,7 @@ class FlowExecutorView(APIView):
|
|||||||
stage_response = self.current_stage_view.get(request, *args, **kwargs)
|
stage_response = self.current_stage_view.get(request, *args, **kwargs)
|
||||||
return to_stage_response(request, stage_response)
|
return to_stage_response(request, stage_response)
|
||||||
except Exception as exc: # pylint: disable=broad-except
|
except Exception as exc: # pylint: disable=broad-except
|
||||||
if settings.DEBUG or settings.TEST:
|
return self.handle_exception(exc)
|
||||||
raise exc
|
|
||||||
capture_exception(exc)
|
|
||||||
self._logger.warning(exc)
|
|
||||||
return to_stage_response(request, FlowErrorResponse(request, exc))
|
|
||||||
|
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
responses={
|
responses={
|
||||||
@ -278,11 +287,7 @@ class FlowExecutorView(APIView):
|
|||||||
stage_response = self.current_stage_view.post(request, *args, **kwargs)
|
stage_response = self.current_stage_view.post(request, *args, **kwargs)
|
||||||
return to_stage_response(request, stage_response)
|
return to_stage_response(request, stage_response)
|
||||||
except Exception as exc: # pylint: disable=broad-except
|
except Exception as exc: # pylint: disable=broad-except
|
||||||
if settings.DEBUG or settings.TEST:
|
return self.handle_exception(exc)
|
||||||
raise exc
|
|
||||||
capture_exception(exc)
|
|
||||||
self._logger.warning(exc)
|
|
||||||
return to_stage_response(request, FlowErrorResponse(request, exc))
|
|
||||||
|
|
||||||
def _initiate_plan(self) -> FlowPlan:
|
def _initiate_plan(self) -> FlowPlan:
|
||||||
planner = FlowPlanner(self.flow)
|
planner = FlowPlanner(self.flow)
|
||||||
@ -317,13 +322,15 @@ class FlowExecutorView(APIView):
|
|||||||
"""User Successfully passed all stages"""
|
"""User Successfully passed all stages"""
|
||||||
# Since this is wrapped by the ExecutorShell, the next argument is saved in the session
|
# Since this is wrapped by the ExecutorShell, the next argument is saved in the session
|
||||||
# extract the next param before cancel as that cleans it
|
# extract the next param before cancel as that cleans it
|
||||||
next_param = None
|
if self.plan and PLAN_CONTEXT_REDIRECT in self.plan.context:
|
||||||
if self.plan:
|
# The context `redirect` variable can only be set by
|
||||||
next_param = self.plan.context.get(PLAN_CONTEXT_REDIRECT)
|
# an expression policy or authentik itself, so we don't
|
||||||
if not next_param:
|
# check if its an absolute URL or a relative one
|
||||||
next_param = self.request.session.get(SESSION_KEY_GET, {}).get(
|
self.cancel()
|
||||||
NEXT_ARG_NAME, "authentik_core:root-redirect"
|
return redirect(self.plan.context.get(PLAN_CONTEXT_REDIRECT))
|
||||||
)
|
next_param = self.request.session.get(SESSION_KEY_GET, {}).get(
|
||||||
|
NEXT_ARG_NAME, "authentik_core:root-redirect"
|
||||||
|
)
|
||||||
self.cancel()
|
self.cancel()
|
||||||
return to_stage_response(self.request, redirect_with_qs(next_param))
|
return to_stage_response(self.request, redirect_with_qs(next_param))
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ def redirect_with_qs(view: str, get_query_set=None, **kwargs) -> HttpResponse:
|
|||||||
except NoReverseMatch:
|
except NoReverseMatch:
|
||||||
if not is_url_absolute(view):
|
if not is_url_absolute(view):
|
||||||
return redirect(view)
|
return redirect(view)
|
||||||
LOGGER.debug("redirect target is not a valid view", view=view)
|
LOGGER.warning("redirect target is not a valid view", view=view)
|
||||||
raise
|
raise
|
||||||
else:
|
else:
|
||||||
if get_query_set:
|
if get_query_set:
|
||||||
|
@ -28,6 +28,7 @@ from authentik.outposts.models import (
|
|||||||
OutpostServiceConnection,
|
OutpostServiceConnection,
|
||||||
OutpostState,
|
OutpostState,
|
||||||
OutpostType,
|
OutpostType,
|
||||||
|
ServiceConnectionInvalid,
|
||||||
)
|
)
|
||||||
from authentik.providers.ldap.controllers.docker import LDAPDockerController
|
from authentik.providers.ldap.controllers.docker import LDAPDockerController
|
||||||
from authentik.providers.ldap.controllers.kubernetes import LDAPKubernetesController
|
from authentik.providers.ldap.controllers.kubernetes import LDAPKubernetesController
|
||||||
@ -114,7 +115,7 @@ def outpost_controller(
|
|||||||
for log in logs:
|
for log in logs:
|
||||||
LOGGER.debug(log)
|
LOGGER.debug(log)
|
||||||
LOGGER.debug("-----------------Outpost Controller logs end-------------------")
|
LOGGER.debug("-----------------Outpost Controller logs end-------------------")
|
||||||
except ControllerException as exc:
|
except (ControllerException, ServiceConnectionInvalid) as exc:
|
||||||
self.set_status(TaskResult(TaskResultStatus.ERROR).with_error(exc))
|
self.set_status(TaskResult(TaskResultStatus.ERROR).with_error(exc))
|
||||||
else:
|
else:
|
||||||
self.set_status(TaskResult(TaskResultStatus.SUCCESSFUL, logs))
|
self.set_status(TaskResult(TaskResultStatus.SUCCESSFUL, logs))
|
||||||
|
54
authentik/providers/oauth2/tests/test_jwks.py
Normal file
54
authentik/providers/oauth2/tests/test_jwks.py
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
"""JWKS tests"""
|
||||||
|
import json
|
||||||
|
|
||||||
|
from django.test import RequestFactory
|
||||||
|
from django.urls.base import reverse
|
||||||
|
from django.utils.encoding import force_str
|
||||||
|
|
||||||
|
from authentik.core.models import Application
|
||||||
|
from authentik.crypto.models import CertificateKeyPair
|
||||||
|
from authentik.flows.models import Flow
|
||||||
|
from authentik.providers.oauth2.models import OAuth2Provider
|
||||||
|
from authentik.providers.oauth2.tests.utils import OAuthTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class TestJWKS(OAuthTestCase):
|
||||||
|
"""Test JWKS view"""
|
||||||
|
|
||||||
|
def setUp(self) -> None:
|
||||||
|
super().setUp()
|
||||||
|
self.factory = RequestFactory()
|
||||||
|
|
||||||
|
def test_rs256(self):
|
||||||
|
"""Test JWKS request with RS256"""
|
||||||
|
provider = OAuth2Provider.objects.create(
|
||||||
|
name="test",
|
||||||
|
client_id="test",
|
||||||
|
authorization_flow=Flow.objects.first(),
|
||||||
|
redirect_uris="http://local.invalid",
|
||||||
|
rsa_key=CertificateKeyPair.objects.first(),
|
||||||
|
)
|
||||||
|
app = Application.objects.create(name="test", slug="test", provider=provider)
|
||||||
|
response = self.client.get(
|
||||||
|
reverse(
|
||||||
|
"authentik_providers_oauth2:jwks", kwargs={"application_slug": app.slug}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
body = json.loads(force_str(response.content))
|
||||||
|
self.assertEqual(len(body["keys"]), 1)
|
||||||
|
|
||||||
|
def test_hs256(self):
|
||||||
|
"""Test JWKS request with HS256"""
|
||||||
|
provider = OAuth2Provider.objects.create(
|
||||||
|
name="test",
|
||||||
|
client_id="test",
|
||||||
|
authorization_flow=Flow.objects.first(),
|
||||||
|
redirect_uris="http://local.invalid",
|
||||||
|
)
|
||||||
|
app = Application.objects.create(name="test", slug="test", provider=provider)
|
||||||
|
response = self.client.get(
|
||||||
|
reverse(
|
||||||
|
"authentik_providers_oauth2:jwks", kwargs={"application_slug": app.slug}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.assertJSONEqual(force_str(response.content), {})
|
@ -30,7 +30,7 @@ class JWKSView(View):
|
|||||||
|
|
||||||
response_data = {}
|
response_data = {}
|
||||||
|
|
||||||
if provider.jwt_alg == JWTAlgorithms.RS256:
|
if provider.jwt_alg == JWTAlgorithms.RS256 and provider.rsa_key:
|
||||||
public_key: RSAPublicKey = provider.rsa_key.private_key.public_key()
|
public_key: RSAPublicKey = provider.rsa_key.private_key.public_key()
|
||||||
public_numbers = public_key.public_numbers()
|
public_numbers = public_key.public_numbers()
|
||||||
response_data["keys"] = [
|
response_data["keys"] = [
|
||||||
|
@ -60,12 +60,12 @@ class IngressReconciler(KubernetesObjectReconciler[NetworkingV1beta1Ingress]):
|
|||||||
expected_hosts.sort()
|
expected_hosts.sort()
|
||||||
expected_hosts_tls.sort()
|
expected_hosts_tls.sort()
|
||||||
|
|
||||||
have_hosts = [rule.host for rule in reference.spec.rules]
|
have_hosts = [rule.host for rule in current.spec.rules]
|
||||||
have_hosts.sort()
|
have_hosts.sort()
|
||||||
|
|
||||||
have_hosts_tls = []
|
have_hosts_tls = []
|
||||||
for tls_config in reference.spec.tls:
|
for tls_config in current.spec.tls:
|
||||||
if tls_config:
|
if tls_config and tls_config.hosts:
|
||||||
have_hosts_tls += tls_config.hosts
|
have_hosts_tls += tls_config.hosts
|
||||||
have_hosts_tls.sort()
|
have_hosts_tls.sort()
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
from xml.etree.ElementTree import ParseError # nosec
|
from xml.etree.ElementTree import ParseError # nosec
|
||||||
|
|
||||||
from defusedxml.ElementTree import fromstring
|
from defusedxml.ElementTree import fromstring
|
||||||
from django.http.response import HttpResponse
|
from django.http.response import Http404, HttpResponse
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
@ -116,7 +116,10 @@ class SAMLProviderViewSet(UsedByMixin, ModelViewSet):
|
|||||||
def metadata(self, request: Request, pk: int) -> Response:
|
def metadata(self, request: Request, pk: int) -> Response:
|
||||||
"""Return metadata as XML string"""
|
"""Return metadata as XML string"""
|
||||||
# We don't use self.get_object() on purpose as this view is un-authenticated
|
# We don't use self.get_object() on purpose as this view is un-authenticated
|
||||||
provider = get_object_or_404(SAMLProvider, pk=pk)
|
try:
|
||||||
|
provider = get_object_or_404(SAMLProvider, pk=pk)
|
||||||
|
except ValueError:
|
||||||
|
raise Http404
|
||||||
try:
|
try:
|
||||||
metadata = MetadataProcessor(provider, request).build_entity_descriptor()
|
metadata = MetadataProcessor(provider, request).build_entity_descriptor()
|
||||||
if "download" in request._request.GET:
|
if "download" in request._request.GET:
|
||||||
|
@ -163,7 +163,7 @@ class AssertionProcessor:
|
|||||||
provider=self.provider,
|
provider=self.provider,
|
||||||
)
|
)
|
||||||
if value is not None:
|
if value is not None:
|
||||||
name_id.text = value
|
name_id.text = str(value)
|
||||||
return name_id
|
return name_id
|
||||||
except PropertyMappingExpressionException as exc:
|
except PropertyMappingExpressionException as exc:
|
||||||
Event.new(
|
Event.new(
|
||||||
|
@ -134,10 +134,18 @@ class ServiceProviderMetadataParser:
|
|||||||
# For now we'll only look at the first descriptor.
|
# For now we'll only look at the first descriptor.
|
||||||
# Even if multiple descriptors exist, we can only configure one
|
# Even if multiple descriptors exist, we can only configure one
|
||||||
descriptor = sp_sso_descriptors[0]
|
descriptor = sp_sso_descriptors[0]
|
||||||
auth_n_request_signed = (
|
|
||||||
descriptor.attrib["AuthnRequestsSigned"].lower() == "true"
|
auth_n_request_signed = False
|
||||||
)
|
if "AuthnRequestsSigned" in descriptor.attrib:
|
||||||
assertion_signed = descriptor.attrib["WantAssertionsSigned"].lower() == "true"
|
auth_n_request_signed = (
|
||||||
|
descriptor.attrib["AuthnRequestsSigned"].lower() == "true"
|
||||||
|
)
|
||||||
|
|
||||||
|
assertion_signed = False
|
||||||
|
if "WantAssertionsSigned" in descriptor.attrib:
|
||||||
|
assertion_signed = (
|
||||||
|
descriptor.attrib["WantAssertionsSigned"].lower() == "true"
|
||||||
|
)
|
||||||
|
|
||||||
acs_services = descriptor.findall(
|
acs_services = descriptor.findall(
|
||||||
f"{{{NS_SAML_METADATA}}}AssertionConsumerService"
|
f"{{{NS_SAML_METADATA}}}AssertionConsumerService"
|
||||||
|
@ -20,6 +20,7 @@ class TestSAMLProviderAPI(APITestCase):
|
|||||||
|
|
||||||
def test_metadata(self):
|
def test_metadata(self):
|
||||||
"""Test metadata export (normal)"""
|
"""Test metadata export (normal)"""
|
||||||
|
self.client.logout()
|
||||||
provider = SAMLProvider.objects.create(
|
provider = SAMLProvider.objects.create(
|
||||||
name="test",
|
name="test",
|
||||||
authorization_flow=Flow.objects.get(
|
authorization_flow=Flow.objects.get(
|
||||||
@ -34,6 +35,7 @@ class TestSAMLProviderAPI(APITestCase):
|
|||||||
|
|
||||||
def test_metadata_download(self):
|
def test_metadata_download(self):
|
||||||
"""Test metadata export (download)"""
|
"""Test metadata export (download)"""
|
||||||
|
self.client.logout()
|
||||||
provider = SAMLProvider.objects.create(
|
provider = SAMLProvider.objects.create(
|
||||||
name="test",
|
name="test",
|
||||||
authorization_flow=Flow.objects.get(
|
authorization_flow=Flow.objects.get(
|
||||||
@ -50,6 +52,7 @@ class TestSAMLProviderAPI(APITestCase):
|
|||||||
|
|
||||||
def test_metadata_invalid(self):
|
def test_metadata_invalid(self):
|
||||||
"""Test metadata export (invalid)"""
|
"""Test metadata export (invalid)"""
|
||||||
|
self.client.logout()
|
||||||
# Provider without application
|
# Provider without application
|
||||||
provider = SAMLProvider.objects.create(
|
provider = SAMLProvider.objects.create(
|
||||||
name="test",
|
name="test",
|
||||||
@ -61,6 +64,10 @@ class TestSAMLProviderAPI(APITestCase):
|
|||||||
reverse("authentik_api:samlprovider-metadata", kwargs={"pk": provider.pk}),
|
reverse("authentik_api:samlprovider-metadata", kwargs={"pk": provider.pk}),
|
||||||
)
|
)
|
||||||
self.assertEqual(200, response.status_code)
|
self.assertEqual(200, response.status_code)
|
||||||
|
response = self.client.get(
|
||||||
|
reverse("authentik_api:samlprovider-metadata", kwargs={"pk": "abc"}),
|
||||||
|
)
|
||||||
|
self.assertEqual(404, response.status_code)
|
||||||
|
|
||||||
def test_import_success(self):
|
def test_import_success(self):
|
||||||
"""Test metadata import (success case)"""
|
"""Test metadata import (success case)"""
|
||||||
|
@ -105,15 +105,17 @@ class LDAPPasswordChanger:
|
|||||||
if len(user_attributes["sAMAccountName"]) >= 3:
|
if len(user_attributes["sAMAccountName"]) >= 3:
|
||||||
if password.lower() in user_attributes["sAMAccountName"].lower():
|
if password.lower() in user_attributes["sAMAccountName"].lower():
|
||||||
return False
|
return False
|
||||||
display_name_tokens = split(
|
# No display name set, can't check any further
|
||||||
RE_DISPLAYNAME_SEPARATORS, user_attributes["displayName"]
|
if len(user_attributes["displayName"]) < 1:
|
||||||
)
|
return True
|
||||||
for token in display_name_tokens:
|
for display_name in user_attributes["displayName"]:
|
||||||
# Ignore tokens under 3 chars
|
display_name_tokens = split(RE_DISPLAYNAME_SEPARATORS, display_name)
|
||||||
if len(token) < 3:
|
for token in display_name_tokens:
|
||||||
continue
|
# Ignore tokens under 3 chars
|
||||||
if token.lower() in password.lower():
|
if len(token) < 3:
|
||||||
return False
|
continue
|
||||||
|
if token.lower() in password.lower():
|
||||||
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def ad_password_complexity(
|
def ad_password_complexity(
|
||||||
|
10
authentik/sources/plex/settings.py
Normal file
10
authentik/sources/plex/settings.py
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
"""Plex source settings"""
|
||||||
|
from celery.schedules import crontab
|
||||||
|
|
||||||
|
CELERY_BEAT_SCHEDULE = {
|
||||||
|
"check_plex_token": {
|
||||||
|
"task": "authentik.sources.plex.tasks.check_plex_token_all",
|
||||||
|
"schedule": crontab(minute="31", hour="*/3"),
|
||||||
|
"options": {"queue": "authentik_scheduled"},
|
||||||
|
},
|
||||||
|
}
|
43
authentik/sources/plex/tasks.py
Normal file
43
authentik/sources/plex/tasks.py
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
"""Plex tasks"""
|
||||||
|
from requests import RequestException
|
||||||
|
|
||||||
|
from authentik.events.models import Event, EventAction
|
||||||
|
from authentik.events.monitored_tasks import MonitoredTask, TaskResult, TaskResultStatus
|
||||||
|
from authentik.root.celery import CELERY_APP
|
||||||
|
from authentik.sources.plex.models import PlexSource
|
||||||
|
from authentik.sources.plex.plex import PlexAuth
|
||||||
|
|
||||||
|
|
||||||
|
@CELERY_APP.task()
|
||||||
|
def check_plex_token_all():
|
||||||
|
"""Check plex token for all plex sources"""
|
||||||
|
for source in PlexSource.objects.all():
|
||||||
|
check_plex_token.delay(source.slug)
|
||||||
|
|
||||||
|
|
||||||
|
@CELERY_APP.task(bind=True, base=MonitoredTask)
|
||||||
|
def check_plex_token(self: MonitoredTask, source_slug: int):
|
||||||
|
"""Check the validity of a Plex source."""
|
||||||
|
sources = PlexSource.objects.filter(slug=source_slug)
|
||||||
|
if not sources.exists():
|
||||||
|
return
|
||||||
|
source: PlexSource = sources.first()
|
||||||
|
self.set_uid(source.slug)
|
||||||
|
auth = PlexAuth(source, source.plex_token)
|
||||||
|
try:
|
||||||
|
auth.get_user_info()
|
||||||
|
self.set_status(
|
||||||
|
TaskResult(TaskResultStatus.SUCCESSFUL, ["Plex token is valid."])
|
||||||
|
)
|
||||||
|
except RequestException as exc:
|
||||||
|
self.set_status(
|
||||||
|
TaskResult(
|
||||||
|
TaskResultStatus.ERROR,
|
||||||
|
["Plex token is invalid/an error occurred:", str(exc)],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
Event.new(
|
||||||
|
EventAction.CONFIGURATION_ERROR,
|
||||||
|
message=f"Plex token invalid, please re-authenticate source.\n{str(exc)}",
|
||||||
|
source=source,
|
||||||
|
).save()
|
@ -1,10 +1,13 @@
|
|||||||
"""plex Source tests"""
|
"""plex Source tests"""
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
from requests.exceptions import RequestException
|
||||||
from requests_mock import Mocker
|
from requests_mock import Mocker
|
||||||
|
|
||||||
|
from authentik.events.models import Event, EventAction
|
||||||
from authentik.providers.oauth2.generators import generate_client_secret
|
from authentik.providers.oauth2.generators import generate_client_secret
|
||||||
from authentik.sources.plex.models import PlexSource
|
from authentik.sources.plex.models import PlexSource
|
||||||
from authentik.sources.plex.plex import PlexAuth
|
from authentik.sources.plex.plex import PlexAuth
|
||||||
|
from authentik.sources.plex.tasks import check_plex_token_all
|
||||||
|
|
||||||
USER_INFO_RESPONSE = {
|
USER_INFO_RESPONSE = {
|
||||||
"id": 1234123419,
|
"id": 1234123419,
|
||||||
@ -62,3 +65,18 @@ class TestPlexSource(TestCase):
|
|||||||
with Mocker() as mocker:
|
with Mocker() as mocker:
|
||||||
mocker.get("https://plex.tv/api/v2/resources", json=RESOURCES_RESPONSE)
|
mocker.get("https://plex.tv/api/v2/resources", json=RESOURCES_RESPONSE)
|
||||||
self.assertTrue(api.check_server_overlap())
|
self.assertTrue(api.check_server_overlap())
|
||||||
|
|
||||||
|
def test_check_task(self):
|
||||||
|
"""Test token check task"""
|
||||||
|
with Mocker() as mocker:
|
||||||
|
mocker.get("https://plex.tv/api/v2/user", json=USER_INFO_RESPONSE)
|
||||||
|
check_plex_token_all()
|
||||||
|
self.assertFalse(
|
||||||
|
Event.objects.filter(action=EventAction.CONFIGURATION_ERROR).exists()
|
||||||
|
)
|
||||||
|
with Mocker() as mocker:
|
||||||
|
mocker.get("https://plex.tv/api/v2/user", exc=RequestException())
|
||||||
|
check_plex_token_all()
|
||||||
|
self.assertTrue(
|
||||||
|
Event.objects.filter(action=EventAction.CONFIGURATION_ERROR).exists()
|
||||||
|
)
|
||||||
|
@ -67,10 +67,15 @@ class EmailStageView(ChallengeStageView):
|
|||||||
"user": pending_user,
|
"user": pending_user,
|
||||||
"identifier": f"ak-email-stage-{current_stage.name}-{pending_user}",
|
"identifier": f"ak-email-stage-{current_stage.name}-{pending_user}",
|
||||||
}
|
}
|
||||||
tokens = Token.filter_not_expired(**token_filters)
|
# Don't check for validity here, we only care if the token exists
|
||||||
|
tokens = Token.objects.filter(**token_filters)
|
||||||
if not tokens.exists():
|
if not tokens.exists():
|
||||||
return Token.objects.create(expires=now() + valid_delta, **token_filters)
|
return Token.objects.create(expires=now() + valid_delta, **token_filters)
|
||||||
return tokens.first()
|
token = tokens.first()
|
||||||
|
# Check if token is expired and rotate key if so
|
||||||
|
if token.is_expired:
|
||||||
|
token.expire_action()
|
||||||
|
return token
|
||||||
|
|
||||||
def send_email(self):
|
def send_email(self):
|
||||||
"""Helper function that sends the actual email. Implies that you've
|
"""Helper function that sends the actual email. Implies that you've
|
||||||
|
@ -91,7 +91,7 @@ def send_mail(
|
|||||||
messages=["Successfully sent Mail."],
|
messages=["Successfully sent Mail."],
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
except (SMTPException, ConnectionError) as exc:
|
except (SMTPException, ConnectionError, OSError) as exc:
|
||||||
LOGGER.debug("Error sending email, retrying...", exc=exc)
|
LOGGER.debug("Error sending email, retrying...", exc=exc)
|
||||||
self.set_status(TaskResult(TaskResultStatus.ERROR).with_error(exc))
|
self.set_status(TaskResult(TaskResultStatus.ERROR).with_error(exc))
|
||||||
raise exc
|
raise exc
|
||||||
|
@ -1,18 +1,23 @@
|
|||||||
"""invitation stage logic"""
|
"""invitation stage logic"""
|
||||||
from copy import deepcopy
|
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
from deepmerge import always_merger
|
||||||
from django.http import HttpRequest, HttpResponse
|
from django.http import HttpRequest, HttpResponse
|
||||||
|
from django.http.response import HttpResponseBadRequest
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
|
from structlog.stdlib import get_logger
|
||||||
|
|
||||||
|
from authentik.flows.models import in_memory_stage
|
||||||
from authentik.flows.stage import StageView
|
from authentik.flows.stage import StageView
|
||||||
from authentik.flows.views import SESSION_KEY_GET
|
from authentik.flows.views import SESSION_KEY_GET
|
||||||
from authentik.stages.invitation.models import Invitation, InvitationStage
|
from authentik.stages.invitation.models import Invitation, InvitationStage
|
||||||
from authentik.stages.invitation.signals import invitation_used
|
from authentik.stages.invitation.signals import invitation_used
|
||||||
from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT
|
from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT
|
||||||
|
|
||||||
|
LOGGER = get_logger()
|
||||||
INVITATION_TOKEN_KEY = "token" # nosec
|
INVITATION_TOKEN_KEY = "token" # nosec
|
||||||
INVITATION_IN_EFFECT = "invitation_in_effect"
|
INVITATION_IN_EFFECT = "invitation_in_effect"
|
||||||
|
INVITATION = "invitation"
|
||||||
|
|
||||||
|
|
||||||
class InvitationStageView(StageView):
|
class InvitationStageView(StageView):
|
||||||
@ -39,9 +44,37 @@ class InvitationStageView(StageView):
|
|||||||
return self.executor.stage_invalid()
|
return self.executor.stage_invalid()
|
||||||
|
|
||||||
invite: Invitation = get_object_or_404(Invitation, pk=token)
|
invite: Invitation = get_object_or_404(Invitation, pk=token)
|
||||||
self.executor.plan.context[PLAN_CONTEXT_PROMPT] = deepcopy(invite.fixed_data)
|
|
||||||
self.executor.plan.context[INVITATION_IN_EFFECT] = True
|
self.executor.plan.context[INVITATION_IN_EFFECT] = True
|
||||||
|
self.executor.plan.context[INVITATION] = invite
|
||||||
|
|
||||||
|
context = {}
|
||||||
|
always_merger.merge(
|
||||||
|
context, self.executor.plan.context.get(PLAN_CONTEXT_PROMPT, {})
|
||||||
|
)
|
||||||
|
always_merger.merge(context, invite.fixed_data)
|
||||||
|
self.executor.plan.context[PLAN_CONTEXT_PROMPT] = context
|
||||||
|
|
||||||
invitation_used.send(sender=self, request=request, invitation=invite)
|
invitation_used.send(sender=self, request=request, invitation=invite)
|
||||||
if invite.single_use:
|
if invite.single_use:
|
||||||
invite.delete()
|
self.executor.plan.append_stage(in_memory_stage(InvitationFinalStageView))
|
||||||
|
return self.executor.stage_ok()
|
||||||
|
|
||||||
|
|
||||||
|
class InvitationFinalStageView(StageView):
|
||||||
|
"""Final stage which is injected by invitation stage. Deletes
|
||||||
|
the used invitation."""
|
||||||
|
|
||||||
|
def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
||||||
|
"""Call get as this request may be called with post"""
|
||||||
|
return self.get(request, *args, **kwargs)
|
||||||
|
|
||||||
|
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
||||||
|
"""Delete invitation if single_use is active"""
|
||||||
|
invitation: Invitation = self.executor.plan.context.get(INVITATION, None)
|
||||||
|
if not invitation:
|
||||||
|
LOGGER.warning("InvitationFinalStageView stage called without invitation")
|
||||||
|
return HttpResponseBadRequest
|
||||||
|
if not invitation.single_use:
|
||||||
|
return self.executor.stage_ok()
|
||||||
|
invitation.delete()
|
||||||
return self.executor.stage_ok()
|
return self.executor.stage_ok()
|
||||||
|
@ -156,11 +156,13 @@ class TestUserLoginStage(TestCase):
|
|||||||
base_url = reverse(
|
base_url = reverse(
|
||||||
"authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}
|
"authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}
|
||||||
)
|
)
|
||||||
response = self.client.get(base_url)
|
response = self.client.get(base_url, follow=True)
|
||||||
|
|
||||||
session = self.client.session
|
session = self.client.session
|
||||||
plan: FlowPlan = session[SESSION_KEY_PLAN]
|
plan: FlowPlan = session[SESSION_KEY_PLAN]
|
||||||
self.assertEqual(plan.context[PLAN_CONTEXT_PROMPT], data)
|
self.assertEqual(
|
||||||
|
plan.context[PLAN_CONTEXT_PROMPT], data | plan.context[PLAN_CONTEXT_PROMPT]
|
||||||
|
)
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertJSONEqual(
|
self.assertJSONEqual(
|
||||||
|
@ -21,7 +21,7 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
- internal
|
- internal
|
||||||
server:
|
server:
|
||||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2021.7.1}
|
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2021.7.3}
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
command: server
|
command: server
|
||||||
environment:
|
environment:
|
||||||
@ -44,7 +44,7 @@ services:
|
|||||||
- "0.0.0.0:9000:9000"
|
- "0.0.0.0:9000:9000"
|
||||||
- "0.0.0.0:9443:9443"
|
- "0.0.0.0:9443:9443"
|
||||||
worker:
|
worker:
|
||||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2021.7.1}
|
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2021.7.3}
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
command: worker
|
command: worker
|
||||||
networks:
|
networks:
|
||||||
|
@ -17,4 +17,4 @@ func OutpostUserAgent() string {
|
|||||||
return fmt.Sprintf("authentik-outpost@%s (%s)", VERSION, BUILD())
|
return fmt.Sprintf("authentik-outpost@%s (%s)", VERSION, BUILD())
|
||||||
}
|
}
|
||||||
|
|
||||||
const VERSION = "2021.7.1"
|
const VERSION = "2021.7.3"
|
||||||
|
@ -116,7 +116,7 @@ func (ac *APIController) startWSHealth() {
|
|||||||
|
|
||||||
func (ac *APIController) startIntervalUpdater() {
|
func (ac *APIController) startIntervalUpdater() {
|
||||||
logger := ac.logger.WithField("loop", "interval-updater")
|
logger := ac.logger.WithField("loop", "interval-updater")
|
||||||
ticker := time.NewTicker(time.Second * 150)
|
ticker := time.NewTicker(5 * time.Minute)
|
||||||
for ; true; <-ticker.C {
|
for ; true; <-ticker.C {
|
||||||
err := ac.Server.Refresh()
|
err := ac.Server.Refresh()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -75,7 +75,7 @@ func (pi *ProviderInstance) Bind(username string, req BindRequest) (ldap.LDAPRes
|
|||||||
pi.boundUsersMutex.Lock()
|
pi.boundUsersMutex.Lock()
|
||||||
cs := pi.SearchAccessCheck(userInfo.User)
|
cs := pi.SearchAccessCheck(userInfo.User)
|
||||||
pi.boundUsers[req.BindDN] = UserFlags{
|
pi.boundUsers[req.BindDN] = UserFlags{
|
||||||
UserInfo: userInfo.User,
|
UserPk: userInfo.User.Pk,
|
||||||
CanSearch: cs != nil,
|
CanSearch: cs != nil,
|
||||||
}
|
}
|
||||||
if pi.boundUsers[req.BindDN].CanSearch {
|
if pi.boundUsers[req.BindDN].CanSearch {
|
||||||
@ -88,7 +88,7 @@ func (pi *ProviderInstance) Bind(username string, req BindRequest) (ldap.LDAPRes
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SearchAccessCheck Check if the current user is allowed to search
|
// SearchAccessCheck Check if the current user is allowed to search
|
||||||
func (pi *ProviderInstance) SearchAccessCheck(user api.User) *string {
|
func (pi *ProviderInstance) SearchAccessCheck(user api.UserSelf) *string {
|
||||||
for _, group := range user.Groups {
|
for _, group := range user.Groups {
|
||||||
for _, allowedGroup := range pi.searchAllowedGroups {
|
for _, allowedGroup := range pi.searchAllowedGroups {
|
||||||
pi.log.WithField("userGroup", group.Pk).WithField("allowedGroup", allowedGroup).Trace("Checking search access")
|
pi.log.WithField("userGroup", group.Pk).WithField("allowedGroup", allowedGroup).Trace("Checking search access")
|
||||||
|
@ -11,9 +11,17 @@ import (
|
|||||||
"goauthentik.io/api"
|
"goauthentik.io/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (pi *ProviderInstance) SearchMe(user api.User) (ldap.ServerSearchResult, error) {
|
func (pi *ProviderInstance) SearchMe(req SearchRequest, f UserFlags) (ldap.ServerSearchResult, error) {
|
||||||
|
if f.UserInfo == nil {
|
||||||
|
u, _, err := pi.s.ac.Client.CoreApi.CoreUsersRetrieve(req.ctx, f.UserInfo.Pk).Execute()
|
||||||
|
if err != nil {
|
||||||
|
req.log.WithError(err).Warning("Failed to get user info")
|
||||||
|
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, fmt.Errorf("Failed to get userinfo")
|
||||||
|
}
|
||||||
|
f.UserInfo = &u
|
||||||
|
}
|
||||||
entries := make([]*ldap.Entry, 1)
|
entries := make([]*ldap.Entry, 1)
|
||||||
entries[0] = pi.UserEntry(user)
|
entries[0] = pi.UserEntry(*f.UserInfo)
|
||||||
return ldap.ServerSearchResult{Entries: entries, Referrals: []string{}, Controls: []ldap.Control{}, ResultCode: ldap.LDAPResultSuccess}, nil
|
return ldap.ServerSearchResult{Entries: entries, Referrals: []string{}, Controls: []ldap.Control{}, ResultCode: ldap.LDAPResultSuccess}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,7 +50,7 @@ func (pi *ProviderInstance) Search(req SearchRequest) (ldap.ServerSearchResult,
|
|||||||
}
|
}
|
||||||
if !flags.CanSearch {
|
if !flags.CanSearch {
|
||||||
pi.log.Debug("User can't search, showing info about user")
|
pi.log.Debug("User can't search, showing info about user")
|
||||||
return pi.SearchMe(flags.UserInfo)
|
return pi.SearchMe(req, flags)
|
||||||
}
|
}
|
||||||
accsp.Finish()
|
accsp.Finish()
|
||||||
|
|
||||||
|
@ -39,7 +39,8 @@ type ProviderInstance struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type UserFlags struct {
|
type UserFlags struct {
|
||||||
UserInfo api.User
|
UserInfo *api.User
|
||||||
|
UserPk int32
|
||||||
CanSearch bool
|
CanSearch bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,7 +51,8 @@ logconfig_dict = {
|
|||||||
if SERVICE_HOST_ENV_NAME in os.environ:
|
if SERVICE_HOST_ENV_NAME in os.environ:
|
||||||
workers = 2
|
workers = 2
|
||||||
else:
|
else:
|
||||||
workers = int(os.environ.get("WORKERS", cpu_count() * 2 + 1))
|
default_workers = max(cpu_count() * 0.25, 1) + 1 # Minimum of 2 workers
|
||||||
|
workers = int(os.environ.get("WORKERS", default_workers))
|
||||||
threads = 4
|
threads = 4
|
||||||
|
|
||||||
warnings.simplefilter("once")
|
warnings.simplefilter("once")
|
||||||
|
114
schema.yml
114
schema.yml
@ -1,7 +1,7 @@
|
|||||||
openapi: 3.0.3
|
openapi: 3.0.3
|
||||||
info:
|
info:
|
||||||
title: authentik
|
title: authentik
|
||||||
version: 2021.7.1
|
version: 2021.7.3
|
||||||
description: Making authentication simple.
|
description: Making authentication simple.
|
||||||
contact:
|
contact:
|
||||||
email: hello@beryju.org
|
email: hello@beryju.org
|
||||||
@ -3185,6 +3185,38 @@ paths:
|
|||||||
$ref: '#/components/schemas/ValidationError'
|
$ref: '#/components/schemas/ValidationError'
|
||||||
'403':
|
'403':
|
||||||
$ref: '#/components/schemas/GenericError'
|
$ref: '#/components/schemas/GenericError'
|
||||||
|
/api/v2beta/core/users/update_self/:
|
||||||
|
put:
|
||||||
|
operationId: core_users_update_self_update
|
||||||
|
description: Allow users to change information on their own profile
|
||||||
|
tags:
|
||||||
|
- core
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/UserSelfRequest'
|
||||||
|
application/x-www-form-urlencoded:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/UserSelfRequest'
|
||||||
|
multipart/form-data:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/UserSelfRequest'
|
||||||
|
required: true
|
||||||
|
security:
|
||||||
|
- authentik: []
|
||||||
|
- cookieAuth: []
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/SessionUser'
|
||||||
|
description: ''
|
||||||
|
'400':
|
||||||
|
$ref: '#/components/schemas/ValidationError'
|
||||||
|
'403':
|
||||||
|
$ref: '#/components/schemas/GenericError'
|
||||||
/api/v2beta/crypto/certificatekeypairs/:
|
/api/v2beta/crypto/certificatekeypairs/:
|
||||||
get:
|
get:
|
||||||
operationId: crypto_certificatekeypairs_list
|
operationId: crypto_certificatekeypairs_list
|
||||||
@ -27577,9 +27609,9 @@ components:
|
|||||||
and, if this user is being impersonated, the original user in the `original` property.
|
and, if this user is being impersonated, the original user in the `original` property.
|
||||||
properties:
|
properties:
|
||||||
user:
|
user:
|
||||||
$ref: '#/components/schemas/User'
|
$ref: '#/components/schemas/UserSelf'
|
||||||
original:
|
original:
|
||||||
$ref: '#/components/schemas/User'
|
$ref: '#/components/schemas/UserSelf'
|
||||||
required:
|
required:
|
||||||
- user
|
- user
|
||||||
SetIconRequest:
|
SetIconRequest:
|
||||||
@ -28478,6 +28510,82 @@ components:
|
|||||||
required:
|
required:
|
||||||
- name
|
- name
|
||||||
- username
|
- username
|
||||||
|
UserSelf:
|
||||||
|
type: object
|
||||||
|
description: |-
|
||||||
|
User Serializer for information a user can retrieve about themselves and
|
||||||
|
update about themselves
|
||||||
|
properties:
|
||||||
|
pk:
|
||||||
|
type: integer
|
||||||
|
readOnly: true
|
||||||
|
title: ID
|
||||||
|
username:
|
||||||
|
type: string
|
||||||
|
description: Required. 150 characters or fewer. Letters, digits and @/./+/-/_
|
||||||
|
only.
|
||||||
|
pattern: ^[\w.@+-]+$
|
||||||
|
maxLength: 150
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
description: User's display name.
|
||||||
|
is_active:
|
||||||
|
type: boolean
|
||||||
|
readOnly: true
|
||||||
|
title: Active
|
||||||
|
description: Designates whether this user should be treated as active. Unselect
|
||||||
|
this instead of deleting accounts.
|
||||||
|
is_superuser:
|
||||||
|
type: boolean
|
||||||
|
readOnly: true
|
||||||
|
groups:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/Group'
|
||||||
|
readOnly: true
|
||||||
|
email:
|
||||||
|
type: string
|
||||||
|
format: email
|
||||||
|
title: Email address
|
||||||
|
maxLength: 254
|
||||||
|
avatar:
|
||||||
|
type: string
|
||||||
|
readOnly: true
|
||||||
|
uid:
|
||||||
|
type: string
|
||||||
|
readOnly: true
|
||||||
|
required:
|
||||||
|
- avatar
|
||||||
|
- groups
|
||||||
|
- is_active
|
||||||
|
- is_superuser
|
||||||
|
- name
|
||||||
|
- pk
|
||||||
|
- uid
|
||||||
|
- username
|
||||||
|
UserSelfRequest:
|
||||||
|
type: object
|
||||||
|
description: |-
|
||||||
|
User Serializer for information a user can retrieve about themselves and
|
||||||
|
update about themselves
|
||||||
|
properties:
|
||||||
|
username:
|
||||||
|
type: string
|
||||||
|
description: Required. 150 characters or fewer. Letters, digits and @/./+/-/_
|
||||||
|
only.
|
||||||
|
pattern: ^[\w.@+-]+$
|
||||||
|
maxLength: 150
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
description: User's display name.
|
||||||
|
email:
|
||||||
|
type: string
|
||||||
|
format: email
|
||||||
|
title: Email address
|
||||||
|
maxLength: 254
|
||||||
|
required:
|
||||||
|
- name
|
||||||
|
- username
|
||||||
UserSetting:
|
UserSetting:
|
||||||
type: object
|
type: object
|
||||||
description: Serializer for User settings for stages and sources
|
description: Serializer for User settings for stages and sources
|
||||||
|
@ -2,7 +2,7 @@ version: '3.7'
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
chrome:
|
chrome:
|
||||||
image: selenium/standalone-chrome:3.141
|
image: selenium/standalone-chrome:3.141.59-20210713
|
||||||
volumes:
|
volumes:
|
||||||
- /dev/shm:/dev/shm
|
- /dev/shm:/dev/shm
|
||||||
network_mode: host
|
network_mode: host
|
||||||
|
@ -2,7 +2,7 @@ version: '3.7'
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
chrome:
|
chrome:
|
||||||
image: selenium/standalone-chrome-debug:3.141
|
image: selenium/standalone-chrome-debug:3.141.59-20210713
|
||||||
volumes:
|
volumes:
|
||||||
- /dev/shm:/dev/shm
|
- /dev/shm:/dev/shm
|
||||||
network_mode: host
|
network_mode: host
|
||||||
|
@ -1,20 +1,36 @@
|
|||||||
"""Test Controllers"""
|
"""Test Controllers"""
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
from structlog.stdlib import get_logger
|
||||||
|
|
||||||
from authentik.flows.models import Flow
|
from authentik.flows.models import Flow
|
||||||
|
from authentik.outposts.controllers.kubernetes import KubernetesController
|
||||||
from authentik.outposts.models import KubernetesServiceConnection, Outpost, OutpostType
|
from authentik.outposts.models import KubernetesServiceConnection, Outpost, OutpostType
|
||||||
from authentik.outposts.tasks import outpost_local_connection
|
from authentik.outposts.tasks import outpost_local_connection
|
||||||
|
from authentik.providers.proxy.controllers.k8s.ingress import IngressReconciler
|
||||||
from authentik.providers.proxy.controllers.kubernetes import ProxyKubernetesController
|
from authentik.providers.proxy.controllers.kubernetes import ProxyKubernetesController
|
||||||
from authentik.providers.proxy.models import ProxyProvider
|
from authentik.providers.proxy.models import ProxyMode, ProxyProvider
|
||||||
|
|
||||||
|
LOGGER = get_logger()
|
||||||
|
|
||||||
|
|
||||||
class TestProxyKubernetes(TestCase):
|
class TestProxyKubernetes(TestCase):
|
||||||
"""Test Controllers"""
|
"""Test Controllers"""
|
||||||
|
|
||||||
|
controller: Optional[KubernetesController]
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
# Ensure that local connection have been created
|
# Ensure that local connection have been created
|
||||||
outpost_local_connection()
|
outpost_local_connection()
|
||||||
|
self.controller = None
|
||||||
|
|
||||||
|
def tearDown(self) -> None:
|
||||||
|
if self.controller:
|
||||||
|
for log in self.controller.down_with_logs():
|
||||||
|
LOGGER.info(log)
|
||||||
|
return super().tearDown()
|
||||||
|
|
||||||
def test_kubernetes_controller_static(self):
|
def test_kubernetes_controller_static(self):
|
||||||
"""Test Kubernetes Controller"""
|
"""Test Kubernetes Controller"""
|
||||||
@ -33,18 +49,26 @@ class TestProxyKubernetes(TestCase):
|
|||||||
outpost.providers.add(provider)
|
outpost.providers.add(provider)
|
||||||
outpost.save()
|
outpost.save()
|
||||||
|
|
||||||
controller = ProxyKubernetesController(outpost, service_connection)
|
self.controller = ProxyKubernetesController(outpost, service_connection)
|
||||||
manifest = controller.get_static_deployment()
|
manifest = self.controller.get_static_deployment()
|
||||||
self.assertEqual(len(list(yaml.load_all(manifest, Loader=yaml.SafeLoader))), 4)
|
self.assertEqual(len(list(yaml.load_all(manifest, Loader=yaml.SafeLoader))), 4)
|
||||||
|
|
||||||
def test_kubernetes_controller_deploy(self):
|
def test_kubernetes_controller_ingress(self):
|
||||||
"""Test Kubernetes Controller"""
|
"""Test Kubernetes Controller's Ingress"""
|
||||||
provider: ProxyProvider = ProxyProvider.objects.create(
|
provider: ProxyProvider = ProxyProvider.objects.create(
|
||||||
name="test",
|
name="test",
|
||||||
internal_host="http://localhost",
|
internal_host="http://localhost",
|
||||||
external_host="http://localhost",
|
external_host="https://localhost",
|
||||||
authorization_flow=Flow.objects.first(),
|
authorization_flow=Flow.objects.first(),
|
||||||
)
|
)
|
||||||
|
provider2: ProxyProvider = ProxyProvider.objects.create(
|
||||||
|
name="test2",
|
||||||
|
internal_host="http://otherhost",
|
||||||
|
external_host="https://otherhost",
|
||||||
|
mode=ProxyMode.FORWARD_SINGLE,
|
||||||
|
authorization_flow=Flow.objects.first(),
|
||||||
|
)
|
||||||
|
|
||||||
service_connection = KubernetesServiceConnection.objects.first()
|
service_connection = KubernetesServiceConnection.objects.first()
|
||||||
outpost: Outpost = Outpost.objects.create(
|
outpost: Outpost = Outpost.objects.create(
|
||||||
name="test",
|
name="test",
|
||||||
@ -52,8 +76,19 @@ class TestProxyKubernetes(TestCase):
|
|||||||
service_connection=service_connection,
|
service_connection=service_connection,
|
||||||
)
|
)
|
||||||
outpost.providers.add(provider)
|
outpost.providers.add(provider)
|
||||||
outpost.save()
|
|
||||||
|
|
||||||
controller = ProxyKubernetesController(outpost, service_connection)
|
self.controller = ProxyKubernetesController(outpost, service_connection)
|
||||||
controller.up()
|
|
||||||
controller.down()
|
ingress_rec = IngressReconciler(self.controller)
|
||||||
|
ingress = ingress_rec.retrieve()
|
||||||
|
|
||||||
|
self.assertEqual(len(ingress.spec.rules), 1)
|
||||||
|
self.assertEqual(ingress.spec.rules[0].host, "localhost")
|
||||||
|
|
||||||
|
# add provider, check again
|
||||||
|
outpost.providers.add(provider2)
|
||||||
|
ingress = ingress_rec.retrieve()
|
||||||
|
|
||||||
|
self.assertEqual(len(ingress.spec.rules), 2)
|
||||||
|
self.assertEqual(ingress.spec.rules[0].host, "localhost")
|
||||||
|
self.assertEqual(ingress.spec.rules[1].host, "otherhost")
|
||||||
|
@ -33,15 +33,7 @@ export function configureSentry(canDoPpi: boolean = false): Promise<Config> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (hint.originalException instanceof Response) {
|
if (hint.originalException instanceof Response) {
|
||||||
const response = hint.originalException as Response;
|
return null;
|
||||||
// We only care about server errors
|
|
||||||
if (response.status < 500) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
// Need to clone the response, otherwise the .text() and .json() can't be re-used
|
|
||||||
const resCopy = response.clone();
|
|
||||||
const body = await resCopy.json();
|
|
||||||
event.message = `${response.status} ${response.url}: ${JSON.stringify(body)}`
|
|
||||||
}
|
}
|
||||||
if (event.exception) {
|
if (event.exception) {
|
||||||
me().then(user => {
|
me().then(user => {
|
||||||
|
@ -3,7 +3,7 @@ export const SUCCESS_CLASS = "pf-m-success";
|
|||||||
export const ERROR_CLASS = "pf-m-danger";
|
export const ERROR_CLASS = "pf-m-danger";
|
||||||
export const PROGRESS_CLASS = "pf-m-in-progress";
|
export const PROGRESS_CLASS = "pf-m-in-progress";
|
||||||
export const CURRENT_CLASS = "pf-m-current";
|
export const CURRENT_CLASS = "pf-m-current";
|
||||||
export const VERSION = "2021.7.1";
|
export const VERSION = "2021.7.3";
|
||||||
export const PAGE_SIZE = 20;
|
export const PAGE_SIZE = 20;
|
||||||
export const TITLE_DEFAULT = "authentik";
|
export const TITLE_DEFAULT = "authentik";
|
||||||
export const ROUTE_SEPARATOR = ";";
|
export const ROUTE_SEPARATOR = ";";
|
||||||
|
@ -3,18 +3,20 @@ import { EVENT_REFRESH } from "../../constants";
|
|||||||
import { Form } from "./Form";
|
import { Form } from "./Form";
|
||||||
|
|
||||||
export abstract class ModelForm<T, PKT extends string | number> extends Form<T> {
|
export abstract class ModelForm<T, PKT extends string | number> extends Form<T> {
|
||||||
|
viewportCheck = true;
|
||||||
|
|
||||||
abstract loadInstance(pk: PKT): Promise<T>;
|
abstract loadInstance(pk: PKT): Promise<T>;
|
||||||
|
|
||||||
@property({attribute: false})
|
@property({attribute: false})
|
||||||
set instancePk(value: PKT) {
|
set instancePk(value: PKT) {
|
||||||
this._instancePk = value;
|
this._instancePk = value;
|
||||||
if (this.isInViewport) {
|
if (this.viewportCheck && !this.isInViewport) {
|
||||||
this.loadInstance(value).then(instance => {
|
return;
|
||||||
this.instance = instance;
|
|
||||||
this.requestUpdate();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
this.loadInstance(value).then((instance) => {
|
||||||
|
this.instance = instance;
|
||||||
|
this.requestUpdate();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private _instancePk?: PKT;
|
private _instancePk?: PKT;
|
||||||
|
@ -1077,7 +1077,7 @@ msgstr "Delete Refresh Code"
|
|||||||
msgid "Delete Session"
|
msgid "Delete Session"
|
||||||
msgstr "Delete Session"
|
msgstr "Delete Session"
|
||||||
|
|
||||||
#: src/pages/user-settings/UserDetailsPage.ts
|
#: src/pages/user-settings/UserSelfForm.ts
|
||||||
msgid "Delete account"
|
msgid "Delete account"
|
||||||
msgstr "Delete account"
|
msgstr "Delete account"
|
||||||
|
|
||||||
@ -1297,7 +1297,7 @@ msgstr "Either no applications are defined, or you don't have access to any."
|
|||||||
#: src/flows/stages/identification/IdentificationStage.ts
|
#: src/flows/stages/identification/IdentificationStage.ts
|
||||||
#: src/pages/events/TransportForm.ts
|
#: src/pages/events/TransportForm.ts
|
||||||
#: src/pages/stages/identification/IdentificationStageForm.ts
|
#: src/pages/stages/identification/IdentificationStageForm.ts
|
||||||
#: src/pages/user-settings/UserDetailsPage.ts
|
#: src/pages/user-settings/UserSelfForm.ts
|
||||||
#: src/pages/users/UserForm.ts
|
#: src/pages/users/UserForm.ts
|
||||||
#: src/pages/users/UserViewPage.ts
|
#: src/pages/users/UserViewPage.ts
|
||||||
msgid "Email"
|
msgid "Email"
|
||||||
@ -1434,7 +1434,6 @@ msgstr "Everything is ok."
|
|||||||
msgid "Exception"
|
msgid "Exception"
|
||||||
msgstr "Exception"
|
msgstr "Exception"
|
||||||
|
|
||||||
#: src/pages/flows/FlowListPage.ts
|
|
||||||
#: src/pages/flows/FlowViewPage.ts
|
#: src/pages/flows/FlowViewPage.ts
|
||||||
msgid "Execute"
|
msgid "Execute"
|
||||||
msgstr "Execute"
|
msgstr "Execute"
|
||||||
@ -1487,7 +1486,6 @@ msgstr "Expiry date"
|
|||||||
msgid "Explicit Consent"
|
msgid "Explicit Consent"
|
||||||
msgstr "Explicit Consent"
|
msgstr "Explicit Consent"
|
||||||
|
|
||||||
#: src/pages/flows/FlowListPage.ts
|
|
||||||
#: src/pages/flows/FlowViewPage.ts
|
#: src/pages/flows/FlowViewPage.ts
|
||||||
msgid "Export"
|
msgid "Export"
|
||||||
msgstr "Export"
|
msgstr "Export"
|
||||||
@ -2080,6 +2078,10 @@ msgstr "Link to a user with identical email address. Can have security implicati
|
|||||||
msgid "Link to a user with identical username address. Can have security implications when a username is used with another source."
|
msgid "Link to a user with identical username address. Can have security implications when a username is used with another source."
|
||||||
msgstr "Link to a user with identical username address. Can have security implications when a username is used with another source."
|
msgstr "Link to a user with identical username address. Can have security implications when a username is used with another source."
|
||||||
|
|
||||||
|
#: src/pages/stages/invitation/InvitationListLink.ts
|
||||||
|
msgid "Link to use the invitation."
|
||||||
|
msgstr "Link to use the invitation."
|
||||||
|
|
||||||
#: src/pages/sources/oauth/OAuthSourceForm.ts
|
#: src/pages/sources/oauth/OAuthSourceForm.ts
|
||||||
#: src/pages/sources/plex/PlexSourceForm.ts
|
#: src/pages/sources/plex/PlexSourceForm.ts
|
||||||
msgid "Link users on unique identifier"
|
msgid "Link users on unique identifier"
|
||||||
@ -2109,7 +2111,7 @@ msgstr "Load servers"
|
|||||||
#: src/flows/stages/prompt/PromptStage.ts
|
#: src/flows/stages/prompt/PromptStage.ts
|
||||||
#: src/pages/applications/ApplicationViewPage.ts
|
#: src/pages/applications/ApplicationViewPage.ts
|
||||||
#: src/pages/applications/ApplicationViewPage.ts
|
#: src/pages/applications/ApplicationViewPage.ts
|
||||||
#: src/pages/user-settings/UserDetailsPage.ts
|
#: src/pages/user-settings/UserSelfForm.ts
|
||||||
#: src/utils.ts
|
#: src/utils.ts
|
||||||
msgid "Loading"
|
msgid "Loading"
|
||||||
msgstr "Loading"
|
msgstr "Loading"
|
||||||
@ -2168,6 +2170,7 @@ msgstr "Loading"
|
|||||||
#: src/pages/stages/identification/IdentificationStageForm.ts
|
#: src/pages/stages/identification/IdentificationStageForm.ts
|
||||||
#: src/pages/stages/identification/IdentificationStageForm.ts
|
#: src/pages/stages/identification/IdentificationStageForm.ts
|
||||||
#: src/pages/stages/identification/IdentificationStageForm.ts
|
#: src/pages/stages/identification/IdentificationStageForm.ts
|
||||||
|
#: src/pages/stages/invitation/InvitationListLink.ts
|
||||||
#: src/pages/stages/password/PasswordStageForm.ts
|
#: src/pages/stages/password/PasswordStageForm.ts
|
||||||
#: src/pages/stages/prompt/PromptStageForm.ts
|
#: src/pages/stages/prompt/PromptStageForm.ts
|
||||||
#: src/pages/stages/prompt/PromptStageForm.ts
|
#: src/pages/stages/prompt/PromptStageForm.ts
|
||||||
@ -2397,7 +2400,7 @@ msgstr "My Applications"
|
|||||||
#: src/pages/stages/user_login/UserLoginStageForm.ts
|
#: src/pages/stages/user_login/UserLoginStageForm.ts
|
||||||
#: src/pages/stages/user_logout/UserLogoutStageForm.ts
|
#: src/pages/stages/user_logout/UserLogoutStageForm.ts
|
||||||
#: src/pages/stages/user_write/UserWriteStageForm.ts
|
#: src/pages/stages/user_write/UserWriteStageForm.ts
|
||||||
#: src/pages/user-settings/UserDetailsPage.ts
|
#: src/pages/user-settings/UserSelfForm.ts
|
||||||
#: src/pages/users/UserForm.ts
|
#: src/pages/users/UserForm.ts
|
||||||
#: src/pages/users/UserListPage.ts
|
#: src/pages/users/UserListPage.ts
|
||||||
#: src/pages/users/UserViewPage.ts
|
#: src/pages/users/UserViewPage.ts
|
||||||
@ -3010,6 +3013,10 @@ msgstr "RSA-SHA384"
|
|||||||
msgid "RSA-SHA512"
|
msgid "RSA-SHA512"
|
||||||
msgstr "RSA-SHA512"
|
msgstr "RSA-SHA512"
|
||||||
|
|
||||||
|
#: src/pages/sources/plex/PlexSourceForm.ts
|
||||||
|
msgid "Re-authenticate with plex"
|
||||||
|
msgstr "Re-authenticate with plex"
|
||||||
|
|
||||||
#: src/pages/flows/StageBindingForm.ts
|
#: src/pages/flows/StageBindingForm.ts
|
||||||
msgid "Re-evaluate policies"
|
msgid "Re-evaluate policies"
|
||||||
msgstr "Re-evaluate policies"
|
msgstr "Re-evaluate policies"
|
||||||
@ -3109,7 +3116,7 @@ msgstr "Request token URL"
|
|||||||
msgid "Required"
|
msgid "Required"
|
||||||
msgstr "Required"
|
msgstr "Required"
|
||||||
|
|
||||||
#: src/pages/user-settings/UserDetailsPage.ts
|
#: src/pages/user-settings/UserSelfForm.ts
|
||||||
#: src/pages/users/UserForm.ts
|
#: src/pages/users/UserForm.ts
|
||||||
msgid "Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only."
|
msgid "Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only."
|
||||||
msgstr "Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only."
|
msgstr "Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only."
|
||||||
@ -3263,6 +3270,10 @@ msgstr "Select a provider that this application should use. Alternatively, creat
|
|||||||
msgid "Select all rows"
|
msgid "Select all rows"
|
||||||
msgstr "Select all rows"
|
msgstr "Select all rows"
|
||||||
|
|
||||||
|
#: src/pages/stages/invitation/InvitationListLink.ts
|
||||||
|
msgid "Select an enrollment flow"
|
||||||
|
msgstr "Select an enrollment flow"
|
||||||
|
|
||||||
#: src/flows/stages/authenticator_validate/AuthenticatorValidateStage.ts
|
#: src/flows/stages/authenticator_validate/AuthenticatorValidateStage.ts
|
||||||
msgid "Select an identification method."
|
msgid "Select an identification method."
|
||||||
msgstr "Select an identification method."
|
msgstr "Select an identification method."
|
||||||
@ -3752,7 +3763,7 @@ msgstr "Successfully updated binding."
|
|||||||
msgid "Successfully updated certificate-key pair."
|
msgid "Successfully updated certificate-key pair."
|
||||||
msgstr "Successfully updated certificate-key pair."
|
msgstr "Successfully updated certificate-key pair."
|
||||||
|
|
||||||
#: src/pages/user-settings/UserDetailsPage.ts
|
#: src/pages/user-settings/UserSelfForm.ts
|
||||||
msgid "Successfully updated details."
|
msgid "Successfully updated details."
|
||||||
msgstr "Successfully updated details."
|
msgstr "Successfully updated details."
|
||||||
|
|
||||||
@ -4284,7 +4295,7 @@ msgstr "Up-to-date!"
|
|||||||
#: src/pages/stages/StageListPage.ts
|
#: src/pages/stages/StageListPage.ts
|
||||||
#: src/pages/stages/prompt/PromptListPage.ts
|
#: src/pages/stages/prompt/PromptListPage.ts
|
||||||
#: src/pages/tenants/TenantListPage.ts
|
#: src/pages/tenants/TenantListPage.ts
|
||||||
#: src/pages/user-settings/UserDetailsPage.ts
|
#: src/pages/user-settings/UserSelfForm.ts
|
||||||
#: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
|
#: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
|
||||||
#: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
|
#: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
|
||||||
#: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
|
#: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
|
||||||
@ -4387,7 +4398,7 @@ msgstr "Update User"
|
|||||||
msgid "Update available"
|
msgid "Update available"
|
||||||
msgstr "Update available"
|
msgstr "Update available"
|
||||||
|
|
||||||
#: src/pages/user-settings/UserDetailsPage.ts
|
#: src/pages/user-settings/UserSettingsPage.ts
|
||||||
msgid "Update details"
|
msgid "Update details"
|
||||||
msgstr "Update details"
|
msgstr "Update details"
|
||||||
|
|
||||||
@ -4520,7 +4531,7 @@ msgstr "User {0}"
|
|||||||
msgid "User's avatar"
|
msgid "User's avatar"
|
||||||
msgstr "User's avatar"
|
msgstr "User's avatar"
|
||||||
|
|
||||||
#: src/pages/user-settings/UserDetailsPage.ts
|
#: src/pages/user-settings/UserSelfForm.ts
|
||||||
#: src/pages/users/UserForm.ts
|
#: src/pages/users/UserForm.ts
|
||||||
msgid "User's display name."
|
msgid "User's display name."
|
||||||
msgstr "User's display name."
|
msgstr "User's display name."
|
||||||
@ -4540,7 +4551,7 @@ msgstr "Userinfo URL"
|
|||||||
#: src/flows/stages/identification/IdentificationStage.ts
|
#: src/flows/stages/identification/IdentificationStage.ts
|
||||||
#: src/pages/policies/reputation/UserReputationListPage.ts
|
#: src/pages/policies/reputation/UserReputationListPage.ts
|
||||||
#: src/pages/stages/identification/IdentificationStageForm.ts
|
#: src/pages/stages/identification/IdentificationStageForm.ts
|
||||||
#: src/pages/user-settings/UserDetailsPage.ts
|
#: src/pages/user-settings/UserSelfForm.ts
|
||||||
#: src/pages/users/UserForm.ts
|
#: src/pages/users/UserForm.ts
|
||||||
#: src/pages/users/UserViewPage.ts
|
#: src/pages/users/UserViewPage.ts
|
||||||
msgid "Username"
|
msgid "Username"
|
||||||
|
@ -1071,7 +1071,7 @@ msgstr ""
|
|||||||
msgid "Delete Session"
|
msgid "Delete Session"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/user-settings/UserDetailsPage.ts
|
#: src/pages/user-settings/UserSelfForm.ts
|
||||||
msgid "Delete account"
|
msgid "Delete account"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -1289,7 +1289,7 @@ msgstr ""
|
|||||||
#: src/flows/stages/identification/IdentificationStage.ts
|
#: src/flows/stages/identification/IdentificationStage.ts
|
||||||
#: src/pages/events/TransportForm.ts
|
#: src/pages/events/TransportForm.ts
|
||||||
#: src/pages/stages/identification/IdentificationStageForm.ts
|
#: src/pages/stages/identification/IdentificationStageForm.ts
|
||||||
#: src/pages/user-settings/UserDetailsPage.ts
|
#: src/pages/user-settings/UserSelfForm.ts
|
||||||
#: src/pages/users/UserForm.ts
|
#: src/pages/users/UserForm.ts
|
||||||
#: src/pages/users/UserViewPage.ts
|
#: src/pages/users/UserViewPage.ts
|
||||||
msgid "Email"
|
msgid "Email"
|
||||||
@ -1426,7 +1426,6 @@ msgstr ""
|
|||||||
msgid "Exception"
|
msgid "Exception"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/flows/FlowListPage.ts
|
|
||||||
#: src/pages/flows/FlowViewPage.ts
|
#: src/pages/flows/FlowViewPage.ts
|
||||||
msgid "Execute"
|
msgid "Execute"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -1479,7 +1478,6 @@ msgstr ""
|
|||||||
msgid "Explicit Consent"
|
msgid "Explicit Consent"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/flows/FlowListPage.ts
|
|
||||||
#: src/pages/flows/FlowViewPage.ts
|
#: src/pages/flows/FlowViewPage.ts
|
||||||
msgid "Export"
|
msgid "Export"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -2072,6 +2070,10 @@ msgstr ""
|
|||||||
msgid "Link to a user with identical username address. Can have security implications when a username is used with another source."
|
msgid "Link to a user with identical username address. Can have security implications when a username is used with another source."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/stages/invitation/InvitationListLink.ts
|
||||||
|
msgid "Link to use the invitation."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/sources/oauth/OAuthSourceForm.ts
|
#: src/pages/sources/oauth/OAuthSourceForm.ts
|
||||||
#: src/pages/sources/plex/PlexSourceForm.ts
|
#: src/pages/sources/plex/PlexSourceForm.ts
|
||||||
msgid "Link users on unique identifier"
|
msgid "Link users on unique identifier"
|
||||||
@ -2101,7 +2103,7 @@ msgstr ""
|
|||||||
#: src/flows/stages/prompt/PromptStage.ts
|
#: src/flows/stages/prompt/PromptStage.ts
|
||||||
#: src/pages/applications/ApplicationViewPage.ts
|
#: src/pages/applications/ApplicationViewPage.ts
|
||||||
#: src/pages/applications/ApplicationViewPage.ts
|
#: src/pages/applications/ApplicationViewPage.ts
|
||||||
#: src/pages/user-settings/UserDetailsPage.ts
|
#: src/pages/user-settings/UserSelfForm.ts
|
||||||
#: src/utils.ts
|
#: src/utils.ts
|
||||||
msgid "Loading"
|
msgid "Loading"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -2160,6 +2162,7 @@ msgstr ""
|
|||||||
#: src/pages/stages/identification/IdentificationStageForm.ts
|
#: src/pages/stages/identification/IdentificationStageForm.ts
|
||||||
#: src/pages/stages/identification/IdentificationStageForm.ts
|
#: src/pages/stages/identification/IdentificationStageForm.ts
|
||||||
#: src/pages/stages/identification/IdentificationStageForm.ts
|
#: src/pages/stages/identification/IdentificationStageForm.ts
|
||||||
|
#: src/pages/stages/invitation/InvitationListLink.ts
|
||||||
#: src/pages/stages/password/PasswordStageForm.ts
|
#: src/pages/stages/password/PasswordStageForm.ts
|
||||||
#: src/pages/stages/prompt/PromptStageForm.ts
|
#: src/pages/stages/prompt/PromptStageForm.ts
|
||||||
#: src/pages/stages/prompt/PromptStageForm.ts
|
#: src/pages/stages/prompt/PromptStageForm.ts
|
||||||
@ -2389,7 +2392,7 @@ msgstr ""
|
|||||||
#: src/pages/stages/user_login/UserLoginStageForm.ts
|
#: src/pages/stages/user_login/UserLoginStageForm.ts
|
||||||
#: src/pages/stages/user_logout/UserLogoutStageForm.ts
|
#: src/pages/stages/user_logout/UserLogoutStageForm.ts
|
||||||
#: src/pages/stages/user_write/UserWriteStageForm.ts
|
#: src/pages/stages/user_write/UserWriteStageForm.ts
|
||||||
#: src/pages/user-settings/UserDetailsPage.ts
|
#: src/pages/user-settings/UserSelfForm.ts
|
||||||
#: src/pages/users/UserForm.ts
|
#: src/pages/users/UserForm.ts
|
||||||
#: src/pages/users/UserListPage.ts
|
#: src/pages/users/UserListPage.ts
|
||||||
#: src/pages/users/UserViewPage.ts
|
#: src/pages/users/UserViewPage.ts
|
||||||
@ -3002,6 +3005,10 @@ msgstr ""
|
|||||||
msgid "RSA-SHA512"
|
msgid "RSA-SHA512"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/sources/plex/PlexSourceForm.ts
|
||||||
|
msgid "Re-authenticate with plex"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/flows/StageBindingForm.ts
|
#: src/pages/flows/StageBindingForm.ts
|
||||||
msgid "Re-evaluate policies"
|
msgid "Re-evaluate policies"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -3101,7 +3108,7 @@ msgstr ""
|
|||||||
msgid "Required"
|
msgid "Required"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/user-settings/UserDetailsPage.ts
|
#: src/pages/user-settings/UserSelfForm.ts
|
||||||
#: src/pages/users/UserForm.ts
|
#: src/pages/users/UserForm.ts
|
||||||
msgid "Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only."
|
msgid "Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -3255,6 +3262,10 @@ msgstr ""
|
|||||||
msgid "Select all rows"
|
msgid "Select all rows"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/pages/stages/invitation/InvitationListLink.ts
|
||||||
|
msgid "Select an enrollment flow"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: src/flows/stages/authenticator_validate/AuthenticatorValidateStage.ts
|
#: src/flows/stages/authenticator_validate/AuthenticatorValidateStage.ts
|
||||||
msgid "Select an identification method."
|
msgid "Select an identification method."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -3744,7 +3755,7 @@ msgstr ""
|
|||||||
msgid "Successfully updated certificate-key pair."
|
msgid "Successfully updated certificate-key pair."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/user-settings/UserDetailsPage.ts
|
#: src/pages/user-settings/UserSelfForm.ts
|
||||||
msgid "Successfully updated details."
|
msgid "Successfully updated details."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -4269,7 +4280,7 @@ msgstr ""
|
|||||||
#: src/pages/stages/StageListPage.ts
|
#: src/pages/stages/StageListPage.ts
|
||||||
#: src/pages/stages/prompt/PromptListPage.ts
|
#: src/pages/stages/prompt/PromptListPage.ts
|
||||||
#: src/pages/tenants/TenantListPage.ts
|
#: src/pages/tenants/TenantListPage.ts
|
||||||
#: src/pages/user-settings/UserDetailsPage.ts
|
#: src/pages/user-settings/UserSelfForm.ts
|
||||||
#: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
|
#: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
|
||||||
#: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
|
#: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
|
||||||
#: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
|
#: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
|
||||||
@ -4372,7 +4383,7 @@ msgstr ""
|
|||||||
msgid "Update available"
|
msgid "Update available"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/user-settings/UserDetailsPage.ts
|
#: src/pages/user-settings/UserSettingsPage.ts
|
||||||
msgid "Update details"
|
msgid "Update details"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -4505,7 +4516,7 @@ msgstr ""
|
|||||||
msgid "User's avatar"
|
msgid "User's avatar"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/pages/user-settings/UserDetailsPage.ts
|
#: src/pages/user-settings/UserSelfForm.ts
|
||||||
#: src/pages/users/UserForm.ts
|
#: src/pages/users/UserForm.ts
|
||||||
msgid "User's display name."
|
msgid "User's display name."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -4525,7 +4536,7 @@ msgstr ""
|
|||||||
#: src/flows/stages/identification/IdentificationStage.ts
|
#: src/flows/stages/identification/IdentificationStage.ts
|
||||||
#: src/pages/policies/reputation/UserReputationListPage.ts
|
#: src/pages/policies/reputation/UserReputationListPage.ts
|
||||||
#: src/pages/stages/identification/IdentificationStageForm.ts
|
#: src/pages/stages/identification/IdentificationStageForm.ts
|
||||||
#: src/pages/user-settings/UserDetailsPage.ts
|
#: src/pages/user-settings/UserSelfForm.ts
|
||||||
#: src/pages/users/UserForm.ts
|
#: src/pages/users/UserForm.ts
|
||||||
#: src/pages/users/UserViewPage.ts
|
#: src/pages/users/UserViewPage.ts
|
||||||
msgid "Username"
|
msgid "Username"
|
||||||
|
@ -54,7 +54,7 @@ export class LDAPProviderFormPage extends ModelForm<LDAPProvider, number> {
|
|||||||
name="authorizationFlow">
|
name="authorizationFlow">
|
||||||
<select class="pf-c-form-control">
|
<select class="pf-c-form-control">
|
||||||
${until(tenant().then(t => {
|
${until(tenant().then(t => {
|
||||||
new FlowsApi(DEFAULT_CONFIG).flowsInstancesList({
|
return new FlowsApi(DEFAULT_CONFIG).flowsInstancesList({
|
||||||
ordering: "pk",
|
ordering: "pk",
|
||||||
designation: FlowsInstancesListDesignationEnum.Authentication,
|
designation: FlowsInstancesListDesignationEnum.Authentication,
|
||||||
}).then(flows => {
|
}).then(flows => {
|
||||||
|
@ -124,16 +124,16 @@ export class OAuthSourceForm extends ModelForm<OAuthSource, string> {
|
|||||||
<option value=${UserMatchingModeEnum.Identifier} ?selected=${this.instance?.userMatchingMode === UserMatchingModeEnum.Identifier}>
|
<option value=${UserMatchingModeEnum.Identifier} ?selected=${this.instance?.userMatchingMode === UserMatchingModeEnum.Identifier}>
|
||||||
${t`Link users on unique identifier`}
|
${t`Link users on unique identifier`}
|
||||||
</option>
|
</option>
|
||||||
<option value=${UserMatchingModeEnum.UsernameLink} ?selected=${this.instance?.userMatchingMode === UserMatchingModeEnum.UsernameLink}>
|
<option value=${UserMatchingModeEnum.EmailLink} ?selected=${this.instance?.userMatchingMode === UserMatchingModeEnum.EmailLink}>
|
||||||
${t`Link to a user with identical email address. Can have security implications when a source doesn't validate email addresses`}
|
${t`Link to a user with identical email address. Can have security implications when a source doesn't validate email addresses`}
|
||||||
</option>
|
</option>
|
||||||
<option value=${UserMatchingModeEnum.UsernameDeny} ?selected=${this.instance?.userMatchingMode === UserMatchingModeEnum.UsernameDeny}>
|
<option value=${UserMatchingModeEnum.EmailDeny} ?selected=${this.instance?.userMatchingMode === UserMatchingModeEnum.EmailDeny}>
|
||||||
${t`Use the user's email address, but deny enrollment when the email address already exists.`}
|
${t`Use the user's email address, but deny enrollment when the email address already exists.`}
|
||||||
</option>
|
</option>
|
||||||
<option value=${UserMatchingModeEnum.EmailLink} ?selected=${this.instance?.userMatchingMode === UserMatchingModeEnum.EmailLink}>
|
<option value=${UserMatchingModeEnum.UsernameLink} ?selected=${this.instance?.userMatchingMode === UserMatchingModeEnum.UsernameLink}>
|
||||||
${t`Link to a user with identical username address. Can have security implications when a username is used with another source.`}
|
${t`Link to a user with identical username address. Can have security implications when a username is used with another source.`}
|
||||||
</option>
|
</option>
|
||||||
<option value=${UserMatchingModeEnum.EmailDeny} ?selected=${this.instance?.userMatchingMode === UserMatchingModeEnum.EmailDeny}>
|
<option value=${UserMatchingModeEnum.UsernameDeny} ?selected=${this.instance?.userMatchingMode === UserMatchingModeEnum.UsernameDeny}>
|
||||||
${t`Use the user's username, but deny enrollment when the username already exists.`}
|
${t`Use the user's username, but deny enrollment when the username already exists.`}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
|
@ -85,7 +85,13 @@ export class PlexSourceForm extends ModelForm<PlexSource, string> {
|
|||||||
${t`Load servers`}
|
${t`Load servers`}
|
||||||
</button>`;
|
</button>`;
|
||||||
}
|
}
|
||||||
return html`<ak-form-element-horizontal name="allowFriends">
|
return html`
|
||||||
|
<button class="pf-c-button pf-m-secondary" type="button" @click=${() => {
|
||||||
|
this.doAuth();
|
||||||
|
}}>
|
||||||
|
${t`Re-authenticate with plex`}
|
||||||
|
</button>
|
||||||
|
<ak-form-element-horizontal name="allowFriends">
|
||||||
<div class="pf-c-check">
|
<div class="pf-c-check">
|
||||||
<input type="checkbox" class="pf-c-check__input" ?checked=${first(this.instance?.allowFriends, true)}>
|
<input type="checkbox" class="pf-c-check__input" ?checked=${first(this.instance?.allowFriends, true)}>
|
||||||
<label class="pf-c-check__label">
|
<label class="pf-c-check__label">
|
||||||
@ -140,16 +146,16 @@ export class PlexSourceForm extends ModelForm<PlexSource, string> {
|
|||||||
<option value=${UserMatchingModeEnum.Identifier} ?selected=${this.instance?.userMatchingMode === UserMatchingModeEnum.Identifier}>
|
<option value=${UserMatchingModeEnum.Identifier} ?selected=${this.instance?.userMatchingMode === UserMatchingModeEnum.Identifier}>
|
||||||
${t`Link users on unique identifier`}
|
${t`Link users on unique identifier`}
|
||||||
</option>
|
</option>
|
||||||
<option value=${UserMatchingModeEnum.UsernameLink} ?selected=${this.instance?.userMatchingMode === UserMatchingModeEnum.UsernameLink}>
|
<option value=${UserMatchingModeEnum.EmailLink} ?selected=${this.instance?.userMatchingMode === UserMatchingModeEnum.EmailLink}>
|
||||||
${t`Link to a user with identical email address. Can have security implications when a source doesn't validate email addresses`}
|
${t`Link to a user with identical email address. Can have security implications when a source doesn't validate email addresses`}
|
||||||
</option>
|
</option>
|
||||||
<option value=${UserMatchingModeEnum.UsernameDeny} ?selected=${this.instance?.userMatchingMode === UserMatchingModeEnum.UsernameDeny}>
|
<option value=${UserMatchingModeEnum.EmailDeny} ?selected=${this.instance?.userMatchingMode === UserMatchingModeEnum.EmailDeny}>
|
||||||
${t`Use the user's email address, but deny enrollment when the email address already exists.`}
|
${t`Use the user's email address, but deny enrollment when the email address already exists.`}
|
||||||
</option>
|
</option>
|
||||||
<option value=${UserMatchingModeEnum.EmailLink} ?selected=${this.instance?.userMatchingMode === UserMatchingModeEnum.EmailLink}>
|
<option value=${UserMatchingModeEnum.UsernameLink} ?selected=${this.instance?.userMatchingMode === UserMatchingModeEnum.UsernameLink}>
|
||||||
${t`Link to a user with identical username address. Can have security implications when a username is used with another source.`}
|
${t`Link to a user with identical username address. Can have security implications when a username is used with another source.`}
|
||||||
</option>
|
</option>
|
||||||
<option value=${UserMatchingModeEnum.EmailDeny} ?selected=${this.instance?.userMatchingMode === UserMatchingModeEnum.EmailDeny}>
|
<option value=${UserMatchingModeEnum.UsernameDeny} ?selected=${this.instance?.userMatchingMode === UserMatchingModeEnum.UsernameDeny}>
|
||||||
${t`Use the user's username, but deny enrollment when the username already exists.`}
|
${t`Use the user's username, but deny enrollment when the username already exists.`}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
|
67
web/src/pages/stages/invitation/InvitationListLink.ts
Normal file
67
web/src/pages/stages/invitation/InvitationListLink.ts
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import { t } from "@lingui/macro";
|
||||||
|
import { CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element";
|
||||||
|
import { until } from "lit-html/directives/until";
|
||||||
|
import PFForm from "@patternfly/patternfly/components/Form/form.css";
|
||||||
|
import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css";
|
||||||
|
import AKGlobal from "../../../authentik.css";
|
||||||
|
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||||
|
import PFFlex from "@patternfly/patternfly/layouts/Flex/flex.css";
|
||||||
|
import { FlowsApi, FlowsInstancesListDesignationEnum } from "authentik-api";
|
||||||
|
import { DEFAULT_CONFIG } from "../../../api/Config";
|
||||||
|
import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css";
|
||||||
|
|
||||||
|
@customElement("ak-stage-invitation-list-link")
|
||||||
|
export class InvitationListLink extends LitElement {
|
||||||
|
|
||||||
|
@property()
|
||||||
|
invitation?: string
|
||||||
|
|
||||||
|
@property()
|
||||||
|
selectedFlow?: string;
|
||||||
|
|
||||||
|
static get styles(): CSSResult[] {
|
||||||
|
return [PFBase, PFForm, PFFormControl, PFFlex, PFDescriptionList, AKGlobal];
|
||||||
|
}
|
||||||
|
|
||||||
|
renderLink(): string {
|
||||||
|
return `${window.location.protocol}//${window.location.host}/if/flow/${this.selectedFlow}/?token=${this.invitation}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
render(): TemplateResult {
|
||||||
|
return html`<dl class="pf-c-description-list pf-m-horizontal">
|
||||||
|
<div class="pf-c-description-list__group">
|
||||||
|
<dt class="pf-c-description-list__term">
|
||||||
|
<span class="pf-c-description-list__text">${t`Select an enrollment flow`}</span>
|
||||||
|
</dt>
|
||||||
|
<dd class="pf-c-description-list__description">
|
||||||
|
<div class="pf-c-description-list__text">
|
||||||
|
<select class="pf-c-form-control" @change=${(ev: Event) => {
|
||||||
|
const current = (ev.target as HTMLInputElement).value;
|
||||||
|
this.selectedFlow = current;
|
||||||
|
}}>
|
||||||
|
${until(new FlowsApi(DEFAULT_CONFIG).flowsInstancesList({
|
||||||
|
ordering: "pk",
|
||||||
|
designation: FlowsInstancesListDesignationEnum.Enrollment,
|
||||||
|
}).then(flows => {
|
||||||
|
return flows.results.map(flow => {
|
||||||
|
return html`<option value=${flow.slug} ?selected=${flow.slug === this.selectedFlow}>${flow.slug}</option>`;
|
||||||
|
});
|
||||||
|
}), html`<option>${t`Loading...`}</option>`)}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
<div class="pf-c-description-list__group">
|
||||||
|
<dt class="pf-c-description-list__term">
|
||||||
|
<span class="pf-c-description-list__text">${t`Link to use the invitation.`}</span>
|
||||||
|
</dt>
|
||||||
|
<dd class="pf-c-description-list__description">
|
||||||
|
<div class="pf-c-description-list__text">
|
||||||
|
<input class="pf-c-form-control" readonly type="text" value=${this.renderLink()} />
|
||||||
|
</div>
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
</dl>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -8,6 +8,7 @@ import "../../../elements/buttons/SpinnerButton";
|
|||||||
import "../../../elements/forms/DeleteForm";
|
import "../../../elements/forms/DeleteForm";
|
||||||
import "../../../elements/forms/ModalForm";
|
import "../../../elements/forms/ModalForm";
|
||||||
import "./InvitationForm";
|
import "./InvitationForm";
|
||||||
|
import "./InvitationListLink";
|
||||||
import { TableColumn } from "../../../elements/table/Table";
|
import { TableColumn } from "../../../elements/table/Table";
|
||||||
import { PAGE_SIZE } from "../../../constants";
|
import { PAGE_SIZE } from "../../../constants";
|
||||||
import { Invitation, StagesApi } from "authentik-api";
|
import { Invitation, StagesApi } from "authentik-api";
|
||||||
@ -15,6 +16,8 @@ import { DEFAULT_CONFIG } from "../../../api/Config";
|
|||||||
|
|
||||||
@customElement("ak-stage-invitation-list")
|
@customElement("ak-stage-invitation-list")
|
||||||
export class InvitationListPage extends TablePage<Invitation> {
|
export class InvitationListPage extends TablePage<Invitation> {
|
||||||
|
expandable = true;
|
||||||
|
|
||||||
searchEnabled(): boolean {
|
searchEnabled(): boolean {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -53,7 +56,7 @@ export class InvitationListPage extends TablePage<Invitation> {
|
|||||||
return [
|
return [
|
||||||
html`${item.pk}`,
|
html`${item.pk}`,
|
||||||
html`${item.createdBy?.username}`,
|
html`${item.createdBy?.username}`,
|
||||||
html`${item.expires?.toLocaleString()}`,
|
html`${item.expires?.toLocaleString() || "-"}`,
|
||||||
html`
|
html`
|
||||||
<ak-forms-delete
|
<ak-forms-delete
|
||||||
.obj=${item}
|
.obj=${item}
|
||||||
@ -75,6 +78,18 @@ export class InvitationListPage extends TablePage<Invitation> {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderExpanded(item: Invitation): TemplateResult {
|
||||||
|
return html`
|
||||||
|
<td role="cell" colspan="3">
|
||||||
|
<div class="pf-c-table__expandable-row-content">
|
||||||
|
<ak-stage-invitation-list-link invitation=${item.pk}></ak-stage-invitation-list-link>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>`;
|
||||||
|
}
|
||||||
|
|
||||||
renderToolbar(): TemplateResult {
|
renderToolbar(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
<ak-forms-modal>
|
<ak-forms-modal>
|
||||||
|
@ -1,101 +0,0 @@
|
|||||||
import { t } from "@lingui/macro";
|
|
||||||
import { CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element";
|
|
||||||
import PFCard from "@patternfly/patternfly/components/Card/card.css";
|
|
||||||
import AKGlobal from "../../authentik.css";
|
|
||||||
import PFButton from "@patternfly/patternfly/components/Button/button.css";
|
|
||||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
|
||||||
import PFForm from "@patternfly/patternfly/components/Form/form.css";
|
|
||||||
import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css";
|
|
||||||
import { CoreApi, User } from "authentik-api";
|
|
||||||
import { me } from "../../api/Users";
|
|
||||||
import { ifDefined } from "lit-html/directives/if-defined";
|
|
||||||
import { DEFAULT_CONFIG, tenant } from "../../api/Config";
|
|
||||||
import "../../elements/forms/FormElement";
|
|
||||||
import "../../elements/EmptyState";
|
|
||||||
import "../../elements/forms/Form";
|
|
||||||
import "../../elements/forms/HorizontalFormElement";
|
|
||||||
import { until } from "lit-html/directives/until";
|
|
||||||
|
|
||||||
@customElement("ak-user-details")
|
|
||||||
export class UserDetailsPage extends LitElement {
|
|
||||||
|
|
||||||
static get styles(): CSSResult[] {
|
|
||||||
return [PFBase, PFCard, PFForm, PFFormControl, PFButton, AKGlobal];
|
|
||||||
}
|
|
||||||
|
|
||||||
@property({attribute: false})
|
|
||||||
user?: User;
|
|
||||||
|
|
||||||
firstUpdated(): void {
|
|
||||||
me().then((user) => {
|
|
||||||
this.user = user.user;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
render(): TemplateResult {
|
|
||||||
if (!this.user) {
|
|
||||||
return html`<ak-empty-state
|
|
||||||
?loading="${true}"
|
|
||||||
header=${t`Loading`}>
|
|
||||||
</ak-empty-state>`;
|
|
||||||
}
|
|
||||||
return html`<div class="pf-c-card">
|
|
||||||
<div class="pf-c-card__title">
|
|
||||||
${t`Update details`}
|
|
||||||
</div>
|
|
||||||
<div class="pf-c-card__body">
|
|
||||||
<ak-form
|
|
||||||
successMessage=${t`Successfully updated details.`}
|
|
||||||
.send=${(data: unknown) => {
|
|
||||||
return new CoreApi(DEFAULT_CONFIG).coreUsersUpdate({
|
|
||||||
id: this.user?.pk || 0,
|
|
||||||
userRequest: data as User
|
|
||||||
});
|
|
||||||
}}>
|
|
||||||
<form class="pf-c-form pf-m-horizontal">
|
|
||||||
<ak-form-element-horizontal
|
|
||||||
label=${t`Username`}
|
|
||||||
?required=${true}
|
|
||||||
name="username">
|
|
||||||
<input type="text" value="${ifDefined(this.user?.username)}" class="pf-c-form-control" required>
|
|
||||||
<p class="pf-c-form__helper-text">${t`Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.`}</p>
|
|
||||||
</ak-form-element-horizontal>
|
|
||||||
<ak-form-element-horizontal
|
|
||||||
label=${t`Name`}
|
|
||||||
?required=${true}
|
|
||||||
name="name">
|
|
||||||
<input type="text" value="${ifDefined(this.user?.name)}" class="pf-c-form-control" required>
|
|
||||||
<p class="pf-c-form__helper-text">${t`User's display name.`}</p>
|
|
||||||
</ak-form-element-horizontal>
|
|
||||||
<ak-form-element-horizontal
|
|
||||||
label=${t`Email`}
|
|
||||||
?required=${true}
|
|
||||||
name="email">
|
|
||||||
<input type="email" value="${ifDefined(this.user?.email)}" class="pf-c-form-control" required>
|
|
||||||
</ak-form-element-horizontal>
|
|
||||||
|
|
||||||
<div class="pf-c-form__group pf-m-action">
|
|
||||||
<div class="pf-c-form__horizontal-group">
|
|
||||||
<div class="pf-c-form__actions">
|
|
||||||
<button class="pf-c-button pf-m-primary">
|
|
||||||
${t`Update`}
|
|
||||||
</button>
|
|
||||||
${until(tenant().then(tenant => {
|
|
||||||
if (tenant.flowUnenrollment) {
|
|
||||||
return html`<a class="pf-c-button pf-m-danger"
|
|
||||||
href="/if/flow/${tenant.flowUnenrollment}">
|
|
||||||
${t`Delete account`}
|
|
||||||
</a>`;
|
|
||||||
}
|
|
||||||
return html``;
|
|
||||||
}))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</ak-form>
|
|
||||||
</div>
|
|
||||||
</div>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
100
web/src/pages/user-settings/UserSelfForm.ts
Normal file
100
web/src/pages/user-settings/UserSelfForm.ts
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
import { t } from "@lingui/macro";
|
||||||
|
import { customElement, html, TemplateResult } from "lit-element";
|
||||||
|
import { CoreApi, UserSelf } from "authentik-api";
|
||||||
|
import { ifDefined } from "lit-html/directives/if-defined";
|
||||||
|
import { DEFAULT_CONFIG, tenant } from "../../api/Config";
|
||||||
|
import "../../elements/forms/FormElement";
|
||||||
|
import "../../elements/EmptyState";
|
||||||
|
import "../../elements/forms/Form";
|
||||||
|
import "../../elements/forms/HorizontalFormElement";
|
||||||
|
import { until } from "lit-html/directives/until";
|
||||||
|
import { ModelForm } from "../../elements/forms/ModelForm";
|
||||||
|
|
||||||
|
@customElement("ak-user-self-form")
|
||||||
|
export class UserSelfForm extends ModelForm<UserSelf, number> {
|
||||||
|
viewportCheck = false;
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
loadInstance(pk: number): Promise<UserSelf> {
|
||||||
|
return new CoreApi(DEFAULT_CONFIG).coreUsersMeRetrieve().then((su) => {
|
||||||
|
return su.user;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getSuccessMessage(): string {
|
||||||
|
return t`Successfully updated details.`;
|
||||||
|
}
|
||||||
|
|
||||||
|
send = (data: UserSelf): Promise<UserSelf> => {
|
||||||
|
return new CoreApi(DEFAULT_CONFIG)
|
||||||
|
.coreUsersUpdateSelfUpdate({
|
||||||
|
userSelfRequest: data,
|
||||||
|
})
|
||||||
|
.then((su) => {
|
||||||
|
return su.user;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
renderForm(): TemplateResult {
|
||||||
|
if (!this.instance) {
|
||||||
|
return html`<ak-empty-state ?loading="${true}" header=${t`Loading`}> </ak-empty-state>`;
|
||||||
|
}
|
||||||
|
return html`<form class="pf-c-form pf-m-horizontal">
|
||||||
|
<ak-form-element-horizontal label=${t`Username`} ?required=${true} name="username">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value="${ifDefined(this.instance?.username)}"
|
||||||
|
class="pf-c-form-control"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<p class="pf-c-form__helper-text">
|
||||||
|
${t`Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.`}
|
||||||
|
</p>
|
||||||
|
</ak-form-element-horizontal>
|
||||||
|
<ak-form-element-horizontal label=${t`Name`} ?required=${true} name="name">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value="${ifDefined(this.instance?.name)}"
|
||||||
|
class="pf-c-form-control"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<p class="pf-c-form__helper-text">${t`User's display name.`}</p>
|
||||||
|
</ak-form-element-horizontal>
|
||||||
|
<ak-form-element-horizontal label=${t`Email`} name="email">
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
value="${ifDefined(this.instance?.email)}"
|
||||||
|
class="pf-c-form-control"
|
||||||
|
/>
|
||||||
|
</ak-form-element-horizontal>
|
||||||
|
|
||||||
|
<div class="pf-c-form__group pf-m-action">
|
||||||
|
<div class="pf-c-form__horizontal-group">
|
||||||
|
<div class="pf-c-form__actions">
|
||||||
|
<button
|
||||||
|
@click=${(ev: Event) => {
|
||||||
|
return this.submit(ev);
|
||||||
|
}}
|
||||||
|
class="pf-c-button pf-m-primary"
|
||||||
|
>
|
||||||
|
${t`Update`}
|
||||||
|
</button>
|
||||||
|
${until(
|
||||||
|
tenant().then((tenant) => {
|
||||||
|
if (tenant.flowUnenrollment) {
|
||||||
|
return html`<a
|
||||||
|
class="pf-c-button pf-m-danger"
|
||||||
|
href="/if/flow/${tenant.flowUnenrollment}"
|
||||||
|
>
|
||||||
|
${t`Delete account`}
|
||||||
|
</a>`;
|
||||||
|
}
|
||||||
|
return html``;
|
||||||
|
}),
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>`;
|
||||||
|
}
|
||||||
|
}
|
@ -20,7 +20,7 @@ import { ifDefined } from "lit-html/directives/if-defined";
|
|||||||
import "../../elements/Tabs";
|
import "../../elements/Tabs";
|
||||||
import "../../elements/PageHeader";
|
import "../../elements/PageHeader";
|
||||||
import "./tokens/UserTokenList";
|
import "./tokens/UserTokenList";
|
||||||
import "./UserDetailsPage";
|
import "./UserSelfForm";
|
||||||
import "./settings/UserSettingsAuthenticatorDuo";
|
import "./settings/UserSettingsAuthenticatorDuo";
|
||||||
import "./settings/UserSettingsAuthenticatorStatic";
|
import "./settings/UserSettingsAuthenticatorStatic";
|
||||||
import "./settings/UserSettingsAuthenticatorTOTP";
|
import "./settings/UserSettingsAuthenticatorTOTP";
|
||||||
@ -95,8 +95,17 @@ export class UserSettingsPage extends LitElement {
|
|||||||
description=${t`Configure settings relevant to your user profile.`}>
|
description=${t`Configure settings relevant to your user profile.`}>
|
||||||
</ak-page-header>
|
</ak-page-header>
|
||||||
<ak-tabs ?vertical="${true}" style="height: 100%;">
|
<ak-tabs ?vertical="${true}" style="height: 100%;">
|
||||||
<section slot="page-details" data-tab-title="${t`User details`}" class="pf-c-page__main-section pf-m-no-padding-mobile">
|
<section
|
||||||
<ak-user-details></ak-user-details>
|
slot="page-details"
|
||||||
|
data-tab-title="${t`User details`}"
|
||||||
|
class="pf-c-page__main-section pf-m-no-padding-mobile"
|
||||||
|
>
|
||||||
|
<div class="pf-c-card">
|
||||||
|
<div class="pf-c-card__title">${t`Update details`}</div>
|
||||||
|
<div class="pf-c-card__body">
|
||||||
|
<ak-user-self-form .instancePk=${1}></ak-user-self-form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<section slot="page-tokens" data-tab-title="${t`Tokens`}" class="pf-c-page__main-section pf-m-no-padding-mobile">
|
<section slot="page-tokens" data-tab-title="${t`Tokens`}" class="pf-c-page__main-section pf-m-no-padding-mobile">
|
||||||
<ak-user-token-list></ak-user-token-list>
|
<ak-user-token-list></ak-user-token-list>
|
||||||
|
@ -58,9 +58,8 @@ export class UserForm extends ModelForm<User, number> {
|
|||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
<ak-form-element-horizontal
|
<ak-form-element-horizontal
|
||||||
label=${t`Email`}
|
label=${t`Email`}
|
||||||
?required=${true}
|
|
||||||
name="email">
|
name="email">
|
||||||
<input type="email" autocomplete="off" value="${ifDefined(this.instance?.email)}" class="pf-c-form-control" required>
|
<input type="email" autocomplete="off" value="${ifDefined(this.instance?.email)}" class="pf-c-form-control">
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
<ak-form-element-horizontal
|
<ak-form-element-horizontal
|
||||||
name="isActive">
|
name="isActive">
|
||||||
|
@ -12,11 +12,11 @@ This installation method is for test-setups and small-scale productive setups.
|
|||||||
|
|
||||||
## Preparation
|
## Preparation
|
||||||
|
|
||||||
Download the latest `docker-compose.yml` from [here](https://raw.githubusercontent.com/goauthentik/authentik/version/2021.7.1/docker-compose.yml). Place it in a directory of your choice.
|
Download the latest `docker-compose.yml` from [here](https://raw.githubusercontent.com/goauthentik/authentik/version/2021.7.3/docker-compose.yml). Place it in a directory of your choice.
|
||||||
|
|
||||||
To optionally enable error-reporting, run `echo AUTHENTIK_ERROR_REPORTING__ENABLED=true >> .env`
|
To optionally enable error-reporting, run `echo AUTHENTIK_ERROR_REPORTING__ENABLED=true >> .env`
|
||||||
|
|
||||||
To optionally deploy a different version run `echo AUTHENTIK_TAG=2021.7.1 >> .env`
|
To optionally deploy a different version run `echo AUTHENTIK_TAG=2021.7.3 >> .env`
|
||||||
|
|
||||||
If this is a fresh authentik install run the following commands to generate a password:
|
If this is a fresh authentik install run the following commands to generate a password:
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ version: "3.5"
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
authentik_proxy:
|
authentik_proxy:
|
||||||
image: ghcr.io/goauthentik/proxy:2021.7.1
|
image: ghcr.io/goauthentik/proxy:2021.7.3
|
||||||
ports:
|
ports:
|
||||||
- 4180:4180
|
- 4180:4180
|
||||||
- 4443:4443
|
- 4443:4443
|
||||||
@ -21,7 +21,7 @@ services:
|
|||||||
AUTHENTIK_TOKEN: token-generated-by-authentik
|
AUTHENTIK_TOKEN: token-generated-by-authentik
|
||||||
# Or, for the LDAP Outpost
|
# Or, for the LDAP Outpost
|
||||||
authentik_proxy:
|
authentik_proxy:
|
||||||
image: ghcr.io/goauthentik/ldap:2021.7.1
|
image: ghcr.io/goauthentik/ldap:2021.7.3
|
||||||
ports:
|
ports:
|
||||||
- 389:3389
|
- 389:3389
|
||||||
environment:
|
environment:
|
||||||
|
@ -14,7 +14,7 @@ metadata:
|
|||||||
app.kubernetes.io/instance: __OUTPOST_NAME__
|
app.kubernetes.io/instance: __OUTPOST_NAME__
|
||||||
app.kubernetes.io/managed-by: goauthentik.io
|
app.kubernetes.io/managed-by: goauthentik.io
|
||||||
app.kubernetes.io/name: authentik-proxy
|
app.kubernetes.io/name: authentik-proxy
|
||||||
app.kubernetes.io/version: 2021.7.1
|
app.kubernetes.io/version: 2021.7.3
|
||||||
name: authentik-outpost-api
|
name: authentik-outpost-api
|
||||||
stringData:
|
stringData:
|
||||||
authentik_host: "__AUTHENTIK_URL__"
|
authentik_host: "__AUTHENTIK_URL__"
|
||||||
@ -29,7 +29,7 @@ metadata:
|
|||||||
app.kubernetes.io/instance: __OUTPOST_NAME__
|
app.kubernetes.io/instance: __OUTPOST_NAME__
|
||||||
app.kubernetes.io/managed-by: goauthentik.io
|
app.kubernetes.io/managed-by: goauthentik.io
|
||||||
app.kubernetes.io/name: authentik-proxy
|
app.kubernetes.io/name: authentik-proxy
|
||||||
app.kubernetes.io/version: 2021.7.1
|
app.kubernetes.io/version: 2021.7.3
|
||||||
name: authentik-outpost
|
name: authentik-outpost
|
||||||
spec:
|
spec:
|
||||||
ports:
|
ports:
|
||||||
@ -54,7 +54,7 @@ metadata:
|
|||||||
app.kubernetes.io/instance: __OUTPOST_NAME__
|
app.kubernetes.io/instance: __OUTPOST_NAME__
|
||||||
app.kubernetes.io/managed-by: goauthentik.io
|
app.kubernetes.io/managed-by: goauthentik.io
|
||||||
app.kubernetes.io/name: authentik-proxy
|
app.kubernetes.io/name: authentik-proxy
|
||||||
app.kubernetes.io/version: 2021.7.1
|
app.kubernetes.io/version: 2021.7.3
|
||||||
name: authentik-outpost
|
name: authentik-outpost
|
||||||
spec:
|
spec:
|
||||||
selector:
|
selector:
|
||||||
@ -62,14 +62,14 @@ spec:
|
|||||||
app.kubernetes.io/instance: __OUTPOST_NAME__
|
app.kubernetes.io/instance: __OUTPOST_NAME__
|
||||||
app.kubernetes.io/managed-by: goauthentik.io
|
app.kubernetes.io/managed-by: goauthentik.io
|
||||||
app.kubernetes.io/name: authentik-proxy
|
app.kubernetes.io/name: authentik-proxy
|
||||||
app.kubernetes.io/version: 2021.7.1
|
app.kubernetes.io/version: 2021.7.3
|
||||||
template:
|
template:
|
||||||
metadata:
|
metadata:
|
||||||
labels:
|
labels:
|
||||||
app.kubernetes.io/instance: __OUTPOST_NAME__
|
app.kubernetes.io/instance: __OUTPOST_NAME__
|
||||||
app.kubernetes.io/managed-by: goauthentik.io
|
app.kubernetes.io/managed-by: goauthentik.io
|
||||||
app.kubernetes.io/name: authentik-proxy
|
app.kubernetes.io/name: authentik-proxy
|
||||||
app.kubernetes.io/version: 2021.7.1
|
app.kubernetes.io/version: 2021.7.3
|
||||||
spec:
|
spec:
|
||||||
containers:
|
containers:
|
||||||
- env:
|
- env:
|
||||||
@ -88,7 +88,7 @@ spec:
|
|||||||
secretKeyRef:
|
secretKeyRef:
|
||||||
key: authentik_host_insecure
|
key: authentik_host_insecure
|
||||||
name: authentik-outpost-api
|
name: authentik-outpost-api
|
||||||
image: ghcr.io/goauthentik/proxy:2021.7.1
|
image: ghcr.io/goauthentik/proxy:2021.7.3
|
||||||
name: proxy
|
name: proxy
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 4180
|
- containerPort: 4180
|
||||||
@ -110,7 +110,7 @@ metadata:
|
|||||||
app.kubernetes.io/instance: __OUTPOST_NAME__
|
app.kubernetes.io/instance: __OUTPOST_NAME__
|
||||||
app.kubernetes.io/managed-by: goauthentik.io
|
app.kubernetes.io/managed-by: goauthentik.io
|
||||||
app.kubernetes.io/name: authentik-proxy
|
app.kubernetes.io/name: authentik-proxy
|
||||||
app.kubernetes.io/version: 2021.7.1
|
app.kubernetes.io/version: 2021.7.3
|
||||||
name: authentik-outpost
|
name: authentik-outpost
|
||||||
spec:
|
spec:
|
||||||
rules:
|
rules:
|
||||||
|
Reference in New Issue
Block a user