policies: remove BufferedPolicyAccessView (#20521)

* policies: remove BufferedPolicyAccessView

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

# Conflicts:
#	authentik/policies/views.py
#	authentik/providers/oauth2/views/authorize.py
#	schema.yml
#	tests/e2e/test_provider_saml.py

* format

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
Jens L.
2026-03-16 18:19:15 +01:00
committed by GitHub
parent 060766f16e
commit db9081e7dc
11 changed files with 7 additions and 345 deletions
-7
View File
@@ -7,7 +7,6 @@ For example: The 'dummy' policy is available at `authentik.policies.dummy`.
from prometheus_client import Gauge, Histogram
from authentik.blueprints.apps import ManagedAppConfig
from authentik.tenants.flags import Flag
GAUGE_POLICIES_CACHED = Gauge(
"authentik_policies_cached",
@@ -32,12 +31,6 @@ HIST_POLICIES_EXECUTION_TIME = Histogram(
)
class BufferedPolicyAccessViewFlag(Flag[bool], key="policies_buffered_access_view"):
default = False
visibility = "public"
class AuthentikPoliciesConfig(ManagedAppConfig):
"""authentik policies app config"""
-78
View File
@@ -1,29 +1,19 @@
from django.http import Http404, HttpResponse
from django.test import TestCase
from django.urls import reverse
from authentik.blueprints.tests import apply_blueprint
from authentik.core.models import Application, Group, Provider
from authentik.core.tests.utils import (
RequestFactory,
create_test_brand,
create_test_flow,
create_test_user,
)
from authentik.flows.models import Flow, FlowDesignation
from authentik.flows.planner import FlowPlan
from authentik.flows.views.executor import SESSION_KEY_PLAN
from authentik.lib.generators import generate_id
from authentik.policies.apps import BufferedPolicyAccessViewFlag
from authentik.policies.models import PolicyBinding
from authentik.policies.views import (
QS_BUFFER_ID,
SESSION_KEY_BUFFER,
BufferedPolicyAccessView,
BufferView,
PolicyAccessView,
)
from authentik.tenants.flags import patch_flag
class TestPolicyViews(TestCase):
@@ -124,71 +114,3 @@ class TestPolicyViews(TestCase):
res = TestView.as_view()(req)
self.assertEqual(res.status_code, 302)
self.assertEqual(res.url, "/if/flow/default-authentication-flow/?next=%2F")
@patch_flag(BufferedPolicyAccessViewFlag, True)
def test_pav_buffer(self):
"""Test simple policy access view"""
provider = Provider.objects.create(
name=generate_id(),
)
app = Application.objects.create(name=generate_id(), slug=generate_id(), provider=provider)
flow = create_test_flow(FlowDesignation.AUTHENTICATION)
class TestView(BufferedPolicyAccessView):
def resolve_provider_application(self):
self.provider = provider
self.application = app
def get(self, *args, **kwargs):
return HttpResponse("foo")
req = self.factory.get("/")
req.session[SESSION_KEY_PLAN] = FlowPlan(flow.pk)
req.session.save()
res = TestView.as_view()(req)
self.assertEqual(res.status_code, 302)
self.assertTrue(res.url.startswith(reverse("authentik_policies:buffer")))
@patch_flag(BufferedPolicyAccessViewFlag, True)
@apply_blueprint("default/flow-default-authentication-flow.yaml")
def test_pav_buffer_skip(self):
"""Test simple policy access view (skip buffer)"""
provider = Provider.objects.create(
name=generate_id(),
)
app = Application.objects.create(name=generate_id(), slug=generate_id(), provider=provider)
flow = Flow.objects.get(slug="default-authentication-flow")
class TestView(BufferedPolicyAccessView):
def resolve_provider_application(self):
self.provider = provider
self.application = app
def get(self, *args, **kwargs):
return HttpResponse("foo")
req = self.factory.get("/?skip_buffer=true")
req.brand = create_test_brand(flow_authentication=flow)
req.session[SESSION_KEY_PLAN] = FlowPlan(flow.pk)
req.session.save()
res = TestView.as_view()(req)
self.assertEqual(res.status_code, 302)
self.assertTrue(
res.url.startswith(reverse("authentik_core:if-flow", kwargs={"flow_slug": flow.slug}))
)
def test_buffer(self):
"""Test buffer view"""
uid = generate_id()
req = self.factory.get(f"/?{QS_BUFFER_ID}={uid}")
ts = generate_id()
req.session[SESSION_KEY_BUFFER % uid] = {
"method": "get",
"body": {},
"url": f"/{ts}",
}
req.session.save()
res = BufferView.as_view()(req)
self.assertEqual(res.status_code, 200)
self.assertIn(ts, res.render().content.decode())
-41
View File
@@ -1,12 +1,10 @@
"""authentik access helper classes"""
from typing import Any
from uuid import uuid4
from django.contrib import messages
from django.contrib.auth.mixins import AccessMixin
from django.http import Http404, HttpRequest, HttpResponse, QueryDict
from django.shortcuts import redirect
from django.urls import reverse
from django.utils.http import urlencode
from django.utils.translation import gettext as _
@@ -19,16 +17,13 @@ from authentik.flows.models import Flow, FlowDesignation
from authentik.flows.planner import (
PLAN_CONTEXT_APPLICATION,
PLAN_CONTEXT_POST,
FlowPlan,
FlowPlanner,
)
from authentik.flows.views.executor import (
SESSION_KEY_PLAN,
SESSION_KEY_POST,
ToDefaultFlow,
)
from authentik.lib.sentry import SentryIgnoredException
from authentik.policies.apps import BufferedPolicyAccessViewFlag
from authentik.policies.denied import AccessDeniedResponse
from authentik.policies.engine import PolicyEngine
from authentik.policies.models import PolicyBindingModel
@@ -194,39 +189,3 @@ class BufferView(TemplateView):
kwargs["check_auth_url"] = reverse("authentik_api:user-me")
kwargs["continue_url"] = url_with_qs(buffer["url"], **{QS_BUFFER_ID: buf_id})
return super().get_context_data(**kwargs)
class BufferedPolicyAccessView(PolicyAccessView):
"""PolicyAccessView which buffers access requests in case the user is not logged in"""
def handle_no_permission(self):
plan: FlowPlan | None = self.request.session.get(SESSION_KEY_PLAN)
if plan:
flow = Flow.objects.filter(pk=plan.flow_pk).first()
if not flow or flow.designation != FlowDesignation.AUTHENTICATION:
LOGGER.debug("Not buffering request, no flow or flow not for authentication")
return super().handle_no_permission()
if not plan:
LOGGER.debug("Not buffering request, no flow plan active")
return super().handle_no_permission()
if not BufferedPolicyAccessViewFlag.get():
return super().handle_no_permission()
if self.request.GET.get(QS_SKIP_BUFFER):
LOGGER.debug("Not buffering request, explicit skip")
return super().handle_no_permission()
buffer_id = str(uuid4())
LOGGER.debug("Buffering access request", bf_id=buffer_id)
self.request.session[SESSION_KEY_BUFFER % buffer_id] = {
"body": self.request.POST,
"url": self.request.build_absolute_uri(self.request.get_full_path()),
"method": self.request.method.lower(),
}
return redirect(
url_with_qs(reverse("authentik_policies:buffer"), **{QS_BUFFER_ID: buffer_id})
)
def dispatch(self, request, *args, **kwargs):
response = super().dispatch(request, *args, **kwargs)
if QS_BUFFER_ID in self.request.GET:
self.request.session.pop(SESSION_KEY_BUFFER % self.request.GET[QS_BUFFER_ID], None)
return response
@@ -45,7 +45,7 @@ from authentik.flows.stage import PLAN_CONTEXT_PENDING_USER_IDENTIFIER, StageVie
from authentik.lib.utils.time import timedelta_from_string
from authentik.lib.views import bad_request_message
from authentik.policies.types import PolicyRequest
from authentik.policies.views import BufferedPolicyAccessView, RequestValidationError
from authentik.policies.views import PolicyAccessView, RequestValidationError
from authentik.providers.oauth2.errors import (
AuthorizeError,
ClientIdError,
@@ -338,7 +338,7 @@ class OAuthAuthorizationParams:
return code
class AuthorizationFlowInitView(BufferedPolicyAccessView):
class AuthorizationFlowInitView(PolicyAccessView):
"""OAuth2 Flow initializer, checks access to application and starts flow"""
params: OAuthAuthorizationParams
+2 -2
View File
@@ -18,14 +18,14 @@ from authentik.flows.planner import PLAN_CONTEXT_APPLICATION, FlowPlanner
from authentik.flows.stage import RedirectStage
from authentik.lib.utils.time import timedelta_from_string
from authentik.policies.engine import PolicyEngine
from authentik.policies.views import BufferedPolicyAccessView
from authentik.policies.views import PolicyAccessView
from authentik.providers.rac.models import ConnectionToken, Endpoint, RACProvider
from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT
PLAN_CONNECTION_SETTINGS = "connection_settings"
class RACStartView(BufferedPolicyAccessView):
class RACStartView(PolicyAccessView):
"""Start a RAC connection by checking access and creating a connection token"""
endpoint: Endpoint
+3 -3
View File
@@ -15,7 +15,7 @@ from authentik.flows.models import in_memory_stage
from authentik.flows.planner import PLAN_CONTEXT_APPLICATION, PLAN_CONTEXT_SSO, FlowPlanner
from authentik.flows.views.executor import SESSION_KEY_POST
from authentik.lib.views import bad_request_message
from authentik.policies.views import BufferedPolicyAccessView
from authentik.policies.views import PolicyAccessView
from authentik.providers.saml.exceptions import CannotHandleAssertion
from authentik.providers.saml.models import SAMLBindings, SAMLProvider
from authentik.providers.saml.processors.authn_request_parser import AuthNRequestParser
@@ -35,7 +35,7 @@ from authentik.stages.consent.stage import (
LOGGER = get_logger()
class SAMLSSOView(BufferedPolicyAccessView):
class SAMLSSOView(PolicyAccessView):
"""SAML SSO Base View, which plans a flow and injects our final stage.
Calls get/post handler."""
@@ -88,7 +88,7 @@ class SAMLSSOView(BufferedPolicyAccessView):
def post(self, request: HttpRequest, application_slug: str) -> HttpResponse:
"""GET and POST use the same handler, but we can't
override .dispatch easily because BufferedPolicyAccessView's dispatch"""
override .dispatch easily because PolicyAccessView's dispatch"""
return self.get(request, application_slug)
-12
View File
@@ -37002,8 +37002,6 @@ components:
properties:
enterprise_audit_include_expanded_diff:
type: boolean
policies_buffered_access_view:
type: boolean
flows_continuous_login:
type: boolean
flows_refresh_others:
@@ -37012,7 +37010,6 @@ components:
- enterprise_audit_include_expanded_diff
- flows_continuous_login
- flows_refresh_others
- policies_buffered_access_view
readOnly: true
required:
- branding_custom_css
@@ -50779,8 +50776,6 @@ components:
properties:
enterprise_audit_include_expanded_diff:
type: boolean
policies_buffered_access_view:
type: boolean
flows_continuous_login:
type: boolean
flows_refresh_others:
@@ -50789,7 +50784,6 @@ components:
- enterprise_audit_include_expanded_diff
- flows_continuous_login
- flows_refresh_others
- policies_buffered_access_view
PatchedSourceStageRequest:
type: object
description: SourceStage Serializer
@@ -55481,8 +55475,6 @@ components:
properties:
enterprise_audit_include_expanded_diff:
type: boolean
policies_buffered_access_view:
type: boolean
flows_continuous_login:
type: boolean
flows_refresh_others:
@@ -55491,7 +55483,6 @@ components:
- enterprise_audit_include_expanded_diff
- flows_continuous_login
- flows_refresh_others
- policies_buffered_access_view
required:
- flags
SettingsRequest:
@@ -55562,8 +55553,6 @@ components:
properties:
enterprise_audit_include_expanded_diff:
type: boolean
policies_buffered_access_view:
type: boolean
flows_continuous_login:
type: boolean
flows_refresh_others:
@@ -55572,7 +55561,6 @@ components:
- enterprise_audit_include_expanded_diff
- flows_continuous_login
- flows_refresh_others
- policies_buffered_access_view
required:
- flags
SeverityEnum:
-79
View File
@@ -1,7 +1,6 @@
"""test OAuth2 OpenID Provider flow"""
from time import sleep
from unittest import skip
from docker.types import Healthcheck
from selenium.webdriver.common.by import By
@@ -18,7 +17,6 @@ from authentik.core.models import Application
from authentik.core.tests.utils import create_test_cert
from authentik.flows.models import Flow
from authentik.lib.generators import generate_id, generate_key
from authentik.policies.apps import BufferedPolicyAccessViewFlag
from authentik.policies.expression.models import ExpressionPolicy
from authentik.policies.models import PolicyBinding
from authentik.providers.oauth2.models import (
@@ -28,7 +26,6 @@ from authentik.providers.oauth2.models import (
RedirectURIMatchingMode,
ScopeMapping,
)
from authentik.tenants.flags import patch_flag
from tests.e2e.utils import SeleniumTestCase, retry
@@ -415,79 +412,3 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
self.driver.find_element(By.CSS_SELECTOR, "[data-test-id='card-title']").text,
"Permission denied",
)
@skip("Flaky test")
@retry()
@apply_blueprint(
"default/flow-default-authentication-flow.yaml",
"default/flow-default-invalidation-flow.yaml",
)
@apply_blueprint("default/flow-default-provider-authorization-implicit-consent.yaml")
@apply_blueprint("system/providers-oauth2.yaml")
@reconcile_app("authentik_crypto")
@patch_flag(BufferedPolicyAccessViewFlag, True)
def test_authorization_consent_implied_parallel(self):
"""test OpenID Provider flow (default authorization flow with implied consent)"""
# Bootstrap all needed objects
authorization_flow = Flow.objects.get(
slug="default-provider-authorization-implicit-consent"
)
provider = OAuth2Provider.objects.create(
name=generate_id(),
client_type=ClientTypes.CONFIDENTIAL,
client_id=self.client_id,
client_secret=self.client_secret,
signing_key=create_test_cert(),
redirect_uris=[
RedirectURI(
RedirectURIMatchingMode.STRICT, "http://localhost:3000/login/generic_oauth"
)
],
authorization_flow=authorization_flow,
)
provider.property_mappings.set(
ScopeMapping.objects.filter(
scope_name__in=[
SCOPE_OPENID,
SCOPE_OPENID_EMAIL,
SCOPE_OPENID_PROFILE,
SCOPE_OFFLINE_ACCESS,
]
)
)
Application.objects.create(
name=generate_id(),
slug=self.app_slug,
provider=provider,
)
self.driver.get(self.live_server_url)
login_window = self.driver.current_window_handle
self.driver.switch_to.new_window("tab")
grafana_window = self.driver.current_window_handle
self.driver.get("http://localhost:3000")
self.driver.find_element(By.CLASS_NAME, "btn-service--oauth").click()
self.driver.switch_to.window(login_window)
self.login()
self.driver.switch_to.window(grafana_window)
self.wait_for_url("http://localhost:3000/?orgId=1")
self.driver.get("http://localhost:3000/profile")
self.assertEqual(
self.driver.find_element(By.CLASS_NAME, "page-header__title").text,
self.user.name,
)
self.assertEqual(
self.driver.find_element(By.CSS_SELECTOR, "input[name=name]").get_attribute("value"),
self.user.name,
)
self.assertEqual(
self.driver.find_element(By.CSS_SELECTOR, "input[name=email]").get_attribute("value"),
self.user.email,
)
self.assertEqual(
self.driver.find_element(By.CSS_SELECTOR, "input[name=login]").get_attribute("value"),
self.user.email,
)
-111
View File
@@ -2,7 +2,6 @@
from json import dumps
from time import sleep
from unittest import skip
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as ec
@@ -13,11 +12,9 @@ from authentik.core.models import Application
from authentik.core.tests.utils import create_test_cert
from authentik.flows.models import Flow
from authentik.lib.generators import generate_id
from authentik.policies.apps import BufferedPolicyAccessViewFlag
from authentik.policies.expression.models import ExpressionPolicy
from authentik.policies.models import PolicyBinding
from authentik.providers.saml.models import SAMLBindings, SAMLPropertyMapping, SAMLProvider
from authentik.tenants.flags import patch_flag
from tests.e2e.utils import SeleniumTestCase, retry
@@ -582,111 +579,3 @@ class TestProviderSAML(SeleniumTestCase):
lambda driver: driver.current_url.startswith(should_url),
f"URL {self.driver.current_url} doesn't match expected URL {should_url}",
)
@skip("Flaky test")
@retry()
@apply_blueprint(
"default/flow-default-authentication-flow.yaml",
"default/flow-default-invalidation-flow.yaml",
)
@apply_blueprint(
"default/flow-default-provider-authorization-implicit-consent.yaml",
)
@apply_blueprint(
"system/providers-saml.yaml",
)
@reconcile_app("authentik_crypto")
@patch_flag(BufferedPolicyAccessViewFlag, True)
def test_sp_initiated_implicit_post_buffer(self):
"""test SAML Provider flow SP-initiated flow (implicit consent)"""
# Bootstrap all needed objects
authorization_flow = Flow.objects.get(
slug="default-provider-authorization-implicit-consent"
)
provider: SAMLProvider = SAMLProvider.objects.create(
name=generate_id(),
acs_url=f"http://{self.host}:9009/saml/acs",
audience="authentik-e2e",
issuer="authentik-e2e",
sp_binding=SAMLBindings.POST,
authorization_flow=authorization_flow,
signing_kp=create_test_cert(),
)
provider.property_mappings.set(SAMLPropertyMapping.objects.all())
provider.save()
Application.objects.create(
name="SAML",
slug=generate_id(),
provider=provider,
)
self.setup_client(provider, True, SP_ROOT_URL=f"http://{self.host}:9009")
self.driver.get(self.live_server_url)
login_window = self.driver.current_window_handle
self.driver.switch_to.new_window("tab")
client_window = self.driver.current_window_handle
# We need to access the SP on the same host as the IdP for SameSite cookies
self.driver.get(f"http://{self.host}:9009")
self.driver.switch_to.new_window("tab")
client_window = self.driver.current_window_handle
# We need to access the SP on the same host as the IdP for SameSite cookies
self.driver.get(f"http://{self.host}:9009")
self.driver.switch_to.new_window("tab")
client_window = self.driver.current_window_handle
# We need to access the SP on the same host as the IdP for SameSite cookies
self.driver.get(f"http://{self.host}:9009")
self.driver.switch_to.new_window("tab")
client_window = self.driver.current_window_handle
# We need to access the SP on the same host as the IdP for SameSite cookies
self.driver.get(f"http://{self.host}:9009")
self.driver.switch_to.new_window("tab")
client_window = self.driver.current_window_handle
# We need to access the SP on the same host as the IdP for SameSite cookies
self.driver.get(f"http://{self.host}:9009")
self.driver.switch_to.window(login_window)
self.login()
self.driver.switch_to.window(client_window)
self.wait_for_url(f"http://{self.host}:9009/")
body = self.parse_json_content()
snippet = dumps(body, indent=2)[:500].replace("\n", " ")
attrs = body.get("attr", {})
self.assertEqual(
attrs.get("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"),
[self.user.name],
f"Claim 'name' mismatch at {self.driver.current_url}: {snippet}",
)
self.assertEqual(
attrs.get("http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname"),
[self.user.username],
f"Claim 'windowsaccountname' mismatch at {self.driver.current_url}: {snippet}",
)
self.assertEqual(
attrs.get("http://schemas.goauthentik.io/2021/02/saml/username"),
[self.user.username],
f"Claim 'username' mismatch at {self.driver.current_url}: {snippet}",
)
self.assertEqual(
attrs.get("http://schemas.goauthentik.io/2021/02/saml/uid"),
[str(self.user.pk)],
f"Claim 'uid' mismatch at {self.driver.current_url}: {snippet}",
)
self.assertEqual(
attrs.get("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"),
[self.user.email],
f"Claim 'emailaddress' mismatch at {self.driver.current_url}: {snippet}",
)
self.assertEqual(
attrs.get("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn"),
[self.user.email],
f"Claim 'upn' mismatch at {self.driver.current_url}: {snippet}",
)
sleep(3)
@@ -266,15 +266,6 @@ export class AdminSettingsForm extends Form<SettingsRequest> {
)}
>
<div class="pf-c-form">
<ak-switch-input
name="flags.policiesBufferedAccessView"
?checked=${settings?.flags.policiesBufferedAccessView ?? false}
label=${msg("Buffer PolicyAccessView requests")}
help=${msg(
"When enabled, parallel requests for application authorization will be buffered instead of conflicting with other flows.",
)}
>
</ak-switch-input>
<ak-switch-input
name="flags.flowsRefreshOthers"
?checked=${settings?.flags.flowsRefreshOthers ?? false}
-1
View File
@@ -16,7 +16,6 @@ export const DefaultBrand = {
matchedDomain: "",
defaultLocale: "",
flags: {
policiesBufferedAccessView: false,
flowsRefreshOthers: false,
enterpriseAuditIncludeExpandedDiff: false,
flowsContinuousLogin: false,