Skip to content

Commit

Permalink
Merge branch 'main' into web/email-recovery-language
Browse files Browse the repository at this point in the history
* main: (119 commits)
  stages/email: Fix query parameters getting lost in Email links (#5376)
  core/rbac: fix missing field when removing perm, add delete from object page (#7226)
  website/integrations: grafana: add Helm and Terraform config examples (#7121)
  web: bump @types/codemirror from 5.60.11 to 5.60.12 in /web (#7223)
  translate: Updates for file web/xliff/en.xlf in zh_CN (#7224)
  translate: Updates for file web/xliff/en.xlf in zh-Hans (#7225)
  website/blogs: blog about sso tax (#7202)
  web: Application wizard v2 with tests (#7004)
  web: bump API Client version (#7220)
  core: bump goauthentik.io/api/v3 from 3.2023083.7 to 3.2023083.8 (#7221)
  providers/radius: TOTP MFA support (#7217)
  web: bump API Client version (#7218)
  stage/deny: add custom message (#7144)
  docs: update full-dev-setup docs (#7205)
  enterprise: bump license usage task frequency (#7215)
  web: bump the storybook group in /web with 5 updates (#7212)
  web: bump the sentry group in /web with 2 updates (#7211)
  Revert "web: Updates to the Context and Tasks libraries from lit. (#7168)"
  web: bump @types/codemirror from 5.60.10 to 5.60.11 in /web (#7209)
  web: bump @types/chart.js from 2.9.38 to 2.9.39 in /web (#7206)
  ...
  • Loading branch information
kensternberg-authentik committed Oct 19, 2023
2 parents ed44382 + f036820 commit 1d546f1
Show file tree
Hide file tree
Showing 398 changed files with 20,457 additions and 6,534 deletions.
4 changes: 2 additions & 2 deletions .github/codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ coverage:
# adjust accordingly based on how flaky your tests are
# this allows a 1% drop from the previous base commit coverage
threshold: 1%
notify:
after_n_builds: 3
comment:
after_n_builds: 3
1 change: 1 addition & 0 deletions .github/workflows/ci-main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ jobs:
psql:
- 12-alpine
- 15-alpine
- 16-alpine
steps:
- uses: actions/checkout@v4
- name: Setup authentik env
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci-outpost.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
- name: golangci-lint
uses: golangci/golangci-lint-action@v3
with:
version: v1.52.2
version: v1.54.2
args: --timeout 5000s --verbose
skip-cache: true
test-unittest:
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/ghcr-retention.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
name: ghcr-retention

on:
schedule:
- cron: "0 0 * * *" # every day at midnight
# schedule:
# - cron: "0 0 * * *" # every day at midnight
workflow_dispatch:

jobs:
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -206,3 +206,6 @@ data/
.netlify
.ruff_cache
source_docs/

### Golang ###
/vendor/
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ COPY ./gen-ts-api /work/web/node_modules/@goauthentik/api
RUN npm run build

# Stage 3: Build go proxy
FROM docker.io/golang:1.21.1-bookworm AS go-builder
FROM docker.io/golang:1.21.3-bookworm AS go-builder

WORKDIR /go/src/goauthentik.io

Expand Down
9 changes: 5 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,15 @@ test: ## Run the server tests and produce a coverage report (locally)
coverage report

lint-fix: ## Lint and automatically fix errors in the python source code. Reports spelling errors.
isort authentik $(PY_SOURCES)
black authentik $(PY_SOURCES)
ruff authentik $(PY_SOURCES)
isort $(PY_SOURCES)
black $(PY_SOURCES)
ruff $(PY_SOURCES)
codespell -w $(CODESPELL_ARGS)

lint: ## Lint the python and golang sources
pylint $(PY_SOURCES)
bandit -r $(PY_SOURCES) -x node_modules
./web/node_modules/.bin/pyright $(PY_SOURCES)
pylint $(PY_SOURCES)
golangci-lint run -v

migrate: ## Run the Authentik Django server's migrations
Expand Down
6 changes: 3 additions & 3 deletions authentik/admin/api/meta.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Meta API"""
from drf_spectacular.utils import extend_schema
from rest_framework.fields import CharField
from rest_framework.permissions import IsAdminUser
from rest_framework.permissions import IsAuthenticated
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.viewsets import ViewSet
Expand All @@ -21,7 +21,7 @@ class AppSerializer(PassiveSerializer):
class AppsViewSet(ViewSet):
"""Read-only view list all installed apps"""

permission_classes = [IsAdminUser]
permission_classes = [IsAuthenticated]

@extend_schema(responses={200: AppSerializer(many=True)})
def list(self, request: Request) -> Response:
Expand All @@ -35,7 +35,7 @@ def list(self, request: Request) -> Response:
class ModelViewSet(ViewSet):
"""Read-only view list all installed models"""

permission_classes = [IsAdminUser]
permission_classes = [IsAuthenticated]

@extend_schema(responses={200: AppSerializer(many=True)})
def list(self, request: Request) -> Response:
Expand Down
4 changes: 2 additions & 2 deletions authentik/admin/api/metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from drf_spectacular.utils import extend_schema, extend_schema_field
from guardian.shortcuts import get_objects_for_user
from rest_framework.fields import IntegerField, SerializerMethodField
from rest_framework.permissions import IsAdminUser
from rest_framework.permissions import IsAuthenticated
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.views import APIView
Expand Down Expand Up @@ -68,7 +68,7 @@ def get_authorizations(self, _):
class AdministrationMetricsViewSet(APIView):
"""Login Metrics per 1h"""

permission_classes = [IsAdminUser]
permission_classes = [IsAuthenticated]

@extend_schema(responses={200: LoginMetricsSerializer(many=False)})
def get(self, request: Request) -> Response:
Expand Down
4 changes: 2 additions & 2 deletions authentik/admin/api/system.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
from drf_spectacular.utils import extend_schema
from gunicorn import version_info as gunicorn_version
from rest_framework.fields import SerializerMethodField
from rest_framework.permissions import IsAdminUser
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.views import APIView
Expand All @@ -17,6 +16,7 @@
from authentik.lib.utils.reflection import get_env
from authentik.outposts.apps import MANAGED_OUTPOST
from authentik.outposts.models import Outpost
from authentik.rbac.permissions import HasPermission


class RuntimeDict(TypedDict):
Expand Down Expand Up @@ -88,7 +88,7 @@ def get_embedded_outpost_host(self, request: Request) -> str:
class SystemView(APIView):
"""Get system information."""

permission_classes = [IsAdminUser]
permission_classes = [HasPermission("authentik_rbac.view_system_info")]
pagination_class = None
filter_backends = []
serializer_class = SystemSerializer
Expand Down
6 changes: 4 additions & 2 deletions authentik/admin/api/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,15 @@
ListField,
SerializerMethodField,
)
from rest_framework.permissions import IsAdminUser
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.viewsets import ViewSet
from structlog.stdlib import get_logger

from authentik.api.decorators import permission_required
from authentik.core.api.utils import PassiveSerializer
from authentik.events.monitored_tasks import TaskInfo, TaskResultStatus
from authentik.rbac.permissions import HasPermission

LOGGER = get_logger()

Expand Down Expand Up @@ -63,7 +64,7 @@ def to_representation(self, instance: TaskInfo):
class TaskViewSet(ViewSet):
"""Read-only view set that returns all background tasks"""

permission_classes = [IsAdminUser]
permission_classes = [HasPermission("authentik_rbac.view_system_tasks")]
serializer_class = TaskSerializer

@extend_schema(
Expand Down Expand Up @@ -93,6 +94,7 @@ def list(self, request: Request) -> Response:
tasks = sorted(TaskInfo.all().values(), key=lambda task: task.task_name)
return Response(TaskSerializer(tasks, many=True).data)

@permission_required(None, ["authentik_rbac.run_system_tasks"])
@extend_schema(
request=OpenApiTypes.NONE,
responses={
Expand Down
4 changes: 2 additions & 2 deletions authentik/admin/api/workers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,18 @@
from django.conf import settings
from drf_spectacular.utils import extend_schema, inline_serializer
from rest_framework.fields import IntegerField
from rest_framework.permissions import IsAdminUser
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.views import APIView

from authentik.rbac.permissions import HasPermission
from authentik.root.celery import CELERY_APP


class WorkerView(APIView):
"""Get currently connected worker count."""

permission_classes = [IsAdminUser]
permission_classes = [HasPermission("authentik_rbac.view_system_info")]

@extend_schema(responses=inline_serializer("Workers", fields={"count": IntegerField()}))
def get(self, request: Request) -> Response:
Expand Down
6 changes: 3 additions & 3 deletions authentik/api/authorization.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
from rest_framework.filters import BaseFilterBackend
from rest_framework.permissions import BasePermission
from rest_framework.request import Request
from rest_framework_guardian.filters import ObjectPermissionsFilter

from authentik.api.authentication import validate_auth
from authentik.rbac.filters import ObjectFilter


class OwnerFilter(BaseFilterBackend):
Expand All @@ -26,14 +26,14 @@ def filter_queryset(self, request: Request, queryset: QuerySet, view) -> QuerySe
class SecretKeyFilter(DjangoFilterBackend):
"""Allow access to all objects when authenticated with secret key as token.
Replaces both DjangoFilterBackend and ObjectPermissionsFilter"""
Replaces both DjangoFilterBackend and ObjectFilter"""

def filter_queryset(self, request: Request, queryset: QuerySet, view) -> QuerySet:
auth_header = get_authorization_header(request)
token = validate_auth(auth_header)
if token and token == settings.SECRET_KEY:
return queryset
queryset = ObjectPermissionsFilter().filter_queryset(request, queryset, view)
queryset = ObjectFilter().filter_queryset(request, queryset, view)
return super().filter_queryset(request, queryset, view)


Expand Down
16 changes: 9 additions & 7 deletions authentik/api/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,25 @@
LOGGER = get_logger()


def permission_required(perm: Optional[str] = None, other_perms: Optional[list[str]] = None):
def permission_required(obj_perm: Optional[str] = None, global_perms: Optional[list[str]] = None):
"""Check permissions for a single custom action"""

def wrapper_outter(func: Callable):
"""Check permissions for a single custom action"""

@wraps(func)
def wrapper(self: ModelViewSet, request: Request, *args, **kwargs) -> Response:
if perm:
if obj_perm:
obj = self.get_object()
if not request.user.has_perm(perm, obj):
LOGGER.debug("denying access for object", user=request.user, perm=perm, obj=obj)
if not request.user.has_perm(obj_perm, obj):
LOGGER.debug(
"denying access for object", user=request.user, perm=obj_perm, obj=obj
)
return self.permission_denied(request)
if other_perms:
for other_perm in other_perms:
if global_perms:
for other_perm in global_perms:
if not request.user.has_perm(other_perm):
LOGGER.debug("denying access for other", user=request.user, perm=perm)
LOGGER.debug("denying access for other", user=request.user, perm=other_perm)
return self.permission_denied(request)
return func(self, request, *args, **kwargs)

Expand Down
7 changes: 7 additions & 0 deletions authentik/api/pagination.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,10 @@ def get_paginated_response_schema(self, schema):
},
"required": ["pagination", "results"],
}


class SmallerPagination(Pagination):
"""Smaller pagination for objects which might require a lot of queries
to retrieve all data for."""

max_page_size = 10
1 change: 1 addition & 0 deletions authentik/api/tests/test_viewsets.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ def viewset_tester_factory(test_viewset: type[ModelViewSet]) -> Callable:

def tester(self: TestModelViewSets):
self.assertIsNotNone(getattr(test_viewset, "search_fields", None))
self.assertIsNotNone(getattr(test_viewset, "ordering", None))
filterset_class = getattr(test_viewset, "filterset_class", None)
if not filterset_class:
self.assertIsNotNone(getattr(test_viewset, "filterset_fields", None))
Expand Down
3 changes: 1 addition & 2 deletions authentik/blueprints/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
from rest_framework.decorators import action
from rest_framework.exceptions import ValidationError
from rest_framework.fields import CharField, DateTimeField, JSONField
from rest_framework.permissions import IsAdminUser
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.serializers import ListSerializer, ModelSerializer
Expand Down Expand Up @@ -87,11 +86,11 @@ class Meta:
class BlueprintInstanceViewSet(UsedByMixin, ModelViewSet):
"""Blueprint instances"""

permission_classes = [IsAdminUser]
serializer_class = BlueprintInstanceSerializer
queryset = BlueprintInstance.objects.all()
search_fields = ["name", "path"]
filterset_fields = ["name", "path"]
ordering = ["name"]

@extend_schema(
responses={
Expand Down
18 changes: 14 additions & 4 deletions authentik/blueprints/v1/importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,25 +35,28 @@
Source,
UserSourceConnection,
)
from authentik.enterprise.models import LicenseUsage
from authentik.events.utils import cleanse_dict
from authentik.flows.models import FlowToken, Stage
from authentik.lib.models import SerializerModel
from authentik.lib.sentry import SentryIgnoredException
from authentik.outposts.models import OutpostServiceConnection
from authentik.policies.models import Policy, PolicyBindingModel
from authentik.providers.scim.models import SCIMGroup, SCIMUser

# Context set when the serializer is created in a blueprint context
# Update website/developer-docs/blueprints/v1/models.md when used
SERIALIZER_CONTEXT_BLUEPRINT = "blueprint_entry"


def is_model_allowed(model: type[Model]) -> bool:
"""Check if model is allowed"""
def excluded_models() -> list[type[Model]]:
"""Return a list of all excluded models that shouldn't be exposed via API
or other means (internal only, base classes, non-used objects, etc)"""
# pylint: disable=imported-auth-user
from django.contrib.auth.models import Group as DjangoGroup
from django.contrib.auth.models import User as DjangoUser

excluded_models = (
return (
DjangoUser,
DjangoGroup,
# Base classes
Expand All @@ -69,8 +72,15 @@ def is_model_allowed(model: type[Model]) -> bool:
AuthenticatedSession,
# Classes which are only internally managed
FlowToken,
LicenseUsage,
SCIMGroup,
SCIMUser,
)
return model not in excluded_models and issubclass(model, (SerializerModel, BaseMetaModel))


def is_model_allowed(model: type[Model]) -> bool:
"""Check if model is allowed"""
return model not in excluded_models() and issubclass(model, (SerializerModel, BaseMetaModel))


class DoRollback(SentryIgnoredException):
Expand Down
4 changes: 2 additions & 2 deletions authentik/core/api/applications.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
from rest_framework.response import Response
from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import ModelViewSet
from rest_framework_guardian.filters import ObjectPermissionsFilter
from structlog.stdlib import get_logger
from structlog.testing import capture_logs

Expand All @@ -38,6 +37,7 @@
from authentik.policies.api.exec import PolicyTestResultSerializer
from authentik.policies.engine import PolicyEngine
from authentik.policies.types import PolicyResult
from authentik.rbac.filters import ObjectFilter

LOGGER = get_logger()

Expand Down Expand Up @@ -122,7 +122,7 @@ class ApplicationViewSet(UsedByMixin, ModelViewSet):
def _filter_queryset_for_list(self, queryset: QuerySet) -> QuerySet:
"""Custom filter_queryset method which ignores guardian, but still supports sorting"""
for backend in list(self.filter_backends):
if backend == ObjectPermissionsFilter:
if backend == ObjectFilter:
continue
queryset = backend().filter_queryset(self.request, queryset, self)
return queryset
Expand Down

0 comments on commit 1d546f1

Please sign in to comment.