Compare commits
4 Commits
eap-but-ac
...
root/more-
Author | SHA1 | Date | |
---|---|---|---|
06337283e8 | |||
5d28114a4b | |||
b7c154ccd2 | |||
d1fbf2ed65 |
@ -209,7 +209,7 @@ class ApplicationViewSet(UsedByMixin, ModelViewSet):
|
||||
@extend_schema(
|
||||
parameters=[
|
||||
OpenApiParameter(
|
||||
name="superuser_full_list",
|
||||
name="list_rbac",
|
||||
location=OpenApiParameter.QUERY,
|
||||
type=OpenApiTypes.BOOL,
|
||||
),
|
||||
@ -229,10 +229,8 @@ class ApplicationViewSet(UsedByMixin, ModelViewSet):
|
||||
"""Custom list method that checks Policy based access instead of guardian"""
|
||||
should_cache = request.query_params.get("search", "") == ""
|
||||
|
||||
superuser_full_list = (
|
||||
str(request.query_params.get("superuser_full_list", "false")).lower() == "true"
|
||||
)
|
||||
if superuser_full_list and request.user.is_superuser:
|
||||
list_rbac = str(request.query_params.get("list_rbac", "false")).lower() == "true"
|
||||
if list_rbac:
|
||||
return super().list(request)
|
||||
|
||||
only_with_launch_url = str(
|
||||
|
@ -4,7 +4,7 @@ from typing import Any
|
||||
|
||||
from django.utils.timezone import now
|
||||
from drf_spectacular.utils import OpenApiResponse, extend_schema, inline_serializer
|
||||
from guardian.shortcuts import assign_perm, get_anonymous_user
|
||||
from guardian.shortcuts import assign_perm
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.exceptions import ValidationError
|
||||
from rest_framework.fields import CharField
|
||||
@ -138,13 +138,8 @@ class TokenViewSet(UsedByMixin, ModelViewSet):
|
||||
owner_field = "user"
|
||||
rbac_allow_create_without_perm = True
|
||||
|
||||
def get_queryset(self):
|
||||
user = self.request.user if self.request else get_anonymous_user()
|
||||
if user.is_superuser:
|
||||
return super().get_queryset()
|
||||
return super().get_queryset().filter(user=user.pk)
|
||||
|
||||
def perform_create(self, serializer: TokenSerializer):
|
||||
# TODO: better permission check
|
||||
if not self.request.user.is_superuser:
|
||||
instance = serializer.save(
|
||||
user=self.request.user,
|
||||
|
57
authentik/core/migrations/0043_alter_user_options.py
Normal file
57
authentik/core/migrations/0043_alter_user_options.py
Normal file
@ -0,0 +1,57 @@
|
||||
# Generated by Django 5.0.10 on 2025-01-08 17:39
|
||||
|
||||
from django.db import migrations
|
||||
from django.apps.registry import Apps
|
||||
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
||||
|
||||
|
||||
def migrate_user_debug_attribute(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||
from django.apps import apps as real_apps
|
||||
from django.contrib.auth.management import create_permissions
|
||||
|
||||
db_alias = schema_editor.connection.alias
|
||||
|
||||
User = apps.get_model("authentik_core", "User")
|
||||
USER_ATTRIBUTE_DEBUG = "goauthentik.io/user/debug"
|
||||
|
||||
# Permissions are only created _after_ migrations are run
|
||||
# - https://github.com/django/django/blob/43cdfa8b20e567a801b7d0a09ec67ddd062d5ea4/django/contrib/auth/apps.py#L19
|
||||
# - https://stackoverflow.com/a/72029063/1870445
|
||||
create_permissions(real_apps.get_app_config("authentik_core"), using=db_alias)
|
||||
|
||||
Permission = apps.get_model("auth", "Permission")
|
||||
|
||||
new_prem = Permission.objects.using(db_alias).get(codename="user_view_debug")
|
||||
|
||||
db_alias = schema_editor.connection.alias
|
||||
for user in User.objects.using(db_alias).filter(
|
||||
**{f"attributes__{USER_ATTRIBUTE_DEBUG}": True}
|
||||
):
|
||||
user.permissions.add(new_prem)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_core", "0042_authenticatedsession_authentik_c_expires_08251d_idx_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name="user",
|
||||
options={
|
||||
"permissions": [
|
||||
("reset_user_password", "Reset Password"),
|
||||
("impersonate", "Can impersonate other users"),
|
||||
("assign_user_permissions", "Can assign permissions to users"),
|
||||
("unassign_user_permissions", "Can unassign permissions from users"),
|
||||
("preview_user", "Can preview user data sent to providers"),
|
||||
("view_user_applications", "View applications the user has access to"),
|
||||
("user_view_debug", "User receives additional details for error messages"),
|
||||
],
|
||||
"verbose_name": "User",
|
||||
"verbose_name_plural": "Users",
|
||||
},
|
||||
),
|
||||
migrations.RunPython(migrate_user_debug_attribute),
|
||||
]
|
@ -41,7 +41,6 @@ from authentik.tenants.models import DEFAULT_TOKEN_DURATION, DEFAULT_TOKEN_LENGT
|
||||
from authentik.tenants.utils import get_current_tenant, get_unique_identifier
|
||||
|
||||
LOGGER = get_logger()
|
||||
USER_ATTRIBUTE_DEBUG = "goauthentik.io/user/debug"
|
||||
USER_ATTRIBUTE_GENERATED = "goauthentik.io/user/generated"
|
||||
USER_ATTRIBUTE_EXPIRES = "goauthentik.io/user/expires"
|
||||
USER_ATTRIBUTE_DELETE_ON_LOGOUT = "goauthentik.io/user/delete-on-logout"
|
||||
@ -282,6 +281,7 @@ class User(SerializerModel, GuardianUserMixin, AttributesMixin, AbstractUser):
|
||||
("unassign_user_permissions", _("Can unassign permissions from users")),
|
||||
("preview_user", _("Can preview user data sent to providers")),
|
||||
("view_user_applications", _("View applications the user has access to")),
|
||||
("user_view_debug", _("User receives additional details for error messages")),
|
||||
]
|
||||
indexes = [
|
||||
models.Index(fields=["last_login"]),
|
||||
|
@ -96,7 +96,7 @@ class EndpointViewSet(UsedByMixin, ModelViewSet):
|
||||
OpenApiTypes.STR,
|
||||
),
|
||||
OpenApiParameter(
|
||||
name="superuser_full_list",
|
||||
name="list_rbac",
|
||||
location=OpenApiParameter.QUERY,
|
||||
type=OpenApiTypes.BOOL,
|
||||
),
|
||||
@ -110,8 +110,8 @@ class EndpointViewSet(UsedByMixin, ModelViewSet):
|
||||
"""List accessible endpoints"""
|
||||
should_cache = request.GET.get("search", "") == ""
|
||||
|
||||
superuser_full_list = str(request.GET.get("superuser_full_list", "false")).lower() == "true"
|
||||
if superuser_full_list and request.user.is_superuser:
|
||||
list_rbac = str(request.GET.get("list_rbac", "false")).lower() == "true"
|
||||
if list_rbac:
|
||||
return super().list(request)
|
||||
|
||||
queryset = self._filter_queryset_for_list(self.get_queryset())
|
||||
|
@ -97,12 +97,9 @@ class FlowErrorChallenge(Challenge):
|
||||
if not request or not error:
|
||||
return
|
||||
self.initial_data["request_id"] = request.request_id
|
||||
from authentik.core.models import USER_ATTRIBUTE_DEBUG
|
||||
|
||||
if request.user and request.user.is_authenticated:
|
||||
if request.user.is_superuser or request.user.group_attributes(request).get(
|
||||
USER_ATTRIBUTE_DEBUG, False
|
||||
):
|
||||
if request.user.has_perm("authentik_core.user_view_debug"):
|
||||
self.initial_data["error"] = str(error)
|
||||
self.initial_data["traceback"] = exception_to_string(error)
|
||||
|
||||
|
@ -13,6 +13,7 @@ from paramiko.ssh_exception import SSHException
|
||||
from structlog.stdlib import get_logger
|
||||
from yaml import safe_dump
|
||||
|
||||
from authentik import __version__
|
||||
from authentik.outposts.apps import MANAGED_OUTPOST
|
||||
from authentik.outposts.controllers.base import BaseClient, BaseController, ControllerException
|
||||
from authentik.outposts.docker_ssh import DockerInlineSSH, SSHManagedExternallyException
|
||||
@ -182,10 +183,16 @@ class DockerController(BaseController):
|
||||
`outposts.container_image_base`, but fall back to known-good images"""
|
||||
image = self.get_container_image()
|
||||
try:
|
||||
self.client.images.pull(image)
|
||||
except DockerException: # pragma: no cover
|
||||
image = f"ghcr.io/goauthentik/{self.outpost.type}:latest"
|
||||
self.client.images.pull(image)
|
||||
# See if the image exists...
|
||||
self.client.images.get(image)
|
||||
except DockerException:
|
||||
try:
|
||||
# ...otherwise try to pull it...
|
||||
self.client.images.pull(image)
|
||||
except DockerException:
|
||||
# ...and as a fallback to that default to a sane standard
|
||||
image = f"ghcr.io/goauthentik/{self.outpost.type}:{__version__}"
|
||||
self.client.images.pull(image)
|
||||
return image
|
||||
|
||||
def _get_container(self) -> tuple[Container, bool]:
|
||||
|
@ -7,7 +7,7 @@ from django.template.response import TemplateResponse
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from authentik.core.models import USER_ATTRIBUTE_DEBUG
|
||||
from authentik.core.models import User
|
||||
from authentik.policies.types import PolicyResult
|
||||
|
||||
|
||||
@ -31,12 +31,11 @@ class AccessDeniedResponse(TemplateResponse):
|
||||
if self.error_message:
|
||||
context["error"] = self.error_message
|
||||
# Only show policy result if user is authenticated and
|
||||
# either superuser or has USER_ATTRIBUTE_DEBUG set
|
||||
# has permissions to see them
|
||||
if self.policy_result:
|
||||
if self._request.user and self._request.user.is_authenticated:
|
||||
if self._request.user.is_superuser or self._request.user.group_attributes(
|
||||
self._request
|
||||
).get(USER_ATTRIBUTE_DEBUG, False):
|
||||
user: User = self._request.user
|
||||
if user.has_perm("authentik_core.user_view_debug"):
|
||||
context["policy_result"] = self.policy_result
|
||||
context["cancel"] = reverse("authentik_flows:cancel")
|
||||
return context
|
||||
|
@ -2,11 +2,8 @@
|
||||
|
||||
from json import dumps
|
||||
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from guardian.utils import get_anonymous_user
|
||||
from rest_framework import mixins
|
||||
from rest_framework.fields import CharField, ListField, SerializerMethodField
|
||||
from rest_framework.filters import OrderingFilter, SearchFilter
|
||||
from rest_framework.viewsets import GenericViewSet
|
||||
|
||||
from authentik.core.api.used_by import UsedByMixin
|
||||
@ -66,17 +63,7 @@ class AuthorizationCodeViewSet(
|
||||
serializer_class = ExpiringBaseGrantModelSerializer
|
||||
filterset_fields = ["user", "provider"]
|
||||
ordering = ["provider", "expires"]
|
||||
filter_backends = [
|
||||
DjangoFilterBackend,
|
||||
OrderingFilter,
|
||||
SearchFilter,
|
||||
]
|
||||
|
||||
def get_queryset(self):
|
||||
user = self.request.user if self.request else get_anonymous_user()
|
||||
if user.is_superuser:
|
||||
return super().get_queryset()
|
||||
return super().get_queryset().filter(user=user.pk)
|
||||
owner_field = "user"
|
||||
|
||||
|
||||
class RefreshTokenViewSet(
|
||||
@ -92,17 +79,7 @@ class RefreshTokenViewSet(
|
||||
serializer_class = TokenModelSerializer
|
||||
filterset_fields = ["user", "provider"]
|
||||
ordering = ["provider", "expires"]
|
||||
filter_backends = [
|
||||
DjangoFilterBackend,
|
||||
OrderingFilter,
|
||||
SearchFilter,
|
||||
]
|
||||
|
||||
def get_queryset(self):
|
||||
user = self.request.user if self.request else get_anonymous_user()
|
||||
if user.is_superuser:
|
||||
return super().get_queryset()
|
||||
return super().get_queryset().filter(user=user.pk)
|
||||
owner_field = "user"
|
||||
|
||||
|
||||
class AccessTokenViewSet(
|
||||
@ -118,14 +95,4 @@ class AccessTokenViewSet(
|
||||
serializer_class = TokenModelSerializer
|
||||
filterset_fields = ["user", "provider"]
|
||||
ordering = ["provider", "expires"]
|
||||
filter_backends = [
|
||||
DjangoFilterBackend,
|
||||
OrderingFilter,
|
||||
SearchFilter,
|
||||
]
|
||||
|
||||
def get_queryset(self):
|
||||
user = self.request.user if self.request else get_anonymous_user()
|
||||
if user.is_superuser:
|
||||
return super().get_queryset()
|
||||
return super().get_queryset().filter(user=user.pk)
|
||||
owner_field = "user"
|
||||
|
@ -6445,6 +6445,7 @@
|
||||
"authentik_core.remove_user_from_group",
|
||||
"authentik_core.reset_user_password",
|
||||
"authentik_core.unassign_user_permissions",
|
||||
"authentik_core.user_view_debug",
|
||||
"authentik_core.view_application",
|
||||
"authentik_core.view_applicationentitlement",
|
||||
"authentik_core.view_authenticatedsession",
|
||||
@ -12694,6 +12695,7 @@
|
||||
"authentik_core.remove_user_from_group",
|
||||
"authentik_core.reset_user_password",
|
||||
"authentik_core.unassign_user_permissions",
|
||||
"authentik_core.user_view_debug",
|
||||
"authentik_core.view_application",
|
||||
"authentik_core.view_applicationentitlement",
|
||||
"authentik_core.view_authenticatedsession",
|
||||
@ -13202,6 +13204,7 @@
|
||||
"unassign_user_permissions",
|
||||
"preview_user",
|
||||
"view_user_applications",
|
||||
"user_view_debug",
|
||||
"add_user",
|
||||
"change_user",
|
||||
"delete_user",
|
||||
|
@ -4,6 +4,9 @@ version = "2024.12.2"
|
||||
description = ""
|
||||
authors = ["authentik Team <hello@goauthentik.io>"]
|
||||
|
||||
[tool.poetry.requires-plugins]
|
||||
poetry-plugin-export = ">1.8"
|
||||
|
||||
[tool.black]
|
||||
line-length = 100
|
||||
target-version = ['py312']
|
||||
|
16
schema.yml
16
schema.yml
@ -3391,6 +3391,10 @@ paths:
|
||||
name: group
|
||||
schema:
|
||||
type: string
|
||||
- in: query
|
||||
name: list_rbac
|
||||
schema:
|
||||
type: boolean
|
||||
- in: query
|
||||
name: meta_description
|
||||
schema:
|
||||
@ -3439,10 +3443,6 @@ paths:
|
||||
name: slug
|
||||
schema:
|
||||
type: string
|
||||
- in: query
|
||||
name: superuser_full_list
|
||||
schema:
|
||||
type: boolean
|
||||
tags:
|
||||
- core
|
||||
security:
|
||||
@ -23204,6 +23204,10 @@ paths:
|
||||
operationId: rac_endpoints_list
|
||||
description: List accessible endpoints
|
||||
parameters:
|
||||
- in: query
|
||||
name: list_rbac
|
||||
schema:
|
||||
type: boolean
|
||||
- in: query
|
||||
name: name
|
||||
schema:
|
||||
@ -23234,10 +23238,6 @@ paths:
|
||||
name: search
|
||||
schema:
|
||||
type: string
|
||||
- in: query
|
||||
name: superuser_full_list
|
||||
schema:
|
||||
type: boolean
|
||||
tags:
|
||||
- rac
|
||||
security:
|
||||
|
@ -66,7 +66,7 @@ export class ApplicationListPage extends WithBrandConfig(TablePage<Application>)
|
||||
async apiEndpoint(): Promise<PaginatedResponse<Application>> {
|
||||
return new CoreApi(DEFAULT_CONFIG).coreApplicationsList({
|
||||
...(await this.defaultEndpointConfig()),
|
||||
superuserFullList: true,
|
||||
listRbac: true,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -156,7 +156,7 @@ export class BrandForm extends ModelForm<Brand, string> {
|
||||
.fetchObjects=${async (query?: string): Promise<Application[]> => {
|
||||
const args: CoreApplicationsListRequest = {
|
||||
ordering: "name",
|
||||
superuserFullList: true,
|
||||
listRbac: true,
|
||||
};
|
||||
if (query !== undefined) {
|
||||
args.search = query;
|
||||
|
@ -46,7 +46,7 @@ export class EndpointListPage extends Table<Endpoint> {
|
||||
return new RacApi(DEFAULT_CONFIG).racEndpointsList({
|
||||
...(await this.defaultEndpointConfig()),
|
||||
provider: this.provider?.pk,
|
||||
superuserFullList: true,
|
||||
listRbac: true,
|
||||
});
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user