core: make application's check_access API return a PolicyResult and accept for_user as superuser
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
parent
309d80a921
commit
523621daa2
|
@ -13,7 +13,12 @@ from drf_spectacular.utils import (
|
||||||
inline_serializer,
|
inline_serializer,
|
||||||
)
|
)
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
from rest_framework.fields import CharField, FileField, SerializerMethodField
|
from rest_framework.fields import (
|
||||||
|
CharField,
|
||||||
|
FileField,
|
||||||
|
IntegerField,
|
||||||
|
SerializerMethodField,
|
||||||
|
)
|
||||||
from rest_framework.parsers import MultiPartParser
|
from rest_framework.parsers import MultiPartParser
|
||||||
from rest_framework.request import Request
|
from rest_framework.request import Request
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
@ -25,9 +30,11 @@ from structlog.stdlib import get_logger
|
||||||
from authentik.admin.api.metrics import CoordinateSerializer, get_events_per_1h
|
from authentik.admin.api.metrics import CoordinateSerializer, get_events_per_1h
|
||||||
from authentik.api.decorators import permission_required
|
from authentik.api.decorators import permission_required
|
||||||
from authentik.core.api.providers import ProviderSerializer
|
from authentik.core.api.providers import ProviderSerializer
|
||||||
from authentik.core.models import Application
|
from authentik.core.models import Application, User
|
||||||
from authentik.events.models import EventAction
|
from authentik.events.models import EventAction
|
||||||
|
from authentik.policies.api.exec import PolicyTestResultSerializer
|
||||||
from authentik.policies.engine import PolicyEngine
|
from authentik.policies.engine import PolicyEngine
|
||||||
|
from authentik.policies.types import PolicyResult
|
||||||
from authentik.stages.user_login.stage import USER_LOGIN_AUTHENTICATED
|
from authentik.stages.user_login.stage import USER_LOGIN_AUTHENTICATED
|
||||||
|
|
||||||
LOGGER = get_logger()
|
LOGGER = get_logger()
|
||||||
|
@ -112,23 +119,34 @@ class ApplicationViewSet(ModelViewSet):
|
||||||
return applications
|
return applications
|
||||||
|
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
|
request=inline_serializer(
|
||||||
|
"CheckAccessRequest", fields={"for_user": IntegerField(required=False)}
|
||||||
|
),
|
||||||
responses={
|
responses={
|
||||||
204: OpenApiResponse(description="Access granted"),
|
200: PolicyTestResultSerializer(),
|
||||||
403: OpenApiResponse(description="Access denied"),
|
404: OpenApiResponse(description="for_user user not found"),
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
@action(detail=True, methods=["GET"])
|
@action(detail=True, methods=["POST"])
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
def check_access(self, request: Request, slug: str) -> Response:
|
def check_access(self, request: Request, slug: str) -> Response:
|
||||||
"""Check access to a single application by slug"""
|
"""Check access to a single application by slug"""
|
||||||
# Don't use self.get_object as that checks for view_application permission
|
# Don't use self.get_object as that checks for view_application permission
|
||||||
# which the user might not have, even if they have access
|
# which the user might not have, even if they have access
|
||||||
application = get_object_or_404(Application, slug=slug)
|
application = get_object_or_404(Application, slug=slug)
|
||||||
engine = PolicyEngine(application, self.request.user, self.request)
|
# If the current user is superuser, they can set `for_user`
|
||||||
|
for_user = self.request.user
|
||||||
|
if self.request.user.is_superuser and "for_user" in request.data:
|
||||||
|
for_user = get_object_or_404(User, pk=request.data.get("for_user"))
|
||||||
|
engine = PolicyEngine(application, for_user, self.request)
|
||||||
engine.build()
|
engine.build()
|
||||||
if engine.passing:
|
result = engine.result
|
||||||
return Response(status=204)
|
response = PolicyTestResultSerializer(PolicyResult(False))
|
||||||
return Response(status=403)
|
if result.passing:
|
||||||
|
response = PolicyTestResultSerializer(PolicyResult(True))
|
||||||
|
if self.request.user.is_superuser:
|
||||||
|
response = PolicyTestResultSerializer(result)
|
||||||
|
return Response(response.data)
|
||||||
|
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
parameters=[
|
parameters=[
|
||||||
|
|
|
@ -26,20 +26,26 @@ class TestApplicationsAPI(APITestCase):
|
||||||
def test_check_access(self):
|
def test_check_access(self):
|
||||||
"""Test check_access operation"""
|
"""Test check_access operation"""
|
||||||
self.client.force_login(self.user)
|
self.client.force_login(self.user)
|
||||||
response = self.client.get(
|
response = self.client.post(
|
||||||
reverse(
|
reverse(
|
||||||
"authentik_api:application-check-access",
|
"authentik_api:application-check-access",
|
||||||
kwargs={"slug": self.allowed.slug},
|
kwargs={"slug": self.allowed.slug},
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, 204)
|
self.assertEqual(response.status_code, 200)
|
||||||
response = self.client.get(
|
self.assertJSONEqual(
|
||||||
|
force_str(response.content), {"messages": [], "passing": True}
|
||||||
|
)
|
||||||
|
response = self.client.post(
|
||||||
reverse(
|
reverse(
|
||||||
"authentik_api:application-check-access",
|
"authentik_api:application-check-access",
|
||||||
kwargs={"slug": self.denied.slug},
|
kwargs={"slug": self.denied.slug},
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, 403)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertJSONEqual(
|
||||||
|
force_str(response.content), {"messages": ["dummy"], "passing": False}
|
||||||
|
)
|
||||||
|
|
||||||
def test_list(self):
|
def test_list(self):
|
||||||
"""Test list operation without superuser_full_list"""
|
"""Test list operation without superuser_full_list"""
|
||||||
|
|
|
@ -74,8 +74,8 @@ func (pi *ProviderInstance) Bind(username string, bindDN, bindPW string, conn ne
|
||||||
if !passed {
|
if !passed {
|
||||||
return ldap.LDAPResultInvalidCredentials, nil
|
return ldap.LDAPResultInvalidCredentials, nil
|
||||||
}
|
}
|
||||||
r, err := apiClient.CoreApi.CoreApplicationsCheckAccessRetrieve(context.Background(), pi.appSlug).Execute()
|
p, _, err := apiClient.CoreApi.CoreApplicationsCheckAccessCreate(context.Background(), pi.appSlug).Execute()
|
||||||
if r.StatusCode == 403 {
|
if !p.Passing {
|
||||||
pi.log.WithField("bindDN", bindDN).Info("Access denied for user")
|
pi.log.WithField("bindDN", bindDN).Info("Access denied for user")
|
||||||
return ldap.LDAPResultInsufficientAccessRights, nil
|
return ldap.LDAPResultInsufficientAccessRights, nil
|
||||||
}
|
}
|
||||||
|
|
34
schema.yml
34
schema.yml
|
@ -1368,8 +1368,8 @@ paths:
|
||||||
'403':
|
'403':
|
||||||
$ref: '#/components/schemas/GenericError'
|
$ref: '#/components/schemas/GenericError'
|
||||||
/api/v2beta/core/applications/{slug}/check_access/:
|
/api/v2beta/core/applications/{slug}/check_access/:
|
||||||
get:
|
post:
|
||||||
operationId: core_applications_check_access_retrieve
|
operationId: core_applications_check_access_create
|
||||||
description: Check access to a single application by slug
|
description: Check access to a single application by slug
|
||||||
parameters:
|
parameters:
|
||||||
- in: path
|
- in: path
|
||||||
|
@ -1380,16 +1380,33 @@ paths:
|
||||||
required: true
|
required: true
|
||||||
tags:
|
tags:
|
||||||
- core
|
- core
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/CheckAccessRequestRequest'
|
||||||
|
application/x-www-form-urlencoded:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/CheckAccessRequestRequest'
|
||||||
|
multipart/form-data:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/CheckAccessRequestRequest'
|
||||||
security:
|
security:
|
||||||
- authentik: []
|
- authentik: []
|
||||||
- cookieAuth: []
|
- cookieAuth: []
|
||||||
responses:
|
responses:
|
||||||
'204':
|
'200':
|
||||||
description: Access granted
|
content:
|
||||||
'403':
|
application/json:
|
||||||
description: Access denied
|
schema:
|
||||||
|
$ref: '#/components/schemas/PolicyTestResult'
|
||||||
|
description: ''
|
||||||
|
'404':
|
||||||
|
description: for_user user not found
|
||||||
'400':
|
'400':
|
||||||
$ref: '#/components/schemas/ValidationError'
|
$ref: '#/components/schemas/ValidationError'
|
||||||
|
'403':
|
||||||
|
$ref: '#/components/schemas/GenericError'
|
||||||
/api/v2beta/core/applications/{slug}/metrics/:
|
/api/v2beta/core/applications/{slug}/metrics/:
|
||||||
get:
|
get:
|
||||||
operationId: core_applications_metrics_list
|
operationId: core_applications_metrics_list
|
||||||
|
@ -16120,6 +16137,11 @@ components:
|
||||||
- shell
|
- shell
|
||||||
- redirect
|
- redirect
|
||||||
type: string
|
type: string
|
||||||
|
CheckAccessRequestRequest:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
for_user:
|
||||||
|
type: integer
|
||||||
ClientTypeEnum:
|
ClientTypeEnum:
|
||||||
enum:
|
enum:
|
||||||
- confidential
|
- confidential
|
||||||
|
|
Reference in New Issue