Compare commits
	
		
			25 Commits
		
	
	
		
			dependabot
			...
			core/refac
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 17adf23c79 | |||
| 39c54de2b5 | |||
| 417023e098 | |||
| 6376e4a44b | |||
| c692f91b72 | |||
| 95db1ecf62 | |||
| e28968c896 | |||
| ca0a4cb34f | |||
| e204a3fe16 | |||
| c47a9a6286 | |||
| cf78fad6ec | |||
| 8996630eff | |||
| fb93847860 | |||
| bb7404e884 | |||
| e4c54c2d1f | |||
| b48d5892a8 | |||
| eb87941f61 | |||
| 62e2684ecd | |||
| 5a59513d0b | |||
| c3ff834ea7 | |||
| 9f74d2cb09 | |||
| 30a9e597e9 | |||
| 12d94c8c5e | |||
| 221cb029d8 | |||
| 7f4fbf354e | 
| @ -1,5 +1,5 @@ | ||||
| [bumpversion] | ||||
| current_version = 2025.4.1 | ||||
| current_version = 2025.4.0 | ||||
| tag = True | ||||
| commit = True | ||||
| parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(?:-(?P<rc_t>[a-zA-Z-]+)(?P<rc_n>[1-9]\\d*))? | ||||
|  | ||||
							
								
								
									
										2
									
								
								.github/actions/setup/action.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/actions/setup/action.yml
									
									
									
									
										vendored
									
									
								
							| @ -48,7 +48,7 @@ runs: | ||||
|     - name: Generate config | ||||
|       shell: uv run python {0} | ||||
|       run: | | ||||
|         from authentik.lib.generators import generate_id | ||||
|         from authentik.crypto.generators import generate_id | ||||
|         from yaml import safe_dump | ||||
|  | ||||
|         with open("local.env.yml", "w") as _config: | ||||
|  | ||||
							
								
								
									
										14
									
								
								.github/dependabot.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										14
									
								
								.github/dependabot.yml
									
									
									
									
										vendored
									
									
								
							| @ -23,13 +23,7 @@ updates: | ||||
|   - package-ecosystem: npm | ||||
|     directories: | ||||
|       - "/web" | ||||
|       - "/web/packages/sfe" | ||||
|       - "/web/packages/core" | ||||
|       - "/web/packages/esbuild-plugin-live-reload" | ||||
|       - "/packages/prettier-config" | ||||
|       - "/packages/tsconfig" | ||||
|       - "/packages/docusaurus-config" | ||||
|       - "/packages/eslint-config" | ||||
|       - "/web/sfe" | ||||
|     schedule: | ||||
|       interval: daily | ||||
|       time: "04:00" | ||||
| @ -74,9 +68,6 @@ updates: | ||||
|       wdio: | ||||
|         patterns: | ||||
|           - "@wdio/*" | ||||
|       goauthentik: | ||||
|         patterns: | ||||
|           - "@goauthentik/*" | ||||
|   - package-ecosystem: npm | ||||
|     directory: "/website" | ||||
|     schedule: | ||||
| @ -97,9 +88,6 @@ updates: | ||||
|           - "swc-*" | ||||
|           - "lightningcss*" | ||||
|           - "@rspack/binding*" | ||||
|       goauthentik: | ||||
|         patterns: | ||||
|           - "@goauthentik/*" | ||||
|   - package-ecosystem: npm | ||||
|     directory: "/lifecycle/aws" | ||||
|     schedule: | ||||
|  | ||||
							
								
								
									
										1
									
								
								.github/workflows/api-ts-publish.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/workflows/api-ts-publish.yml
									
									
									
									
										vendored
									
									
								
							| @ -53,7 +53,6 @@ jobs: | ||||
|           signoff: true | ||||
|           # ID from https://api.github.com/users/authentik-automation[bot] | ||||
|           author: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com> | ||||
|           labels: dependencies | ||||
|       - uses: peter-evans/enable-pull-request-automerge@v3 | ||||
|         with: | ||||
|           token: ${{ steps.generate_token.outputs.token }} | ||||
|  | ||||
							
								
								
									
										7
									
								
								.github/workflows/ci-main.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								.github/workflows/ci-main.yml
									
									
									
									
										vendored
									
									
								
							| @ -62,7 +62,6 @@ jobs: | ||||
|         psql: | ||||
|           - 15-alpine | ||||
|           - 16-alpine | ||||
|           - 17-alpine | ||||
|         run_id: [1, 2, 3, 4, 5] | ||||
|     steps: | ||||
|       - uses: actions/checkout@v4 | ||||
| @ -71,7 +70,7 @@ jobs: | ||||
|       - name: checkout stable | ||||
|         run: | | ||||
|           # Copy current, latest config to local | ||||
|           cp authentik/lib/default.yml local.env.yml | ||||
|           cp authentik/common/config/default.yml local.env.yml | ||||
|           cp -R .github .. | ||||
|           cp -R scripts .. | ||||
|           git checkout $(git tag --sort=version:refname | grep '^version/' | grep -vE -- '-rc[0-9]+$' | tail -n1) | ||||
| @ -117,7 +116,6 @@ jobs: | ||||
|         psql: | ||||
|           - 15-alpine | ||||
|           - 16-alpine | ||||
|           - 17-alpine | ||||
|         run_id: [1, 2, 3, 4, 5] | ||||
|     steps: | ||||
|       - uses: actions/checkout@v4 | ||||
| @ -202,7 +200,7 @@ jobs: | ||||
|         uses: actions/cache@v4 | ||||
|         with: | ||||
|           path: web/dist | ||||
|           key: ${{ runner.os }}-web-${{ hashFiles('web/package-lock.json', 'web/src/**', 'web/packages/sfe/src/**') }}-b | ||||
|           key: ${{ runner.os }}-web-${{ hashFiles('web/package-lock.json', 'web/src/**') }} | ||||
|       - name: prepare web ui | ||||
|         if: steps.cache-web.outputs.cache-hit != 'true' | ||||
|         working-directory: web | ||||
| @ -210,7 +208,6 @@ jobs: | ||||
|           npm ci | ||||
|           make -C .. gen-client-ts | ||||
|           npm run build | ||||
|           npm run build:sfe | ||||
|       - name: run e2e | ||||
|         run: | | ||||
|           uv run coverage run manage.py test ${{ matrix.job.glob }} | ||||
|  | ||||
| @ -37,7 +37,6 @@ jobs: | ||||
|           signoff: true | ||||
|           # ID from https://api.github.com/users/authentik-automation[bot] | ||||
|           author: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com> | ||||
|           labels: dependencies | ||||
|       - uses: peter-evans/enable-pull-request-automerge@v3 | ||||
|         with: | ||||
|           token: ${{ steps.generate_token.outputs.token }} | ||||
|  | ||||
							
								
								
									
										1
									
								
								.github/workflows/image-compress.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/workflows/image-compress.yml
									
									
									
									
										vendored
									
									
								
							| @ -53,7 +53,6 @@ jobs: | ||||
|           body: ${{ steps.compress.outputs.markdown }} | ||||
|           delete-branch: true | ||||
|           signoff: true | ||||
|           labels: dependencies | ||||
|       - uses: peter-evans/enable-pull-request-automerge@v3 | ||||
|         if: "${{ github.event_name != 'pull_request' && steps.compress.outputs.markdown != '' }}" | ||||
|         with: | ||||
|  | ||||
							
								
								
									
										16
									
								
								.github/workflows/packages-npm-publish.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										16
									
								
								.github/workflows/packages-npm-publish.yml
									
									
									
									
										vendored
									
									
								
							| @ -7,7 +7,6 @@ on: | ||||
|       - packages/eslint-config/** | ||||
|       - packages/prettier-config/** | ||||
|       - packages/tsconfig/** | ||||
|       - web/packages/esbuild-plugin-live-reload/** | ||||
|   workflow_dispatch: | ||||
| jobs: | ||||
|   publish: | ||||
| @ -17,28 +16,27 @@ jobs: | ||||
|       fail-fast: false | ||||
|       matrix: | ||||
|         package: | ||||
|           - packages/docusaurus-config | ||||
|           - packages/eslint-config | ||||
|           - packages/prettier-config | ||||
|           - packages/tsconfig | ||||
|           - web/packages/esbuild-plugin-live-reload | ||||
|           - docusaurus-config | ||||
|           - eslint-config | ||||
|           - prettier-config | ||||
|           - tsconfig | ||||
|     steps: | ||||
|       - uses: actions/checkout@v4 | ||||
|         with: | ||||
|           fetch-depth: 2 | ||||
|       - uses: actions/setup-node@v4 | ||||
|         with: | ||||
|           node-version-file: ${{ matrix.package }}/package.json | ||||
|           node-version-file: packages/${{ matrix.package }}/package.json | ||||
|           registry-url: "https://registry.npmjs.org" | ||||
|       - name: Get changed files | ||||
|         id: changed-files | ||||
|         uses: tj-actions/changed-files@ed68ef82c095e0d48ec87eccea555d944a631a4c | ||||
|         with: | ||||
|           files: | | ||||
|             ${{ matrix.package }}/package.json | ||||
|             packages/${{ matrix.package }}/package.json | ||||
|       - name: Publish package | ||||
|         if: steps.changed-files.outputs.any_changed == 'true' | ||||
|         working-directory: ${{ matrix.package }} | ||||
|         working-directory: packages/${{ matrix.package}} | ||||
|         run: | | ||||
|           npm ci | ||||
|           npm run build | ||||
|  | ||||
| @ -52,6 +52,3 @@ jobs: | ||||
|           body: "core, web: update translations" | ||||
|           delete-branch: true | ||||
|           signoff: true | ||||
|           labels: dependencies | ||||
|           # ID from https://api.github.com/users/authentik-automation[bot] | ||||
|           author: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com> | ||||
|  | ||||
							
								
								
									
										15
									
								
								.github/workflows/translation-rename.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										15
									
								
								.github/workflows/translation-rename.yml
									
									
									
									
										vendored
									
									
								
							| @ -15,7 +15,6 @@ jobs: | ||||
|     runs-on: ubuntu-latest | ||||
|     if: ${{ github.event.pull_request.user.login == 'transifex-integration[bot]'}} | ||||
|     steps: | ||||
|       - uses: actions/checkout@v4 | ||||
|       - id: generate_token | ||||
|         uses: tibdex/github-app-token@v2 | ||||
|         with: | ||||
| @ -26,13 +25,23 @@ jobs: | ||||
|         env: | ||||
|           GH_TOKEN: ${{ steps.generate_token.outputs.token }} | ||||
|         run: | | ||||
|           title=$(gh pr view ${{ github.event.pull_request.number }} --json  "title" -q ".title") | ||||
|           title=$(curl -q -L \ | ||||
|             -H "Accept: application/vnd.github+json" \ | ||||
|             -H "Authorization: Bearer ${GH_TOKEN}" \ | ||||
|             -H "X-GitHub-Api-Version: 2022-11-28" \ | ||||
|             https://api.github.com/repos/${GITHUB_REPOSITORY}/pulls/${{ github.event.pull_request.number }} | jq -r .title) | ||||
|           echo "title=${title}" >> "$GITHUB_OUTPUT" | ||||
|       - name: Rename | ||||
|         env: | ||||
|           GH_TOKEN: ${{ steps.generate_token.outputs.token }} | ||||
|         run: | | ||||
|           gh pr edit ${{ github.event.pull_request.number }} -t "translate: ${{ steps.title.outputs.title }}" --add-label dependencies | ||||
|           curl -L \ | ||||
|             -X PATCH \ | ||||
|             -H "Accept: application/vnd.github+json" \ | ||||
|             -H "Authorization: Bearer ${GH_TOKEN}" \ | ||||
|             -H "X-GitHub-Api-Version: 2022-11-28" \ | ||||
|             https://api.github.com/repos/${GITHUB_REPOSITORY}/pulls/${{ github.event.pull_request.number }} \ | ||||
|             -d "{\"title\":\"translate: ${{ steps.title.outputs.title }}\"}" | ||||
|       - uses: peter-evans/enable-pull-request-automerge@v3 | ||||
|         with: | ||||
|           token: ${{ steps.generate_token.outputs.token }} | ||||
|  | ||||
| @ -1,7 +1,7 @@ | ||||
| # syntax=docker/dockerfile:1 | ||||
|  | ||||
| # Stage 1: Build website | ||||
| FROM --platform=${BUILDPLATFORM} docker.io/library/node:24 AS website-builder | ||||
| FROM --platform=${BUILDPLATFORM} docker.io/library/node:22 AS website-builder | ||||
|  | ||||
| ENV NODE_ENV=production | ||||
|  | ||||
| @ -20,7 +20,7 @@ COPY ./SECURITY.md /work/ | ||||
| RUN npm run build-bundled | ||||
|  | ||||
| # Stage 2: Build webui | ||||
| FROM --platform=${BUILDPLATFORM} docker.io/library/node:24 AS web-builder | ||||
| FROM --platform=${BUILDPLATFORM} docker.io/library/node:22 AS web-builder | ||||
|  | ||||
| ARG GIT_BUILD_HASH | ||||
| ENV GIT_BUILD_HASH=$GIT_BUILD_HASH | ||||
| @ -40,8 +40,7 @@ COPY ./web /work/web/ | ||||
| COPY ./website /work/website/ | ||||
| COPY ./gen-ts-api /work/web/node_modules/@goauthentik/api | ||||
|  | ||||
| RUN npm run build && \ | ||||
|     npm run build:sfe | ||||
| RUN npm run build | ||||
|  | ||||
| # Stage 3: Build go proxy | ||||
| FROM --platform=${BUILDPLATFORM} docker.io/library/golang:1.24-bookworm AS go-builder | ||||
| @ -94,7 +93,7 @@ RUN --mount=type=secret,id=GEOIPUPDATE_ACCOUNT_ID \ | ||||
|     /bin/sh -c "GEOIPUPDATE_LICENSE_KEY_FILE=/run/secrets/GEOIPUPDATE_LICENSE_KEY /usr/bin/entry.sh || echo 'Failed to get GeoIP database, disabling'; exit 0" | ||||
|  | ||||
| # Stage 5: Download uv | ||||
| FROM ghcr.io/astral-sh/uv:0.7.8 AS uv | ||||
| FROM ghcr.io/astral-sh/uv:0.7.3 AS uv | ||||
| # Stage 6: Base python image | ||||
| FROM ghcr.io/goauthentik/fips-python:3.13.3-slim-bookworm-fips AS python-base | ||||
|  | ||||
|  | ||||
							
								
								
									
										57
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										57
									
								
								Makefile
									
									
									
									
									
								
							| @ -1,7 +1,6 @@ | ||||
| .PHONY: gen dev-reset all clean test web website | ||||
|  | ||||
| SHELL := /usr/bin/env bash | ||||
| .SHELLFLAGS += ${SHELLFLAGS} -e -o pipefail | ||||
| .SHELLFLAGS += ${SHELLFLAGS} -e | ||||
| PWD = $(shell pwd) | ||||
| UID = $(shell id -u) | ||||
| GID = $(shell id -g) | ||||
| @ -9,13 +8,13 @@ NPM_VERSION = $(shell python -m scripts.generate_semver) | ||||
| PY_SOURCES = authentik tests scripts lifecycle .github | ||||
| DOCKER_IMAGE ?= "authentik:test" | ||||
|  | ||||
| GEN_API_TS = gen-ts-api | ||||
| GEN_API_PY = gen-py-api | ||||
| GEN_API_GO = gen-go-api | ||||
| GEN_API_TS = "gen-ts-api" | ||||
| GEN_API_PY = "gen-py-api" | ||||
| GEN_API_GO = "gen-go-api" | ||||
|  | ||||
| pg_user := $(shell uv run python -m authentik.lib.config postgresql.user 2>/dev/null) | ||||
| pg_host := $(shell uv run python -m authentik.lib.config postgresql.host 2>/dev/null) | ||||
| pg_name := $(shell uv run python -m authentik.lib.config postgresql.name 2>/dev/null) | ||||
| pg_user := $(shell uv run python -m authentik.common.config postgresql.user 2>/dev/null) | ||||
| pg_host := $(shell uv run python -m authentik.common.config postgresql.host 2>/dev/null) | ||||
| pg_name := $(shell uv run python -m authentik.common.config postgresql.name 2>/dev/null) | ||||
|  | ||||
| all: lint-fix lint test gen web  ## Lint, build, and test everything | ||||
|  | ||||
| @ -118,19 +117,14 @@ gen-diff:  ## (Release) generate the changelog diff between the current schema a | ||||
| 	npx prettier --write diff.md | ||||
|  | ||||
| gen-clean-ts:  ## Remove generated API client for Typescript | ||||
| 	rm -rf ${PWD}/${GEN_API_TS}/ | ||||
| 	rm -rf ${PWD}/web/node_modules/@goauthentik/api/ | ||||
| 	rm -rf ./${GEN_API_TS}/ | ||||
| 	rm -rf ./web/node_modules/@goauthentik/api/ | ||||
|  | ||||
| gen-clean-go:  ## Remove generated API client for Go | ||||
| 	mkdir -p ${PWD}/${GEN_API_GO} | ||||
| ifneq ($(wildcard ${PWD}/${GEN_API_GO}/.*),) | ||||
| 	make -C ${PWD}/${GEN_API_GO} clean | ||||
| else | ||||
| 	rm -rf ${PWD}/${GEN_API_GO} | ||||
| endif | ||||
| 	rm -rf ./${GEN_API_GO}/ | ||||
|  | ||||
| gen-clean-py:  ## Remove generated API client for Python | ||||
| 	rm -rf ${PWD}/${GEN_API_PY}/ | ||||
| 	rm -rf ./${GEN_API_PY}/ | ||||
|  | ||||
| gen-clean: gen-clean-ts gen-clean-go gen-clean-py  ## Remove generated API clients | ||||
|  | ||||
| @ -147,8 +141,8 @@ gen-client-ts: gen-clean-ts  ## Build and install the authentik API for Typescri | ||||
| 		--git-repo-id authentik \ | ||||
| 		--git-user-id goauthentik | ||||
| 	mkdir -p web/node_modules/@goauthentik/api | ||||
| 	cd ${PWD}/${GEN_API_TS} && npm i | ||||
| 	\cp -rf ${PWD}/${GEN_API_TS}/* web/node_modules/@goauthentik/api | ||||
| 	cd ./${GEN_API_TS} && npm i | ||||
| 	\cp -rf ./${GEN_API_TS}/* web/node_modules/@goauthentik/api | ||||
|  | ||||
| gen-client-py: gen-clean-py ## Build and install the authentik API for Python | ||||
| 	docker run \ | ||||
| @ -162,17 +156,24 @@ gen-client-py: gen-clean-py ## Build and install the authentik API for Python | ||||
| 		--additional-properties=packageVersion=${NPM_VERSION} \ | ||||
| 		--git-repo-id authentik \ | ||||
| 		--git-user-id goauthentik | ||||
| 	pip install ./${GEN_API_PY} | ||||
|  | ||||
| gen-client-go: gen-clean-go  ## Build and install the authentik API for Golang | ||||
| 	mkdir -p ${PWD}/${GEN_API_GO} | ||||
| ifeq ($(wildcard ${PWD}/${GEN_API_GO}/.*),) | ||||
| 	git clone --depth 1 https://github.com/goauthentik/client-go.git ${PWD}/${GEN_API_GO} | ||||
| else | ||||
| 	cd ${PWD}/${GEN_API_GO} && git pull | ||||
| endif | ||||
| 	cp ${PWD}/schema.yml ${PWD}/${GEN_API_GO} | ||||
| 	make -C ${PWD}/${GEN_API_GO} build | ||||
| 	mkdir -p ./${GEN_API_GO} ./${GEN_API_GO}/templates | ||||
| 	wget https://raw.githubusercontent.com/goauthentik/client-go/main/config.yaml -O ./${GEN_API_GO}/config.yaml | ||||
| 	wget https://raw.githubusercontent.com/goauthentik/client-go/main/templates/README.mustache -O ./${GEN_API_GO}/templates/README.mustache | ||||
| 	wget https://raw.githubusercontent.com/goauthentik/client-go/main/templates/go.mod.mustache -O ./${GEN_API_GO}/templates/go.mod.mustache | ||||
| 	cp schema.yml ./${GEN_API_GO}/ | ||||
| 	docker run \ | ||||
| 		--rm -v ${PWD}/${GEN_API_GO}:/local \ | ||||
| 		--user ${UID}:${GID} \ | ||||
| 		docker.io/openapitools/openapi-generator-cli:v6.5.0 generate \ | ||||
| 		-i /local/schema.yml \ | ||||
| 		-g go \ | ||||
| 		-o /local/ \ | ||||
| 		-c /local/config.yaml | ||||
| 	go mod edit -replace goauthentik.io/api/v3=./${GEN_API_GO} | ||||
| 	rm -rf ./${GEN_API_GO}/config.yaml ./${GEN_API_GO}/templates/ | ||||
|  | ||||
| gen-dev-config:  ## Generate a local development config file | ||||
| 	uv run scripts/generate_config.py | ||||
| @ -243,7 +244,7 @@ docker:  ## Build a docker image of the current source tree | ||||
| 	DOCKER_BUILDKIT=1 docker build . --progress plain --tag ${DOCKER_IMAGE} | ||||
|  | ||||
| test-docker: | ||||
| 	BUILD=true ${PWD}/scripts/test_docker.sh | ||||
| 	BUILD=true ./scripts/test_docker.sh | ||||
|  | ||||
| ######################### | ||||
| ## CI | ||||
|  | ||||
| @ -2,7 +2,7 @@ | ||||
|  | ||||
| from os import environ | ||||
|  | ||||
| __version__ = "2025.4.1" | ||||
| __version__ = "2025.4.0" | ||||
| ENV_GIT_HASH_KEY = "GIT_BUILD_HASH" | ||||
|  | ||||
|  | ||||
|  | ||||
| @ -7,8 +7,8 @@ from rest_framework.request import Request | ||||
| from rest_framework.response import Response | ||||
| from rest_framework.viewsets import ViewSet | ||||
|  | ||||
| from authentik.common.utils.reflection import get_apps | ||||
| from authentik.core.api.utils import PassiveSerializer | ||||
| from authentik.lib.utils.reflection import get_apps | ||||
| from authentik.policies.event_matcher.models import model_choices | ||||
|  | ||||
|  | ||||
|  | ||||
| @ -7,6 +7,7 @@ from sys import version as python_version | ||||
| from typing import TypedDict | ||||
|  | ||||
| from cryptography.hazmat.backends.openssl.backend import backend | ||||
| from django.apps import apps | ||||
| from django.conf import settings | ||||
| from django.utils.timezone import now | ||||
| from django.views.debug import SafeExceptionReporterFilter | ||||
| @ -17,12 +18,10 @@ from rest_framework.response import Response | ||||
| from rest_framework.views import APIView | ||||
|  | ||||
| from authentik import get_full_version | ||||
| from authentik.common.config import CONFIG | ||||
| from authentik.common.utils.reflection import get_env | ||||
| from authentik.core.api.utils import PassiveSerializer | ||||
| from authentik.enterprise.license import LicenseKey | ||||
| from authentik.lib.config import CONFIG | ||||
| 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 | ||||
|  | ||||
|  | ||||
| @ -103,6 +102,12 @@ class SystemInfoSerializer(PassiveSerializer): | ||||
|  | ||||
|     def get_embedded_outpost_host(self, request: Request) -> str: | ||||
|         """Get the FQDN configured on the embedded outpost""" | ||||
|         if not apps.is_installed("authentik.outposts"): | ||||
|             return "" | ||||
|  | ||||
|         from authentik.outposts.apps import MANAGED_OUTPOST | ||||
|         from authentik.outposts.models import Outpost | ||||
|  | ||||
|         outposts = Outpost.objects.filter(managed=MANAGED_OUTPOST) | ||||
|         if not outposts.exists():  # pragma: no cover | ||||
|             return "" | ||||
|  | ||||
| @ -2,7 +2,7 @@ | ||||
|  | ||||
| from celery.schedules import crontab | ||||
|  | ||||
| from authentik.lib.utils.time import fqdn_rand | ||||
| from authentik.common.utils.time import fqdn_rand | ||||
|  | ||||
| CELERY_BEAT_SCHEDULE = { | ||||
|     "admin_latest_version": { | ||||
|  | ||||
| @ -9,10 +9,10 @@ from structlog.stdlib import get_logger | ||||
|  | ||||
| from authentik import __version__, get_build_hash | ||||
| from authentik.admin.apps import PROM_INFO | ||||
| from authentik.common.config import CONFIG | ||||
| from authentik.common.utils.http import get_http_session | ||||
| from authentik.events.models import Event, EventAction, Notification | ||||
| from authentik.events.system_tasks import SystemTask, TaskStatus, prefill_task | ||||
| from authentik.lib.config import CONFIG | ||||
| from authentik.lib.utils.http import get_http_session | ||||
| from authentik.root.celery import CELERY_APP | ||||
|  | ||||
| LOGGER = get_logger() | ||||
|  | ||||
| @ -8,7 +8,7 @@ from django.urls import reverse | ||||
| from authentik import __version__ | ||||
| from authentik.blueprints.tests import reconcile_app | ||||
| from authentik.core.models import Group, User | ||||
| from authentik.lib.generators import generate_id | ||||
| from authentik.crypto.generators import generate_id | ||||
|  | ||||
|  | ||||
| class TestAdminAPI(TestCase): | ||||
|  | ||||
| @ -9,8 +9,8 @@ from authentik.admin.tasks import ( | ||||
|     clear_update_notifications, | ||||
|     update_latest_version, | ||||
| ) | ||||
| from authentik.common.config import CONFIG | ||||
| from authentik.events.models import Event, EventAction | ||||
| from authentik.lib.config import CONFIG | ||||
|  | ||||
| RESPONSE_VALID = { | ||||
|     "$schema": "https://version.goauthentik.io/schema.json", | ||||
|  | ||||
| @ -1,30 +1,18 @@ | ||||
| """API Authentication""" | ||||
|  | ||||
| from hmac import compare_digest | ||||
| from pathlib import Path | ||||
| from tempfile import gettempdir | ||||
| from typing import Any | ||||
|  | ||||
| from django.conf import settings | ||||
| from django.contrib.auth.models import AnonymousUser | ||||
| from drf_spectacular.extensions import OpenApiAuthenticationExtension | ||||
| from rest_framework.authentication import BaseAuthentication, get_authorization_header | ||||
| from rest_framework.exceptions import AuthenticationFailed | ||||
| from rest_framework.request import Request | ||||
| from structlog.stdlib import get_logger | ||||
|  | ||||
| from authentik.common.oauth.constants import SCOPE_AUTHENTIK_API | ||||
| from authentik.core.middleware import CTX_AUTH_VIA | ||||
| from authentik.core.models import Token, TokenIntents, User, UserTypes | ||||
| from authentik.outposts.models import Outpost | ||||
| from authentik.providers.oauth2.constants import SCOPE_AUTHENTIK_API | ||||
| from authentik.core.models import Token, TokenIntents, User | ||||
|  | ||||
| LOGGER = get_logger() | ||||
| _tmp = Path(gettempdir()) | ||||
| try: | ||||
|     with open(_tmp / "authentik-core-ipc.key") as _f: | ||||
|         ipc_key = _f.read() | ||||
| except OSError: | ||||
|     ipc_key = None | ||||
|  | ||||
|  | ||||
| def validate_auth(header: bytes) -> str | None: | ||||
| @ -77,70 +65,9 @@ def auth_user_lookup(raw_header: bytes) -> User | None: | ||||
|             raise AuthenticationFailed("Token invalid/expired") | ||||
|         CTX_AUTH_VIA.set("jwt") | ||||
|         return jwt_token.user | ||||
|     # then try to auth via secret key (for embedded outpost/etc) | ||||
|     user = token_secret_key(auth_credentials) | ||||
|     if user: | ||||
|         CTX_AUTH_VIA.set("secret_key") | ||||
|         return user | ||||
|     # then try to auth via secret key (for embedded outpost/etc) | ||||
|     user = token_ipc(auth_credentials) | ||||
|     if user: | ||||
|         CTX_AUTH_VIA.set("ipc") | ||||
|         return user | ||||
|     raise AuthenticationFailed("Token invalid/expired") | ||||
|  | ||||
|  | ||||
| def token_secret_key(value: str) -> User | None: | ||||
|     """Check if the token is the secret key | ||||
|     and return the service account for the managed outpost""" | ||||
|     from authentik.outposts.apps import MANAGED_OUTPOST | ||||
|  | ||||
|     if not compare_digest(value, settings.SECRET_KEY): | ||||
|         return None | ||||
|     outposts = Outpost.objects.filter(managed=MANAGED_OUTPOST) | ||||
|     if not outposts: | ||||
|         return None | ||||
|     outpost = outposts.first() | ||||
|     return outpost.user | ||||
|  | ||||
|  | ||||
| class IPCUser(AnonymousUser): | ||||
|     """'Virtual' user for IPC communication between authentik core and the authentik router""" | ||||
|  | ||||
|     username = "authentik:system" | ||||
|     is_active = True | ||||
|     is_superuser = True | ||||
|  | ||||
|     @property | ||||
|     def type(self): | ||||
|         return UserTypes.INTERNAL_SERVICE_ACCOUNT | ||||
|  | ||||
|     def has_perm(self, perm, obj=None): | ||||
|         return True | ||||
|  | ||||
|     def has_perms(self, perm_list, obj=None): | ||||
|         return True | ||||
|  | ||||
|     def has_module_perms(self, module): | ||||
|         return True | ||||
|  | ||||
|     @property | ||||
|     def is_anonymous(self): | ||||
|         return False | ||||
|  | ||||
|     @property | ||||
|     def is_authenticated(self): | ||||
|         return True | ||||
|  | ||||
|  | ||||
| def token_ipc(value: str) -> User | None: | ||||
|     """Check if the token is the secret key | ||||
|     and return the service account for the managed outpost""" | ||||
|     if not ipc_key or not compare_digest(value, ipc_key): | ||||
|         return None | ||||
|     return IPCUser() | ||||
|  | ||||
|  | ||||
| class TokenAuthentication(BaseAuthentication): | ||||
|     """Token-based authentication using HTTP Bearer authentication""" | ||||
|  | ||||
|  | ||||
| @ -3,19 +3,15 @@ | ||||
| import json | ||||
| from base64 import b64encode | ||||
|  | ||||
| from django.conf import settings | ||||
| from django.test import TestCase | ||||
| from django.utils import timezone | ||||
| from rest_framework.exceptions import AuthenticationFailed | ||||
|  | ||||
| from authentik.api.authentication import bearer_auth | ||||
| from authentik.blueprints.tests import reconcile_app | ||||
| from authentik.core.models import Token, TokenIntents, User, UserTypes | ||||
| from authentik.common.oauth.constants import SCOPE_AUTHENTIK_API | ||||
| from authentik.core.models import Token, TokenIntents | ||||
| from authentik.core.tests.utils import create_test_admin_user, create_test_flow | ||||
| from authentik.lib.generators import generate_id | ||||
| from authentik.outposts.apps import MANAGED_OUTPOST | ||||
| from authentik.outposts.models import Outpost | ||||
| from authentik.providers.oauth2.constants import SCOPE_AUTHENTIK_API | ||||
| from authentik.crypto.generators import generate_id | ||||
| from authentik.providers.oauth2.models import AccessToken, OAuth2Provider | ||||
|  | ||||
|  | ||||
| @ -52,21 +48,6 @@ class TestAPIAuth(TestCase): | ||||
|         with self.assertRaises(AuthenticationFailed): | ||||
|             bearer_auth(f"Bearer {token.key}".encode()) | ||||
|  | ||||
|     @reconcile_app("authentik_outposts") | ||||
|     def test_managed_outpost_fail(self): | ||||
|         """Test managed outpost""" | ||||
|         outpost = Outpost.objects.filter(managed=MANAGED_OUTPOST).first() | ||||
|         outpost.user.delete() | ||||
|         outpost.delete() | ||||
|         with self.assertRaises(AuthenticationFailed): | ||||
|             bearer_auth(f"Bearer {settings.SECRET_KEY}".encode()) | ||||
|  | ||||
|     @reconcile_app("authentik_outposts") | ||||
|     def test_managed_outpost_success(self): | ||||
|         """Test managed outpost""" | ||||
|         user: User = bearer_auth(f"Bearer {settings.SECRET_KEY}".encode()) | ||||
|         self.assertEqual(user.type, UserTypes.INTERNAL_SERVICE_ACCOUNT) | ||||
|  | ||||
|     def test_jwt_valid(self): | ||||
|         """Test valid JWT""" | ||||
|         provider = OAuth2Provider.objects.create( | ||||
|  | ||||
| @ -19,9 +19,9 @@ from rest_framework.request import Request | ||||
| from rest_framework.response import Response | ||||
| from rest_framework.views import APIView | ||||
|  | ||||
| from authentik.common.config import CONFIG | ||||
| from authentik.core.api.utils import PassiveSerializer | ||||
| from authentik.events.context_processors.base import get_context_processors | ||||
| from authentik.lib.config import CONFIG | ||||
|  | ||||
| capabilities = Signal() | ||||
|  | ||||
|  | ||||
| @ -11,7 +11,7 @@ from structlog.stdlib import get_logger | ||||
|  | ||||
| from authentik.api.v3.config import ConfigView | ||||
| from authentik.api.views import APIBrowserView | ||||
| from authentik.lib.utils.reflection import get_apps | ||||
| from authentik.common.utils.reflection import get_apps | ||||
|  | ||||
| LOGGER = get_logger() | ||||
|  | ||||
|  | ||||
| @ -12,8 +12,8 @@ from structlog.stdlib import get_logger | ||||
| from yaml import load | ||||
|  | ||||
| from authentik.blueprints.v1.common import BlueprintLoader, EntryInvalidError | ||||
| from authentik.common.utils.errors import exception_to_string | ||||
| from authentik.core.management.commands.shell import get_banner_text | ||||
| from authentik.lib.utils.errors import exception_to_string | ||||
|  | ||||
| LOGGER = get_logger() | ||||
|  | ||||
|  | ||||
| @ -15,7 +15,7 @@ from authentik import __version__ | ||||
| from authentik.blueprints.v1.common import BlueprintEntryDesiredState | ||||
| from authentik.blueprints.v1.importer import SERIALIZER_CONTEXT_BLUEPRINT, is_model_allowed | ||||
| from authentik.blueprints.v1.meta.registry import BaseMetaModel, registry | ||||
| from authentik.lib.models import SerializerModel | ||||
| from authentik.common.models import SerializerModel | ||||
|  | ||||
| LOGGER = get_logger() | ||||
|  | ||||
|  | ||||
| @ -11,7 +11,7 @@ from django.db.backends.base.schema import BaseDatabaseSchemaEditor | ||||
| from yaml import load | ||||
|  | ||||
| from authentik.blueprints.v1.labels import LABEL_AUTHENTIK_SYSTEM | ||||
| from authentik.lib.config import CONFIG | ||||
| from authentik.common.config import CONFIG | ||||
|  | ||||
|  | ||||
| def check_blueprint_v1_file(BlueprintInstance: type, db_alias, path: Path): | ||||
|  | ||||
| @ -2,7 +2,7 @@ | ||||
|  | ||||
| from django.db import migrations, models | ||||
|  | ||||
| from authentik.lib.migrations import fallback_names | ||||
| from authentik.common.migrations import fallback_names | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
| @ -10,14 +10,14 @@ from rest_framework.serializers import Serializer | ||||
| from structlog import get_logger | ||||
|  | ||||
| from authentik.blueprints.v1.oci import OCI_PREFIX, BlueprintOCIClient, OCIException | ||||
| from authentik.lib.config import CONFIG | ||||
| from authentik.lib.models import CreatedUpdatedModel, SerializerModel | ||||
| from authentik.lib.sentry import SentryIgnoredException | ||||
| from authentik.common.config import CONFIG | ||||
| from authentik.common.exceptions import NotReportedException | ||||
| from authentik.common.models import CreatedUpdatedModel, SerializerModel | ||||
|  | ||||
| LOGGER = get_logger() | ||||
|  | ||||
|  | ||||
| class BlueprintRetrievalFailed(SentryIgnoredException): | ||||
| class BlueprintRetrievalFailed(NotReportedException): | ||||
|     """Error raised when we are unable to fetch the blueprint contents, whether it be HTTP files | ||||
|     not being accessible or local files not being readable""" | ||||
|  | ||||
|  | ||||
| @ -2,7 +2,7 @@ | ||||
|  | ||||
| from celery.schedules import crontab | ||||
|  | ||||
| from authentik.lib.utils.time import fqdn_rand | ||||
| from authentik.common.utils.time import fqdn_rand | ||||
|  | ||||
| CELERY_BEAT_SCHEDULE = { | ||||
|     "blueprints_v1_discover": { | ||||
|  | ||||
| @ -3,7 +3,7 @@ | ||||
| from django.test import TestCase | ||||
|  | ||||
| from authentik.blueprints.models import BlueprintInstance, BlueprintRetrievalFailed | ||||
| from authentik.lib.generators import generate_id | ||||
| from authentik.crypto.generators import generate_id | ||||
|  | ||||
|  | ||||
| class TestModels(TestCase): | ||||
|  | ||||
| @ -6,7 +6,7 @@ from django.apps import apps | ||||
| from django.test import TestCase | ||||
|  | ||||
| from authentik.blueprints.v1.importer import is_model_allowed | ||||
| from authentik.lib.models import SerializerModel | ||||
| from authentik.common.models import SerializerModel | ||||
| from authentik.providers.oauth2.models import RefreshToken | ||||
|  | ||||
|  | ||||
|  | ||||
| @ -6,10 +6,10 @@ from django.test import TransactionTestCase | ||||
|  | ||||
| from authentik.blueprints.v1.exporter import FlowExporter | ||||
| from authentik.blueprints.v1.importer import Importer, transaction_rollback | ||||
| from authentik.common.tests import load_fixture | ||||
| from authentik.core.models import Group | ||||
| from authentik.crypto.generators import generate_id | ||||
| from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding | ||||
| from authentik.lib.generators import generate_id | ||||
| from authentik.lib.tests.utils import load_fixture | ||||
| from authentik.policies.expression.models import ExpressionPolicy | ||||
| from authentik.policies.models import PolicyBinding | ||||
| from authentik.sources.oauth.models import OAuthSource | ||||
|  | ||||
| @ -7,8 +7,8 @@ from django.urls import reverse | ||||
| from rest_framework.test import APITestCase | ||||
| from yaml import dump | ||||
|  | ||||
| from authentik.common.config import CONFIG | ||||
| from authentik.core.tests.utils import create_test_admin_user | ||||
| from authentik.lib.config import CONFIG | ||||
|  | ||||
| TMP = mkdtemp("authentik-blueprints") | ||||
|  | ||||
|  | ||||
| @ -3,11 +3,11 @@ | ||||
| from django.test import TransactionTestCase | ||||
|  | ||||
| from authentik.blueprints.v1.importer import Importer | ||||
| from authentik.common.tests import load_fixture | ||||
| from authentik.core.models import Application, Token, User | ||||
| from authentik.core.tests.utils import create_test_admin_user | ||||
| from authentik.crypto.generators import generate_id | ||||
| from authentik.flows.models import Flow | ||||
| from authentik.lib.generators import generate_id | ||||
| from authentik.lib.tests.utils import load_fixture | ||||
| from authentik.sources.oauth.models import OAuthSource | ||||
|  | ||||
|  | ||||
|  | ||||
| @ -3,9 +3,9 @@ | ||||
| from django.test import TransactionTestCase | ||||
|  | ||||
| from authentik.blueprints.v1.importer import Importer | ||||
| from authentik.common.tests import load_fixture | ||||
| from authentik.crypto.generators import generate_id | ||||
| from authentik.flows.models import Flow | ||||
| from authentik.lib.generators import generate_id | ||||
| from authentik.lib.tests.utils import load_fixture | ||||
|  | ||||
|  | ||||
| class TestBlueprintsV1Conditions(TransactionTestCase): | ||||
|  | ||||
| @ -4,10 +4,10 @@ from django.test import TransactionTestCase | ||||
| from guardian.shortcuts import get_perms | ||||
|  | ||||
| from authentik.blueprints.v1.importer import Importer | ||||
| from authentik.common.tests import load_fixture | ||||
| from authentik.core.models import User | ||||
| from authentik.crypto.generators import generate_id | ||||
| from authentik.flows.models import Flow | ||||
| from authentik.lib.generators import generate_id | ||||
| from authentik.lib.tests.utils import load_fixture | ||||
| from authentik.rbac.models import Role | ||||
|  | ||||
|  | ||||
|  | ||||
| @ -3,9 +3,9 @@ | ||||
| from django.test import TransactionTestCase | ||||
|  | ||||
| from authentik.blueprints.v1.importer import Importer | ||||
| from authentik.common.tests import load_fixture | ||||
| from authentik.crypto.generators import generate_id | ||||
| from authentik.flows.models import Flow | ||||
| from authentik.lib.generators import generate_id | ||||
| from authentik.lib.tests.utils import load_fixture | ||||
|  | ||||
|  | ||||
| class TestBlueprintsV1State(TransactionTestCase): | ||||
|  | ||||
| @ -8,8 +8,8 @@ from yaml import dump | ||||
|  | ||||
| from authentik.blueprints.models import BlueprintInstance, BlueprintInstanceStatus | ||||
| from authentik.blueprints.v1.tasks import apply_blueprint, blueprints_discovery, blueprints_find | ||||
| from authentik.lib.config import CONFIG | ||||
| from authentik.lib.generators import generate_id | ||||
| from authentik.common.config import CONFIG | ||||
| from authentik.crypto.generators import generate_id | ||||
|  | ||||
| TMP = mkdtemp("authentik-blueprints") | ||||
|  | ||||
|  | ||||
| @ -19,8 +19,8 @@ from rest_framework.fields import Field | ||||
| from rest_framework.serializers import Serializer | ||||
| from yaml import SafeDumper, SafeLoader, ScalarNode, SequenceNode | ||||
|  | ||||
| from authentik.lib.models import SerializerModel | ||||
| from authentik.lib.sentry import SentryIgnoredException | ||||
| from authentik.common.exceptions import NotReportedException | ||||
| from authentik.common.models import SerializerModel | ||||
| from authentik.policies.models import PolicyBindingModel | ||||
|  | ||||
|  | ||||
| @ -661,7 +661,7 @@ class BlueprintLoader(SafeLoader): | ||||
|         self.add_constructor("!AtIndex", AtIndex) | ||||
|  | ||||
|  | ||||
| class EntryInvalidError(SentryIgnoredException): | ||||
| class EntryInvalidError(NotReportedException): | ||||
|     """Error raised when an entry is invalid""" | ||||
|  | ||||
|     entry_model: str | None | ||||
|  | ||||
| @ -8,14 +8,11 @@ from dacite.config import Config | ||||
| from dacite.core import from_dict | ||||
| from dacite.exceptions import DaciteError | ||||
| from deepmerge import always_merger | ||||
| from django.contrib.auth.models import Permission | ||||
| from django.contrib.contenttypes.models import ContentType | ||||
| from django.core.exceptions import FieldError | ||||
| from django.db.models import Model | ||||
| from django.db.models.query_utils import Q | ||||
| from django.db.transaction import atomic | ||||
| from django.db.utils import IntegrityError | ||||
| from guardian.models import UserObjectPermission | ||||
| from guardian.shortcuts import assign_perm | ||||
| from rest_framework.exceptions import ValidationError | ||||
| from rest_framework.serializers import BaseSerializer, Serializer | ||||
| @ -31,119 +28,26 @@ from authentik.blueprints.v1.common import ( | ||||
|     EntryInvalidError, | ||||
| ) | ||||
| from authentik.blueprints.v1.meta.registry import BaseMetaModel, registry | ||||
| from authentik.core.models import ( | ||||
|     AuthenticatedSession, | ||||
|     GroupSourceConnection, | ||||
|     PropertyMapping, | ||||
|     Provider, | ||||
|     Session, | ||||
|     Source, | ||||
|     User, | ||||
|     UserSourceConnection, | ||||
| ) | ||||
| from authentik.common.exceptions import NotReportedException | ||||
| from authentik.common.models import SerializerModel, excluded_models | ||||
| from authentik.common.utils.reflection import get_apps | ||||
| from authentik.core.models import User | ||||
| from authentik.enterprise.license import LicenseKey | ||||
| from authentik.enterprise.models import LicenseUsage | ||||
| from authentik.enterprise.providers.google_workspace.models import ( | ||||
|     GoogleWorkspaceProviderGroup, | ||||
|     GoogleWorkspaceProviderUser, | ||||
| ) | ||||
| from authentik.enterprise.providers.microsoft_entra.models import ( | ||||
|     MicrosoftEntraProviderGroup, | ||||
|     MicrosoftEntraProviderUser, | ||||
| ) | ||||
| from authentik.enterprise.providers.ssf.models import StreamEvent | ||||
| from authentik.enterprise.stages.authenticator_endpoint_gdtc.models import ( | ||||
|     EndpointDevice, | ||||
|     EndpointDeviceConnection, | ||||
| ) | ||||
| from authentik.events.logs import LogEvent, capture_logs | ||||
| from authentik.events.models import SystemTask | ||||
| 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.lib.utils.reflection import get_apps | ||||
| from authentik.outposts.models import OutpostServiceConnection | ||||
| from authentik.policies.models import Policy, PolicyBindingModel | ||||
| from authentik.policies.reputation.models import Reputation | ||||
| from authentik.providers.oauth2.models import ( | ||||
|     AccessToken, | ||||
|     AuthorizationCode, | ||||
|     DeviceToken, | ||||
|     RefreshToken, | ||||
| ) | ||||
| from authentik.providers.rac.models import ConnectionToken | ||||
| from authentik.providers.scim.models import SCIMProviderGroup, SCIMProviderUser | ||||
| from authentik.rbac.models import Role | ||||
| from authentik.sources.scim.models import SCIMSourceGroup, SCIMSourceUser | ||||
| from authentik.stages.authenticator_webauthn.models import WebAuthnDeviceType | ||||
| from authentik.tenants.models import Tenant | ||||
|  | ||||
| # Context set when the serializer is created in a blueprint context | ||||
| # Update website/docs/customize/blueprints/v1/models.md when used | ||||
| SERIALIZER_CONTEXT_BLUEPRINT = "blueprint_entry" | ||||
|  | ||||
|  | ||||
| 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)""" | ||||
|  | ||||
|     from django.contrib.auth.models import Group as DjangoGroup | ||||
|     from django.contrib.auth.models import User as DjangoUser | ||||
|  | ||||
|     return ( | ||||
|         # Django only classes | ||||
|         DjangoUser, | ||||
|         DjangoGroup, | ||||
|         ContentType, | ||||
|         Permission, | ||||
|         UserObjectPermission, | ||||
|         # Base classes | ||||
|         Provider, | ||||
|         Source, | ||||
|         PropertyMapping, | ||||
|         UserSourceConnection, | ||||
|         GroupSourceConnection, | ||||
|         Stage, | ||||
|         OutpostServiceConnection, | ||||
|         Policy, | ||||
|         PolicyBindingModel, | ||||
|         # Classes that have other dependencies | ||||
|         Session, | ||||
|         AuthenticatedSession, | ||||
|         # Classes which are only internally managed | ||||
|         # FIXME: these shouldn't need to be explicitly listed, but rather based off of a mixin | ||||
|         FlowToken, | ||||
|         LicenseUsage, | ||||
|         SCIMProviderGroup, | ||||
|         SCIMProviderUser, | ||||
|         Tenant, | ||||
|         SystemTask, | ||||
|         ConnectionToken, | ||||
|         AuthorizationCode, | ||||
|         AccessToken, | ||||
|         RefreshToken, | ||||
|         Reputation, | ||||
|         WebAuthnDeviceType, | ||||
|         SCIMSourceUser, | ||||
|         SCIMSourceGroup, | ||||
|         GoogleWorkspaceProviderUser, | ||||
|         GoogleWorkspaceProviderGroup, | ||||
|         MicrosoftEntraProviderUser, | ||||
|         MicrosoftEntraProviderGroup, | ||||
|         EndpointDevice, | ||||
|         EndpointDeviceConnection, | ||||
|         DeviceToken, | ||||
|         StreamEvent, | ||||
|     ) | ||||
|  | ||||
|  | ||||
| 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): | ||||
| class DoRollback(NotReportedException): | ||||
|     """Exception to trigger a rollback""" | ||||
|  | ||||
|  | ||||
|  | ||||
| @ -16,14 +16,14 @@ from requests.exceptions import RequestException | ||||
| from structlog import get_logger | ||||
| from structlog.stdlib import BoundLogger | ||||
|  | ||||
| from authentik.lib.sentry import SentryIgnoredException | ||||
| from authentik.lib.utils.http import authentik_user_agent | ||||
| from authentik.common.exceptions import NotReportedException | ||||
| from authentik.common.utils.http import authentik_user_agent | ||||
|  | ||||
| OCI_MEDIA_TYPE = "application/vnd.goauthentik.blueprint.v1+yaml" | ||||
| OCI_PREFIX = "oci://" | ||||
|  | ||||
|  | ||||
| class OCIException(SentryIgnoredException): | ||||
| class OCIException(NotReportedException): | ||||
|     """OCI-related errors""" | ||||
|  | ||||
|  | ||||
|  | ||||
| @ -30,11 +30,11 @@ from authentik.blueprints.v1.common import BlueprintLoader, BlueprintMetadata, E | ||||
| from authentik.blueprints.v1.importer import Importer | ||||
| from authentik.blueprints.v1.labels import LABEL_AUTHENTIK_INSTANTIATE | ||||
| from authentik.blueprints.v1.oci import OCI_PREFIX | ||||
| from authentik.common.config import CONFIG | ||||
| from authentik.events.logs import capture_logs | ||||
| from authentik.events.models import TaskStatus | ||||
| from authentik.events.system_tasks import SystemTask, prefill_task | ||||
| from authentik.events.utils import sanitize_dict | ||||
| from authentik.lib.config import CONFIG | ||||
| from authentik.root.celery import CELERY_APP | ||||
| from authentik.tenants.models import Tenant | ||||
|  | ||||
|  | ||||
| @ -59,7 +59,6 @@ class BrandSerializer(ModelSerializer): | ||||
|             "flow_device_code", | ||||
|             "default_application", | ||||
|             "web_certificate", | ||||
|             "client_certificates", | ||||
|             "attributes", | ||||
|         ] | ||||
|         extra_kwargs = { | ||||
| @ -121,7 +120,6 @@ class BrandViewSet(UsedByMixin, ModelViewSet): | ||||
|         "domain", | ||||
|         "branding_title", | ||||
|         "web_certificate__name", | ||||
|         "client_certificates__name", | ||||
|     ] | ||||
|     filterset_fields = [ | ||||
|         "brand_uuid", | ||||
| @ -138,7 +136,6 @@ class BrandViewSet(UsedByMixin, ModelViewSet): | ||||
|         "flow_user_settings", | ||||
|         "flow_device_code", | ||||
|         "web_certificate", | ||||
|         "client_certificates", | ||||
|     ] | ||||
|     ordering = ["domain"] | ||||
|  | ||||
|  | ||||
| @ -5,7 +5,7 @@ import uuid | ||||
| import django.db.models.deletion | ||||
| from django.db import migrations, models | ||||
|  | ||||
| import authentik.lib.utils.time | ||||
| import authentik.common.utils.time | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
| @ -104,7 +104,7 @@ class Migration(migrations.Migration): | ||||
|                     "Events will be deleted after this duration.(Format:" | ||||
|                     " weeks=3;days=2;hours=3,seconds=2)." | ||||
|                 ), | ||||
|                 validators=[authentik.lib.utils.time.timedelta_string_validator], | ||||
|                 validators=[authentik.common.utils.time.timedelta_string_validator], | ||||
|             ), | ||||
|         ), | ||||
|         migrations.AddField( | ||||
|  | ||||
| @ -1,37 +0,0 @@ | ||||
| # Generated by Django 5.1.9 on 2025-05-19 15:09 | ||||
|  | ||||
| import django.db.models.deletion | ||||
| from django.db import migrations, models | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ("authentik_brands", "0009_brand_branding_default_flow_background"), | ||||
|         ("authentik_crypto", "0004_alter_certificatekeypair_name"), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.AddField( | ||||
|             model_name="brand", | ||||
|             name="client_certificates", | ||||
|             field=models.ManyToManyField( | ||||
|                 blank=True, | ||||
|                 default=None, | ||||
|                 help_text="Certificates used for client authentication.", | ||||
|                 to="authentik_crypto.certificatekeypair", | ||||
|             ), | ||||
|         ), | ||||
|         migrations.AlterField( | ||||
|             model_name="brand", | ||||
|             name="web_certificate", | ||||
|             field=models.ForeignKey( | ||||
|                 default=None, | ||||
|                 help_text="Web Certificate used by the authentik Core webserver.", | ||||
|                 null=True, | ||||
|                 on_delete=django.db.models.deletion.SET_DEFAULT, | ||||
|                 related_name="+", | ||||
|                 to="authentik_crypto.certificatekeypair", | ||||
|             ), | ||||
|         ), | ||||
|     ] | ||||
| @ -8,10 +8,10 @@ from django.utils.translation import gettext_lazy as _ | ||||
| from rest_framework.serializers import Serializer | ||||
| from structlog.stdlib import get_logger | ||||
|  | ||||
| from authentik.common.config import CONFIG | ||||
| from authentik.common.models import SerializerModel | ||||
| from authentik.crypto.models import CertificateKeyPair | ||||
| from authentik.flows.models import Flow | ||||
| from authentik.lib.config import CONFIG | ||||
| from authentik.lib.models import SerializerModel | ||||
|  | ||||
| LOGGER = get_logger() | ||||
|  | ||||
| @ -73,13 +73,6 @@ class Brand(SerializerModel): | ||||
|         default=None, | ||||
|         on_delete=models.SET_DEFAULT, | ||||
|         help_text=_("Web Certificate used by the authentik Core webserver."), | ||||
|         related_name="+", | ||||
|     ) | ||||
|     client_certificates = models.ManyToManyField( | ||||
|         CertificateKeyPair, | ||||
|         default=None, | ||||
|         blank=True, | ||||
|         help_text=_("Certificates used for client authentication."), | ||||
|     ) | ||||
|     attributes = models.JSONField(default=dict, blank=True) | ||||
|  | ||||
|  | ||||
| @ -7,7 +7,7 @@ from authentik.brands.api import Themes | ||||
| from authentik.brands.models import Brand | ||||
| from authentik.core.models import Application | ||||
| from authentik.core.tests.utils import create_test_admin_user, create_test_brand | ||||
| from authentik.lib.generators import generate_id | ||||
| from authentik.crypto.generators import generate_id | ||||
| from authentik.providers.oauth2.models import OAuth2Provider | ||||
| from authentik.providers.saml.models import SAMLProvider | ||||
|  | ||||
|  | ||||
| @ -5,10 +5,10 @@ from typing import Any | ||||
| from django.db.models import F, Q | ||||
| from django.db.models import Value as V | ||||
| from django.http.request import HttpRequest | ||||
| from sentry_sdk import get_current_span | ||||
|  | ||||
| from authentik import get_full_version | ||||
| from authentik.brands.models import Brand | ||||
| from authentik.lib.sentry import get_http_meta | ||||
| from authentik.tenants.models import Tenant | ||||
|  | ||||
| _q_default = Q(default=True) | ||||
| @ -32,9 +32,13 @@ def context_processor(request: HttpRequest) -> dict[str, Any]: | ||||
|     """Context Processor that injects brand object into every template""" | ||||
|     brand = getattr(request, "brand", DEFAULT_BRAND) | ||||
|     tenant = getattr(request, "tenant", Tenant()) | ||||
|     trace = "" | ||||
|     span = get_current_span() | ||||
|     if span: | ||||
|         trace = span.to_traceparent() | ||||
|     return { | ||||
|         "brand": brand, | ||||
|         "footer_links": tenant.footer_links, | ||||
|         "html_meta": {**get_http_meta()}, | ||||
|         "sentry_trace": trace, | ||||
|         "version": get_full_version(), | ||||
|     } | ||||
|  | ||||
| @ -18,11 +18,11 @@ from typing import Any | ||||
| from urllib.parse import quote_plus, urlparse | ||||
| 
 | ||||
| import yaml | ||||
| from django.conf import ImproperlyConfigured | ||||
| from django.core.exceptions import ImproperlyConfigured | ||||
| 
 | ||||
| from authentik.lib.utils.dict import get_path_from_dict, set_path_in_dict | ||||
| from authentik.common.utils.dict import get_path_from_dict, set_path_in_dict | ||||
| 
 | ||||
| SEARCH_PATHS = ["authentik/lib/default.yml", "/etc/authentik/config.yml", ""] + glob( | ||||
| SEARCH_PATHS = ["authentik/common/config/default.yml", "/etc/authentik/config.yml", ""] + glob( | ||||
|     "/etc/authentik/config.d/*.yml", recursive=True | ||||
| ) | ||||
| ENV_PREFIX = "AUTHENTIK" | ||||
| @ -100,7 +100,7 @@ class ConfigLoader: | ||||
|     def __init__(self, **kwargs): | ||||
|         super().__init__() | ||||
|         self.__config = {} | ||||
|         base_dir = Path(__file__).parent.joinpath(Path("../..")).resolve() | ||||
|         base_dir = Path(__file__).parent.joinpath(Path("../../..")).resolve() | ||||
|         for _path in SEARCH_PATHS: | ||||
|             path = Path(_path) | ||||
|             # Check if path is relative, and if so join with base_dir | ||||
| @ -363,9 +363,6 @@ def django_db_config(config: ConfigLoader | None = None) -> dict: | ||||
|         pool_options = config.get_dict_from_b64_json("postgresql.pool_options", True) | ||||
|         if not pool_options: | ||||
|             pool_options = True | ||||
|     # FIXME: Temporarily force pool to be deactivated. | ||||
|     # See https://github.com/goauthentik/authentik/issues/14320 | ||||
|     pool_options = False | ||||
| 
 | ||||
|     db = { | ||||
|         "default": { | ||||
| @ -1,4 +1,4 @@ | ||||
| package lib | ||||
| package config | ||||
| 
 | ||||
| import _ "embed" | ||||
| 
 | ||||
| @ -8,12 +8,12 @@ | ||||
| # make gen-dev-config | ||||
| # ``` | ||||
| # | ||||
| # You may edit the generated file to override the configuration below.   | ||||
| # You may edit the generated file to override the configuration below. | ||||
| # | ||||
| # When making modifying the default configuration file,  | ||||
| # When making modifying the default configuration file, | ||||
| # ensure that the corresponding documentation is updated to match. | ||||
| # | ||||
| # @see {@link ../../website/docs/install-config/configuration/configuration.mdx Configuration documentation} for more information. | ||||
| # @see {@link ../../../website/docs/install-config/configuration/configuration.mdx Configuration documentation} for more information. | ||||
| 
 | ||||
| postgresql: | ||||
|   host: localhost | ||||
| @ -6,10 +6,10 @@ from os import chmod, environ, unlink, write | ||||
| from tempfile import mkstemp | ||||
| from unittest import mock | ||||
| 
 | ||||
| from django.conf import ImproperlyConfigured | ||||
| from django.core.exceptions import ImproperlyConfigured | ||||
| from django.test import TestCase | ||||
| 
 | ||||
| from authentik.lib.config import ( | ||||
| from authentik.common.config import ( | ||||
|     ENV_PREFIX, | ||||
|     UNSET, | ||||
|     Attr, | ||||
| @ -494,88 +494,86 @@ class TestConfig(TestCase): | ||||
|             }, | ||||
|         ) | ||||
| 
 | ||||
|     # FIXME: Temporarily force pool to be deactivated. | ||||
|     # See https://github.com/goauthentik/authentik/issues/14320 | ||||
|     # def test_db_pool(self): | ||||
|     #     """Test DB Config with pool""" | ||||
|     #     config = ConfigLoader() | ||||
|     #     config.set("postgresql.host", "foo") | ||||
|     #     config.set("postgresql.name", "foo") | ||||
|     #     config.set("postgresql.user", "foo") | ||||
|     #     config.set("postgresql.password", "foo") | ||||
|     #     config.set("postgresql.port", "foo") | ||||
|     #     config.set("postgresql.test.name", "foo") | ||||
|     #     config.set("postgresql.use_pool", True) | ||||
|     #     conf = django_db_config(config) | ||||
|     #     self.assertEqual( | ||||
|     #         conf, | ||||
|     #         { | ||||
|     #             "default": { | ||||
|     #                 "ENGINE": "authentik.root.db", | ||||
|     #                 "HOST": "foo", | ||||
|     #                 "NAME": "foo", | ||||
|     #                 "OPTIONS": { | ||||
|     #                     "pool": True, | ||||
|     #                     "sslcert": None, | ||||
|     #                     "sslkey": None, | ||||
|     #                     "sslmode": None, | ||||
|     #                     "sslrootcert": None, | ||||
|     #                 }, | ||||
|     #                 "PASSWORD": "foo", | ||||
|     #                 "PORT": "foo", | ||||
|     #                 "TEST": {"NAME": "foo"}, | ||||
|     #                 "USER": "foo", | ||||
|     #                 "CONN_MAX_AGE": 0, | ||||
|     #                 "CONN_HEALTH_CHECKS": False, | ||||
|     #                 "DISABLE_SERVER_SIDE_CURSORS": False, | ||||
|     #             } | ||||
|     #         }, | ||||
|     #     ) | ||||
|     def test_db_pool(self): | ||||
|         """Test DB Config with pool""" | ||||
|         config = ConfigLoader() | ||||
|         config.set("postgresql.host", "foo") | ||||
|         config.set("postgresql.name", "foo") | ||||
|         config.set("postgresql.user", "foo") | ||||
|         config.set("postgresql.password", "foo") | ||||
|         config.set("postgresql.port", "foo") | ||||
|         config.set("postgresql.test.name", "foo") | ||||
|         config.set("postgresql.use_pool", True) | ||||
|         conf = django_db_config(config) | ||||
|         self.assertEqual( | ||||
|             conf, | ||||
|             { | ||||
|                 "default": { | ||||
|                     "ENGINE": "authentik.root.db", | ||||
|                     "HOST": "foo", | ||||
|                     "NAME": "foo", | ||||
|                     "OPTIONS": { | ||||
|                         "pool": True, | ||||
|                         "sslcert": None, | ||||
|                         "sslkey": None, | ||||
|                         "sslmode": None, | ||||
|                         "sslrootcert": None, | ||||
|                     }, | ||||
|                     "PASSWORD": "foo", | ||||
|                     "PORT": "foo", | ||||
|                     "TEST": {"NAME": "foo"}, | ||||
|                     "USER": "foo", | ||||
|                     "CONN_MAX_AGE": 0, | ||||
|                     "CONN_HEALTH_CHECKS": False, | ||||
|                     "DISABLE_SERVER_SIDE_CURSORS": False, | ||||
|                 } | ||||
|             }, | ||||
|         ) | ||||
| 
 | ||||
|     # def test_db_pool_options(self): | ||||
|     #     """Test DB Config with pool""" | ||||
|     #     config = ConfigLoader() | ||||
|     #     config.set("postgresql.host", "foo") | ||||
|     #     config.set("postgresql.name", "foo") | ||||
|     #     config.set("postgresql.user", "foo") | ||||
|     #     config.set("postgresql.password", "foo") | ||||
|     #     config.set("postgresql.port", "foo") | ||||
|     #     config.set("postgresql.test.name", "foo") | ||||
|     #     config.set("postgresql.use_pool", True) | ||||
|     #     config.set( | ||||
|     #         "postgresql.pool_options", | ||||
|     #         base64.b64encode( | ||||
|     #             dumps( | ||||
|     #                 { | ||||
|     #                     "max_size": 15, | ||||
|     #                 } | ||||
|     #             ).encode() | ||||
|     #         ).decode(), | ||||
|     #     ) | ||||
|     #     conf = django_db_config(config) | ||||
|     #     self.assertEqual( | ||||
|     #         conf, | ||||
|     #         { | ||||
|     #             "default": { | ||||
|     #                 "ENGINE": "authentik.root.db", | ||||
|     #                 "HOST": "foo", | ||||
|     #                 "NAME": "foo", | ||||
|     #                 "OPTIONS": { | ||||
|     #                     "pool": { | ||||
|     #                         "max_size": 15, | ||||
|     #                     }, | ||||
|     #                     "sslcert": None, | ||||
|     #                     "sslkey": None, | ||||
|     #                     "sslmode": None, | ||||
|     #                     "sslrootcert": None, | ||||
|     #                 }, | ||||
|     #                 "PASSWORD": "foo", | ||||
|     #                 "PORT": "foo", | ||||
|     #                 "TEST": {"NAME": "foo"}, | ||||
|     #                 "USER": "foo", | ||||
|     #                 "CONN_MAX_AGE": 0, | ||||
|     #                 "CONN_HEALTH_CHECKS": False, | ||||
|     #                 "DISABLE_SERVER_SIDE_CURSORS": False, | ||||
|     #             } | ||||
|     #         }, | ||||
|     #     ) | ||||
|     def test_db_pool_options(self): | ||||
|         """Test DB Config with pool""" | ||||
|         config = ConfigLoader() | ||||
|         config.set("postgresql.host", "foo") | ||||
|         config.set("postgresql.name", "foo") | ||||
|         config.set("postgresql.user", "foo") | ||||
|         config.set("postgresql.password", "foo") | ||||
|         config.set("postgresql.port", "foo") | ||||
|         config.set("postgresql.test.name", "foo") | ||||
|         config.set("postgresql.use_pool", True) | ||||
|         config.set( | ||||
|             "postgresql.pool_options", | ||||
|             base64.b64encode( | ||||
|                 dumps( | ||||
|                     { | ||||
|                         "max_size": 15, | ||||
|                     } | ||||
|                 ).encode() | ||||
|             ).decode(), | ||||
|         ) | ||||
|         conf = django_db_config(config) | ||||
|         self.assertEqual( | ||||
|             conf, | ||||
|             { | ||||
|                 "default": { | ||||
|                     "ENGINE": "authentik.root.db", | ||||
|                     "HOST": "foo", | ||||
|                     "NAME": "foo", | ||||
|                     "OPTIONS": { | ||||
|                         "pool": { | ||||
|                             "max_size": 15, | ||||
|                         }, | ||||
|                         "sslcert": None, | ||||
|                         "sslkey": None, | ||||
|                         "sslmode": None, | ||||
|                         "sslrootcert": None, | ||||
|                     }, | ||||
|                     "PASSWORD": "foo", | ||||
|                     "PORT": "foo", | ||||
|                     "TEST": {"NAME": "foo"}, | ||||
|                     "USER": "foo", | ||||
|                     "CONN_MAX_AGE": 0, | ||||
|                     "CONN_HEALTH_CHECKS": False, | ||||
|                     "DISABLE_SERVER_SIDE_CURSORS": False, | ||||
|                 } | ||||
|             }, | ||||
|         ) | ||||
							
								
								
									
										7
									
								
								authentik/common/exceptions/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								authentik/common/exceptions/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | ||||
| class AuthentikException(Exception): | ||||
|     """Base class for authentik exceptions""" | ||||
|  | ||||
|  | ||||
| class NotReportedException(AuthentikException): | ||||
|     """Exception base class for all errors that are suppressed, | ||||
|     and not sent to any kind of monitoring.""" | ||||
| @ -2,7 +2,8 @@ | ||||
| 
 | ||||
| from django.test import TestCase | ||||
| 
 | ||||
| from authentik.lib.sentry import SentryIgnoredException, before_send | ||||
| from authentik.common.exceptions import NotReportedException | ||||
| from authentik.root.sentry import before_send | ||||
| 
 | ||||
| 
 | ||||
| class TestSentry(TestCase): | ||||
| @ -10,7 +11,7 @@ class TestSentry(TestCase): | ||||
| 
 | ||||
|     def test_error_not_sent(self): | ||||
|         """Test SentryIgnoredError not sent""" | ||||
|         self.assertIsNone(before_send({}, {"exc_info": (0, SentryIgnoredException(), 0)})) | ||||
|         self.assertIsNone(before_send({}, {"exc_info": (0, NotReportedException(), 0)})) | ||||
| 
 | ||||
|     def test_error_sent(self): | ||||
|         """Test error sent""" | ||||
| @ -18,11 +18,11 @@ from sentry_sdk import start_span | ||||
| from sentry_sdk.tracing import Span | ||||
| from structlog.stdlib import get_logger | ||||
| 
 | ||||
| from authentik.common.expression.exceptions import ControlFlowException | ||||
| from authentik.common.utils.http import get_http_session | ||||
| from authentik.common.utils.time import timedelta_from_string | ||||
| from authentik.core.models import User | ||||
| from authentik.events.models import Event | ||||
| from authentik.lib.expression.exceptions import ControlFlowException | ||||
| from authentik.lib.utils.http import get_http_session | ||||
| from authentik.lib.utils.time import timedelta_from_string | ||||
| from authentik.policies.models import Policy, PolicyBinding | ||||
| from authentik.policies.process import PolicyProcess | ||||
| from authentik.policies.types import PolicyRequest, PolicyResult | ||||
| @ -234,7 +234,7 @@ class BaseEvaluator: | ||||
|         """Parse and evaluate expression. If the syntax is incorrect, a SyntaxError is raised. | ||||
|         If any exception is raised during execution, it is raised. | ||||
|         The result is returned without any type-checking.""" | ||||
|         with start_span(op="authentik.lib.evaluator.evaluate") as span: | ||||
|         with start_span(op="authentik.common.evaluator.evaluate") as span: | ||||
|             span: Span | ||||
|             span.description = self._filename | ||||
|             span.set_data("expression", expression_source) | ||||
							
								
								
									
										6
									
								
								authentik/common/expression/exceptions.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								authentik/common/expression/exceptions.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,6 @@ | ||||
| from authentik.common.exceptions import NotReportedException | ||||
|  | ||||
|  | ||||
| class ControlFlowException(NotReportedException): | ||||
|     """Exceptions used to control the flow from exceptions, not reported as a warning/ | ||||
|     error in logs""" | ||||
| @ -5,10 +5,10 @@ from django.urls import reverse | ||||
| from jwt import decode | ||||
| 
 | ||||
| from authentik.blueprints.tests import apply_blueprint | ||||
| from authentik.common.expression.evaluator import BaseEvaluator | ||||
| from authentik.core.tests.utils import create_test_admin_user, create_test_flow, create_test_user | ||||
| from authentik.crypto.generators import generate_id | ||||
| from authentik.events.models import Event | ||||
| from authentik.lib.expression.evaluator import BaseEvaluator | ||||
| from authentik.lib.generators import generate_id | ||||
| from authentik.providers.oauth2.models import OAuth2Provider, ScopeMapping | ||||
| 
 | ||||
| 
 | ||||
							
								
								
									
										1
									
								
								authentik/common/ldap/constants.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								authentik/common/ldap/constants.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | ||||
| LDAP_DISTINGUISHED_NAME = "distinguishedName" | ||||
| @ -2,11 +2,15 @@ | ||||
| 
 | ||||
| import re | ||||
| 
 | ||||
| from django.contrib.auth.models import Permission | ||||
| from django.contrib.contenttypes.models import ContentType | ||||
| from django.core.validators import URLValidator | ||||
| from django.db import models | ||||
| from django.db.models import Model | ||||
| from django.utils.regex_helper import _lazy_re_compile | ||||
| from guardian.models import UserObjectPermission | ||||
| from model_utils.managers import InheritanceManager | ||||
| from rest_framework.serializers import BaseSerializer | ||||
| from rest_framework.serializers import Serializer | ||||
| 
 | ||||
| 
 | ||||
| class SerializerModel(models.Model): | ||||
| @ -16,17 +20,8 @@ class SerializerModel(models.Model): | ||||
|         abstract = True | ||||
| 
 | ||||
|     @property | ||||
|     def serializer(self) -> type[BaseSerializer]: | ||||
|         """Get serializer for this model""" | ||||
|         # Special handling for built-in source | ||||
|         if ( | ||||
|             hasattr(self, "managed") | ||||
|             and hasattr(self, "MANAGED_INBUILT") | ||||
|             and self.managed == self.MANAGED_INBUILT | ||||
|         ): | ||||
|             from authentik.core.api.sources import SourceSerializer | ||||
| 
 | ||||
|             return SourceSerializer | ||||
|     def serializer(self) -> type[Serializer]: | ||||
|         """Get serializer type for this model""" | ||||
|         raise NotImplementedError | ||||
| 
 | ||||
| 
 | ||||
| @ -103,3 +98,32 @@ class DomainlessFormattedURLValidator(DomainlessURLValidator): | ||||
|             re.IGNORECASE, | ||||
|         ) | ||||
|         self.schemes = ["http", "https", "blank"] + list(self.schemes) | ||||
| 
 | ||||
| 
 | ||||
| __internal_models = [] | ||||
| 
 | ||||
| 
 | ||||
| def internal_model(cls): | ||||
|     """Mark a model class as an internal model, which means it cannot be | ||||
|     managed by blueprints, and creations/changes will not be logged in the events.""" | ||||
|     __internal_models.append(cls) | ||||
|     return cls | ||||
| 
 | ||||
| 
 | ||||
| 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)""" | ||||
| 
 | ||||
|     from django.apps import apps | ||||
|     from django.contrib.auth.models import Group as DjangoGroup | ||||
|     from django.contrib.auth.models import User as DjangoUser | ||||
| 
 | ||||
|     static = [ | ||||
|         # Django only classes | ||||
|         DjangoUser, | ||||
|         DjangoGroup, | ||||
|         ContentType, | ||||
|         Permission, | ||||
|         UserObjectPermission, | ||||
|     ] | ||||
|     return tuple(static + [x for x in apps.get_models() if x in __internal_models]) | ||||
| @ -5,9 +5,9 @@ from collections.abc import Callable | ||||
| from django.test import TestCase | ||||
| from rest_framework.serializers import BaseSerializer | ||||
| 
 | ||||
| from authentik.common.models import SerializerModel | ||||
| from authentik.common.utils.reflection import all_subclasses | ||||
| from authentik.flows.models import Stage | ||||
| from authentik.lib.models import SerializerModel | ||||
| from authentik.lib.utils.reflection import all_subclasses | ||||
| 
 | ||||
| 
 | ||||
| class TestModels(TestCase): | ||||
| @ -50,4 +50,3 @@ AMR_PASSWORD = "pwd"  # nosec | ||||
| AMR_MFA = "mfa" | ||||
| AMR_OTP = "otp" | ||||
| AMR_WEBAUTHN = "user" | ||||
| AMR_SMART_CARD = "sc" | ||||
| @ -4,13 +4,13 @@ from urllib.parse import quote, urlparse | ||||
| 
 | ||||
| from django.http import HttpRequest, HttpResponse, HttpResponseRedirect | ||||
| 
 | ||||
| from authentik.common.exceptions import NotReportedException | ||||
| from authentik.common.views import bad_request_message | ||||
| from authentik.events.models import Event, EventAction | ||||
| from authentik.lib.sentry import SentryIgnoredException | ||||
| from authentik.lib.views import bad_request_message | ||||
| from authentik.providers.oauth2.models import GrantTypes, RedirectURI | ||||
| 
 | ||||
| 
 | ||||
| class OAuth2Error(SentryIgnoredException): | ||||
| class OAuth2Error(NotReportedException): | ||||
|     """Base class for all OAuth2 Errors""" | ||||
| 
 | ||||
|     error: str | ||||
							
								
								
									
										10
									
								
								authentik/common/saml/api.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								authentik/common/saml/api.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,10 @@ | ||||
| from rest_framework.fields import CharField | ||||
|  | ||||
| from authentik.core.api.utils import PassiveSerializer | ||||
|  | ||||
|  | ||||
| class SAMLMetadataSerializer(PassiveSerializer): | ||||
|     """SAML Provider Metadata serializer""" | ||||
|  | ||||
|     metadata = CharField(read_only=True) | ||||
|     download_url = CharField(read_only=True, required=False) | ||||
| @ -1,9 +1,9 @@ | ||||
| """authentik saml source exceptions""" | ||||
| 
 | ||||
| from authentik.lib.sentry import SentryIgnoredException | ||||
| from authentik.common.exceptions import NotReportedException | ||||
| 
 | ||||
| 
 | ||||
| class SAMLException(SentryIgnoredException): | ||||
| class SAMLException(NotReportedException): | ||||
|     """Base SAML Exception""" | ||||
| 
 | ||||
| 
 | ||||
| @ -3,12 +3,12 @@ from collections.abc import Generator | ||||
| from django.db.models import QuerySet | ||||
| from django.http import HttpRequest | ||||
| 
 | ||||
| from authentik.common.expression.exceptions import ControlFlowException | ||||
| from authentik.core.expression.evaluator import PropertyMappingEvaluator | ||||
| from authentik.core.expression.exceptions import ( | ||||
|     PropertyMappingExpressionException, | ||||
| ) | ||||
| from authentik.core.models import PropertyMapping, User | ||||
| from authentik.lib.expression.exceptions import ControlFlowException | ||||
| 
 | ||||
| 
 | ||||
| class PropertyMappingManager: | ||||
| @ -7,12 +7,12 @@ from rest_framework.fields import BooleanField, CharField, ChoiceField | ||||
| from rest_framework.request import Request | ||||
| from rest_framework.response import Response | ||||
| 
 | ||||
| from authentik.common.sync.outgoing.models import OutgoingSyncProvider | ||||
| from authentik.common.utils.reflection import class_to_path | ||||
| from authentik.core.api.utils import ModelSerializer, PassiveSerializer | ||||
| from authentik.core.models import Group, User | ||||
| from authentik.events.api.tasks import SystemTaskSerializer | ||||
| from authentik.events.logs import LogEvent, LogEventSerializer | ||||
| from authentik.lib.sync.outgoing.models import OutgoingSyncProvider | ||||
| from authentik.lib.utils.reflection import class_to_path | ||||
| from authentik.rbac.filters import ObjectFilter | ||||
| 
 | ||||
| 
 | ||||
| @ -7,22 +7,23 @@ from deepmerge import always_merger | ||||
| from django.db import DatabaseError | ||||
| from structlog.stdlib import get_logger | ||||
| 
 | ||||
| from authentik.common.expression.exceptions import ControlFlowException | ||||
| from authentik.common.sync.mapper import PropertyMappingManager | ||||
| from authentik.common.sync.outgoing.exceptions import NotFoundSyncException, StopSync | ||||
| from authentik.common.utils.errors import exception_to_string | ||||
| from authentik.core.expression.exceptions import ( | ||||
|     PropertyMappingExpressionException, | ||||
| ) | ||||
| from authentik.events.models import Event, EventAction | ||||
| from authentik.lib.expression.exceptions import ControlFlowException | ||||
| from authentik.lib.sync.mapper import PropertyMappingManager | ||||
| from authentik.lib.sync.outgoing.exceptions import NotFoundSyncException, StopSync | ||||
| from authentik.lib.utils.errors import exception_to_string | ||||
| 
 | ||||
| if TYPE_CHECKING: | ||||
|     from django.db.models import Model | ||||
| 
 | ||||
|     from authentik.lib.sync.outgoing.models import OutgoingSyncProvider | ||||
|     from authentik.common.sync.outgoing.models import OutgoingSyncProvider | ||||
| 
 | ||||
| 
 | ||||
| class Direction(StrEnum): | ||||
| 
 | ||||
|     add = "add" | ||||
|     remove = "remove" | ||||
| 
 | ||||
| @ -36,16 +37,13 @@ SAFE_METHODS = [ | ||||
| 
 | ||||
| 
 | ||||
| class BaseOutgoingSyncClient[ | ||||
|     TModel: "Model", | ||||
|     TConnection: "Model", | ||||
|     TSchema: dict, | ||||
|     TProvider: "OutgoingSyncProvider", | ||||
|     TModel: "Model", TConnection: "Model", TSchema: dict, TProvider: "OutgoingSyncProvider" | ||||
| ]: | ||||
|     """Basic Outgoing sync client Client""" | ||||
| 
 | ||||
|     provider: TProvider | ||||
|     connection_type: type[TConnection] | ||||
|     connection_attr: str | ||||
|     connection_type_query: str | ||||
|     mapper: PropertyMappingManager | ||||
| 
 | ||||
|     can_discover = False | ||||
| @ -65,7 +63,9 @@ class BaseOutgoingSyncClient[ | ||||
|     def write(self, obj: TModel) -> tuple[TConnection, bool]: | ||||
|         """Write object to destination. Uses self.create and self.update, but | ||||
|         can be overwritten for further logic""" | ||||
|         connection = getattr(obj, self.connection_attr).filter(provider=self.provider).first() | ||||
|         connection = self.connection_type.objects.filter( | ||||
|             provider=self.provider, **{self.connection_type_query: obj} | ||||
|         ).first() | ||||
|         try: | ||||
|             if not connection: | ||||
|                 connection = self.create(obj) | ||||
| @ -1,7 +1,7 @@ | ||||
| from authentik.lib.sentry import SentryIgnoredException | ||||
| from authentik.common.exceptions import NotReportedException | ||||
| 
 | ||||
| 
 | ||||
| class BaseSyncException(SentryIgnoredException): | ||||
| class BaseSyncException(NotReportedException): | ||||
|     """Base class for all sync exceptions""" | ||||
| 
 | ||||
| 
 | ||||
| @ -5,8 +5,8 @@ from django.db import connection, models | ||||
| from django.db.models import Model, QuerySet, TextChoices | ||||
| from django.utils.translation import gettext_lazy as _ | ||||
| 
 | ||||
| from authentik.common.sync.outgoing.base import BaseOutgoingSyncClient | ||||
| from authentik.core.models import Group, User | ||||
| from authentik.lib.sync.outgoing.base import BaseOutgoingSyncClient | ||||
| 
 | ||||
| 
 | ||||
| class OutgoingSyncDeleteAction(TextChoices): | ||||
| @ -5,11 +5,11 @@ from django.db.models import Model | ||||
| from django.db.models.query import Q | ||||
| from django.db.models.signals import m2m_changed, post_save, pre_delete | ||||
| 
 | ||||
| from authentik.common.sync.outgoing import PAGE_SIZE, PAGE_TIMEOUT | ||||
| from authentik.common.sync.outgoing.base import Direction | ||||
| from authentik.common.sync.outgoing.models import OutgoingSyncProvider | ||||
| from authentik.common.utils.reflection import class_to_path | ||||
| from authentik.core.models import Group, User | ||||
| from authentik.lib.sync.outgoing import PAGE_SIZE, PAGE_TIMEOUT | ||||
| from authentik.lib.sync.outgoing.base import Direction | ||||
| from authentik.lib.sync.outgoing.models import OutgoingSyncProvider | ||||
| from authentik.lib.utils.reflection import class_to_path | ||||
| 
 | ||||
| 
 | ||||
| def register_signals( | ||||
| @ -10,22 +10,22 @@ from django.utils.text import slugify | ||||
| from django.utils.translation import gettext_lazy as _ | ||||
| from structlog.stdlib import BoundLogger, get_logger | ||||
| 
 | ||||
| from authentik.common.sync.outgoing import PAGE_SIZE, PAGE_TIMEOUT | ||||
| from authentik.common.sync.outgoing.base import Direction | ||||
| from authentik.common.sync.outgoing.exceptions import ( | ||||
|     BadRequestSyncException, | ||||
|     DryRunRejected, | ||||
|     StopSync, | ||||
|     TransientSyncException, | ||||
| ) | ||||
| from authentik.common.sync.outgoing.models import OutgoingSyncProvider | ||||
| from authentik.common.utils.reflection import class_to_path, path_to_class | ||||
| from authentik.core.expression.exceptions import SkipObjectException | ||||
| from authentik.core.models import Group, User | ||||
| from authentik.events.logs import LogEvent | ||||
| from authentik.events.models import TaskStatus | ||||
| from authentik.events.system_tasks import SystemTask | ||||
| from authentik.events.utils import sanitize_item | ||||
| from authentik.lib.sync.outgoing import PAGE_SIZE, PAGE_TIMEOUT | ||||
| from authentik.lib.sync.outgoing.base import Direction | ||||
| from authentik.lib.sync.outgoing.exceptions import ( | ||||
|     BadRequestSyncException, | ||||
|     DryRunRejected, | ||||
|     StopSync, | ||||
|     TransientSyncException, | ||||
| ) | ||||
| from authentik.lib.sync.outgoing.models import OutgoingSyncProvider | ||||
| from authentik.lib.utils.reflection import class_to_path, path_to_class | ||||
| 
 | ||||
| 
 | ||||
| class SyncTasks: | ||||
							
								
								
									
										0
									
								
								authentik/common/utils/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								authentik/common/utils/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @ -2,7 +2,7 @@ | ||||
| 
 | ||||
| from traceback import extract_tb | ||||
| 
 | ||||
| from authentik.lib.utils.reflection import class_to_path | ||||
| from authentik.common.utils.reflection import class_to_path | ||||
| 
 | ||||
| TRACEBACK_HEADER = "Traceback (most recent call last):" | ||||
| 
 | ||||
| @ -6,7 +6,7 @@ from requests.sessions import PreparedRequest, Session | ||||
| from structlog.stdlib import get_logger | ||||
| 
 | ||||
| from authentik import get_full_version | ||||
| from authentik.lib.config import CONFIG | ||||
| from authentik.common.config import CONFIG | ||||
| 
 | ||||
| LOGGER = get_logger() | ||||
| 
 | ||||
| @ -7,7 +7,7 @@ from tempfile import gettempdir | ||||
| 
 | ||||
| from django.conf import settings | ||||
| 
 | ||||
| from authentik.lib.config import CONFIG | ||||
| from authentik.common.config import CONFIG | ||||
| 
 | ||||
| SERVICE_HOST_ENV_NAME = "KUBERNETES_SERVICE_HOST" | ||||
| 
 | ||||
							
								
								
									
										0
									
								
								authentik/common/utils/tests/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								authentik/common/utils/tests/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @ -4,7 +4,7 @@ from datetime import datetime | ||||
| 
 | ||||
| from django.test import TestCase | ||||
| 
 | ||||
| from authentik.lib.utils.reflection import path_to_class | ||||
| from authentik.common.utils.reflection import path_to_class | ||||
| 
 | ||||
| 
 | ||||
| class TestReflectionUtils(TestCase): | ||||
| @ -5,7 +5,7 @@ from datetime import timedelta | ||||
| from django.core.exceptions import ValidationError | ||||
| from django.test import TestCase | ||||
| 
 | ||||
| from authentik.lib.utils.time import timedelta_from_string, timedelta_string_validator | ||||
| from authentik.common.utils.time import timedelta_from_string, timedelta_string_validator | ||||
| 
 | ||||
| 
 | ||||
| class TestTimeUtils(TestCase): | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user
	