Compare commits

...

34 Commits

Author SHA1 Message Date
5725e54334 release: 2021.2.6-stable 2021-02-27 18:16:46 +01:00
c20856ca17 web: fix colourstyles not being included in common_styles
# Conflicts:
#	authentik/events/geo.py
#	web/src/elements/buttons/TokenCopyButton.ts
2021-02-27 18:16:32 +01:00
9c73e9cf4e web: fix colourstyles not being included in common_styles
# Conflicts:
#	authentik/events/geo.py
#	web/src/elements/buttons/TokenCopyButton.ts
2021-02-27 17:36:07 +01:00
b10c3db13d web: add sentry CaptureConsole
# Conflicts:
#	web/package.json
2021-02-27 17:19:43 +01:00
fe290aa214 sources/ldap: fix password setter on users which are not LDAP 2021-02-27 17:04:16 +01:00
a2e69bd250 sources/ldap: fix API error when source has not synced yet 2021-02-27 17:04:16 +01:00
d2a35eb8de admin: fix missing success_url for clean views 2021-02-27 17:04:16 +01:00
3437d8b4b0 flows: handle error when app cannot be found during import 2021-02-27 17:04:16 +01:00
b862bf4284 providers/oauth2: fix error when no login event could be found 2021-02-27 17:04:16 +01:00
de22a367b1 events: fix error when event can't be loaded into rule task 2021-02-27 17:04:15 +01:00
17ab895652 flows: fix glob pattern for doc flows 2021-02-27 17:04:06 +01:00
a4d5815e1b policies: sort groups in groupmembership policy and binding
closes #595

# Conflicts:
#	authentik/policies/group_membership/forms.py
2021-02-27 17:02:34 +01:00
e81d3dad3e release: 2021.2.5-stable 2021-02-24 09:54:06 +01:00
5aabaebd96 root: fix request_id not being logged for actual asgi requests 2021-02-24 09:45:52 +01:00
7b60bca297 web: fix SiteShell breaking links when handlers are updated twice 2021-02-24 09:45:08 +01:00
a07d7456c8 web: fix outpost edit/delete buttons 2021-02-24 09:44:55 +01:00
f33369bf0c helm: add initial wait for healthcheck 2021-02-24 09:44:39 +01:00
1abcff39c7 outpost: improve logging output, ensure fields match api server 2021-02-24 09:44:24 +01:00
c1caf84d92 events: fix user QuerySet being passed 2021-02-24 09:44:05 +01:00
86c069fe64 admin: fix policy list not having a refresh button 2021-02-24 09:43:57 +01:00
ce0140ef67 events: pass Event's user to Notification policy engine when present 2021-02-24 09:43:50 +01:00
bba43c5109 sources/oauth: fix buttons not being ak-root-link 2021-02-24 09:23:44 +01:00
d99a415502 web: fix library not being full height, again 2021-02-24 09:23:40 +01:00
bd48955f39 release: 2021.2.4-stable 2021-02-23 23:00:43 +01:00
53adcd9157 core: fix user-settings not loading sources 2021-02-23 22:55:08 +01:00
c5a2bb8914 admin: fix success_urls 2021-02-23 22:55:01 +01:00
7da90ff7e4 release: 2021.2.3-stable 2021-02-10 20:47:33 +01:00
61b5714652 docs: update release notes 2021-02-10 20:47:06 +01:00
d2df426489 core: fix tokens using wrong lookup 2021-02-10 20:32:54 +01:00
e6c75ed173 web: fix untranslated strings 2021-02-10 20:21:04 +01:00
a353c6956e web: fix missing source create button 2021-02-10 20:13:12 +01:00
a367d8515f core: add source endpoint 2021-02-10 20:12:07 +01:00
2b7a22a29a core: add providers/types endpoint 2021-02-10 20:11:54 +01:00
e6712a50d2 docs: update changelog 2021-02-10 13:45:24 +01:00
65 changed files with 493 additions and 172 deletions

View File

@ -1,5 +1,5 @@
[bumpversion]
current_version = 2021.2.2-stable
current_version = 2021.2.6-stable
tag = True
commit = True
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-(?P<release>.*)

View File

@ -18,11 +18,11 @@ jobs:
- name: Building Docker Image
run: docker build
--no-cache
-t beryju/authentik:2021.2.2-stable
-t beryju/authentik:2021.2.6-stable
-t beryju/authentik:latest
-f Dockerfile .
- name: Push Docker Container to Registry (versioned)
run: docker push beryju/authentik:2021.2.2-stable
run: docker push beryju/authentik:2021.2.6-stable
- name: Push Docker Container to Registry (latest)
run: docker push beryju/authentik:latest
build-proxy:
@ -48,11 +48,11 @@ jobs:
cd outpost/
docker build \
--no-cache \
-t beryju/authentik-proxy:2021.2.2-stable \
-t beryju/authentik-proxy:2021.2.6-stable \
-t beryju/authentik-proxy:latest \
-f proxy.Dockerfile .
- name: Push Docker Container to Registry (versioned)
run: docker push beryju/authentik-proxy:2021.2.2-stable
run: docker push beryju/authentik-proxy:2021.2.6-stable
- name: Push Docker Container to Registry (latest)
run: docker push beryju/authentik-proxy:latest
build-static:
@ -69,11 +69,11 @@ jobs:
cd web/
docker build \
--no-cache \
-t beryju/authentik-static:2021.2.2-stable \
-t beryju/authentik-static:2021.2.6-stable \
-t beryju/authentik-static:latest \
-f Dockerfile .
- name: Push Docker Container to Registry (versioned)
run: docker push beryju/authentik-static:2021.2.2-stable
run: docker push beryju/authentik-static:2021.2.6-stable
- name: Push Docker Container to Registry (latest)
run: docker push beryju/authentik-static:latest
test-release:
@ -107,5 +107,5 @@ jobs:
SENTRY_PROJECT: authentik
SENTRY_URL: https://sentry.beryju.org
with:
tagName: 2021.2.2-stable
tagName: 2021.2.6-stable
environment: beryjuorg-prod

View File

@ -1,2 +1,2 @@
"""authentik"""
__version__ = "2021.2.2-stable"
__version__ = "2021.2.6-stable"

View File

@ -41,6 +41,9 @@
{% endfor %}
</ul>
</ak-dropdown>
<button role="ak-refresh" class="pf-c-button pf-m-primary">
{% trans 'Refresh' %}
</button>
</div>
{% include 'partials/pagination.html' %}
</div>

View File

@ -27,6 +27,7 @@ class ApplicationCreateView(
form_class = ApplicationForm
permission_required = "authentik_core.add_application"
success_url = "/"
template_name = "generic/create.html"
success_message = _("Successfully created Application")
@ -44,6 +45,7 @@ class ApplicationUpdateView(
form_class = ApplicationForm
permission_required = "authentik_core.change_application"
success_url = "/"
template_name = "generic/update.html"
success_message = _("Successfully updated Application")
@ -56,5 +58,6 @@ class ApplicationDeleteView(
model = Application
permission_required = "authentik_core.delete_application"
success_url = "/"
template_name = "generic/delete.html"
success_message = _("Successfully deleted Application")

View File

@ -27,6 +27,7 @@ class NotificationRuleCreateView(
form_class = NotificationRuleForm
permission_required = "authentik_events.add_NotificationRule"
success_url = "/"
template_name = "generic/create.html"
success_message = _("Successfully created Notification Rule")
@ -44,6 +45,7 @@ class NotificationRuleUpdateView(
form_class = NotificationRuleForm
permission_required = "authentik_events.change_NotificationRule"
success_url = "/"
template_name = "generic/update.html"
success_message = _("Successfully updated Notification Rule")
@ -56,5 +58,6 @@ class NotificationRuleDeleteView(
model = NotificationRule
permission_required = "authentik_events.delete_NotificationRule"
success_url = "/"
template_name = "generic/delete.html"
success_message = _("Successfully deleted Notification Rule")

View File

@ -26,7 +26,7 @@ class NotificationTransportCreateView(
model = NotificationTransport
form_class = NotificationTransportForm
permission_required = "authentik_events.add_notificationtransport"
success_url = "/"
template_name = "generic/create.html"
success_message = _("Successfully created Notification Transport")
@ -43,7 +43,7 @@ class NotificationTransportUpdateView(
model = NotificationTransport
form_class = NotificationTransportForm
permission_required = "authentik_events.change_notificationtransport"
success_url = "/"
template_name = "generic/update.html"
success_message = _("Successfully updated Notification Transport")
@ -55,6 +55,6 @@ class NotificationTransportDeleteView(
model = NotificationTransport
permission_required = "authentik_events.delete_notificationtransport"
success_url = "/"
template_name = "generic/delete.html"
success_message = _("Successfully deleted Notification Transport")

View File

@ -29,7 +29,7 @@ class OutpostCreateView(
model = Outpost
form_class = OutpostForm
permission_required = "authentik_outposts.add_outpost"
success_url = "/"
template_name = "generic/create.html"
success_message = _("Successfully created Outpost")
@ -53,7 +53,7 @@ class OutpostUpdateView(
model = Outpost
form_class = OutpostForm
permission_required = "authentik_outposts.change_outpost"
success_url = "/"
template_name = "generic/update.html"
success_message = _("Successfully updated Outpost")
@ -63,6 +63,6 @@ class OutpostDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessa
model = Outpost
permission_required = "authentik_outposts.delete_outpost"
success_url = "/"
template_name = "generic/delete.html"
success_message = _("Successfully deleted Outpost")

View File

@ -18,7 +18,7 @@ class PolicyCacheClearView(AdminRequiredMixin, SuccessMessageMixin, FormView):
"""View to clear Policy cache"""
form_class = PolicyCacheClearForm
success_url = "/"
template_name = "generic/form_non_model.html"
success_message = _("Successfully cleared Policy cache")
@ -36,7 +36,7 @@ class FlowCacheClearView(AdminRequiredMixin, SuccessMessageMixin, FormView):
"""View to clear Flow cache"""
form_class = FlowCacheClearForm
success_url = "/"
template_name = "generic/form_non_model.html"
success_message = _("Successfully cleared Flow cache")

View File

@ -34,7 +34,7 @@ class PropertyMappingCreateView(
model = PropertyMapping
permission_required = "authentik_core.add_propertymapping"
success_url = "/"
template_name = "generic/create.html"
success_message = _("Successfully created Property Mapping")
@ -50,7 +50,7 @@ class PropertyMappingUpdateView(
model = PropertyMapping
permission_required = "authentik_core.change_propertymapping"
success_url = "/"
template_name = "generic/update.html"
success_message = _("Successfully updated Property Mapping")
@ -62,7 +62,7 @@ class PropertyMappingDeleteView(
model = PropertyMapping
permission_required = "authentik_core.delete_propertymapping"
success_url = "/"
template_name = "generic/delete.html"
success_message = _("Successfully deleted Property Mapping")

View File

@ -27,7 +27,7 @@ class ProviderCreateView(
model = Provider
permission_required = "authentik_core.add_provider"
success_url = "/"
template_name = "generic/create.html"
success_message = _("Successfully created Provider")
@ -43,7 +43,7 @@ class ProviderUpdateView(
model = Provider
permission_required = "authentik_core.change_provider"
success_url = "/"
template_name = "generic/update.html"
success_message = _("Successfully updated Provider")
@ -55,6 +55,6 @@ class ProviderDeleteView(
model = Provider
permission_required = "authentik_core.delete_provider"
success_url = "/"
template_name = "generic/delete.html"
success_message = _("Successfully deleted Provider")

View File

@ -28,6 +28,7 @@ class SourceCreateView(
model = Source
permission_required = "authentik_core.add_source"
success_url = "/"
template_name = "generic/create.html"
success_message = _("Successfully created Source")
@ -44,6 +45,7 @@ class SourceUpdateView(
model = Source
permission_required = "authentik_core.change_source"
success_url = "/"
template_name = "generic/update.html"
success_message = _("Successfully updated Source")
@ -54,5 +56,6 @@ class SourceDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessag
model = Source
permission_required = "authentik_core.delete_source"
success_url = "/"
template_name = "generic/delete.html"
success_message = _("Successfully deleted Source")

View File

@ -1,10 +1,18 @@
"""Provider API Views"""
from django.shortcuts import reverse
from django.utils.translation import gettext_lazy as _
from drf_yasg2.utils import swagger_auto_schema
from rest_framework.decorators import action
from rest_framework.fields import ReadOnlyField
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.serializers import ModelSerializer, SerializerMethodField
from rest_framework.viewsets import ModelViewSet
from authentik.core.api.utils import MetaNameSerializer
from authentik.core.api.utils import MetaNameSerializer, TypeCreateSerializer
from authentik.core.models import Provider
from authentik.lib.templatetags.authentik_utils import verbose_name
from authentik.lib.utils.reflection import all_subclasses
class ProviderSerializer(ModelSerializer, MetaNameSerializer):
@ -51,3 +59,26 @@ class ProviderViewSet(ModelViewSet):
def get_queryset(self):
return Provider.objects.select_subclasses()
@swagger_auto_schema(responses={200: TypeCreateSerializer(many=True)})
@action(detail=False)
def types(self, request: Request) -> Response:
"""Get all creatable provider types"""
data = []
for subclass in all_subclasses(self.queryset.model):
data.append(
{
"name": verbose_name(subclass),
"description": subclass.__doc__,
"link": reverse("authentik_admin:provider-create")
+ f"?type={subclass.__name__}",
}
)
data.append(
{
"name": _("SAML Provider from Metadata"),
"description": _("Create a SAML Provider by importing its Metadata."),
"link": reverse("authentik_admin:provider-saml-from-metadata"),
}
)
return Response(TypeCreateSerializer(data, many=True).data)

View File

@ -1,9 +1,16 @@
"""Source API Views"""
from django.shortcuts import reverse
from drf_yasg2.utils import swagger_auto_schema
from rest_framework.decorators import action
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.serializers import ModelSerializer, SerializerMethodField
from rest_framework.viewsets import ReadOnlyModelViewSet
from authentik.core.api.utils import MetaNameSerializer
from authentik.core.api.utils import MetaNameSerializer, TypeCreateSerializer
from authentik.core.models import Source
from authentik.lib.templatetags.authentik_utils import verbose_name
from authentik.lib.utils.reflection import all_subclasses
class SourceSerializer(ModelSerializer, MetaNameSerializer):
@ -40,3 +47,19 @@ class SourceViewSet(ReadOnlyModelViewSet):
def get_queryset(self):
return Source.objects.select_subclasses()
@swagger_auto_schema(responses={200: TypeCreateSerializer(many=True)})
@action(detail=False)
def types(self, request: Request) -> Response:
"""Get all creatable source types"""
data = []
for subclass in all_subclasses(self.queryset.model):
data.append(
{
"name": verbose_name(subclass),
"description": subclass.__doc__,
"link": reverse("authentik_admin:source-create")
+ f"?type={subclass.__name__}",
}
)
return Response(TypeCreateSerializer(data, many=True).data)

View File

@ -43,12 +43,12 @@ class TokenViewSet(ModelViewSet):
@swagger_auto_schema(responses={200: TokenViewSerializer(many=False)})
@action(detail=True)
# pylint: disable=unused-argument
def view_key(self, request: Request, identifier: str) -> Response:
"""Return token key and log access"""
tokens = Token.filter_not_expired(identifier=identifier)
if not tokens.exists():
token: Token = self.get_object()
if token.is_expired:
raise Http404
token = tokens.first()
Event.new(EventAction.SECRET_VIEW, secret=token).from_http( # noqa # nosec
request
)

View File

@ -1,5 +1,6 @@
"""API Utilities"""
from django.db.models import Model
from rest_framework.fields import CharField
from rest_framework.serializers import Serializer, SerializerMethodField
@ -22,3 +23,17 @@ class MetaNameSerializer(Serializer):
def get_verbose_name_plural(self, obj: Model) -> str:
"""Return object's plural verbose_name"""
return obj._meta.verbose_name_plural
class TypeCreateSerializer(Serializer):
"""Types of an object that can be created"""
name = CharField(read_only=True)
description = CharField(read_only=True)
link = CharField(read_only=True)
def create(self, validated_data: dict) -> Model:
raise NotImplementedError
def update(self, instance: Model, validated_data: dict) -> Model:
raise NotImplementedError

View File

@ -9,6 +9,7 @@ from django.http import HttpRequest, HttpResponse
SESSION_IMPERSONATE_USER = "authentik_impersonate_user"
SESSION_IMPERSONATE_ORIGINAL_USER = "authentik_impersonate_original_user"
LOCAL = local()
RESPONSE_HEADER_ID = "X-authentik-id"
class ImpersonateMiddleware:
@ -43,7 +44,7 @@ class RequestIDMiddleware:
setattr(request, "request_id", request_id)
LOCAL.authentik = {"request_id": request_id}
response = self.get_response(request)
response["X-authentik-id"] = request.request_id
response[RESPONSE_HEADER_ID] = request.request_id
del LOCAL.authentik["request_id"]
return response

View File

@ -41,8 +41,8 @@
</section>
{% endfor %}
{% user_sources as user_sources_loc %}
{% for source, source_link in user_sources_loc.item %}
<section slot="page-{{ source.pk }}" data-tab-title="{{ source|verbose_name }}" class="pf-c-page__main-section pf-m-no-padding-mobile">
{% for source, source_link in user_sources_loc.items %}
<section slot="page-{{ source.pk }}" data-tab-title="{{ source.name }}" class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-u-display-flex pf-u-justify-content-center">
<div class="pf-u-w-75">
<ak-site-shell url="{{ source_link }}">

View File

@ -2,6 +2,7 @@
from guardian.shortcuts import get_anonymous_user
from structlog import get_logger
from authentik.core.models import User
from authentik.events.models import (
Event,
Notification,
@ -29,7 +30,11 @@ def event_notification_handler(event_uuid: str):
@CELERY_APP.task()
def event_trigger_handler(event_uuid: str, trigger_name: str):
"""Check if policies attached to NotificationRule match event"""
event: Event = Event.objects.get(event_uuid=event_uuid)
events = Event.objects.filter(event_uuid=event_uuid)
if not events.exists():
LOGGER.warning("event doesn't exist yet or anymore", event_uuid=event_uuid)
return
event: Event = events.first()
trigger: NotificationRule = NotificationRule.objects.get(name=trigger_name)
if "policy_uuid" in event.context:
@ -53,7 +58,8 @@ def event_trigger_handler(event_uuid: str, trigger_name: str):
return
LOGGER.debug("e(trigger): checking if trigger applies", trigger=trigger)
policy_engine = PolicyEngine(trigger, get_anonymous_user())
user = User.objects.filter(pk=event.user.get("pk")).first() or get_anonymous_user()
policy_engine = PolicyEngine(trigger, user)
policy_engine.mode = PolicyEngineMode.MODE_OR
policy_engine.empty_result = False
policy_engine.use_cache = False

View File

@ -24,6 +24,6 @@ def pbflow_tester(file_name: str) -> Callable:
return tester
for flow_file in glob("website/static/flows/*.pbflow"):
for flow_file in glob("website/static/flows/*.akflow"):
method_name = Path(flow_file).stem.replace("-", "_").replace(".", "_")
setattr(TestTransferDocs, f"test_flow_{method_name}", pbflow_tester(flow_file))

View File

@ -152,7 +152,13 @@ class FlowImporter:
entries = deepcopy(self.__import.entries)
for entry in entries:
model_app_label, model_name = entry.model.split(".")
model: SerializerModel = apps.get_model(model_app_label, model_name)
try:
model: SerializerModel = apps.get_model(model_app_label, model_name)
except LookupError:
self.logger.error(
"app or model does not exist", app=model_app_label, model=model_name
)
return False
# Validate each single entry
try:
serializer = self._validate_single(entry)

View File

@ -2,6 +2,7 @@
from django import forms
from authentik.core.models import Group
from authentik.policies.forms import GENERAL_FIELDS
from authentik.policies.group_membership.models import GroupMembershipPolicy
@ -9,6 +10,8 @@ from authentik.policies.group_membership.models import GroupMembershipPolicy
class GroupMembershipPolicyForm(forms.ModelForm):
"""GroupMembershipPolicy Form"""
group = forms.ModelChoiceField(queryset=Group.objects.all().order_by("name"))
class Meta:
model = GroupMembershipPolicy

View File

@ -4,6 +4,7 @@ import binascii
import json
import time
from dataclasses import asdict, dataclass, field
from datetime import datetime
from hashlib import sha256
from typing import Any, Dict, List, Optional, Type
from urllib.parse import urlparse
@ -480,10 +481,14 @@ class RefreshToken(ExpiringModel, BaseGrantModel):
now + timedelta_from_string(self.provider.token_validity).seconds
)
# We use the timestamp of the user's last successful login (EventAction.LOGIN) for auth_time
auth_event = Event.objects.filter(
auth_events = Event.objects.filter(
action=EventAction.LOGIN, user=get_user(user)
).latest("created")
auth_time = int(dateformat.format(auth_event.created, "U"))
).order_by("-created")
# Fallback in case we can't find any login events
auth_time = datetime.now()
if auth_events.exists():
auth_time = auth_events.first().created
auth_time = int(dateformat.format(auth_time, "U"))
token = IDToken(
iss=self.provider.get_issuer(request),

View File

@ -18,6 +18,8 @@ from django.core.asgi import get_asgi_application
from sentry_sdk.integrations.asgi import SentryAsgiMiddleware
from structlog.stdlib import get_logger
from authentik.core.middleware import RESPONSE_HEADER_ID
# DJANGO_SETTINGS_MODULE is set in gunicorn.conf.py
defuse_stdlib()
@ -67,6 +69,7 @@ class ASGILogger:
status_code: int
start: float
content_length: int
request_id: str
def __init__(self, app: ASGIApp):
self.app = app
@ -75,23 +78,29 @@ class ASGILogger:
self.scope = scope
self.content_length = 0
self.headers = dict(scope.get("headers", []))
self.request_id = ""
async def send_hooked(message: Message) -> None:
"""Hooked send method, which records status code and content-length, and for the final
requests logs it"""
headers = dict(message.get("headers", []))
if "status" in message:
self.status_code = message["status"]
if b"Content-Length" in headers:
self.content_length += int(headers.get(b"Content-Length", b"0"))
if message["type"] == "http.response.start":
response_headers = dict(message["headers"])
self.request_id = response_headers.get(
RESPONSE_HEADER_ID.encode(), b""
).decode()
if message["type"] == "http.response.body" and not message.get(
"more_body", None
"more_body", True
):
runtime = int((time() - self.start) * 1000)
self.log(runtime)
self.log(runtime, request_id=self.request_id)
await send(message)
self.start = time()
@ -111,7 +120,7 @@ class ASGILogger:
# Check if header has multiple values, and use the first one
return client_ip.split(", ")[0]
def log(self, runtime: float):
def log(self, runtime: float, **kwargs):
"""Outpot access logs in a structured format"""
host = self._get_ip()
query_string = ""
@ -125,6 +134,7 @@ class ASGILogger:
status=self.status_code,
size=self.content_length / 1000 if self.content_length > 0 else 0,
runtime=runtime,
**kwargs,
)

View File

@ -1,5 +1,6 @@
"""Source API Views"""
from datetime import datetime
from time import time
from django.core.cache import cache
from django.db.models.base import Model
@ -68,7 +69,7 @@ class LDAPSourceViewSet(ModelViewSet):
def sync_status(self, request: Request, slug: str) -> Response:
"""Get source's sync status"""
source = self.get_object()
last_sync = cache.get(source.state_cache_prefix("last_sync"), None)
last_sync = cache.get(source.state_cache_prefix("last_sync"), time())
return Response(
LDAPSourceSyncStatusSerializer(
{"last_sync": datetime.fromtimestamp(last_sync)}

View File

@ -77,7 +77,8 @@ class LDAPPasswordChanger:
"""Change user's password"""
user_dn = user.attributes.get(LDAP_DISTINGUISHED_NAME, None)
if not user_dn:
raise AttributeError(f"User has no {LDAP_DISTINGUISHED_NAME} set.")
LOGGER.info(f"User has no {LDAP_DISTINGUISHED_NAME} set.")
return
self._source.connection.extend.microsoft.modify_password(user_dn, password)
def _ad_check_password_existing(self, password: str, user_dn: str) -> bool:

View File

@ -9,13 +9,13 @@
<div class="pf-c-card__body">
{% if connections.exists %}
<p>{% trans 'Connected.' %}</p>
<a class="pf-c-button pf-m-danger"
<a class="pf-c-button pf-m-danger ak-root-link"
href="{% url 'authentik_sources_oauth:oauth-client-disconnect' source_slug=source.slug %}">
{% trans 'Disconnect' %}
</a>
{% else %}
<p>Not connected.</p>
<a class="pf-c-button pf-m-primary"
<a class="pf-c-button pf-m-primary ak-root-link"
href="{% url 'authentik_sources_oauth:oauth-client-login' source_slug=source.slug %}">
{% trans 'Connect' %}
</a>

View File

@ -19,7 +19,7 @@ services:
networks:
- internal
server:
image: beryju/authentik:${AUTHENTIK_TAG:-2021.2.2-stable}
image: beryju/authentik:${AUTHENTIK_TAG:-2021.2.6-stable}
command: server
environment:
AUTHENTIK_REDIS__HOST: redis
@ -45,7 +45,7 @@ services:
env_file:
- .env
worker:
image: beryju/authentik:${AUTHENTIK_TAG:-2021.2.2-stable}
image: beryju/authentik:${AUTHENTIK_TAG:-2021.2.6-stable}
command: worker
networks:
- internal
@ -62,7 +62,7 @@ services:
env_file:
- .env
static:
image: beryju/authentik-static:${AUTHENTIK_TAG:-2021.2.2-stable}
image: beryju/authentik-static:${AUTHENTIK_TAG:-2021.2.6-stable}
networks:
- internal
labels:

View File

@ -4,7 +4,7 @@ name: authentik
home: https://goauthentik.io
sources:
- https://github.com/BeryJu/authentik
version: "2021.2.2-stable"
version: "2021.2.6-stable"
icon: https://raw.githubusercontent.com/BeryJu/authentik/master/web/icons/icon.svg
dependencies:
- name: postgresql

View File

@ -4,7 +4,7 @@
|-----------------------------------|-------------------------|-------------|
| image.name | beryju/authentik | Image used to run the authentik server and worker |
| image.name_static | beryju/authentik-static | Image used to run the authentik static server (CSS and JS Files) |
| image.tag | 2021.2.2-stable | Image tag |
| image.tag | 2021.2.6-stable | Image tag |
| image.pullPolicy | IfNotPresent | Image Pull Policy used for all deployments |
| serverReplicas | 1 | Replicas for the Server deployment |
| workerReplicas | 1 | Replicas for the Worker deployment |

View File

@ -99,10 +99,12 @@ spec:
httpGet:
path: /-/health/live/
port: http
initialDelaySeconds: 15
readinessProbe:
httpGet:
path: /-/health/ready/
port: http
initialDelaySeconds: 15
resources:
requests:
cpu: 100m

View File

@ -5,7 +5,7 @@ image:
name: beryju/authentik
name_static: beryju/authentik-static
name_outposts: beryju/authentik # Prefix used for Outpost deployments, Outpost type and version is appended
tag: 2021.2.2-stable
tag: 2021.2.6-stable
pullPolicy: IfNotPresent
serverReplicas: 1

View File

@ -49,12 +49,14 @@ func NewAPIController(pbURL url.URL, token string) *APIController {
// create the API client, with the transport
apiClient := client.New(transport, strfmt.Default)
log := log.WithField("logger", "authentik.outpost.ak-api-controller")
// Because we don't know the outpost UUID, we simply do a list and pick the first
// The service account this token belongs to should only have access to a single outpost
outposts, err := apiClient.Outposts.OutpostsOutpostsList(outposts.NewOutpostsOutpostsListParams(), auth)
if err != nil {
panic(err)
log.WithError(err).Panic("Failed to fetch configuration")
}
outpost := outposts.Payload.Results[0]
doGlobalSetup(outpost.Config.(map[string]interface{}))
@ -64,7 +66,7 @@ func NewAPIController(pbURL url.URL, token string) *APIController {
Auth: auth,
token: token,
logger: log.WithField("component", "ak-api-controller"),
logger: log,
reloadOffset: time.Duration(rand.Intn(10)) * time.Second,

View File

@ -40,7 +40,7 @@ func (ac *APIController) initWS(pbURL url.URL, outpostUUID strfmt.UUID) {
}
ws.Dial(fmt.Sprintf(pathTemplate, scheme, pbURL.Host, outpostUUID.String()), header)
ac.logger.WithField("component", "ak-ws").WithField("outpost", outpostUUID.String()).Debug("connecting to authentik")
ac.logger.WithField("logger", "authentik.outpost.ak-ws").WithField("outpost", outpostUUID.String()).Debug("connecting to authentik")
ac.wsConn = ws
// Send hello message with our version
@ -52,7 +52,7 @@ func (ac *APIController) initWS(pbURL url.URL, outpostUUID strfmt.UUID) {
}
err := ws.WriteJSON(msg)
if err != nil {
ac.logger.WithField("component", "ak-ws").WithError(err).Warning("Failed to hello to authentik")
ac.logger.WithField("logger", "authentik.outpost.ak-ws").WithError(err).Warning("Failed to hello to authentik")
}
}

View File

@ -13,7 +13,12 @@ import (
)
func doGlobalSetup(config map[string]interface{}) {
log.SetFormatter(&log.JSONFormatter{})
log.SetFormatter(&log.JSONFormatter{
FieldMap: log.FieldMap{
log.FieldKeyMsg: "event",
log.FieldKeyTime: "timestamp",
},
})
switch config[ConfigLogLevel].(string) {
case "debug":
log.SetLevel(log.DebugLevel)

View File

@ -31,7 +31,7 @@ func (s *Server) bundleProviders(providers []*models.ProxyOutpostConfig) []*prov
bundles[idx] = &providerBundle{
s: s,
Host: externalHost.Host,
log: log.WithField("component", "proxy-bundle").WithField("provider", provider.Name),
log: log.WithField("logger", "authentik.outpost.proxy-bundle").WithField("provider", provider.Name),
}
bundles[idx].Build(provider)
}

View File

@ -129,7 +129,7 @@ func (pb *providerBundle) Build(provider *models.ProxyOutpostConfig) {
log.Printf("%s", err)
os.Exit(1)
}
oauthproxy, err := NewOAuthProxy(opts)
oauthproxy, err := NewOAuthProxy(opts, provider)
if err != nil {
log.Errorf("ERROR: Failed to initialise OAuth2 Proxy: %v", err)
os.Exit(1)

View File

@ -95,7 +95,7 @@ type loggingHandler struct {
func LoggingHandler(h http.Handler) http.Handler {
return loggingHandler{
handler: h,
logger: log.WithField("component", "proxy-http-server"),
logger: log.WithField("logger", "authentik.outpost.proxy-http-server"),
}
}
@ -104,19 +104,17 @@ func (h loggingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
url := *req.URL
responseLogger := &responseLogger{w: w}
h.handler.ServeHTTP(responseLogger, req)
duration := float64(time.Since(t)) / float64(time.Second)
duration := float64(time.Since(t)) / float64(time.Millisecond)
h.logger.WithFields(log.Fields{
"Client": req.RemoteAddr,
"Host": req.Host,
"Protocol": req.Proto,
"RequestDuration": fmt.Sprintf("%0.3f", duration),
"RequestMethod": req.Method,
"ResponseSize": responseLogger.Size(),
"StatusCode": responseLogger.Status(),
"Timestamp": t,
"Upstream": responseLogger.upstream,
"UserAgent": req.UserAgent(),
"Username": responseLogger.authInfo,
"host": req.RemoteAddr,
"vhost": req.Host,
"request_protocol": req.Proto,
"runtime": fmt.Sprintf("%0.3f", duration),
"method": req.Method,
"size": responseLogger.Size(),
"status": responseLogger.Status(),
"upstream": responseLogger.upstream,
"request_useragent": req.UserAgent(),
"request_username": responseLogger.authInfo,
}).Info(url.RequestURI())
// logger.PrintReq(responseLogger.authInfo, responseLogger.upstream, req, url, t, , )
}

View File

@ -21,6 +21,7 @@ import (
"github.com/oauth2-proxy/oauth2-proxy/pkg/sessions"
"github.com/oauth2-proxy/oauth2-proxy/pkg/upstream"
"github.com/oauth2-proxy/oauth2-proxy/providers"
"goauthentik.io/outpost/pkg/models"
log "github.com/sirupsen/logrus"
)
@ -92,8 +93,8 @@ type OAuthProxy struct {
}
// NewOAuthProxy creates a new instance of OAuthProxy from the options provided
func NewOAuthProxy(opts *options.Options) (*OAuthProxy, error) {
logger := log.WithField("component", "proxy").WithField("client-id", opts.ClientID)
func NewOAuthProxy(opts *options.Options, provider *models.ProxyOutpostConfig) (*OAuthProxy, error) {
logger := log.WithField("logger", "authentik.outpost.proxy").WithField("provider", provider.Name)
sessionStore, err := sessions.NewSessionStore(&opts.Session, &opts.Cookie)
if err != nil {
return nil, fmt.Errorf("error initialising session store: %v", err)
@ -434,6 +435,7 @@ func (p *OAuthProxy) addHeadersForProxying(rw http.ResponseWriter, req *http.Req
authVal := b64.StdEncoding.EncodeToString([]byte(username + ":" + password))
req.Header["Authorization"] = []string{fmt.Sprintf("Basic %s", authVal)}
}
rw.Header().Set("GAP-Auth", session.PreferredUsername)
// Check if user has additional headers set that we should sent
if additionalHeaders, ok := userAttributes["additionalHeaders"].(map[string]string); ok {
if additionalHeaders == nil {

View File

@ -6,6 +6,7 @@ import (
"errors"
"net"
"net/http"
"strings"
"time"
log "github.com/sirupsen/logrus"
@ -30,7 +31,7 @@ func NewServer(ac *ak.APIController) *Server {
}
return &Server{
Handlers: make(map[string]*providerBundle),
logger: log.WithField("component", "proxy-http-server"),
logger: log.WithField("logger", "authentik.outpost.proxy-http-server"),
defaultCert: defaultCert,
ak: ac,
}
@ -50,12 +51,15 @@ func (s *Server) handler(w http.ResponseWriter, r *http.Request) {
return
}
}
s.logger.WithField("host", r.Host).Debug("Host header does not match any we know of")
s.logger.Printf("%v+\n", s.Handlers)
w.WriteHeader(400)
// Get a list of all host keys we know
hostKeys := make([]string, 0, len(s.Handlers))
for k := range s.Handlers {
hostKeys = append(hostKeys, k)
}
s.logger.WithField("host", r.Host).WithField("known-hosts", strings.Join(hostKeys, ", ")).Debug("Host header does not match any we know of")
w.WriteHeader(404)
return
}
s.logger.WithField("host", r.Host).Debug("passing request from host head")
handler.ServeHTTP(w, r)
}

View File

@ -1,3 +1,3 @@
package pkg
const VERSION = "2021.2.2-stable"
const VERSION = "2021.2.6-stable"

View File

@ -4312,6 +4312,47 @@ paths:
tags:
- providers
parameters: []
/providers/all/types/:
get:
operationId: providers_all_types
description: Get all creatable provider types
parameters:
- name: application__isnull
in: query
description: ''
required: false
type: string
- name: ordering
in: query
description: Which field to use when ordering the results.
required: false
type: string
- name: search
in: query
description: A search term.
required: false
type: string
- name: page
in: query
description: A page number within the paginated result set.
required: false
type: integer
- name: page_size
in: query
description: Number of results to return per page.
required: false
type: integer
responses:
'200':
description: Types of an object that can be created
schema:
description: ''
type: array
items:
$ref: '#/definitions/TypeCreate'
tags:
- providers
parameters: []
/providers/all/{id}/:
get:
operationId: providers_all_read
@ -4868,6 +4909,42 @@ paths:
tags:
- sources
parameters: []
/sources/all/types/:
get:
operationId: sources_all_types
description: Get all creatable source types
parameters:
- name: ordering
in: query
description: Which field to use when ordering the results.
required: false
type: string
- name: search
in: query
description: A search term.
required: false
type: string
- name: page
in: query
description: A page number within the paginated result set.
required: false
type: integer
- name: page_size
in: query
description: Number of results to return per page.
required: false
type: integer
responses:
'200':
description: Types of an object that can be created
schema:
description: ''
type: array
items:
$ref: '#/definitions/TypeCreate'
tags:
- sources
parameters: []
/sources/all/{slug}/:
get:
operationId: sources_all_read
@ -9005,6 +9082,25 @@ definitions:
title: Verbose name plural
type: string
readOnly: true
TypeCreate:
description: Types of an object that can be created
type: object
properties:
name:
title: Name
type: string
readOnly: true
minLength: 1
description:
title: Description
type: string
readOnly: true
minLength: 1
link:
title: Link
type: string
readOnly: true
minLength: 1
OAuth2Provider:
description: OAuth2Provider Serializer
required:

39
web/package-lock.json generated
View File

@ -245,6 +245,24 @@
}
}
},
"@sentry/integrations": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/@sentry/integrations/-/integrations-6.2.0.tgz",
"integrity": "sha512-gvAhP61qs2fog2xCTDs94ZT8cZbWEjFZmOWfT1VXlZDIVopIporj5Qe6IgrLTiCWL61Yko5h5nFnPZ4mpjf+0w==",
"requires": {
"@sentry/types": "6.2.0",
"@sentry/utils": "6.2.0",
"localforage": "^1.8.1",
"tslib": "^1.9.3"
},
"dependencies": {
"tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
}
}
},
"@sentry/minimal": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.1.0.tgz",
@ -1756,6 +1774,11 @@
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz",
"integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw=="
},
"immediate": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
"integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps="
},
"import-fresh": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
@ -2010,6 +2033,14 @@
"type-check": "~0.4.0"
}
},
"lie": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz",
"integrity": "sha1-mkNrLMd0bKWd56QfpGmz77dr2H4=",
"requires": {
"immediate": "~3.0.5"
}
},
"lit-analyzer": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/lit-analyzer/-/lit-analyzer-1.2.1.tgz",
@ -2191,6 +2222,14 @@
"resolved": "https://registry.npmjs.org/lit-html/-/lit-html-1.3.0.tgz",
"integrity": "sha512-0Q1bwmaFH9O14vycPHw8C/IeHMk/uSDldVLIefu/kfbTBGIc44KGH6A8p1bDfxUfHdc8q6Ct7kQklWoHgr4t1Q=="
},
"localforage": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/localforage/-/localforage-1.9.0.tgz",
"integrity": "sha512-rR1oyNrKulpe+VM9cYmcFn6tsHuokyVHFaCM3+osEmxaHTbEk8oQu6eGDfS6DQLWi/N67XRmB8ECG37OES368g==",
"requires": {
"lie": "3.1.1"
}
},
"locate-path": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",

View File

@ -14,7 +14,8 @@
"@patternfly/patternfly": "^4.80.3",
"@sentry/browser": "^6.1.0",
"@sentry/tracing": "^6.1.0",
"@types/chart.js": "^2.9.30",
"@sentry/integrations": "^6.2.0",
"@types/chart.js": "^2.9.31",
"@types/codemirror": "0.0.108",
"chart.js": "^2.9.4",
"codemirror": "^5.59.2",

View File

@ -3,6 +3,7 @@ import * as Sentry from "@sentry/browser";
import { Integrations } from "@sentry/tracing";
import { VERSION } from "../constants";
import { SentryIgnoredError } from "../common/errors";
import { CaptureConsole as CaptureConsoleIntegration } from "@sentry/integrations";
export class Config {
branding_logo: string;
@ -22,7 +23,10 @@ export class Config {
Sentry.init({
dsn: "https://a579bb09306d4f8b8d8847c052d3a1d3@sentry.beryju.org/8",
release: `authentik@${VERSION}`,
integrations: [new Integrations.BrowserTracing()],
integrations: [
new Integrations.BrowserTracing(),
new CaptureConsoleIntegration(),
],
tracesSampleRate: 0.6,
environment: config.error_reporting_environment,
beforeSend(event: Sentry.Event, hint: Sentry.EventHint) {

View File

@ -1,5 +1,11 @@
import { BaseInheritanceModel, DefaultClient, AKResponse, QueryArguments } from "./Client";
export interface TypeCreate {
name: string;
description: string;
link: string;
}
export class Provider implements BaseInheritanceModel {
pk: number;
name: string;
@ -24,6 +30,10 @@ export class Provider implements BaseInheritanceModel {
return DefaultClient.fetch<AKResponse<Provider>>(["providers", "all"], filter);
}
static getTypes(): Promise<TypeCreate[]> {
return DefaultClient.fetch<TypeCreate[]>(["providers", "all", "types"]);
}
static adminUrl(rest: string): string {
return `/administration/providers/${rest}`;
}

View File

@ -1,4 +1,5 @@
import { BaseInheritanceModel, DefaultClient, AKResponse, QueryArguments } from "./Client";
import { TypeCreate } from "./Providers";
export class Source implements BaseInheritanceModel {
pk: string;
@ -23,6 +24,10 @@ export class Source implements BaseInheritanceModel {
return DefaultClient.fetch<AKResponse<Source>>(["sources", "all"], filter);
}
static getTypes(): Promise<TypeCreate[]> {
return DefaultClient.fetch<TypeCreate[]>(["sources", "all", "types"]);
}
static adminUrl(rest: string): string {
return `/administration/sources/${rest}`;
}

View File

@ -1,3 +1,4 @@
import { css, CSSResult } from "lit-element";
// @ts-ignore
import PF from "@patternfly/patternfly/patternfly.css";
// @ts-ignore
@ -6,10 +7,31 @@ import PFAddons from "@patternfly/patternfly/patternfly-addons.css";
import FA from "@fortawesome/fontawesome-free/css/fontawesome.css";
// @ts-ignore
import AKGlobal from "../authentik.css";
import { CSSResult } from "lit-element";
// @ts-ignore
import CodeMirrorStyle from "codemirror/lib/codemirror.css";
// @ts-ignore
import CodeMirrorTheme from "codemirror/theme/monokai.css";
export const COMMON_STYLES: CSSResult[] = [PF, PFAddons, FA, AKGlobal, CodeMirrorStyle, CodeMirrorTheme];
export const ColorStyles = css`
.pf-m-success {
color: var(--pf-global--success-color--100);
}
.pf-c-button.pf-m-success {
color: var(--pf-c-button--m-primary--Color);
background-color: var(--pf-global--success-color--100);
}
.pf-m-warning {
color: var(--pf-global--warning-color--100);
}
.pf-c-button.pf-m-warning {
color: var(--pf-c-button--m-primary--Color);
background-color: var(--pf-global--warning-color--100);
}
.pf-m-danger {
color: var(--pf-global--danger-color--100);
}
.pf-c-button.pf-m-danger {
color: var(--pf-c-button--m-primary--Color);
background-color: var(--pf-global--danger-color--100);
}
`;
export const COMMON_STYLES: CSSResult[] = [PF, PFAddons, FA, AKGlobal, CodeMirrorStyle, CodeMirrorTheme, ColorStyles];

View File

@ -1,31 +1,6 @@
import { css } from "lit-element";
export const PRIMARY_CLASS = "pf-m-primary";
export const SUCCESS_CLASS = "pf-m-success";
export const ERROR_CLASS = "pf-m-danger";
export const PROGRESS_CLASS = "pf-m-in-progress";
export const CURRENT_CLASS = "pf-m-current";
export const ColorStyles = css`
.pf-m-success {
color: var(--pf-global--success-color--100);
}
.pf-c-button.pf-m-success {
color: var(--pf-c-button--m-primary--Color);
background-color: var(--pf-global--success-color--100);
}
.pf-m-warning {
color: var(--pf-global--warning-color--100);
}
.pf-c-button.pf-m-warning {
color: var(--pf-c-button--m-primary--Color);
background-color: var(--pf-global--warning-color--100);
}
.pf-m-danger {
color: var(--pf-global--danger-color--100);
}
.pf-c-button.pf-m-danger {
color: var(--pf-c-button--m-primary--Color);
background-color: var(--pf-global--danger-color--100);
}
`;
export const VERSION = "2021.2.2-stable";
export const VERSION = "2021.2.6-stable";

View File

@ -5,7 +5,8 @@ import GlobalsStyle from "@patternfly/patternfly/base/patternfly-globals.css";
import ButtonStyle from "@patternfly/patternfly/components/Button/button.css";
// @ts-ignore
import SpinnerStyle from "@patternfly/patternfly/components/Spinner/spinner.css";
import { ColorStyles, PRIMARY_CLASS, PROGRESS_CLASS } from "../../constants";
import { PRIMARY_CLASS, PROGRESS_CLASS } from "../../constants";
import { ColorStyles } from "../../common/styles";
@customElement("ak-spinner-button")
export class SpinnerButton extends LitElement {

View File

@ -4,7 +4,8 @@ import GlobalsStyle from "@patternfly/patternfly/base/patternfly-globals.css";
// @ts-ignore
import ButtonStyle from "@patternfly/patternfly/components/Button/button.css";
import { tokenByIdentifier } from "../../api/Tokens";
import { ColorStyles, ERROR_CLASS, PRIMARY_CLASS, SUCCESS_CLASS } from "../../constants";
import { ERROR_CLASS, PRIMARY_CLASS, SUCCESS_CLASS } from "../../constants";
import { ColorStyles } from "../../common/styles";
@customElement("ak-token-copy-button")
export class TokenCopyButton extends LitElement {

View File

@ -2,7 +2,6 @@ import { gettext } from "django";
import { css, CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element";
import { ifDefined } from "lit-html/directives/if-defined";
import { COMMON_STYLES } from "../../common/styles";
import { ColorStyles } from "../../constants";
@customElement("ak-aggregate-card")
export class AggregateCard extends LitElement {
@ -25,7 +24,7 @@ export class AggregateCard extends LitElement {
text-align: center;
color: var(--pf-global--Color--100);
}
`, ColorStyles]);
`]);
}
renderInner(): TemplateResult {

View File

@ -41,13 +41,13 @@ export class BoundPoliciesList extends Table<PolicyBinding> {
html`
<ak-modal-button href="${PolicyBinding.adminUrl(`${item.pk}/update/`)}">
<ak-spinner-button slot="trigger" class="pf-m-secondary">
Edit
${gettext("Edit")}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>&nbsp;
<ak-modal-button href="${PolicyBinding.adminUrl(`${item.pk}/delete/`)}">
<ak-spinner-button slot="trigger" class="pf-m-danger">
Delete
${gettext("Delete")}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>

View File

@ -3,7 +3,6 @@ import { css, CSSResult, customElement, html, LitElement, property, TemplateResu
import CodeMirrorStyle from "codemirror/lib/codemirror.css";
// @ts-ignore
import CodeMirrorTheme from "codemirror/theme/monokai.css";
import { ColorStyles } from "../../constants";
import { COMMON_STYLES } from "../../common/styles";
import { Route } from "./Route";
import { ROUTES } from "../../routes";
@ -23,7 +22,6 @@ export class RouterOutlet extends LitElement {
return [
CodeMirrorStyle,
CodeMirrorTheme,
ColorStyles,
css`
:host {
height: 100vh;

View File

@ -55,7 +55,12 @@ export class LibraryPage extends LitElement {
apps?: AKResponse<Application>;
static get styles(): CSSResult[] {
return COMMON_STYLES;
return COMMON_STYLES.concat(css`
:host,
main {
height: 100%;
}
`);
}
firstUpdated(): void {

View File

@ -62,13 +62,13 @@ export class ApplicationListPage extends TablePage<Application> {
html`
<ak-modal-button href="${Application.adminUrl(`${item.pk}/update/`)}">
<ak-spinner-button slot="trigger" class="pf-m-secondary">
Edit
${gettext("Edit")}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>&nbsp;
<ak-modal-button href="${Application.adminUrl(`${item.pk}/delete/`)}">
<ak-spinner-button slot="trigger" class="pf-m-danger">
Delete
${gettext("Delete")}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>

View File

@ -42,13 +42,13 @@ export class BoundStagesList extends Table<FlowStageBinding> {
html`
<ak-modal-button href="${FlowStageBinding.adminUrl(`${item.pk}/update/`)}">
<ak-spinner-button slot="trigger" class="pf-m-secondary">
Edit
${gettext("Edit")}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
<ak-modal-button href="${FlowStageBinding.adminUrl(`${item.pk}/delete/`)}">
<ak-spinner-button slot="trigger" class="pf-m-danger">
Delete
${gettext("Delete")}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>

View File

@ -89,10 +89,14 @@ export class SiteShell extends LitElement {
if (a.href === "") {
return;
}
if (a.href.startsWith("#")) {
return;
}
try {
const url = new URL(a.href);
const qs = url.search || "";
a.href = `#${url.pathname}${qs}`;
const hash = (url.hash || "#").substring(2, Infinity);
a.href = `#${url.pathname}${qs}${hash}`;
} catch (e) {
console.debug(`authentik/site-shell: error ${e}`);
a.href = `#${a.href}`;

View File

@ -52,13 +52,13 @@ export class OutpostListPage extends TablePage<Outpost> {
})}</ul>`,
html`<ak-outpost-health outpostId=${item.pk}></ak-outpost-health>`,
html`
<ak-modal-button href="${Outpost.adminUrl(`${item.pk}/update`)}">
<ak-modal-button href="${Outpost.adminUrl(`${item.pk}/update/`)}">
<ak-spinner-button slot="trigger" class="pf-m-secondary">
${gettext("Edit")}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>&nbsp;
<ak-modal-button href="${Outpost.adminUrl(`${item.pk}/delete`)}">
<ak-modal-button href="${Outpost.adminUrl(`${item.pk}/delete/`)}">
<ak-spinner-button slot="trigger" class="pf-m-danger">
${gettext("Delete")}
</ak-spinner-button>

View File

@ -8,6 +8,7 @@ import "../../elements/buttons/ModalButton";
import "../../elements/buttons/SpinnerButton";
import "../../elements/buttons/Dropdown";
import { TableColumn } from "../../elements/table/Table";
import { until } from "lit-html/directives/until";
@customElement("ak-provider-list")
export class ProviderListPage extends TablePage<Provider> {
@ -81,46 +82,18 @@ export class ProviderListPage extends TablePage<Provider> {
<i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i>
</button>
<ul class="pf-c-dropdown__menu" hidden>
<li>
<ak-modal-button href="${Provider.adminUrl("/create/?type=OAuth2Provider")}">
<button slot="trigger" class="pf-c-dropdown__menu-item">${gettext("OAuth2/OpenID Provider")}<br>
<small>
${gettext("OAuth2 Provider for generic OAuth and OpenID Connect Applications.")}
</small>
</button>
<div slot="modal"></div>
</ak-modal-button>
</li>
<li>
<ak-modal-button href="${Provider.adminUrl("/create/?type=ProxyProvider")}">
<button slot="trigger" class="pf-c-dropdown__menu-item">${gettext("Proxy Provider")}<br>
<small>
${gettext("Protect applications that don't support any of the other Protocols by using a Reverse-Proxy.")}
</small>
</button>
<div slot="modal"></div>
</ak-modal-button>
</li>
<li>
<ak-modal-button href="${Provider.adminUrl("/create/?type=SAMLProvider")}">
<button slot="trigger" class="pf-c-dropdown__menu-item">${gettext("SAML Provider")}<br>
<small>
${gettext("SAML 2.0 Endpoint for applications which support SAML.")}
</small>
</button>
<div slot="modal"></div>
</ak-modal-button>
</li>
<li>
<ak-modal-button href="${Provider.adminUrl("/create/saml/from-metadata/")}">
<button slot="trigger" class="pf-c-dropdown__menu-item">${gettext("SAML Provider from Metadata")}<br>
<small>
${gettext("Create a SAML Provider by importing its Metadata.")}
</small>
</button>
<div slot="modal"></div>
</ak-modal-button>
</li>
${until(Provider.getTypes().then((types) => {
return types.map((type) => {
return html`<li>
<ak-modal-button href="${type.link}">
<button slot="trigger" class="pf-c-dropdown__menu-item">${type.name}<br>
<small>${type.description}</small>
</button>
<div slot="modal"></div>
</ak-modal-button>
</li>`;
});
}), html`<ak-spinner></ak-spinner>`)}
</ul>
</ak-dropdown>
${super.renderToolbar()}`;

View File

@ -7,6 +7,8 @@ import { TablePage } from "../../elements/table/TablePage";
import "../../elements/buttons/ModalButton";
import "../../elements/buttons/SpinnerButton";
import "../../elements/buttons/Dropdown";
import { until } from "lit-html/directives/until";
@customElement("ak-source-list")
export class SourceListPage extends TablePage<Source> {
@ -52,13 +54,13 @@ export class SourceListPage extends TablePage<Source> {
html`
<ak-modal-button href="${Source.adminUrl(`${item.pk}/update/`)}">
<ak-spinner-button slot="trigger" class="pf-m-secondary">
Edit
${gettext("Edit")}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>&nbsp;
<ak-modal-button href="${Source.adminUrl(`${item.pk}/delete/`)}">
<ak-spinner-button slot="trigger" class="pf-m-danger">
Delete
${gettext("Delete")}
</ak-spinner-button>
<div slot="modal"></div>
</ak-modal-button>
@ -66,4 +68,29 @@ export class SourceListPage extends TablePage<Source> {
];
}
renderToolbar(): TemplateResult {
return html`
<ak-dropdown class="pf-c-dropdown">
<button class="pf-m-primary pf-c-dropdown__toggle" type="button">
<span class="pf-c-dropdown__toggle-text">${gettext("Create")}</span>
<i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i>
</button>
<ul class="pf-c-dropdown__menu" hidden>
${until(Source.getTypes().then((types) => {
return types.map((type) => {
return html`<li>
<ak-modal-button href="${type.link}">
<button slot="trigger" class="pf-c-dropdown__menu-item">${type.name}<br>
<small>${type.description}</small>
</button>
<div slot="modal"></div>
</ak-modal-button>
</li>`;
});
}), html`<ak-spinner></ak-spinner>`)}
</ul>
</ak-dropdown>
${super.renderToolbar()}`;
}
}

View File

@ -15,7 +15,7 @@ Download the latest `docker-compose.yml` from [here](https://raw.githubuserconte
To optionally enable error-reporting, run `echo AUTHENTIK_ERROR_REPORTING__ENABLED=true >> .env`
To optionally deploy a different version run `echo AUTHENTIK_TAG=2021.2.2-stable >> .env`
To optionally deploy a different version run `echo AUTHENTIK_TAG=2021.2.6-stable >> .env`
If this is a fresh authentik install run the following commands to generate a password:

View File

@ -24,7 +24,7 @@ image:
name: beryju/authentik
name_static: beryju/authentik-static
name_outposts: beryju/authentik # Prefix used for Outpost deployments, Outpost type and version is appended
tag: 2021.2.2-stable
tag: 2021.2.6-stable
serverReplicas: 1
workerReplicas: 1

View File

@ -57,6 +57,26 @@ title: Release 2021.1.2
- providers/oauth2: add unofficial groups attribute to default profile claim
- web: fix sidebar being active when stage prompts is selected
## Fixed in 2021.2.2-stable
- crypto: move certificate and key data to separate api calls to create events
- events: rename context.token to context.secret
- events: rename token_view to secret_view
- lib: fix stacktrace for general expressions
- outposts: fix ProxyProvider update not triggering outpost update
- policies: skip cache on debug request
- providers/proxy: fix certificates without key being selectable
- root: log runtime in milliseconds
- sources/*: switch API to use slug in URL
- sources/ldap: add API for sync status
- sources/oauth: add callback URL to api
- web: fix ModalButton working in global scope, causing issues on 2nd use
## Fixed in 2021.2.3-stable
- core: fix tokens using wrong lookup
- web: fix missing source create button
## Upgrading
This release does not introduce any new requirements.

View File

@ -0,0 +1,14 @@
---
title: Next release
---
## Headline Changes
- Simplify role-based access
Instead of having to create a Group Membership policy for every group you want to use, you can now select a Group and even a User directly in a binding.
When a group is selected, the binding behaves the same as if a Group Membership policy exists.
When a user is selected, the binding checks the user of the request, and denies the request when the user doesn't match.