diff --git a/README.md b/README.md
index 1c6c3f51f..b624e6d5f 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,6 @@
-
+
-
{% trans 'Skip to content' %}
- {% block page_content %}
- {% endblock %}
-
-{% endblock %}
diff --git a/authentik/core/templates/error/generic.html b/authentik/core/templates/error/generic.html
index 17ba3a5a2..ca1d53de5 100644
--- a/authentik/core/templates/error/generic.html
+++ b/authentik/core/templates/error/generic.html
@@ -1,4 +1,4 @@
-{% extends 'base/page.html' %}
+{% extends 'base/skeleton.html' %}
{% load i18n %}
{% load authentik_utils %}
diff --git a/authentik/core/views/user.py b/authentik/core/views/user.py
index 13939ca4f..8554a9bc1 100644
--- a/authentik/core/views/user.py
+++ b/authentik/core/views/user.py
@@ -7,7 +7,6 @@ from django.contrib.auth.mixins import (
)
from django.contrib.messages.views import SuccessMessageMixin
from django.http.response import HttpResponse
-from django.urls import reverse_lazy
from django.utils.translation import gettext as _
from django.views.generic import UpdateView
from django.views.generic.base import TemplateView
@@ -35,7 +34,7 @@ class UserDetailsView(SuccessMessageMixin, LoginRequiredMixin, UpdateView):
form_class = UserDetailForm
success_message = _("Successfully updated user.")
- success_url = reverse_lazy("authentik_core:user-details")
+ success_url = "/"
def get_object(self):
return self.request.user
@@ -62,7 +61,7 @@ class TokenCreateView(
permission_required = "authentik_core.add_token"
template_name = "generic/create.html"
- success_url = reverse_lazy("authentik_core:user-tokens")
+ success_url = "/"
success_message = _("Successfully created Token")
def form_valid(self, form: UserTokenForm) -> HttpResponse:
@@ -80,7 +79,7 @@ class TokenUpdateView(
form_class = UserTokenForm
permission_required = "authentik_core.change_token"
template_name = "generic/update.html"
- success_url = reverse_lazy("authentik_core:user-tokens")
+ success_url = "/"
success_message = _("Successfully updated Token")
def get_object(self) -> Token:
@@ -100,7 +99,7 @@ class TokenDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteMessage
model = Token
permission_required = "authentik_core.delete_token"
template_name = "generic/delete.html"
- success_url = reverse_lazy("authentik_core:user-tokens")
+ success_url = "/"
success_message = _("Successfully deleted Token")
def get_object(self) -> Token:
diff --git a/authentik/lib/utils/urls.py b/authentik/lib/utils/urls.py
index 302fabd36..5dc92a753 100644
--- a/authentik/lib/utils/urls.py
+++ b/authentik/lib/utils/urls.py
@@ -2,8 +2,8 @@
from urllib.parse import urlparse
from django.http import HttpResponse
-from django.shortcuts import redirect, reverse
-from django.urls import NoReverseMatch
+from django.shortcuts import redirect
+from django.urls import NoReverseMatch, reverse
from django.utils.http import urlencode
from structlog.stdlib import get_logger
diff --git a/authentik/policies/event_matcher/migrations/0011_auto_20210302_0856.py b/authentik/policies/event_matcher/migrations/0011_auto_20210302_0856.py
new file mode 100644
index 000000000..6d4d58cd8
--- /dev/null
+++ b/authentik/policies/event_matcher/migrations/0011_auto_20210302_0856.py
@@ -0,0 +1,87 @@
+# Generated by Django 3.1.7 on 2021-03-02 08:56
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("authentik_policies_event_matcher", "0010_auto_20210222_1821"),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name="eventmatcherpolicy",
+ name="app",
+ field=models.TextField(
+ blank=True,
+ choices=[
+ ("authentik.admin", "authentik Admin"),
+ ("authentik.api", "authentik API"),
+ ("authentik.events", "authentik Events"),
+ ("authentik.crypto", "authentik Crypto"),
+ ("authentik.flows", "authentik Flows"),
+ ("authentik.outposts", "authentik Outpost"),
+ ("authentik.lib", "authentik lib"),
+ ("authentik.policies", "authentik Policies"),
+ ("authentik.policies.dummy", "authentik Policies.Dummy"),
+ (
+ "authentik.policies.event_matcher",
+ "authentik Policies.Event Matcher",
+ ),
+ ("authentik.policies.expiry", "authentik Policies.Expiry"),
+ ("authentik.policies.expression", "authentik Policies.Expression"),
+ (
+ "authentik.policies.group_membership",
+ "authentik Policies.Group Membership",
+ ),
+ ("authentik.policies.hibp", "authentik Policies.HaveIBeenPwned"),
+ ("authentik.policies.password", "authentik Policies.Password"),
+ ("authentik.policies.reputation", "authentik Policies.Reputation"),
+ ("authentik.providers.proxy", "authentik Providers.Proxy"),
+ ("authentik.providers.oauth2", "authentik Providers.OAuth2"),
+ ("authentik.providers.saml", "authentik Providers.SAML"),
+ ("authentik.recovery", "authentik Recovery"),
+ ("authentik.sources.ldap", "authentik Sources.LDAP"),
+ ("authentik.sources.oauth", "authentik Sources.OAuth"),
+ ("authentik.sources.saml", "authentik Sources.SAML"),
+ (
+ "authentik.stages.authenticator_static",
+ "authentik Stages.Authenticator.Static",
+ ),
+ (
+ "authentik.stages.authenticator_totp",
+ "authentik Stages.Authenticator.TOTP",
+ ),
+ (
+ "authentik.stages.authenticator_validate",
+ "authentik Stages.Authenticator.Validate",
+ ),
+ (
+ "authentik.stages.authenticator_webauthn",
+ "authentik Stages.Authenticator.WebAuthn",
+ ),
+ ("authentik.stages.captcha", "authentik Stages.Captcha"),
+ ("authentik.stages.consent", "authentik Stages.Consent"),
+ ("authentik.stages.deny", "authentik Stages.Deny"),
+ ("authentik.stages.dummy", "authentik Stages.Dummy"),
+ ("authentik.stages.email", "authentik Stages.Email"),
+ (
+ "authentik.stages.identification",
+ "authentik Stages.Identification",
+ ),
+ ("authentik.stages.invitation", "authentik Stages.User Invitation"),
+ ("authentik.stages.password", "authentik Stages.Password"),
+ ("authentik.stages.prompt", "authentik Stages.Prompt"),
+ ("authentik.stages.user_delete", "authentik Stages.User Delete"),
+ ("authentik.stages.user_login", "authentik Stages.User Login"),
+ ("authentik.stages.user_logout", "authentik Stages.User Logout"),
+ ("authentik.stages.user_write", "authentik Stages.User Write"),
+ ("authentik.managed", "authentik Managed"),
+ ("authentik.core", "authentik Core"),
+ ],
+ default="",
+ help_text="Match events created by selected application. When left empty, all applications are matched.",
+ ),
+ ),
+ ]
diff --git a/authentik/policies/views.py b/authentik/policies/views.py
index c40454b9d..36184b0de 100644
--- a/authentik/policies/views.py
+++ b/authentik/policies/views.py
@@ -62,11 +62,15 @@ class PolicyAccessView(AccessMixin, View):
return self.handle_no_permission()
try:
self.resolve_provider_application()
- except (Application.DoesNotExist, Provider.DoesNotExist):
- return self.handle_no_permission_authenticated()
+ except (Application.DoesNotExist, Provider.DoesNotExist) as exc:
+ LOGGER.warning("failed to resolve application", exc=exc)
+ return self.handle_no_permission_authenticated(
+ PolicyResult(False, _("Failed to resolve application"))
+ )
# Check if user is unauthenticated, so we pass the application
# for the identification stage
if not request.user.is_authenticated:
+ LOGGER.warning("user not authenticated")
return self.handle_no_permission()
# Check permissions
result = self.user_has_access()
diff --git a/authentik/providers/oauth2/api.py b/authentik/providers/oauth2/api.py
index 7e6ef24ee..84982d475 100644
--- a/authentik/providers/oauth2/api.py
+++ b/authentik/providers/oauth2/api.py
@@ -45,6 +45,7 @@ class OAuth2ProviderSetupURLs(Serializer):
token = ReadOnlyField()
user_info = ReadOnlyField()
provider_info = ReadOnlyField()
+ logout = ReadOnlyField()
def create(self, request: Request) -> Response:
raise NotImplementedError
@@ -83,6 +84,7 @@ class OAuth2ProviderViewSet(ModelViewSet):
)
),
"provider_info": None,
+ "logout": None,
}
try:
data["provider_info"] = request.build_absolute_uri(
@@ -91,6 +93,12 @@ class OAuth2ProviderViewSet(ModelViewSet):
kwargs={"application_slug": provider.application.slug},
)
)
+ data["logout"] = request.build_absolute_uri(
+ reverse(
+ "authentik_providers_oauth2:end-session",
+ kwargs={"application_slug": provider.application.slug},
+ )
+ )
except Provider.application.RelatedObjectDoesNotExist: # pylint: disable=no-member
pass
return Response(data)
diff --git a/authentik/sources/ldap/sync/users.py b/authentik/sources/ldap/sync/users.py
index 37031b037..703da2f31 100644
--- a/authentik/sources/ldap/sync/users.py
+++ b/authentik/sources/ldap/sync/users.py
@@ -1,7 +1,10 @@
"""Sync LDAP Users into authentik"""
+from datetime import datetime
+
import ldap3
import ldap3.core.exceptions
from django.db.utils import IntegrityError
+from pytz import UTC
from authentik.core.models import User
from authentik.sources.ldap.sync.base import LDAP_UNIQUENESS, BaseLDAPSynchronizer
@@ -53,11 +56,21 @@ class UserLDAPSynchronizer(BaseLDAPSynchronizer):
)
)
else:
- if created:
- ak_user.set_unusable_password()
- ak_user.save()
self._logger.debug(
"Synced User", user=ak_user.username, created=created
)
user_count += 1
+ # pylint: disable=no-value-for-parameter
+ pwd_last_set = UTC.localize(
+ attributes.get("pwdLastSet", datetime.now())
+ )
+ if created or pwd_last_set >= ak_user.password_change_date:
+ self._logger.debug(
+ "Reset user's password",
+ user=ak_user.username,
+ created=created,
+ pwd_last_set=pwd_last_set,
+ )
+ ak_user.set_unusable_password()
+ ak_user.save()
return user_count
diff --git a/authentik/stages/authenticator_webauthn/forms.py b/authentik/stages/authenticator_webauthn/forms.py
index 78368c190..881bf54d7 100644
--- a/authentik/stages/authenticator_webauthn/forms.py
+++ b/authentik/stages/authenticator_webauthn/forms.py
@@ -1,7 +1,10 @@
"""Webauthn stage forms"""
from django import forms
-from authentik.stages.authenticator_webauthn.models import AuthenticateWebAuthnStage
+from authentik.stages.authenticator_webauthn.models import (
+ AuthenticateWebAuthnStage,
+ WebAuthnDevice,
+)
class AuthenticateWebAuthnStageForm(forms.ModelForm):
@@ -15,3 +18,16 @@ class AuthenticateWebAuthnStageForm(forms.ModelForm):
widgets = {
"name": forms.TextInput(),
}
+
+
+class DeviceEditForm(forms.ModelForm):
+ """Form to edit webauthn device"""
+
+ class Meta:
+
+ model = WebAuthnDevice
+ fields = ["name"]
+
+ widgets = {
+ "name": forms.TextInput(),
+ }
diff --git a/authentik/stages/authenticator_webauthn/models.py b/authentik/stages/authenticator_webauthn/models.py
index f84c0b799..cc63da205 100644
--- a/authentik/stages/authenticator_webauthn/models.py
+++ b/authentik/stages/authenticator_webauthn/models.py
@@ -79,3 +79,8 @@ class WebAuthnDevice(Device):
def __str__(self):
return self.name or str(self.user)
+
+ class Meta:
+
+ verbose_name = _("WebAuthn Device")
+ verbose_name_plural = _("WebAuthn Devices")
diff --git a/authentik/stages/authenticator_webauthn/templates/stages/authenticator_webauthn/user_settings.html b/authentik/stages/authenticator_webauthn/templates/stages/authenticator_webauthn/user_settings.html
index 89a26a8a3..38daec51a 100644
--- a/authentik/stages/authenticator_webauthn/templates/stages/authenticator_webauthn/user_settings.html
+++ b/authentik/stages/authenticator_webauthn/templates/stages/authenticator_webauthn/user_settings.html
@@ -17,6 +17,20 @@
Created {{ created_on }}
{% endblocktrans %}
+