Compare commits

..

34 Commits

Author SHA1 Message Date
5a7508d2e0 core: fix token expiration not being updated upon key rotation
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-12 17:24:19 +02:00
9c31ea1aa6 core: fix expired tokens not being returned by API
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-12 17:24:19 +02:00
18211a2033 release: 2021.7.3 2021-08-05 19:23:03 +02:00
b4cfc56e5e web/admin: fix source form's userMatchingMode being swapped
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

# Conflicts:
#	web/src/pages/sources/oauth/OAuthSourceForm.ts
#	web/src/pages/sources/plex/PlexSourceForm.ts
2021-08-05 18:48:02 +02:00
8e797fa76b outpost/ldap: fix errors with new UserSelf serializer
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-05 18:16:06 +02:00
1b91543add core: add UserSelfSerializer and separate method for users to update themselves with limited fields
rework user settings page to better use form
closes #1227

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

# Conflicts:
#	authentik/core/api/users.py
#	web/src/elements/forms/ModelForm.ts
#	web/src/pages/user-settings/UserDetailsPage.ts
#	web/src/pages/user-settings/UserSettingsPage.ts
2021-08-05 17:47:45 +02:00
1cd59be8dc web/admin: fix email being required
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

# Conflicts:
#	web/src/pages/user-settings/UserDetailsPage.ts
#	web/src/pages/users/UserForm.ts
2021-08-05 17:46:28 +02:00
66bb68a747 lifecycle: decrease default worker count on compose
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-05 09:44:58 +02:00
aa4f7fb2b6 providers/saml: fix error when PropertyMapping return value isn't string
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-04 00:22:07 +02:00
4f1c11c5ef providers/saml: add WantAssertionsSigned
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

# Conflicts:
#	authentik/providers/saml/processors/metadata_parser.py
2021-08-04 00:21:54 +02:00
add7a80fdc release: 2021.7.2 2021-08-01 19:11:50 +02:00
aac91c2e9d stages/email: handle OSError
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-01 18:25:53 +02:00
85e86351cd flows: fix flows not redirecting correctly
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-01 18:25:53 +02:00
d767504474 flows: don't check redirect URL when set from flow plan (set from authentik or policy)
closes #1203

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-01 15:23:46 +02:00
f84cd6208c flows: fix unhandled error in stage execution not being logged as SYSTEM_EXCEPTION event
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-01 15:23:46 +02:00
1ec540ea9a providers/saml: fix metadata being inaccessible without authentication
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-01 15:23:46 +02:00
29fe731bbf providers/saml: fix Error when getting metadata for invalid ID
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-01 14:09:22 +02:00
26e66969c9 stages/invitation: delete invite only after full enrollment flow is completed
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-01 13:22:02 +02:00
fe629f8b51 web/admin: fix empty column when no invitation expiry was set
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-01 13:22:02 +02:00
72b7642c5a outposts: catch invalid ServiceConnection error in outpost controller
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-01 12:33:21 +02:00
a97f842112 sources/plex: add background task to monitor validity of plex token
closes #1205

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-01 12:33:21 +02:00
16e6e4c3b7 web/admin: add re-authenticate button for plex
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

#1205
2021-08-01 12:33:21 +02:00
dc0d715885 web/admin: add UI to copy invitation link
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-01 12:33:20 +02:00
7ecd57ecff outpost: bump timer for periodic config reloads
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-08-01 12:33:20 +02:00
0cb4d64b57 stages/email: fix error when re-requesting email after token has expired
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-30 09:39:42 +02:00
a4fd58a0db events: ensure fallback result is set for on_failure
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-30 09:39:42 +02:00
fb6e8ca1eb events: remove default result for MonitoredTasks, only save when result was set
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-29 22:43:29 +02:00
4c41948e75 e2e: fix broken selenium by locking images
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-29 21:53:09 +02:00
a5c8caf909 providers/oauth2: fix error when requesting jwks keys with no rs256 aet
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-29 21:22:59 +02:00
970655ab21 ci: fix sentry sourcemap path
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-29 21:22:52 +02:00
c8c7202c61 web/admin: fix LDAP Provider bind flow list being empty
closes #1192

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-29 11:35:54 +02:00
a3981dd3cd providers/proxy: fix hosts for ingress not being compared correctly
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-29 11:35:50 +02:00
affafc31cf sources/ldap: improve ms-ad password complexity checking
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-29 11:35:47 +02:00
602aed674b web/admin: fully remove response cloning due to errors
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
2021-07-29 11:35:44 +02:00
54 changed files with 792 additions and 276 deletions

View File

@ -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>.*)

View File

@ -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
View File

@ -200,4 +200,4 @@ media/
*mmdb *mmdb
.idea/ .idea/
api/ /api/

View File

@ -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"

View File

@ -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",

View File

@ -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=[])

View File

@ -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,

View File

@ -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(

View File

@ -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))

View File

@ -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:

View File

@ -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))

View 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), {})

View File

@ -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"] = [

View File

@ -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()

View File

@ -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:

View File

@ -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(

View File

@ -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"

View File

@ -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)"""

View File

@ -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(

View 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"},
},
}

View 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()

View File

@ -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()
)

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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(

View File

@ -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:

View File

@ -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"

View File

@ -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 {

View File

@ -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")

View File

@ -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()

View File

@ -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
} }

View File

@ -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")

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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")

View File

@ -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 => {

View File

@ -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 = ";";

View File

@ -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;

View File

@ -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"

View File

@ -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"

View File

@ -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 => {

View File

@ -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>

View File

@ -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>

View 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>`;
}
}

View File

@ -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>

View File

@ -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>`;
}
}

View 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>`;
}
}

View File

@ -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>

View File

@ -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">

View File

@ -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:

View File

@ -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:

View File

@ -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: