mirror of
https://github.com/goauthentik/authentik.git
synced 2026-06-17 19:09:11 +03:00
@@ -28,6 +28,7 @@ from authentik.core.api.utils import ModelSerializer, ThemedUrlsSerializer
|
||||
from authentik.core.apps import AppAccessWithoutBindings
|
||||
from authentik.core.models import Application, User
|
||||
from authentik.events.logs import LogEventSerializer, capture_logs
|
||||
from authentik.lib.utils.reflection import ConditionalInheritance
|
||||
from authentik.policies.api.exec import PolicyTestResultSerializer
|
||||
from authentik.policies.engine import ListPolicyEngine, PolicyEngine
|
||||
from authentik.policies.types import CACHE_PREFIX, PolicyResult
|
||||
@@ -128,7 +129,11 @@ class ApplicationSerializer(ModelSerializer):
|
||||
}
|
||||
|
||||
|
||||
class ApplicationViewSet(UsedByMixin, ModelViewSet):
|
||||
class ApplicationViewSet(
|
||||
ConditionalInheritance("authentik.pam.api.apps.ApplicationsRequestableMixin"),
|
||||
UsedByMixin,
|
||||
ModelViewSet,
|
||||
):
|
||||
"""Application Viewset"""
|
||||
|
||||
queryset = (
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
from http import HTTPMethod
|
||||
|
||||
from django.db.models import QuerySet
|
||||
from drf_spectacular.utils import extend_schema
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
|
||||
from authentik.api.pagination import Pagination
|
||||
from authentik.core.api.applications import ApplicationSerializer
|
||||
from authentik.core.apps import AppAccessWithoutBindings
|
||||
from authentik.core.models import Application
|
||||
from authentik.policies.engine import ListPolicyEngine
|
||||
from authentik.policies.models import PolicyBinding
|
||||
|
||||
|
||||
class ApplicationsRequestableMixin:
|
||||
|
||||
queryset: QuerySet[Application]
|
||||
|
||||
@extend_schema(
|
||||
responses={
|
||||
200: ApplicationSerializer(many=True),
|
||||
},
|
||||
)
|
||||
@action(methods=[HTTPMethod.GET], detail=False)
|
||||
def requestable(self, request: Request) -> Response:
|
||||
"""List applications which the current user can request access to"""
|
||||
all_requestable_apps = self.queryset.filter(request_rules__isnull=False).prefetch_related(
|
||||
"request_rules"
|
||||
)
|
||||
|
||||
requestable_apps = []
|
||||
for app in all_requestable_apps:
|
||||
print(app.request_rules.all())
|
||||
engine = ListPolicyEngine(app.request_rules.all())
|
||||
engine.empty_result = AppAccessWithoutBindings.get()
|
||||
print(request.user)
|
||||
print(PolicyBinding.objects.filter(user=request.user))
|
||||
print(PolicyBinding.objects.filter(target=app.request_rules.first()))
|
||||
applicable_rules = list(engine.evaluate_for(request.user, request))
|
||||
print(applicable_rules)
|
||||
if len(applicable_rules) > 0:
|
||||
requestable_apps.append(app)
|
||||
print(requestable_apps)
|
||||
|
||||
paginator: Pagination = self.paginator
|
||||
paginated_apps = paginator.paginate_queryset(requestable_apps, request)
|
||||
|
||||
serializer = self.get_serializer(paginated_apps, many=True)
|
||||
return self.get_paginated_response(serializer.data)
|
||||
@@ -0,0 +1,74 @@
|
||||
from json import loads
|
||||
|
||||
from django.urls import reverse
|
||||
from rest_framework.test import APITestCase
|
||||
|
||||
from authentik.core.models import Application
|
||||
from authentik.core.tests.utils import create_test_user
|
||||
from authentik.lib.generators import generate_id
|
||||
from authentik.pam.models import PolicyBindingModelRequestRule
|
||||
from authentik.policies.models import PolicyBinding
|
||||
|
||||
|
||||
class AppRequestTests(APITestCase):
|
||||
def setUp(self):
|
||||
Application.objects.all().delete()
|
||||
|
||||
def test_requestable_none(self):
|
||||
user = create_test_user()
|
||||
self.client.force_login(user)
|
||||
res = self.client.get(reverse("authentik_api:application-requestable"))
|
||||
content = loads(res.content.decode())
|
||||
self.assertEqual(content["pagination"]["count"], 0)
|
||||
self.assertEqual(len(content["results"]), 0)
|
||||
|
||||
def test_requestable_no_policy(self):
|
||||
user = create_test_user()
|
||||
self.client.force_login(user)
|
||||
|
||||
app = Application.objects.create(
|
||||
name=generate_id(),
|
||||
slug=generate_id(),
|
||||
)
|
||||
PolicyBindingModelRequestRule.objects.create(pbm=app)
|
||||
|
||||
res = self.client.get(reverse("authentik_api:application-requestable"))
|
||||
content = loads(res.content.decode())
|
||||
self.assertEqual(content["pagination"]["count"], 1)
|
||||
self.assertEqual(len(content["results"]), 1)
|
||||
self.assertEqual(content["results"][0]["slug"], app.slug)
|
||||
|
||||
def test_requestable_no_access(self):
|
||||
other_user = create_test_user()
|
||||
|
||||
user = create_test_user()
|
||||
self.client.force_login(user)
|
||||
|
||||
app = Application.objects.create(
|
||||
name=generate_id(),
|
||||
slug=generate_id(),
|
||||
)
|
||||
rule = PolicyBindingModelRequestRule.objects.create(pbm=app, name=generate_id())
|
||||
PolicyBinding.objects.create(target=rule, user=other_user, order=0)
|
||||
|
||||
res = self.client.get(reverse("authentik_api:application-requestable"))
|
||||
content = loads(res.content.decode())
|
||||
self.assertEqual(content["pagination"]["count"], 0)
|
||||
self.assertEqual(len(content["results"]), 0)
|
||||
|
||||
def test_requestable_access(self):
|
||||
user = create_test_user()
|
||||
self.client.force_login(user)
|
||||
|
||||
app = Application.objects.create(
|
||||
name=generate_id(),
|
||||
slug=generate_id(),
|
||||
)
|
||||
rule = PolicyBindingModelRequestRule.objects.create(pbm=app, name=generate_id())
|
||||
PolicyBinding.objects.create(target=rule, user=user, order=0)
|
||||
|
||||
res = self.client.get(reverse("authentik_api:application-requestable"))
|
||||
content = loads(res.content.decode())
|
||||
self.assertEqual(content["pagination"]["count"], 1)
|
||||
self.assertEqual(len(content["results"]), 1)
|
||||
self.assertEqual(content["results"][0]["slug"], app.slug)
|
||||
@@ -233,9 +233,9 @@ class PolicyEngine:
|
||||
return self.result.passing
|
||||
|
||||
|
||||
class ListPolicyEngine:
|
||||
class ListPolicyEngine[T: PolicyBindingModel]:
|
||||
|
||||
def __init__(self, objs: QuerySet[PolicyBindingModel]):
|
||||
def __init__(self, objs: QuerySet[T]):
|
||||
self.qs = objs
|
||||
self.empty_result = True
|
||||
|
||||
@@ -244,5 +244,6 @@ class ListPolicyEngine:
|
||||
engine = PolicyEngine(obj, request.user, request)
|
||||
engine.empty_result = self.empty_result
|
||||
engine.build()
|
||||
print(engine.result)
|
||||
if engine.passing:
|
||||
yield obj
|
||||
|
||||
Generated
+112
@@ -195,6 +195,19 @@ export interface CoreApplicationsPartialUpdateRequest {
|
||||
patchedApplicationRequest?: PatchedApplicationRequest;
|
||||
}
|
||||
|
||||
export interface CoreApplicationsRequestableListRequest {
|
||||
group?: string;
|
||||
metaDescription?: string;
|
||||
metaLaunchUrl?: string;
|
||||
metaPublisher?: string;
|
||||
name?: string;
|
||||
ordering?: string;
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
search?: string;
|
||||
slug?: string;
|
||||
}
|
||||
|
||||
export interface CoreApplicationsRetrieveRequest {
|
||||
slug: string;
|
||||
}
|
||||
@@ -1428,6 +1441,105 @@ export class CoreApi extends runtime.BaseAPI {
|
||||
return await response.value();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates request options for coreApplicationsRequestableList without sending the request
|
||||
*/
|
||||
async coreApplicationsRequestableListRequestOpts(
|
||||
requestParameters: CoreApplicationsRequestableListRequest,
|
||||
): Promise<runtime.RequestOpts> {
|
||||
const queryParameters: any = {};
|
||||
|
||||
if (requestParameters["group"] != null) {
|
||||
queryParameters["group"] = requestParameters["group"];
|
||||
}
|
||||
|
||||
if (requestParameters["metaDescription"] != null) {
|
||||
queryParameters["meta_description"] = requestParameters["metaDescription"];
|
||||
}
|
||||
|
||||
if (requestParameters["metaLaunchUrl"] != null) {
|
||||
queryParameters["meta_launch_url"] = requestParameters["metaLaunchUrl"];
|
||||
}
|
||||
|
||||
if (requestParameters["metaPublisher"] != null) {
|
||||
queryParameters["meta_publisher"] = requestParameters["metaPublisher"];
|
||||
}
|
||||
|
||||
if (requestParameters["name"] != null) {
|
||||
queryParameters["name"] = requestParameters["name"];
|
||||
}
|
||||
|
||||
if (requestParameters["ordering"] != null) {
|
||||
queryParameters["ordering"] = requestParameters["ordering"];
|
||||
}
|
||||
|
||||
if (requestParameters["page"] != null) {
|
||||
queryParameters["page"] = requestParameters["page"];
|
||||
}
|
||||
|
||||
if (requestParameters["pageSize"] != null) {
|
||||
queryParameters["page_size"] = requestParameters["pageSize"];
|
||||
}
|
||||
|
||||
if (requestParameters["search"] != null) {
|
||||
queryParameters["search"] = requestParameters["search"];
|
||||
}
|
||||
|
||||
if (requestParameters["slug"] != null) {
|
||||
queryParameters["slug"] = requestParameters["slug"];
|
||||
}
|
||||
|
||||
const headerParameters: runtime.HTTPHeaders = {};
|
||||
|
||||
if (this.configuration && this.configuration.accessToken) {
|
||||
const token = this.configuration.accessToken;
|
||||
const tokenString = await token("authentik", []);
|
||||
|
||||
if (tokenString) {
|
||||
headerParameters["Authorization"] = `Bearer ${tokenString}`;
|
||||
}
|
||||
}
|
||||
|
||||
let urlPath = `/core/applications/requestable/`;
|
||||
|
||||
return {
|
||||
path: urlPath,
|
||||
method: "GET",
|
||||
headers: headerParameters,
|
||||
query: queryParameters,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* List applications which the current user can request access to
|
||||
*/
|
||||
async coreApplicationsRequestableListRaw(
|
||||
requestParameters: CoreApplicationsRequestableListRequest,
|
||||
initOverrides?: RequestInit | runtime.InitOverrideFunction,
|
||||
): Promise<runtime.ApiResponse<PaginatedApplicationList>> {
|
||||
const requestOptions =
|
||||
await this.coreApplicationsRequestableListRequestOpts(requestParameters);
|
||||
const response = await this.request(requestOptions, initOverrides);
|
||||
|
||||
return new runtime.JSONApiResponse(response, (jsonValue) =>
|
||||
PaginatedApplicationListFromJSON(jsonValue),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* List applications which the current user can request access to
|
||||
*/
|
||||
async coreApplicationsRequestableList(
|
||||
requestParameters: CoreApplicationsRequestableListRequest = {},
|
||||
initOverrides?: RequestInit | runtime.InitOverrideFunction,
|
||||
): Promise<PaginatedApplicationList> {
|
||||
const response = await this.coreApplicationsRequestableListRaw(
|
||||
requestParameters,
|
||||
initOverrides,
|
||||
);
|
||||
return await response.value();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates request options for coreApplicationsRetrieve without sending the request
|
||||
*/
|
||||
|
||||
+45
@@ -2975,6 +2975,51 @@ paths:
|
||||
$ref: '#/components/responses/ValidationErrorResponse'
|
||||
'403':
|
||||
$ref: '#/components/responses/GenericErrorResponse'
|
||||
/core/applications/requestable/:
|
||||
get:
|
||||
operationId: core_applications_requestable_list
|
||||
description: List applications which the current user can request access to
|
||||
parameters:
|
||||
- in: query
|
||||
name: group
|
||||
schema:
|
||||
type: string
|
||||
- in: query
|
||||
name: meta_description
|
||||
schema:
|
||||
type: string
|
||||
- in: query
|
||||
name: meta_launch_url
|
||||
schema:
|
||||
type: string
|
||||
- in: query
|
||||
name: meta_publisher
|
||||
schema:
|
||||
type: string
|
||||
- $ref: '#/components/parameters/QueryName'
|
||||
- $ref: '#/components/parameters/QueryPaginationOrdering'
|
||||
- $ref: '#/components/parameters/QueryPaginationPage'
|
||||
- $ref: '#/components/parameters/QueryPaginationPageSize'
|
||||
- $ref: '#/components/parameters/QuerySearch'
|
||||
- in: query
|
||||
name: slug
|
||||
schema:
|
||||
type: string
|
||||
tags:
|
||||
- core
|
||||
security:
|
||||
- authentik: []
|
||||
responses:
|
||||
'200':
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PaginatedApplicationList'
|
||||
description: ''
|
||||
'400':
|
||||
$ref: '#/components/responses/ValidationErrorResponse'
|
||||
'403':
|
||||
$ref: '#/components/responses/GenericErrorResponse'
|
||||
/core/authenticated_sessions/:
|
||||
get:
|
||||
operationId: core_authenticated_sessions_list
|
||||
|
||||
@@ -15,6 +15,7 @@ import PFContent from "@patternfly/patternfly/components/Content/content.css";
|
||||
import PFForm from "@patternfly/patternfly/components/Form/form.css";
|
||||
import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css";
|
||||
import PFPage from "@patternfly/patternfly/components/Page/page.css";
|
||||
import { aki } from "#common/api/client";
|
||||
|
||||
@customElement("ak-discovery")
|
||||
export class DiscoverPage extends AKElement {
|
||||
@@ -26,13 +27,7 @@ export class DiscoverPage extends AKElement {
|
||||
public override connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
|
||||
new CoreApi(DEFAULT_CONFIG)
|
||||
.coreApplicationsList({
|
||||
superuserFullList: true,
|
||||
})
|
||||
.then((apps) => {
|
||||
this.apps = apps;
|
||||
});
|
||||
aki(CoreApi).coreApplicationsRequestableList({}).then(apps => this.apps = apps);
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
Reference in New Issue
Block a user