flows/inspector: add button to open flow inspector (#12656)

* flows: differentiate between flow inspector being available and open

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

* add overlay button to open inspector

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

* update docs

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

* Apply suggestions from code review

Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: Jens L. <jens@beryju.org>

* fix perm check

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

* fix tests

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

* rewrite docs

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Signed-off-by: Jens L. <jens@beryju.org>
Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
This commit is contained in:
Jens L.
2025-01-13 19:55:34 +01:00
committed by GitHub
parent 3098313981
commit 629d5df763
8 changed files with 62 additions and 24 deletions

View File

@ -159,9 +159,17 @@ class FlowPlan:
stage = final_stage(request=request, executor=temp_exec)
return stage.dispatch(request)
get_qs = request.GET.copy()
if request.user.is_authenticated and (
# Object-scoped permission or global permission
request.user.has_perm("authentik_flows.inspect_flow", flow)
or request.user.has_perm("authentik_flows.inspect_flow")
):
get_qs["inspector"] = "available"
return redirect_with_qs(
"authentik_core:if-flow",
request.GET,
get_qs,
flow_slug=flow.slug,
)

View File

@ -78,7 +78,9 @@ class FlowInspectorView(APIView):
self.flow = get_object_or_404(Flow.objects.select_related(), slug=flow_slug)
if settings.DEBUG:
return
if request.user.has_perm("authentik_flow.inspect_flow", self.flow):
if request.user.has_perm(
"authentik_flows.inspect_flow", self.flow
) or request.user.has_perm("authentik_flows.inspect_flow"):
return
raise Http404

View File

@ -49,7 +49,9 @@ class TesOAuth2DeviceInit(OAuthTestCase):
kwargs={
"flow_slug": self.device_flow.slug,
},
),
)
+ "?"
+ urlencode({"inspector": "available"}),
)
def test_device_init_post(self):
@ -63,7 +65,9 @@ class TesOAuth2DeviceInit(OAuthTestCase):
kwargs={
"flow_slug": self.device_flow.slug,
},
),
)
+ "?"
+ urlencode({"inspector": "available"}),
)
res = self.api_client.get(
reverse(
@ -118,7 +122,9 @@ class TesOAuth2DeviceInit(OAuthTestCase):
kwargs={
"flow_slug": provider.authorization_flow.slug,
},
),
)
+ "?"
+ urlencode({"inspector": "available"}),
},
)
@ -150,7 +156,7 @@ class TesOAuth2DeviceInit(OAuthTestCase):
},
)
+ "?"
+ urlencode({QS_KEY_CODE: token.user_code}),
+ urlencode({QS_KEY_CODE: token.user_code, "inspector": "available"}),
)
def test_device_init_denied(self):

View File

@ -260,6 +260,7 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
self.wait_for_url(
self.url(
"authentik_core:if-flow",
query={"inspector": "available"},
flow_slug=invalidation_flow.slug,
)
)

View File

@ -10,6 +10,7 @@ from sys import stderr
from time import sleep
from typing import Any
from unittest.case import TestCase
from urllib.parse import urlencode
from django.apps import apps
from django.contrib.staticfiles.testing import StaticLiveServerTestCase
@ -209,9 +210,12 @@ class SeleniumTestCase(DockerTestCase, StaticLiveServerTestCase):
f"URL {self.driver.current_url} doesn't match expected URL {desired_url}",
)
def url(self, view, **kwargs) -> str:
def url(self, view, query: dict | None = None, **kwargs) -> str:
"""reverse `view` with `**kwargs` into full URL using live_server_url"""
return self.live_server_url + reverse(view, kwargs=kwargs)
url = self.live_server_url + reverse(view, kwargs=kwargs)
if query:
return url + "?" + urlencode(query)
return url
def if_user_url(self, path: str | None = None) -> str:
"""same as self.url() but show URL in shell"""

View File

@ -191,7 +191,7 @@ export class FlowViewPage extends AKElement {
const finalURL = `${
link.link
}?${encodeURI(
`inspector&next=/#${window.location.hash}`,
`inspector=open&next=/#${window.location.hash}`,
)}`;
window.open(finalURL, "_blank");
})

View File

@ -77,6 +77,9 @@ export class FlowExecutor extends Interface implements StageHost {
@state()
inspectorOpen = false;
@state()
inspectorAvailable = false;
@state()
flowInfo?: ContextualFlowInfo;
@ -160,14 +163,24 @@ export class FlowExecutor extends Interface implements StageHost {
padding: 0 2rem;
max-height: inherit;
}
.inspector-toggle {
position: absolute;
top: 1rem;
right: 1rem;
z-index: 100;
}
`);
}
constructor() {
super();
this.ws = new WebsocketClient();
if (window.location.search.includes("inspector")) {
const inspector = new URL(window.location.toString()).searchParams.get("inspector");
if (inspector === "" || inspector === "open") {
this.inspectorOpen = true;
this.inspectorAvailable = true;
} else if (inspector === "available") {
this.inspectorAvailable = true;
}
this.addEventListener(EVENT_FLOW_INSPECTOR_TOGGLE, () => {
this.inspectorOpen = !this.inspectorOpen;
@ -231,12 +244,8 @@ export class FlowExecutor extends Interface implements StageHost {
async firstUpdated(): Promise<void> {
configureSentry();
if (
this.config?.capabilities.includes(CapabilitiesEnum.CanDebug) &&
// Only open inspector automatically in debug when we have enough space for it
window.innerWidth >= 768
) {
this.inspectorOpen = true;
if (this.config?.capabilities.includes(CapabilitiesEnum.CanDebug)) {
this.inspectorAvailable = true;
}
this.loading = true;
try {
@ -545,6 +554,16 @@ export class FlowExecutor extends Interface implements StageHost {
</div>
</div>
</div>
${(this.inspectorAvailable ?? !this.inspectorOpen)
? html`<button
class="inspector-toggle pf-c-button pf-m-primary"
@click=${() => {
this.inspectorOpen = true;
}}
>
<i class="fa fa-search-plus" aria-hidden="true"></i>
</button>`
: nothing}
${until(this.renderInspector())}
</div>
</div>

View File

@ -14,13 +14,13 @@ As shown in the screenshot below, the flow inspector displays next to the select
Be aware that when running a flow with the inspector enabled, the flow is still executed normally. This means that for example, a [User write](../stages/user_write.md) stage _will_ write user data.
:::
### Permissions and debug mode
By default, the inspector is only enabled when the currently authenticated user is a superuser, OR if a user has been granted the [permission](../../../users-sources/access-control/permissions.md) **Can inspect a Flow's execution** (or is a user assigned to role with the permission).
The inspector is accessible to users that have been granted the [permission](../../../users-sources/access-control/permissions.md) **Can inspect a Flow's execution**, either directly or through a role. Superusers can always inspect flow executions.
When developing authentik with the debug mode enabled, the inspector is enabled by default and can be accessed by both unauthenticated users and standard users. However the debug mode should only be used for the development of authentik. So unless you are a developer and need the more verbose error information, the best practice for using the flow inspector is to assign the permission, not use debug mode.
### Open the Flow Inspector
Starting with authentik 2025.2, for users with appropriate permissions to access the inspector a button is shown in the top right of the [default flow executor](./executors/if-flow.md) which opens the flow inspector.
### Manually running a flow with the inspector
1. To access the inspector, open the Admin interface and navigate to **Flows and Stages -> Flows**.
@ -35,10 +35,8 @@ Alternatively, a user with the correct permission can launch the inspector by ad
:::info
Troubleshooting:
- If the flow inspector does not launch and a "Bad request" error displays, this is likely either because you selected a flow that is not defined in your instance or the flow has a policy bound directly to it that prevents access, so the inspector won't open because the flow can't run results.
- If the flow inspector launches but is empty, you can refresh the browser or advance the flow to load the inspector. This can occur when a race condition happens (the inspector tries to fetch the data before the flow plan is fully planned and as such the panel just shows blank).
:::
- If the flow inspector does not launch and a "Bad request" error displays, this is likely either because you selected a flow that has a policy bound directly to it that prevents access (so the inspector won't open because the flow can't be executed) or because you do not have view permission on that specific flow.
:::
### Flow Inspector Details