mirror of
https://github.com/goauthentik/authentik
synced 2026-05-06 07:02:51 +02:00
Compare commits
19 Commits
a11y-user-
...
explicit-m
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
07aa1f99f6 | ||
|
|
91f4777aa4 | ||
|
|
1226e60c73 | ||
|
|
5fa3dd95be | ||
|
|
9c588565df | ||
|
|
fa43b4b421 | ||
|
|
69fbd4f425 | ||
|
|
d44f33068f | ||
|
|
26de7a21c7 | ||
|
|
234530a8c2 | ||
|
|
1f01c722ba | ||
|
|
36e03443f6 | ||
|
|
3d734f1433 | ||
|
|
5dbee033dc | ||
|
|
d3abff1f5a | ||
|
|
823a3b312b | ||
|
|
fae89e1f74 | ||
|
|
6a218d818a | ||
|
|
a821ea6074 |
36
.bumpversion.cfg
Normal file
36
.bumpversion.cfg
Normal file
@@ -0,0 +1,36 @@
|
||||
[bumpversion]
|
||||
current_version = 2025.6.4
|
||||
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*))?
|
||||
serialize =
|
||||
{major}.{minor}.{patch}-{rc_t}{rc_n}
|
||||
{major}.{minor}.{patch}
|
||||
message = release: {new_version}
|
||||
tag_name = version/{new_version}
|
||||
|
||||
[bumpversion:part:rc_t]
|
||||
values =
|
||||
rc
|
||||
final
|
||||
optional_value = final
|
||||
|
||||
[bumpversion:file:pyproject.toml]
|
||||
|
||||
[bumpversion:file:uv.lock]
|
||||
|
||||
[bumpversion:file:package.json]
|
||||
|
||||
[bumpversion:file:package-lock.json]
|
||||
|
||||
[bumpversion:file:docker-compose.yml]
|
||||
|
||||
[bumpversion:file:schema.yml]
|
||||
|
||||
[bumpversion:file:blueprints/schema.json]
|
||||
|
||||
[bumpversion:file:authentik/__init__.py]
|
||||
|
||||
[bumpversion:file:internal/constants/constants.go]
|
||||
|
||||
[bumpversion:file:lifecycle/aws/template.yaml]
|
||||
@@ -1,6 +1,5 @@
|
||||
htmlcov
|
||||
*.env.yml
|
||||
node_modules
|
||||
**/node_modules
|
||||
dist/**
|
||||
build/**
|
||||
|
||||
267
.github/actions/cherry-pick/action.yml
vendored
267
.github/actions/cherry-pick/action.yml
vendored
@@ -1,267 +0,0 @@
|
||||
name: "Cherry-picker"
|
||||
description: "Cherry-pick PRs based on their labels"
|
||||
|
||||
inputs:
|
||||
token:
|
||||
description: "GitHub Token"
|
||||
required: true
|
||||
git_user:
|
||||
description: "Git user for pushing the cherry-pick PR"
|
||||
required: true
|
||||
git_user_email:
|
||||
description: "Git user email for pushing the cherry-pick PR"
|
||||
required: true
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Check if workflow should run
|
||||
id: should_run
|
||||
shell: bash
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ inputs.token }}
|
||||
run: |
|
||||
set -e -o pipefail
|
||||
# For issues events, check if it's actually a PR
|
||||
if [ "${{ github.event_name }}" = "issues" ]; then
|
||||
# Check if this issue is actually a PR
|
||||
PR_DATA=$(gh api repos/${{ github.repository }}/pulls/${{ github.event.issue.number }} 2>/dev/null || echo "null")
|
||||
if [ "$PR_DATA" = "null" ]; then
|
||||
echo "should_run=false" >> $GITHUB_OUTPUT
|
||||
echo "reason=not_a_pr" >> $GITHUB_OUTPUT
|
||||
echo "This is an issue, not a PR. Skipping."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Get PR data
|
||||
PR_MERGED=$(echo "$PR_DATA" | jq -r '.merged')
|
||||
PR_NUMBER="${{ github.event.issue.number }}"
|
||||
MERGE_COMMIT_SHA=$(echo "$PR_DATA" | jq -r '.merge_commit_sha')
|
||||
|
||||
# Check if it's a backport label
|
||||
LABEL_NAME="${{ github.event.label.name }}"
|
||||
if [[ "$LABEL_NAME" =~ ^backport/(.+)$ ]]; then
|
||||
if [ "$PR_MERGED" = "true" ]; then
|
||||
echo "should_run=true" >> $GITHUB_OUTPUT
|
||||
echo "reason=label_added_to_merged_pr" >> $GITHUB_OUTPUT
|
||||
echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT
|
||||
echo "merge_commit_sha=$MERGE_COMMIT_SHA" >> $GITHUB_OUTPUT
|
||||
exit 0
|
||||
else
|
||||
echo "should_run=false" >> $GITHUB_OUTPUT
|
||||
echo "reason=label_added_to_open_pr" >> $GITHUB_OUTPUT
|
||||
echo "Backport label added to open PR. Will run after PR is merged."
|
||||
exit 0
|
||||
fi
|
||||
else
|
||||
echo "should_run=false" >> $GITHUB_OUTPUT
|
||||
echo "reason=non_backport_label" >> $GITHUB_OUTPUT
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# For pull_request and pull_request_target events
|
||||
PR_NUMBER="${{ github.event.pull_request.number }}"
|
||||
MERGE_COMMIT_SHA="${{ github.event.pull_request.merge_commit_sha }}"
|
||||
|
||||
# Case 1: PR was just merged (closed + merged = true)
|
||||
if [ "${{ github.event.action }}" = "closed" ] && [ "${{ github.event.pull_request.merged }}" = "true" ]; then
|
||||
echo "should_run=true" >> $GITHUB_OUTPUT
|
||||
echo "reason=pr_merged" >> $GITHUB_OUTPUT
|
||||
echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT
|
||||
echo "merge_commit_sha=$MERGE_COMMIT_SHA" >> $GITHUB_OUTPUT
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Case 2: Label was added
|
||||
if [ "${{ github.event.action }}" = "labeled" ]; then
|
||||
LABEL_NAME="${{ github.event.label.name }}"
|
||||
# Check if it's a backport label
|
||||
if [[ "$LABEL_NAME" =~ ^backport/(.+)$ ]]; then
|
||||
# Check if PR is already merged
|
||||
if [ "${{ github.event.pull_request.merged }}" = "true" ]; then
|
||||
echo "should_run=true" >> $GITHUB_OUTPUT
|
||||
echo "reason=label_added_to_merged_pr" >> $GITHUB_OUTPUT
|
||||
echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT
|
||||
echo "merge_commit_sha=$MERGE_COMMIT_SHA" >> $GITHUB_OUTPUT
|
||||
exit 0
|
||||
else
|
||||
echo "should_run=false" >> $GITHUB_OUTPUT
|
||||
echo "reason=label_added_to_open_pr" >> $GITHUB_OUTPUT
|
||||
echo "Backport label added to open PR. Will run after PR is merged."
|
||||
exit 0
|
||||
fi
|
||||
else
|
||||
echo "should_run=false" >> $GITHUB_OUTPUT
|
||||
echo "reason=non_backport_label" >> $GITHUB_OUTPUT
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "should_run=false" >> $GITHUB_OUTPUT
|
||||
echo "reason=unknown" >> $GITHUB_OUTPUT
|
||||
- name: Configure Git
|
||||
if: steps.should_run.outputs.should_run == 'true'
|
||||
shell: bash
|
||||
env:
|
||||
user: ${{ inputs.git_user }}
|
||||
email: ${{ inputs.git_user_email }}
|
||||
run: |
|
||||
git config --global user.name "${user}"
|
||||
git config --global user.email "${email}"
|
||||
- name: Get PR details and extract backport labels
|
||||
if: steps.should_run.outputs.should_run == 'true'
|
||||
id: pr_details
|
||||
shell: bash
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ inputs.token }}
|
||||
run: |
|
||||
set -e -o pipefail
|
||||
PR_NUMBER="${{ steps.should_run.outputs.pr_number }}"
|
||||
|
||||
# Get PR details
|
||||
PR_DATA=$(gh api repos/${{ github.repository }}/pulls/$PR_NUMBER)
|
||||
PR_TITLE=$(echo "$PR_DATA" | jq -r '.title')
|
||||
PR_AUTHOR=$(echo "$PR_DATA" | jq -r '.user.login')
|
||||
|
||||
echo "pr_title=$PR_TITLE" >> $GITHUB_OUTPUT
|
||||
echo "pr_author=$PR_AUTHOR" >> $GITHUB_OUTPUT
|
||||
|
||||
# Determine which labels to process
|
||||
if [ "${{ steps.should_run.outputs.reason }}" = "label_added_to_merged_pr" ]; then
|
||||
# Only process the specific label that was just added
|
||||
if [ "${{ github.event_name }}" = "issues" ]; then
|
||||
LABEL_NAME="${{ github.event.label.name }}"
|
||||
else
|
||||
LABEL_NAME="${{ github.event.label.name }}"
|
||||
fi
|
||||
|
||||
if [[ "$LABEL_NAME" =~ ^backport/(.+)$ ]]; then
|
||||
echo "labels=$LABEL_NAME" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "Label $LABEL_NAME does not match backport pattern"
|
||||
echo "labels=" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
else
|
||||
# PR was just merged, process all backport labels
|
||||
LABELS=$(gh pr view $PR_NUMBER --json labels --jq '.labels[].name' | grep '^backport/' | tr '\n' ' ' || true)
|
||||
echo "labels=$LABELS" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
- name: Cherry-pick to target branches
|
||||
if: steps.should_run.outputs.should_run == 'true' && steps.pr_details.outputs.labels != ''
|
||||
shell: bash
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ inputs.token }}
|
||||
run: |
|
||||
set -e -o pipefail
|
||||
PR_NUMBER='${{ steps.should_run.outputs.pr_number }}'
|
||||
COMMIT_SHA='${{ steps.should_run.outputs.merge_commit_sha }}'
|
||||
PR_TITLE='${{ steps.pr_details.outputs.pr_title }}'
|
||||
PR_AUTHOR='${{ steps.pr_details.outputs.pr_author }}'
|
||||
LABELS='${{ steps.pr_details.outputs.labels }}'
|
||||
|
||||
echo "Processing PR #$PR_NUMBER (reason: ${{ steps.should_run.outputs.reason }})"
|
||||
echo "Found backport labels: $LABELS"
|
||||
|
||||
# Process each backport label
|
||||
for label in $LABELS; do
|
||||
if [[ "$label" =~ ^backport/(.+)$ ]]; then
|
||||
TARGET_BRANCH="${BASH_REMATCH[1]}"
|
||||
echo "Processing backport to branch: $TARGET_BRANCH"
|
||||
|
||||
# Check if target branch exists
|
||||
if ! git ls-remote --heads origin "$TARGET_BRANCH" | grep -q "$TARGET_BRANCH"; then
|
||||
echo "❌ Target branch $TARGET_BRANCH does not exist, skipping"
|
||||
|
||||
# Comment on the original PR about the missing branch
|
||||
gh pr comment $PR_NUMBER --body "⚠️ Cannot backport to \`$TARGET_BRANCH\`: branch does not exist."
|
||||
continue
|
||||
fi
|
||||
|
||||
# Create a unique branch name for the cherry-pick
|
||||
CHERRY_PICK_BRANCH="cherry-pick-${PR_NUMBER}-to-${TARGET_BRANCH}"
|
||||
|
||||
# Check if a cherry-pick PR already exists
|
||||
EXISTING_PR=$(gh pr list --head "$CHERRY_PICK_BRANCH" --json number --jq '.[0].number' 2>/dev/null || echo "")
|
||||
if [ -n "$EXISTING_PR" ]; then
|
||||
echo "⚠️ Cherry-pick PR already exists: #$EXISTING_PR"
|
||||
gh pr comment $PR_NUMBER --body "Cherry-pick to \`$TARGET_BRANCH\` already exists: #$EXISTING_PR"
|
||||
continue
|
||||
fi
|
||||
|
||||
# Fetch and checkout target branch
|
||||
git fetch origin "$TARGET_BRANCH"
|
||||
git checkout -b "$CHERRY_PICK_BRANCH" "origin/$TARGET_BRANCH"
|
||||
|
||||
# Attempt cherry-pick
|
||||
if git cherry-pick "$COMMIT_SHA"; then
|
||||
echo "✅ Cherry-pick successful for $TARGET_BRANCH"
|
||||
|
||||
# Push the cherry-pick branch
|
||||
git push origin "$CHERRY_PICK_BRANCH"
|
||||
|
||||
# Create PR for the cherry-pick
|
||||
CHERRY_PICK_TITLE="$PR_TITLE (cherry-pick #$PR_NUMBER)"
|
||||
CHERRY_PICK_BODY="Cherry-pick of #$PR_NUMBER to \`$TARGET_BRANCH\` branch.
|
||||
|
||||
**Original PR:** #$PR_NUMBER
|
||||
**Original Author:** @$PR_AUTHOR
|
||||
**Cherry-picked commit:** $COMMIT_SHA"
|
||||
|
||||
NEW_PR=$(gh pr create \
|
||||
--title "$CHERRY_PICK_TITLE" \
|
||||
--body "$CHERRY_PICK_BODY" \
|
||||
--base "$TARGET_BRANCH" \
|
||||
--head "$CHERRY_PICK_BRANCH" \
|
||||
--label "cherry-pick")
|
||||
|
||||
echo "✅ Created cherry-pick PR $NEW_PR for $TARGET_BRANCH"
|
||||
|
||||
# Comment on original PR
|
||||
gh pr comment $PR_NUMBER --body "🍒 Cherry-pick to \`$TARGET_BRANCH\` created: $NEW_PR"
|
||||
|
||||
else
|
||||
echo "⚠️ Cherry-pick failed for $TARGET_BRANCH, creating conflict resolution PR"
|
||||
|
||||
# Add conflicted files and commit
|
||||
git add .
|
||||
git commit -m "Cherry-pick #$PR_NUMBER to $TARGET_BRANCH (with conflicts)
|
||||
|
||||
This cherry-pick has conflicts that need manual resolution.
|
||||
|
||||
Original PR: #$PR_NUMBER
|
||||
Original commit: $COMMIT_SHA"
|
||||
|
||||
# Push the branch with conflicts
|
||||
git push origin "$CHERRY_PICK_BRANCH"
|
||||
|
||||
# Create PR with conflict notice
|
||||
CONFLICT_TITLE="$PR_TITLE (backport of #$PR_NUMBER)"
|
||||
CONFLICT_BODY="⚠️ **This cherry-pick has conflicts that require manual resolution.**
|
||||
|
||||
Cherry-pick of #$PR_NUMBER to \`$TARGET_BRANCH\` branch.
|
||||
|
||||
**Original PR:** #$PR_NUMBER
|
||||
**Original Author:** @$PR_AUTHOR
|
||||
**Cherry-picked commit:** $COMMIT_SHA
|
||||
|
||||
**Please resolve the conflicts in this PR before merging.**"
|
||||
|
||||
NEW_PR=$(gh pr create \
|
||||
--title "$CONFLICT_TITLE" \
|
||||
--body "$CONFLICT_BODY" \
|
||||
--base "$TARGET_BRANCH" \
|
||||
--head "$CHERRY_PICK_BRANCH" \
|
||||
--label "cherry-pick")
|
||||
|
||||
echo "⚠️ Created conflict resolution PR $NEW_PR for $TARGET_BRANCH"
|
||||
|
||||
# Comment on original PR
|
||||
gh pr comment $PR_NUMBER --body "⚠️ Cherry-pick to \`$TARGET_BRANCH\` has conflicts: $NEW_PR"
|
||||
fi
|
||||
|
||||
# Clean up - go back to main branch
|
||||
git checkout main
|
||||
git branch -D "$CHERRY_PICK_BRANCH" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
@@ -54,10 +54,6 @@ outputs:
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Setup authentik env
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
dependencies: "python"
|
||||
- name: Generate config
|
||||
id: ev
|
||||
shell: bash
|
||||
@@ -68,4 +64,4 @@ runs:
|
||||
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
||||
REF: ${{ github.ref }}
|
||||
run: |
|
||||
uv run python3 ${{ github.action_path }}/push_vars.py
|
||||
python3 ${{ github.action_path }}/push_vars.py
|
||||
|
||||
@@ -1,29 +1,19 @@
|
||||
"""Helper script to get the actual branch name, docker safe"""
|
||||
|
||||
import configparser
|
||||
import os
|
||||
from json import dumps
|
||||
from sys import exit as sysexit
|
||||
from time import time
|
||||
|
||||
from authentik import authentik_version
|
||||
|
||||
|
||||
def must_or_fail(input: str | None, error: str) -> str:
|
||||
if not input:
|
||||
print(f"::error::{error}")
|
||||
sysexit(1)
|
||||
return input
|
||||
|
||||
parser = configparser.ConfigParser()
|
||||
parser.read(".bumpversion.cfg")
|
||||
|
||||
# Decide if we should push the image or not
|
||||
should_push = True
|
||||
if len(os.environ.get("DOCKER_USERNAME", "")) < 1:
|
||||
# Don't push if we don't have DOCKER_USERNAME, i.e. no secrets are available
|
||||
should_push = False
|
||||
if (
|
||||
must_or_fail(os.environ.get("GITHUB_REPOSITORY"), "Repo required").lower()
|
||||
== "goauthentik/authentik-internal"
|
||||
):
|
||||
if os.environ.get("GITHUB_REPOSITORY").lower() == "goauthentik/authentik-internal":
|
||||
# Don't push on the internal repo
|
||||
should_push = False
|
||||
|
||||
@@ -32,19 +22,16 @@ if os.environ.get("GITHUB_HEAD_REF", "") != "":
|
||||
branch_name = os.environ["GITHUB_HEAD_REF"]
|
||||
safe_branch_name = branch_name.replace("refs/heads/", "").replace("/", "-").replace("'", "-")
|
||||
|
||||
image_names = must_or_fail(os.getenv("IMAGE_NAME"), "Image name required").split(",")
|
||||
image_names = os.getenv("IMAGE_NAME").split(",")
|
||||
image_arch = os.getenv("IMAGE_ARCH") or None
|
||||
|
||||
is_pull_request = bool(os.getenv("PR_HEAD_SHA"))
|
||||
is_release = "dev" not in image_names[0]
|
||||
|
||||
sha = must_or_fail(
|
||||
os.environ["GITHUB_SHA"] if not is_pull_request else os.getenv("PR_HEAD_SHA"),
|
||||
"could not determine SHA",
|
||||
)
|
||||
sha = os.environ["GITHUB_SHA"] if not is_pull_request else os.getenv("PR_HEAD_SHA")
|
||||
|
||||
# 2042.1.0 or 2042.1.0-rc1
|
||||
version = authentik_version()
|
||||
version = parser.get("bumpversion", "current_version")
|
||||
# 2042.1
|
||||
version_family = ".".join(version.split("-", 1)[0].split(".")[:-1])
|
||||
prerelease = "-" in version
|
||||
@@ -73,7 +60,7 @@ else:
|
||||
image_main_tag = image_tags[0].split(":")[-1]
|
||||
|
||||
|
||||
def get_attest_image_names(image_with_tags: list[str]) -> str:
|
||||
def get_attest_image_names(image_with_tags: list[str]):
|
||||
"""Attestation only for GHCR"""
|
||||
image_tags = []
|
||||
for image_name in set(name.split(":")[0] for name in image_with_tags):
|
||||
@@ -97,6 +84,7 @@ if os.getenv("RELEASE", "false").lower() == "true":
|
||||
image_build_args = [f"VERSION={os.getenv('REF')}"]
|
||||
else:
|
||||
image_build_args = [f"GIT_BUILD_HASH={sha}"]
|
||||
image_build_args = "\n".join(image_build_args)
|
||||
|
||||
with open(os.environ["GITHUB_OUTPUT"], "a+", encoding="utf-8") as _output:
|
||||
print(f"shouldPush={str(should_push).lower()}", file=_output)
|
||||
@@ -109,4 +97,4 @@ with open(os.environ["GITHUB_OUTPUT"], "a+", encoding="utf-8") as _output:
|
||||
print(f"imageMainTag={image_main_tag}", file=_output)
|
||||
print(f"imageMainName={image_tags[0]}", file=_output)
|
||||
print(f"cacheTo={cache_to}", file=_output)
|
||||
print(f"imageBuildArgs={"\n".join(image_build_args)}", file=_output)
|
||||
print(f"imageBuildArgs={image_build_args}", file=_output)
|
||||
|
||||
@@ -4,7 +4,7 @@ SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
||||
GITHUB_OUTPUT=/dev/stdout \
|
||||
GITHUB_REF=ref \
|
||||
GITHUB_SHA=sha \
|
||||
IMAGE_NAME=ghcr.io/goauthentik/server,authentik/server \
|
||||
IMAGE_NAME=ghcr.io/goauthentik/server,beryju/authentik \
|
||||
GITHUB_REPOSITORY=goauthentik/authentik \
|
||||
python $SCRIPT_DIR/push_vars.py
|
||||
|
||||
@@ -12,7 +12,7 @@ GITHUB_OUTPUT=/dev/stdout \
|
||||
GITHUB_OUTPUT=/dev/stdout \
|
||||
GITHUB_REF=ref \
|
||||
GITHUB_SHA=sha \
|
||||
IMAGE_NAME=ghcr.io/goauthentik/server,authentik/server \
|
||||
IMAGE_NAME=ghcr.io/goauthentik/server,beryju/authentik \
|
||||
GITHUB_REPOSITORY=goauthentik/authentik \
|
||||
DOCKER_USERNAME=foo \
|
||||
python $SCRIPT_DIR/push_vars.py
|
||||
|
||||
13
.github/actions/setup/action.yml
vendored
13
.github/actions/setup/action.yml
vendored
@@ -2,9 +2,6 @@ name: "Setup authentik testing environment"
|
||||
description: "Setup authentik testing environment"
|
||||
|
||||
inputs:
|
||||
dependencies:
|
||||
description: "List of dependencies to setup"
|
||||
default: "system,python,node,go,runtime"
|
||||
postgresql_version:
|
||||
description: "Optional postgresql image tag"
|
||||
default: "16"
|
||||
@@ -13,52 +10,42 @@ runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Install apt deps
|
||||
if: ${{ contains(inputs.dependencies, 'system') || contains(inputs.dependencies, 'python') }}
|
||||
shell: bash
|
||||
run: |
|
||||
sudo apt-get remove --purge man-db
|
||||
sudo apt-get update
|
||||
sudo apt-get install --no-install-recommends -y libpq-dev openssl libxmlsec1-dev pkg-config gettext libkrb5-dev krb5-kdc krb5-user krb5-admin-server
|
||||
- name: Install uv
|
||||
if: ${{ contains(inputs.dependencies, 'python') }}
|
||||
uses: astral-sh/setup-uv@v5
|
||||
with:
|
||||
enable-cache: true
|
||||
- name: Setup python
|
||||
if: ${{ contains(inputs.dependencies, 'python') }}
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version-file: "pyproject.toml"
|
||||
- name: Install Python deps
|
||||
if: ${{ contains(inputs.dependencies, 'python') }}
|
||||
shell: bash
|
||||
run: uv sync --all-extras --dev --frozen
|
||||
- name: Setup node
|
||||
if: ${{ contains(inputs.dependencies, 'node') }}
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: web/package.json
|
||||
cache: "npm"
|
||||
cache-dependency-path: web/package-lock.json
|
||||
- name: Setup go
|
||||
if: ${{ contains(inputs.dependencies, 'go') }}
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: "go.mod"
|
||||
- name: Setup docker cache
|
||||
if: ${{ contains(inputs.dependencies, 'runtime') }}
|
||||
uses: AndreKurait/docker-cache@0fe76702a40db986d9663c24954fc14c6a6031b7
|
||||
with:
|
||||
key: docker-images-${{ runner.os }}-${{ hashFiles('.github/actions/setup/docker-compose.yml', 'Makefile') }}-${{ inputs.postgresql_version }}
|
||||
- name: Setup dependencies
|
||||
if: ${{ contains(inputs.dependencies, 'runtime') }}
|
||||
shell: bash
|
||||
run: |
|
||||
export PSQL_TAG=${{ inputs.postgresql_version }}
|
||||
docker compose -f .github/actions/setup/docker-compose.yml up -d
|
||||
cd web && npm ci
|
||||
- name: Generate config
|
||||
if: ${{ contains(inputs.dependencies, 'python') }}
|
||||
shell: uv run python {0}
|
||||
run: |
|
||||
from authentik.lib.generators import generate_id
|
||||
|
||||
2
.github/cherry-pick-bot.yml
vendored
Normal file
2
.github/cherry-pick-bot.yml
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
enabled: true
|
||||
preservePullRequestTitle: true
|
||||
6
.github/dependabot.yml
vendored
6
.github/dependabot.yml
vendored
@@ -77,12 +77,6 @@ updates:
|
||||
goauthentik:
|
||||
patterns:
|
||||
- "@goauthentik/*"
|
||||
react:
|
||||
patterns:
|
||||
- "react"
|
||||
- "react-dom"
|
||||
- "@types/react"
|
||||
- "@types/react-dom"
|
||||
- package-ecosystem: npm
|
||||
directory: "/website"
|
||||
schedule:
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
---
|
||||
# Re-usable workflow for a single-architecture build
|
||||
name: Reusable - Single-arch Container build
|
||||
name: Single-arch Container build
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
@@ -42,14 +41,14 @@ jobs:
|
||||
# Needed for checkout
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v4
|
||||
- uses: docker/setup-qemu-action@v3.6.0
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
- name: prepare variables
|
||||
uses: ./.github/actions/docker-push-variables
|
||||
id: ev
|
||||
env:
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_CORP_USERNAME }}
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
with:
|
||||
image-name: ${{ inputs.image_name }}
|
||||
image-arch: ${{ inputs.image_arch }}
|
||||
@@ -58,8 +57,8 @@ jobs:
|
||||
if: ${{ inputs.registry_dockerhub }}
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_CORP_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_CORP_PASSWORD }}
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
- name: Login to GitHub Container Registry
|
||||
if: ${{ inputs.registry_ghcr }}
|
||||
uses: docker/login-action@v3
|
||||
@@ -90,7 +89,7 @@ jobs:
|
||||
platforms: linux/${{ inputs.image_arch }}
|
||||
cache-from: type=registry,ref=${{ steps.ev.outputs.attestImageNames }}:buildcache-${{ inputs.image_arch }}
|
||||
cache-to: ${{ steps.ev.outputs.cacheTo }}
|
||||
- uses: actions/attest-build-provenance@v3
|
||||
- uses: actions/attest-build-provenance@v2
|
||||
id: attest
|
||||
if: ${{ steps.ev.outputs.shouldPush == 'true' }}
|
||||
with:
|
||||
@@ -1,6 +1,5 @@
|
||||
---
|
||||
# Re-usable workflow for a multi-architecture build
|
||||
name: Reusable - Multi-arch container build
|
||||
name: Multi-arch container build
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
@@ -21,7 +20,7 @@ on:
|
||||
|
||||
jobs:
|
||||
build-server-amd64:
|
||||
uses: ./.github/workflows/_reusable-docker-build-single.yml
|
||||
uses: ./.github/workflows/_reusable-docker-build-single.yaml
|
||||
secrets: inherit
|
||||
with:
|
||||
image_name: ${{ inputs.image_name }}
|
||||
@@ -31,7 +30,7 @@ jobs:
|
||||
registry_ghcr: ${{ inputs.registry_ghcr }}
|
||||
release: ${{ inputs.release }}
|
||||
build-server-arm64:
|
||||
uses: ./.github/workflows/_reusable-docker-build-single.yml
|
||||
uses: ./.github/workflows/_reusable-docker-build-single.yaml
|
||||
secrets: inherit
|
||||
with:
|
||||
image_name: ${{ inputs.image_name }}
|
||||
@@ -49,12 +48,12 @@ jobs:
|
||||
tags: ${{ steps.ev.outputs.imageTagsJSON }}
|
||||
shouldPush: ${{ steps.ev.outputs.shouldPush }}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v4
|
||||
- name: prepare variables
|
||||
uses: ./.github/actions/docker-push-variables
|
||||
id: ev
|
||||
env:
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_CORP_USERNAME }}
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
with:
|
||||
image-name: ${{ inputs.image_name }}
|
||||
merge-server:
|
||||
@@ -69,20 +68,20 @@ jobs:
|
||||
matrix:
|
||||
tag: ${{ fromJson(needs.get-tags.outputs.tags) }}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v4
|
||||
- name: prepare variables
|
||||
uses: ./.github/actions/docker-push-variables
|
||||
id: ev
|
||||
env:
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_CORP_USERNAME }}
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
with:
|
||||
image-name: ${{ inputs.image_name }}
|
||||
- name: Login to Docker Hub
|
||||
if: ${{ inputs.registry_dockerhub }}
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_CORP_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_CORP_PASSWORD }}
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
- name: Login to GitHub Container Registry
|
||||
if: ${{ inputs.registry_ghcr }}
|
||||
uses: docker/login-action@v3
|
||||
@@ -97,7 +96,7 @@ jobs:
|
||||
sources: |
|
||||
${{ steps.ev.outputs.attestImageNames }}@${{ needs.build-server-amd64.outputs.image-digest }}
|
||||
${{ steps.ev.outputs.attestImageNames }}@${{ needs.build-server-arm64.outputs.image-digest }}
|
||||
- uses: actions/attest-build-provenance@v3
|
||||
- uses: actions/attest-build-provenance@v2
|
||||
id: attest
|
||||
with:
|
||||
subject-name: ${{ steps.ev.outputs.attestImageNames }}
|
||||
65
.github/workflows/api-py-publish.yml
vendored
Normal file
65
.github/workflows/api-py-publish.yml
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
name: authentik-api-py-publish
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- "schema.yml"
|
||||
workflow_dispatch:
|
||||
jobs:
|
||||
build:
|
||||
if: ${{ github.repository != 'goauthentik/authentik-internal' }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
id-token: write
|
||||
steps:
|
||||
- id: generate_token
|
||||
uses: tibdex/github-app-token@v2
|
||||
with:
|
||||
app_id: ${{ secrets.GH_APP_ID }}
|
||||
private_key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
token: ${{ steps.generate_token.outputs.token }}
|
||||
- name: Install poetry & deps
|
||||
shell: bash
|
||||
run: |
|
||||
pipx install poetry || true
|
||||
sudo apt-get update
|
||||
sudo apt-get install --no-install-recommends -y libpq-dev openssl libxmlsec1-dev pkg-config gettext
|
||||
- name: Setup python and restore poetry
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version-file: "pyproject.toml"
|
||||
- name: Generate API Client
|
||||
run: make gen-client-py
|
||||
- name: Publish package
|
||||
working-directory: gen-py-api/
|
||||
run: |
|
||||
poetry build
|
||||
- name: Publish package to PyPI
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
packages-dir: gen-py-api/dist/
|
||||
# We can't easily upgrade the API client being used due to poetry being poetry
|
||||
# so we'll have to rely on dependabot
|
||||
# - name: Upgrade /
|
||||
# run: |
|
||||
# export VERSION=$(cd gen-py-api && poetry version -s)
|
||||
# poetry add "authentik_client=$VERSION" --allow-prereleases --lock
|
||||
# - uses: peter-evans/create-pull-request@v6
|
||||
# id: cpr
|
||||
# with:
|
||||
# token: ${{ steps.generate_token.outputs.token }}
|
||||
# branch: update-root-api-client
|
||||
# commit-message: "root: bump API Client version"
|
||||
# title: "root: bump API Client version"
|
||||
# body: "root: bump API Client version"
|
||||
# delete-branch: true
|
||||
# signoff: true
|
||||
# # ID from https://api.github.com/users/authentik-automation[bot]
|
||||
# author: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
|
||||
# - uses: peter-evans/enable-pull-request-automerge@v3
|
||||
# with:
|
||||
# token: ${{ steps.generate_token.outputs.token }}
|
||||
# pull-request-number: ${{ steps.cpr.outputs.pull-request-number }}
|
||||
# merge-method: squash
|
||||
9
.github/workflows/api-ts-publish.yml
vendored
9
.github/workflows/api-ts-publish.yml
vendored
@@ -1,13 +1,10 @@
|
||||
---
|
||||
name: API - Publish Typescript client
|
||||
|
||||
name: authentik-api-ts-publish
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- "schema.yml"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: ${{ github.repository != 'goauthentik/authentik-internal' }}
|
||||
@@ -18,10 +15,10 @@ jobs:
|
||||
with:
|
||||
app_id: ${{ secrets.GH_APP_ID }}
|
||||
private_key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
token: ${{ steps.generate_token.outputs.token }}
|
||||
- uses: actions/setup-node@v5
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: web/package.json
|
||||
registry-url: "https://registry.npmjs.org"
|
||||
|
||||
15
.github/workflows/ci-api-docs.yml
vendored
15
.github/workflows/ci-api-docs.yml
vendored
@@ -1,5 +1,4 @@
|
||||
---
|
||||
name: CI - API Docs
|
||||
name: authentik-ci-api-docs
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -21,7 +20,7 @@ jobs:
|
||||
command:
|
||||
- prettier-check
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Dependencies
|
||||
working-directory: website/
|
||||
run: npm ci
|
||||
@@ -32,8 +31,8 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-node@v5
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: website/package.json
|
||||
cache: "npm"
|
||||
@@ -66,12 +65,12 @@ jobs:
|
||||
- lint
|
||||
- build
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/download-artifact@v5
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: api-docs
|
||||
path: website/api/build
|
||||
- uses: actions/setup-node@v5
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: website/package.json
|
||||
cache: "npm"
|
||||
|
||||
7
.github/workflows/ci-aws-cfn.yml
vendored
7
.github/workflows/ci-aws-cfn.yml
vendored
@@ -1,5 +1,4 @@
|
||||
---
|
||||
name: CI - AWS cfn
|
||||
name: authentik-ci-aws-cfn
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -21,10 +20,10 @@ jobs:
|
||||
check-changes-applied:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup authentik env
|
||||
uses: ./.github/actions/setup
|
||||
- uses: actions/setup-node@v5
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: lifecycle/aws/package.json
|
||||
cache: "npm"
|
||||
|
||||
19
.github/workflows/ci-docs.yml
vendored
19
.github/workflows/ci-docs.yml
vendored
@@ -1,5 +1,4 @@
|
||||
---
|
||||
name: CI - Docs
|
||||
name: authentik-ci-docs
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -21,7 +20,7 @@ jobs:
|
||||
command:
|
||||
- prettier-check
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install dependencies
|
||||
working-directory: website/
|
||||
run: npm ci
|
||||
@@ -32,8 +31,8 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-node@v5
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: website/package.json
|
||||
cache: "npm"
|
||||
@@ -48,8 +47,8 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-node@v5
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: website/package.json
|
||||
cache: "npm"
|
||||
@@ -70,7 +69,7 @@ jobs:
|
||||
id-token: write
|
||||
attestations: write
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
- name: Set up QEMU
|
||||
@@ -81,7 +80,7 @@ jobs:
|
||||
uses: ./.github/actions/docker-push-variables
|
||||
id: ev
|
||||
env:
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_CORP_USERNAME }}
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
with:
|
||||
image-name: ghcr.io/goauthentik/dev-docs
|
||||
- name: Login to Container Registry
|
||||
@@ -102,7 +101,7 @@ jobs:
|
||||
context: .
|
||||
cache-from: type=registry,ref=ghcr.io/goauthentik/dev-docs:buildcache
|
||||
cache-to: ${{ steps.ev.outputs.shouldPush == 'true' && 'type=registry,ref=ghcr.io/goauthentik/dev-docs:buildcache,mode=max' || '' }}
|
||||
- uses: actions/attest-build-provenance@v3
|
||||
- uses: actions/attest-build-provenance@v2
|
||||
id: attest
|
||||
if: ${{ steps.ev.outputs.shouldPush == 'true' }}
|
||||
with:
|
||||
|
||||
4
.github/workflows/ci-main-daily.yml
vendored
4
.github/workflows/ci-main-daily.yml
vendored
@@ -1,5 +1,5 @@
|
||||
---
|
||||
name: CI - Main daily
|
||||
name: authentik-ci-main-daily
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
- version-2025-4
|
||||
- version-2025-2
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v4
|
||||
- run: |
|
||||
current="$(pwd)"
|
||||
dir="/tmp/authentik/${{ matrix.version }}"
|
||||
|
||||
39
.github/workflows/ci-main.yml
vendored
39
.github/workflows/ci-main.yml
vendored
@@ -1,5 +1,5 @@
|
||||
---
|
||||
name: CI - Main
|
||||
name: authentik-ci-main
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -17,12 +17,6 @@ env:
|
||||
POSTGRES_USER: authentik
|
||||
POSTGRES_PASSWORD: "EK-5jnKfjrGRm<77"
|
||||
|
||||
permissions:
|
||||
# Needed for checkout
|
||||
contents: read
|
||||
# Needed for codecov OIDC token
|
||||
id-token: write
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
strategy:
|
||||
@@ -34,10 +28,9 @@ jobs:
|
||||
- codespell
|
||||
- pending-migrations
|
||||
- ruff
|
||||
- mypy
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup authentik env
|
||||
uses: ./.github/actions/setup
|
||||
- name: run job
|
||||
@@ -45,7 +38,7 @@ jobs:
|
||||
test-migrations:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup authentik env
|
||||
uses: ./.github/actions/setup
|
||||
- name: run migrations
|
||||
@@ -72,7 +65,7 @@ jobs:
|
||||
- 17-alpine
|
||||
run_id: [1, 2, 3, 4, 5]
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: checkout stable
|
||||
@@ -127,7 +120,7 @@ jobs:
|
||||
- 17-alpine
|
||||
run_id: [1, 2, 3, 4, 5]
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup authentik env
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
@@ -143,18 +136,18 @@ jobs:
|
||||
uses: codecov/codecov-action@v5
|
||||
with:
|
||||
flags: unit
|
||||
use_oidc: true
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
- if: ${{ !cancelled() }}
|
||||
uses: codecov/test-results-action@v1
|
||||
with:
|
||||
flags: unit
|
||||
file: unittest.xml
|
||||
use_oidc: true
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
test-integration:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup authentik env
|
||||
uses: ./.github/actions/setup
|
||||
- name: Create k8s Kind Cluster
|
||||
@@ -167,13 +160,13 @@ jobs:
|
||||
uses: codecov/codecov-action@v5
|
||||
with:
|
||||
flags: integration
|
||||
use_oidc: true
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
- if: ${{ !cancelled() }}
|
||||
uses: codecov/test-results-action@v1
|
||||
with:
|
||||
flags: integration
|
||||
file: unittest.xml
|
||||
use_oidc: true
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
test-e2e:
|
||||
name: test-e2e (${{ matrix.job.name }})
|
||||
runs-on: ubuntu-latest
|
||||
@@ -199,7 +192,7 @@ jobs:
|
||||
- name: flows
|
||||
glob: tests/e2e/test_flows*
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup authentik env
|
||||
uses: ./.github/actions/setup
|
||||
- name: Setup e2e env (chrome, etc)
|
||||
@@ -226,13 +219,13 @@ jobs:
|
||||
uses: codecov/codecov-action@v5
|
||||
with:
|
||||
flags: e2e
|
||||
use_oidc: true
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
- if: ${{ !cancelled() }}
|
||||
uses: codecov/test-results-action@v1
|
||||
with:
|
||||
flags: e2e
|
||||
file: unittest.xml
|
||||
use_oidc: true
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
ci-core-mark:
|
||||
if: always()
|
||||
needs:
|
||||
@@ -257,7 +250,7 @@ jobs:
|
||||
# Needed for checkout
|
||||
contents: read
|
||||
needs: ci-core-mark
|
||||
uses: ./.github/workflows/_reusable-docker-build.yml
|
||||
uses: ./.github/workflows/_reusable-docker-build.yaml
|
||||
secrets: inherit
|
||||
with:
|
||||
image_name: ${{ github.repository == 'goauthentik/authentik-internal' && 'ghcr.io/goauthentik/internal-server' || 'ghcr.io/goauthentik/dev-server' }}
|
||||
@@ -272,14 +265,14 @@ jobs:
|
||||
pull-requests: write
|
||||
timeout-minutes: 120
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
- name: prepare variables
|
||||
uses: ./.github/actions/docker-push-variables
|
||||
id: ev
|
||||
env:
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_CORP_USERNAME }}
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
with:
|
||||
image-name: ghcr.io/goauthentik/dev-server
|
||||
- name: Comment on PR
|
||||
|
||||
22
.github/workflows/ci-outpost.yml
vendored
22
.github/workflows/ci-outpost.yml
vendored
@@ -1,5 +1,5 @@
|
||||
---
|
||||
name: CI - Outpost
|
||||
name: authentik-ci-outpost
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -16,8 +16,8 @@ jobs:
|
||||
lint-golint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-go@v6
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: "go.mod"
|
||||
- name: Prepare and generate API
|
||||
@@ -37,8 +37,8 @@ jobs:
|
||||
test-unittest:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-go@v6
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: "go.mod"
|
||||
- name: Setup authentik env
|
||||
@@ -79,7 +79,7 @@ jobs:
|
||||
id-token: write
|
||||
attestations: write
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
- name: Set up QEMU
|
||||
@@ -90,7 +90,7 @@ jobs:
|
||||
uses: ./.github/actions/docker-push-variables
|
||||
id: ev
|
||||
env:
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_CORP_USERNAME }}
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
with:
|
||||
image-name: ghcr.io/goauthentik/dev-${{ matrix.type }}
|
||||
- name: Login to Container Registry
|
||||
@@ -115,7 +115,7 @@ jobs:
|
||||
context: .
|
||||
cache-from: type=registry,ref=ghcr.io/goauthentik/dev-${{ matrix.type }}:buildcache
|
||||
cache-to: ${{ steps.ev.outputs.shouldPush == 'true' && format('type=registry,ref=ghcr.io/goauthentik/dev-{0}:buildcache,mode=max', matrix.type) || '' }}
|
||||
- uses: actions/attest-build-provenance@v3
|
||||
- uses: actions/attest-build-provenance@v2
|
||||
id: attest
|
||||
if: ${{ steps.ev.outputs.shouldPush == 'true' }}
|
||||
with:
|
||||
@@ -138,13 +138,13 @@ jobs:
|
||||
goos: [linux]
|
||||
goarch: [amd64, arm64]
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
- uses: actions/setup-go@v6
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: "go.mod"
|
||||
- uses: actions/setup-node@v5
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: web/package.json
|
||||
cache: "npm"
|
||||
|
||||
15
.github/workflows/ci-web.yml
vendored
15
.github/workflows/ci-web.yml
vendored
@@ -1,5 +1,4 @@
|
||||
---
|
||||
name: CI - Web
|
||||
name: authentik-ci-web
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -31,8 +30,8 @@ jobs:
|
||||
- command: lit-analyse
|
||||
project: web
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-node@v5
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: ${{ matrix.project }}/package.json
|
||||
cache: "npm"
|
||||
@@ -48,8 +47,8 @@ jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-node@v5
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: web/package.json
|
||||
cache: "npm"
|
||||
@@ -76,8 +75,8 @@ jobs:
|
||||
- ci-web-mark
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-node@v5
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: web/package.json
|
||||
cache: "npm"
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
---
|
||||
name: QA - CodeQL
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -24,7 +23,7 @@ jobs:
|
||||
language: ["go", "javascript", "python"]
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup authentik env
|
||||
uses: ./.github/actions/setup
|
||||
- name: Initialize CodeQL
|
||||
@@ -1,6 +1,4 @@
|
||||
---
|
||||
name: Gen - Webauthn MDS
|
||||
|
||||
name: authentik-gen-update-webauthn-mds
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
@@ -21,7 +19,7 @@ jobs:
|
||||
with:
|
||||
app_id: ${{ secrets.GH_APP_ID }}
|
||||
private_key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
token: ${{ steps.generate_token.outputs.token }}
|
||||
- name: Setup authentik env
|
||||
|
||||
36
.github/workflows/gh-cherry-pick.yml
vendored
36
.github/workflows/gh-cherry-pick.yml
vendored
@@ -1,36 +0,0 @@
|
||||
name: GH - Cherry-pick
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [closed, labeled]
|
||||
|
||||
jobs:
|
||||
cherry-pick:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- id: app-token
|
||||
name: Generate app token
|
||||
uses: actions/create-github-app-token@v2
|
||||
if: ${{ env.GH_APP_ID != '' }}
|
||||
with:
|
||||
app-id: ${{ secrets.GH_APP_ID }}
|
||||
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
||||
env:
|
||||
GH_APP_ID: ${{ secrets.GH_APP_ID }}
|
||||
- uses: actions/checkout@v5
|
||||
if: ${{ steps.app-token.outcome != 'skipped' }}
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: "${{ steps.app-token.outputs.token }}"
|
||||
- id: get-user-id
|
||||
if: ${{ steps.app-token.outcome != 'skipped' }}
|
||||
name: Get GitHub app user ID
|
||||
run: echo "user-id=$(gh api "/users/${{ steps.app-token.outputs.app-slug }}[bot]" --jq .id)" >> "$GITHUB_OUTPUT"
|
||||
env:
|
||||
GH_TOKEN: "${{ steps.app-token.outputs.token }}"
|
||||
- uses: ./.github/actions/cherry-pick
|
||||
if: ${{ steps.app-token.outcome != 'skipped' }}
|
||||
with:
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
git_user: ${{ steps.app-token.outputs.app-slug }}[bot]
|
||||
git_user_email: '${{ steps.get-user-id.outputs.user-id }}+${{ steps.app-token.outputs.app-slug }}[bot]@users.noreply.github.com'
|
||||
@@ -1,7 +1,6 @@
|
||||
---
|
||||
# See https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#force-deleting-cache-entries
|
||||
name: GH - Cleanup actions cache after PR is closed
|
||||
|
||||
name: Cleanup cache after PR is closed
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
@@ -16,7 +15,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Cleanup
|
||||
run: |
|
||||
@@ -1,5 +1,4 @@
|
||||
---
|
||||
name: GH - GHCR retention
|
||||
name: ghcr-retention
|
||||
|
||||
on:
|
||||
# schedule:
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
name: Gen - Compress images
|
||||
name: authentik-compress-images
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -33,7 +33,7 @@ jobs:
|
||||
with:
|
||||
app_id: ${{ secrets.GH_APP_ID }}
|
||||
private_key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
token: ${{ steps.generate_token.outputs.token }}
|
||||
- name: Compress images
|
||||
11
.github/workflows/packages-npm-publish.yml
vendored
11
.github/workflows/packages-npm-publish.yml
vendored
@@ -1,6 +1,4 @@
|
||||
---
|
||||
name: Packages - Publish NPM packages
|
||||
|
||||
name: authentik-packages-npm-publish
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
@@ -11,7 +9,6 @@ on:
|
||||
- packages/tsconfig/**
|
||||
- packages/esbuild-plugin-live-reload/**
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
if: ${{ github.repository != 'goauthentik/authentik-internal' }}
|
||||
@@ -26,16 +23,16 @@ jobs:
|
||||
- packages/tsconfig
|
||||
- packages/esbuild-plugin-live-reload
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 2
|
||||
- uses: actions/setup-node@v5
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: ${{ matrix.package }}/package.json
|
||||
registry-url: "https://registry.npmjs.org"
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@24d32ffd492484c1d75e0c0b894501ddb9d30d62
|
||||
uses: tj-actions/changed-files@ed68ef82c095e0d48ec87eccea555d944a631a4c
|
||||
with:
|
||||
files: |
|
||||
${{ matrix.package }}/package.json
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
---
|
||||
name: CI - Source code docs
|
||||
name: authentik-publish-source-docs
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -17,7 +16,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 120
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup authentik env
|
||||
uses: ./.github/actions/setup
|
||||
- name: generate docs
|
||||
86
.github/workflows/release-branch-off.yml
vendored
86
.github/workflows/release-branch-off.yml
vendored
@@ -1,86 +0,0 @@
|
||||
---
|
||||
name: Release - Branch-off
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
next_version:
|
||||
description: Next major version (for example, if releasing 2042.2, this is 2042.4)
|
||||
required: true
|
||||
type: string
|
||||
|
||||
env:
|
||||
POSTGRES_DB: authentik
|
||||
POSTGRES_USER: authentik
|
||||
POSTGRES_PASSWORD: "EK-5jnKfjrGRm<77"
|
||||
|
||||
jobs:
|
||||
check-inputs:
|
||||
name: Check inputs validity
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: |
|
||||
echo "${{ inputs.next_version }}" | grep -E "^[0-9]{4}\.[0-9]{1,2}$"
|
||||
branch-off:
|
||||
name: Branch-off
|
||||
needs:
|
||||
- check-inputs
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- id: app-token
|
||||
name: Generate app token
|
||||
uses: actions/create-github-app-token@v2
|
||||
with:
|
||||
app-id: ${{ secrets.GH_APP_ID }}
|
||||
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
||||
- name: Checkout main
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
ref: main
|
||||
token: "${{ steps.app-token.outputs.token }}"
|
||||
- name: Setup authentik env
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
dependencies: python
|
||||
- name: Create version branch
|
||||
env:
|
||||
GH_TOKEN: "${{ steps.app-token.outputs.token }}"
|
||||
run: |
|
||||
current_major_version="$(uv version --short | grep -oE "^[0-9]{4}\.[0-9]{1,2}")"
|
||||
git checkout -b "version-${current_major_version}"
|
||||
git push origin "version-${current_major_version}"
|
||||
gh label create "backport/version-${current_major_version}" --description "Add this label to PRs to backport changes to version-${current_major_version}" --color "fbca04"
|
||||
bump-version-pr:
|
||||
name: Open version bump PR
|
||||
needs:
|
||||
- branch-off
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- id: generate_token
|
||||
uses: tibdex/github-app-token@v2
|
||||
with:
|
||||
app_id: ${{ secrets.GH_APP_ID }}
|
||||
private_key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
||||
- name: Checkout main
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
ref: main
|
||||
token: ${{ steps.generate_token.outputs.token }}
|
||||
- name: Setup authentik env
|
||||
uses: ./.github/actions/setup
|
||||
- name: Run migrations
|
||||
run: make migrate
|
||||
- name: Bump version
|
||||
run: "make bump version=${{ inputs.next_version }}.0-rc1"
|
||||
- name: Create pull request
|
||||
uses: peter-evans/create-pull-request@v7
|
||||
with:
|
||||
token: ${{ steps.generate_token.outputs.token }}
|
||||
branch: release-bump-${{ inputs.next_version }}
|
||||
commit-message: "root: bump version to ${{ inputs.next_version }}.0-rc1"
|
||||
title: "root: bump version to ${{ inputs.next_version }}.0-rc1"
|
||||
body: "root: bump version to ${{ inputs.next_version }}.0-rc1"
|
||||
delete-branch: true
|
||||
signoff: true
|
||||
# ID from https://api.github.com/users/authentik-automation[bot]
|
||||
author: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
|
||||
5
.github/workflows/release-next-branch.yml
vendored
5
.github/workflows/release-next-branch.yml
vendored
@@ -1,5 +1,4 @@
|
||||
---
|
||||
name: Release - Update next branch
|
||||
name: authentik-on-release-next-branch
|
||||
|
||||
on:
|
||||
schedule:
|
||||
@@ -16,7 +15,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
environment: internal-production
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: main
|
||||
- run: |
|
||||
|
||||
45
.github/workflows/release-publish.yml
vendored
45
.github/workflows/release-publish.yml
vendored
@@ -1,5 +1,5 @@
|
||||
---
|
||||
name: Release - On publish
|
||||
name: authentik-on-release
|
||||
|
||||
on:
|
||||
release:
|
||||
@@ -7,31 +7,29 @@ on:
|
||||
|
||||
jobs:
|
||||
build-server:
|
||||
uses: ./.github/workflows/_reusable-docker-build.yml
|
||||
uses: ./.github/workflows/_reusable-docker-build.yaml
|
||||
secrets: inherit
|
||||
permissions:
|
||||
contents: read
|
||||
# Needed to upload container images to ghcr.io
|
||||
packages: write
|
||||
# Needed for attestation
|
||||
id-token: write
|
||||
attestations: write
|
||||
with:
|
||||
image_name: ghcr.io/goauthentik/server,authentik/server
|
||||
image_name: ghcr.io/goauthentik/server,beryju/authentik
|
||||
release: true
|
||||
registry_dockerhub: true
|
||||
registry_ghcr: true
|
||||
build-docs:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
# Needed to upload container images to ghcr.io
|
||||
packages: write
|
||||
# Needed for attestation
|
||||
id-token: write
|
||||
attestations: write
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3.6.0
|
||||
- name: Set up Docker Buildx
|
||||
@@ -40,7 +38,7 @@ jobs:
|
||||
uses: ./.github/actions/docker-push-variables
|
||||
id: ev
|
||||
env:
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_CORP_USERNAME }}
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
with:
|
||||
image-name: ghcr.io/goauthentik/docs
|
||||
- name: Login to GitHub Container Registry
|
||||
@@ -58,7 +56,7 @@ jobs:
|
||||
push: true
|
||||
platforms: linux/amd64,linux/arm64
|
||||
context: .
|
||||
- uses: actions/attest-build-provenance@v3
|
||||
- uses: actions/attest-build-provenance@v2
|
||||
id: attest
|
||||
if: true
|
||||
with:
|
||||
@@ -68,7 +66,6 @@ jobs:
|
||||
build-outpost:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
# Needed to upload container images to ghcr.io
|
||||
packages: write
|
||||
# Needed for attestation
|
||||
@@ -83,8 +80,8 @@ jobs:
|
||||
- radius
|
||||
- rac
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-go@v6
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: "go.mod"
|
||||
- name: Set up QEMU
|
||||
@@ -95,9 +92,9 @@ jobs:
|
||||
uses: ./.github/actions/docker-push-variables
|
||||
id: ev
|
||||
env:
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_CORP_USERNAME }}
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
with:
|
||||
image-name: ghcr.io/goauthentik/${{ matrix.type }},authentik/${{ matrix.type }}
|
||||
image-name: ghcr.io/goauthentik/${{ matrix.type }},beryju/authentik-${{ matrix.type }}
|
||||
- name: make empty clients
|
||||
run: |
|
||||
mkdir -p ./gen-ts-api
|
||||
@@ -105,8 +102,8 @@ jobs:
|
||||
- name: Docker Login Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_CORP_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_CORP_PASSWORD }}
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
@@ -124,7 +121,7 @@ jobs:
|
||||
file: ${{ matrix.type }}.Dockerfile
|
||||
platforms: linux/amd64,linux/arm64
|
||||
context: .
|
||||
- uses: actions/attest-build-provenance@v3
|
||||
- uses: actions/attest-build-provenance@v2
|
||||
id: attest
|
||||
with:
|
||||
subject-name: ${{ steps.ev.outputs.attestImageNames }}
|
||||
@@ -146,11 +143,11 @@ jobs:
|
||||
goos: [linux, darwin]
|
||||
goarch: [amd64, arm64]
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-go@v6
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: "go.mod"
|
||||
- uses: actions/setup-node@v5
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: web/package.json
|
||||
cache: "npm"
|
||||
@@ -186,8 +183,8 @@ jobs:
|
||||
AWS_REGION: eu-central-1
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: aws-actions/configure-aws-credentials@v5
|
||||
- uses: actions/checkout@v4
|
||||
- uses: aws-actions/configure-aws-credentials@v4
|
||||
with:
|
||||
role-to-assume: "arn:aws:iam::016170277896:role/github_goauthentik_authentik"
|
||||
aws-region: ${{ env.AWS_REGION }}
|
||||
@@ -202,7 +199,7 @@ jobs:
|
||||
- build-outpost-binary
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v4
|
||||
- name: Run test suite in final docker images
|
||||
run: |
|
||||
echo "PG_PASS=$(openssl rand 32 | base64 -w 0)" >> .env
|
||||
@@ -218,12 +215,12 @@ jobs:
|
||||
- build-outpost-binary
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v4
|
||||
- name: prepare variables
|
||||
uses: ./.github/actions/docker-push-variables
|
||||
id: ev
|
||||
env:
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_CORP_USERNAME }}
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
with:
|
||||
image-name: ghcr.io/goauthentik/server
|
||||
- name: Get static files from docker image
|
||||
|
||||
206
.github/workflows/release-tag.yml
vendored
206
.github/workflows/release-tag.yml
vendored
@@ -1,195 +1,39 @@
|
||||
---
|
||||
name: Release - Tag new version
|
||||
name: authentik-on-tag
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: Version
|
||||
required: true
|
||||
type: string
|
||||
release_reason:
|
||||
description: Release reason
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- bugfix
|
||||
- feature
|
||||
- security
|
||||
- other
|
||||
- prerelease
|
||||
|
||||
env:
|
||||
POSTGRES_DB: authentik
|
||||
POSTGRES_USER: authentik
|
||||
POSTGRES_PASSWORD: "EK-5jnKfjrGRm<77"
|
||||
push:
|
||||
tags:
|
||||
- "version/*"
|
||||
|
||||
jobs:
|
||||
check-inputs:
|
||||
name: Check inputs validity
|
||||
build:
|
||||
name: Create Release from Tag
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- id: check
|
||||
- uses: actions/checkout@v4
|
||||
- name: Pre-release test
|
||||
run: |
|
||||
echo "${{ inputs.version }}" | grep -E '^[0-9]{4}\.(0?[1-9]|1[0-2])\.[0-9]+(-rc[0-9]+)?$'
|
||||
echo "major_version=${{ inputs.version }}" | grep -oE "^major_version=[0-9]{4}\.[0-9]{1,2}" >> "$GITHUB_OUTPUT"
|
||||
- id: changelog-url
|
||||
run: |
|
||||
if [ "${{ inputs.release_reason }}" = "feature" ] || [ "${{ inputs.release_reason }}" = "prerelease" ]; then
|
||||
changelog_url="https://docs.goauthentik.io/docs/releases/${{ steps.check.outputs.major_version }}"
|
||||
else
|
||||
changelog_url="https://docs.goauthentik.io/docs/releases/${{ steps.check.outputs.major_version }}#fixed-in-$(echo -n ${{ inputs.version }} | sed 's/\.//g')"
|
||||
fi
|
||||
echo "changelog_url=${changelog_url}" >> "$GITHUB_OUTPUT"
|
||||
outputs:
|
||||
major_version: "${{ steps.check.outputs.major_version }}"
|
||||
changelog_url: "${{ steps.changelog-url.outputs.changelog_url }}"
|
||||
test:
|
||||
name: Pre-release test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- run: make test-docker
|
||||
bump-authentik:
|
||||
name: Bump authentik version
|
||||
needs:
|
||||
- check-inputs
|
||||
- test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- id: app-token
|
||||
name: Generate app token
|
||||
uses: actions/create-github-app-token@v2
|
||||
make test-docker
|
||||
- id: generate_token
|
||||
uses: tibdex/github-app-token@v2
|
||||
with:
|
||||
app-id: ${{ secrets.GH_APP_ID }}
|
||||
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
||||
- id: get-user-id
|
||||
name: Get GitHub app user ID
|
||||
run: echo "user-id=$(gh api "/users/${{ steps.app-token.outputs.app-slug }}[bot]" --jq .id)" >> "$GITHUB_OUTPUT"
|
||||
app_id: ${{ secrets.GH_APP_ID }}
|
||||
private_key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
||||
- name: prepare variables
|
||||
uses: ./.github/actions/docker-push-variables
|
||||
id: ev
|
||||
env:
|
||||
GH_TOKEN: "${{ steps.app-token.outputs.token }}"
|
||||
- uses: actions/checkout@v5
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
with:
|
||||
ref: "version-${{ needs.check-inputs.outputs.major_version }}"
|
||||
token: "${{ steps.app-token.outputs.token }}"
|
||||
- name: Setup authentik env
|
||||
uses: ./.github/actions/setup
|
||||
- name: Run migrations
|
||||
run: make migrate
|
||||
- name: Bump version
|
||||
run: "make bump version=${{ inputs.version }}"
|
||||
- name: Commit and push
|
||||
run: |
|
||||
# ID from https://api.github.com/users/authentik-automation[bot]
|
||||
git config --global user.name '${{ steps.app-token.outputs.app-slug }}[bot]'
|
||||
git config --global user.email '${{ steps.get-user-id.outputs.user-id }}+${{ steps.app-token.outputs.app-slug }}[bot]@users.noreply.github.com'
|
||||
git commit -a -m "release: ${{ inputs.version }}" --allow-empty
|
||||
git tag "version/${{ inputs.version }}" HEAD -m "version/${{ inputs.version }}"
|
||||
git push --follow-tags
|
||||
image-name: ghcr.io/goauthentik/server
|
||||
- name: Create Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
id: create_release
|
||||
uses: actions/create-release@v1.1.4
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
|
||||
with:
|
||||
token: "${{ steps.app-token.outputs.token }}"
|
||||
tag_name: "version/${{ inputs.version }}"
|
||||
name: Release ${{ inputs.version }}
|
||||
tag_name: ${{ github.ref }}
|
||||
release_name: Release ${{ steps.ev.outputs.version }}
|
||||
draft: true
|
||||
prerelease: ${{ inputs.release_reason == 'prerelease' }}
|
||||
generate_release_notes: true
|
||||
body: |
|
||||
See ${{ needs.check-inputs.outputs.changelog_url }}
|
||||
bump-helm:
|
||||
name: Bump Helm version
|
||||
if: ${{ inputs.release_reason != 'prerelease' }}
|
||||
needs:
|
||||
- bump-authentik
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- id: app-token
|
||||
name: Generate app token
|
||||
uses: actions/create-github-app-token@v2
|
||||
with:
|
||||
app-id: ${{ secrets.GH_APP_ID }}
|
||||
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
||||
repositories: helm
|
||||
- id: get-user-id
|
||||
name: Get GitHub app user ID
|
||||
run: echo "user-id=$(gh api "/users/${{ steps.app-token.outputs.app-slug }}[bot]" --jq .id)" >> "$GITHUB_OUTPUT"
|
||||
env:
|
||||
GH_TOKEN: "${{ steps.app-token.outputs.token }}"
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
repository: "${{ github.repository_owner }}/helm"
|
||||
token: "${{ steps.app-token.outputs.token }}"
|
||||
- name: Bump version
|
||||
run: |
|
||||
sed -i 's/^version: .*/version: ${{ inputs.version }}/' charts/authentik/Chart.yaml
|
||||
sed -i 's/^appVersion: .*/appVersion: ${{ inputs.version }}/' charts/authentik/Chart.yaml
|
||||
sed -i 's/upgrade to authentik .*/upgrade to authentik ${{ inputs.version }}/' charts/authentik/Chart.yaml
|
||||
sed -E -i 's/[0-9]{4}\.[0-9]{1,2}\.[0-9]+$/${{ inputs.version }}/' charts/authentik/Chart.yaml
|
||||
./scripts/helm-docs.sh
|
||||
- name: Create pull request
|
||||
uses: peter-evans/create-pull-request@v7
|
||||
with:
|
||||
token: "${{ steps.app-token.outputs.token }}"
|
||||
branch: bump-${{ inputs.version }}
|
||||
commit-message: "charts/authentik: bump to ${{ inputs.version }}"
|
||||
title: "charts/authentik: bump to ${{ inputs.version }}"
|
||||
body: "charts/authentik: bump to ${{ inputs.version }}"
|
||||
delete-branch: true
|
||||
signoff: true
|
||||
author: "${{ steps.app-token.outputs.app-slug }}[bot] <${{ steps.get-user-id.outputs.user-id }}+${{ steps.app-token.outputs.app-slug }}[bot]@users.noreply.github.com>"
|
||||
bump-version:
|
||||
name: Bump version repository
|
||||
if: ${{ inputs.release_reason != 'prerelease' }}
|
||||
needs:
|
||||
- check-inputs
|
||||
- bump-authentik
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- id: app-token
|
||||
name: Generate app token
|
||||
uses: actions/create-github-app-token@v2
|
||||
with:
|
||||
app-id: ${{ secrets.GH_APP_ID }}
|
||||
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
||||
repositories: version
|
||||
- id: get-user-id
|
||||
name: Get GitHub app user ID
|
||||
run: echo "user-id=$(gh api "/users/${{ steps.app-token.outputs.app-slug }}[bot]" --jq .id)" >> "$GITHUB_OUTPUT"
|
||||
env:
|
||||
GH_TOKEN: "${{ steps.app-token.outputs.token }}"
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
repository: "${{ github.repository_owner }}/version"
|
||||
token: "${{ steps.app-token.outputs.token }}"
|
||||
- name: Bump version
|
||||
if: "${{ inputs.release_reason == 'feature' }}"
|
||||
run: |
|
||||
changelog_url="https://docs.goauthentik.io/docs/releases/${{ needs.check-inputs.outputs.major_version }}"
|
||||
jq \
|
||||
--arg version "${{ inputs.version }}" \
|
||||
--arg changelog "See ${changelog_url}" \
|
||||
--arg changelog_url "${changelog_url}" \
|
||||
'.stable.version = $version | .stable.changelog = $changelog | .stable.changelog_url = $changelog_url' version.json > version.new.json
|
||||
mv version.new.json version.json
|
||||
- name: Bump version
|
||||
if: "${{ inputs.release_reason != 'feature' }}"
|
||||
run: |
|
||||
changelog_url="https://docs.goauthentik.io/docs/releases/${{ needs.check-inputs.outputs.major_version }}#fixed-in-$(echo -n ${{ inputs.version}} | sed 's/\.//g')"
|
||||
jq \
|
||||
--arg version "${{ inputs.version }}" \
|
||||
--arg changelog "See ${changelog_url}" \
|
||||
--arg changelog_url "${changelog_url}" \
|
||||
'.stable.version = $version | .stable.changelog = $changelog | .stable.changelog_url = $changelog_url' version.json > version.new.json
|
||||
mv version.new.json version.json
|
||||
- name: Create pull request
|
||||
uses: peter-evans/create-pull-request@v7
|
||||
with:
|
||||
token: "${{ steps.app-token.outputs.token }}"
|
||||
branch: bump-${{ inputs.version }}
|
||||
commit-message: "version: bump to ${{ inputs.version }}"
|
||||
title: "version: bump to ${{ inputs.version }}"
|
||||
body: "version: bump to ${{ inputs.version }}"
|
||||
delete-branch: true
|
||||
signoff: true
|
||||
author: "${{ steps.app-token.outputs.app-slug }}[bot] <${{ steps.get-user-id.outputs.user-id }}+${{ steps.app-token.outputs.app-slug }}[bot]@users.noreply.github.com>"
|
||||
prerelease: ${{ steps.ev.outputs.prerelease == 'true' }}
|
||||
|
||||
5
.github/workflows/repo-mirror-cleanup.yml
vendored
5
.github/workflows/repo-mirror-cleanup.yml
vendored
@@ -1,5 +1,4 @@
|
||||
---
|
||||
name: Repo - Cleanup internal mirror
|
||||
name: "authentik-repo-mirror-cleanup"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
@@ -9,7 +8,7 @@ jobs:
|
||||
if: ${{ github.repository != 'goauthentik/authentik-internal' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- if: ${{ env.MIRROR_KEY != '' }}
|
||||
|
||||
5
.github/workflows/repo-mirror.yml
vendored
5
.github/workflows/repo-mirror.yml
vendored
@@ -1,5 +1,4 @@
|
||||
---
|
||||
name: Repo - Mirror to internal
|
||||
name: "authentik-repo-mirror"
|
||||
|
||||
on: [push, delete]
|
||||
|
||||
@@ -8,7 +7,7 @@ jobs:
|
||||
if: ${{ github.repository != 'goauthentik/authentik-internal' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- if: ${{ env.MIRROR_KEY != '' }}
|
||||
|
||||
5
.github/workflows/repo-stale.yml
vendored
5
.github/workflows/repo-stale.yml
vendored
@@ -1,5 +1,4 @@
|
||||
---
|
||||
name: Repo - Mark and close stale issues
|
||||
name: "authentik-repo-stale"
|
||||
|
||||
on:
|
||||
schedule:
|
||||
@@ -20,7 +19,7 @@ jobs:
|
||||
with:
|
||||
app_id: ${{ secrets.GH_APP_ID }}
|
||||
private_key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
||||
- uses: actions/stale@v10
|
||||
- uses: actions/stale@v9
|
||||
with:
|
||||
repo-token: ${{ steps.generate_token.outputs.token }}
|
||||
days-before-stale: 60
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
---
|
||||
name: QA - Semgrep
|
||||
|
||||
name: authentik-semgrep
|
||||
on:
|
||||
workflow_dispatch: {}
|
||||
pull_request: {}
|
||||
@@ -9,11 +7,10 @@ on:
|
||||
- main
|
||||
- master
|
||||
paths:
|
||||
- .github/workflows/qa-semgrep.yml
|
||||
- .github/workflows/semgrep.yml
|
||||
schedule:
|
||||
# random HH:MM to avoid a load spike on GitHub Actions at 00:00
|
||||
- cron: '12 15 * * *'
|
||||
|
||||
jobs:
|
||||
semgrep:
|
||||
name: semgrep/ci
|
||||
@@ -26,5 +23,5 @@ jobs:
|
||||
image: semgrep/semgrep
|
||||
if: (github.actor != 'dependabot[bot]')
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v4
|
||||
- run: semgrep ci
|
||||
3
.github/workflows/translation-advice.yml
vendored
3
.github/workflows/translation-advice.yml
vendored
@@ -1,5 +1,4 @@
|
||||
---
|
||||
name: Translation - Post advice
|
||||
name: authentik-translation-advice
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
---
|
||||
name: Translation - Extract and compile
|
||||
|
||||
name: authentik-translate-extract-compile
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 0 * * *" # every day at midnight
|
||||
@@ -26,11 +25,11 @@ jobs:
|
||||
with:
|
||||
app_id: ${{ secrets.GH_APP_ID }}
|
||||
private_key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v4
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
with:
|
||||
token: ${{ steps.generate_token.outputs.token }}
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v4
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
- name: Setup authentik env
|
||||
uses: ./.github/actions/setup
|
||||
|
||||
5
.github/workflows/translation-rename.yml
vendored
5
.github/workflows/translation-rename.yml
vendored
@@ -1,7 +1,6 @@
|
||||
---
|
||||
# Rename transifex pull requests to have a correct naming
|
||||
# Also enables auto squash-merge
|
||||
name: Translation - Auto-rename Transifex PRs
|
||||
name: authentik-translation-transifex-rename
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
@@ -16,7 +15,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event.pull_request.user.login == 'transifex-integration[bot]'}}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v4
|
||||
- id: generate_token
|
||||
uses: tibdex/github-app-token@v2
|
||||
with:
|
||||
|
||||
12
.vscode/settings.json
vendored
12
.vscode/settings.json
vendored
@@ -1,16 +1,4 @@
|
||||
{
|
||||
"[css]": {
|
||||
"editor.minimap.markSectionHeaderRegex": "#\\bregion\\s*(?<separator>-?)\\s*(?<label>.*)\\*/$"
|
||||
},
|
||||
"[makefile]": {
|
||||
"editor.minimap.markSectionHeaderRegex": "^#{25}\n##\\s\\s*(?<separator>-?)\\s*(?<label>[^\n]*)\n#{25}$"
|
||||
},
|
||||
"[dockerfile]": {
|
||||
"editor.minimap.markSectionHeaderRegex": "\\bStage\\s*\\d:(?<separator>-?)\\s*(?<label>.*)$"
|
||||
},
|
||||
"[jsonc]": {
|
||||
"editor.minimap.markSectionHeaderRegex": "#\\bregion\\s*(?<separator>-?)\\s*(?<label>.*)$"
|
||||
},
|
||||
"todo-tree.tree.showCountsInTree": true,
|
||||
"todo-tree.tree.showBadges": true,
|
||||
"yaml.customTags": [
|
||||
|
||||
@@ -33,12 +33,17 @@ packages/prettier-config @goauthentik/frontend
|
||||
packages/tsconfig @goauthentik/frontend
|
||||
# Web
|
||||
web/ @goauthentik/frontend
|
||||
tests/wdio/ @goauthentik/frontend
|
||||
# Locale
|
||||
locale/ @goauthentik/backend @goauthentik/frontend
|
||||
web/xliff/ @goauthentik/backend @goauthentik/frontend
|
||||
# Docs
|
||||
# Docs & Website
|
||||
docs/ @goauthentik/docs
|
||||
# TODO Remove after moving website to docs
|
||||
website/ @goauthentik/docs
|
||||
CODE_OF_CONDUCT.md @goauthentik/docs
|
||||
# Security
|
||||
SECURITY.md @goauthentik/security @goauthentik/docs
|
||||
# TODO Remove after moving website to docs
|
||||
website/security/ @goauthentik/security @goauthentik/docs
|
||||
docs/security/ @goauthentik/security @goauthentik/docs
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
# Contributing to authentik
|
||||
|
||||
Thanks for your interest in contributing! Please see our [contributing guide](https://docs.goauthentik.io/docs/developer-docs/?utm_source=github) for more information.
|
||||
|
||||
1
CONTRIBUTING.md
Symbolic link
1
CONTRIBUTING.md
Symbolic link
@@ -0,0 +1 @@
|
||||
website/docs/developer-docs/index.md
|
||||
@@ -26,7 +26,7 @@ RUN npm run build && \
|
||||
npm run build:sfe
|
||||
|
||||
# Stage 2: Build go proxy
|
||||
FROM --platform=${BUILDPLATFORM} docker.io/library/golang:1.25-bookworm AS go-builder
|
||||
FROM --platform=${BUILDPLATFORM} docker.io/library/golang:1.24-bookworm AS go-builder
|
||||
|
||||
ARG TARGETOS
|
||||
ARG TARGETARCH
|
||||
@@ -76,9 +76,9 @@ 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 4: Download uv
|
||||
FROM ghcr.io/astral-sh/uv:0.8.22 AS uv
|
||||
FROM ghcr.io/astral-sh/uv:0.8.4 AS uv
|
||||
# Stage 5: Base python image
|
||||
FROM ghcr.io/goauthentik/fips-python:3.13.7-slim-trixie-fips AS python-base
|
||||
FROM ghcr.io/goauthentik/fips-python:3.13.5-slim-bookworm-fips AS python-base
|
||||
|
||||
ENV VENV_PATH="/ak-root/.venv" \
|
||||
PATH="/lifecycle:/ak-root/.venv/bin:$PATH" \
|
||||
@@ -175,7 +175,6 @@ COPY ./lifecycle/ /lifecycle
|
||||
COPY ./authentik/sources/kerberos/krb5.conf /etc/krb5.conf
|
||||
COPY --from=go-builder /go/authentik /bin/authentik
|
||||
COPY ./packages/ /ak-root/packages
|
||||
RUN ln -s /ak-root/packages /packages
|
||||
COPY --from=python-deps /ak-root/.venv /ak-root/.venv
|
||||
COPY --from=node-builder /work/web/dist/ /web/dist/
|
||||
COPY --from=node-builder /work/web/authentik/ /web/authentik/
|
||||
|
||||
95
Makefile
95
Makefile
@@ -16,26 +16,8 @@ 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)
|
||||
redis_db := $(shell uv run python -m authentik.lib.config redis.db 2>/dev/null)
|
||||
|
||||
UNAME := $(shell uname)
|
||||
|
||||
# For macOS users, add the libxml2 installed from brew libxmlsec1 to the build path
|
||||
# to prevent SAML-related tests from failing and ensure correct pip dependency compilation
|
||||
ifeq ($(UNAME), Darwin)
|
||||
# Only add for brew users who installed libxmlsec1
|
||||
BREW_EXISTS := $(shell command -v brew 2> /dev/null)
|
||||
ifdef BREW_EXISTS
|
||||
LIBXML2_EXISTS := $(shell brew list libxml2 2> /dev/null)
|
||||
ifdef LIBXML2_EXISTS
|
||||
BREW_LDFLAGS := -L$(shell brew --prefix libxml2)/lib $(LDFLAGS)
|
||||
BREW_CPPFLAGS := -I$(shell brew --prefix libxml2)/include $(CPPFLAGS)
|
||||
BREW_PKG_CONFIG_PATH := $(shell brew --prefix libxml2)/lib/pkgconfig:$(PKG_CONFIG_PATH)
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
all: lint-fix lint gen web test ## Lint, build, and test everything
|
||||
all: lint-fix lint test gen web ## Lint, build, and test everything
|
||||
|
||||
HELP_WIDTH := $(shell grep -h '^[a-z][^ ]*:.*\#\#' $(MAKEFILE_LIST) 2>/dev/null | \
|
||||
cut -d':' -f1 | awk '{printf "%d\n", length}' | sort -rn | head -1)
|
||||
@@ -67,14 +49,7 @@ lint: ## Lint the python and golang sources
|
||||
golangci-lint run -v
|
||||
|
||||
core-install:
|
||||
ifdef LIBXML2_EXISTS
|
||||
# Clear cache to ensure fresh compilation
|
||||
uv cache clean
|
||||
# Force compilation from source for lxml and xmlsec with correct environment
|
||||
LDFLAGS="$(BREW_LDFLAGS)" CPPFLAGS="$(BREW_CPPFLAGS)" PKG_CONFIG_PATH="$(BREW_PKG_CONFIG_PATH)" uv sync --frozen --reinstall-package lxml --reinstall-package xmlsec --no-binary-package lxml --no-binary-package xmlsec
|
||||
else
|
||||
uv sync --frozen
|
||||
endif
|
||||
|
||||
migrate: ## Run the Authentik Django server's migrations
|
||||
uv run python -m lifecycle.migrate
|
||||
@@ -82,7 +57,7 @@ migrate: ## Run the Authentik Django server's migrations
|
||||
i18n-extract: core-i18n-extract web-i18n-extract ## Extract strings that require translation into files to send to a translation service
|
||||
|
||||
aws-cfn:
|
||||
cd lifecycle/aws && npm i && uv run npm run aws-cfn
|
||||
cd lifecycle/aws && npm run aws-cfn
|
||||
|
||||
run-server: ## Run the main authentik server process
|
||||
uv run ak server
|
||||
@@ -104,10 +79,10 @@ core-i18n-extract:
|
||||
install: node-install docs-install core-install ## Install all requires dependencies for `node`, `docs` and `core`
|
||||
|
||||
dev-drop-db:
|
||||
dropdb -U ${pg_user} -h ${pg_host} ${pg_name} || true
|
||||
dropdb -U ${pg_user} -h ${pg_host} ${pg_name}
|
||||
# Also remove the test-db if it exists
|
||||
dropdb -U ${pg_user} -h ${pg_host} test_${pg_name} || true
|
||||
redis-cli -n ${redis_db} flushall
|
||||
redis-cli -n 0 flushall
|
||||
|
||||
dev-create-db:
|
||||
createdb -U ${pg_user} -h ${pg_host} ${pg_name}
|
||||
@@ -118,17 +93,6 @@ update-test-mmdb: ## Update test GeoIP and ASN Databases
|
||||
curl -L https://raw.githubusercontent.com/maxmind/MaxMind-DB/refs/heads/main/test-data/GeoLite2-ASN-Test.mmdb -o ${PWD}/tests/GeoLite2-ASN-Test.mmdb
|
||||
curl -L https://raw.githubusercontent.com/maxmind/MaxMind-DB/refs/heads/main/test-data/GeoLite2-City-Test.mmdb -o ${PWD}/tests/GeoLite2-City-Test.mmdb
|
||||
|
||||
bump: ## Bump authentik version. Usage: make bump version=20xx.xx.xx
|
||||
ifndef version
|
||||
$(error Usage: make bump version=20xx.xx.xx )
|
||||
endif
|
||||
sed -i 's/^version = ".*"/version = "$(version)"/' pyproject.toml
|
||||
sed -i 's/^VERSION = ".*"/VERSION = "$(version)"/' authentik/__init__.py
|
||||
$(MAKE) gen-build gen-compose aws-cfn
|
||||
npm version --no-git-tag-version --allow-same-version $(version)
|
||||
cd ${PWD}/web && npm version --no-git-tag-version --allow-same-version $(version)
|
||||
echo -n $(version) > ${PWD}/internal/constants/VERSION
|
||||
|
||||
#########################
|
||||
## API Schema
|
||||
#########################
|
||||
@@ -143,9 +107,6 @@ gen-build: ## Extract the schema from the database
|
||||
AUTHENTIK_OUTPOSTS__DISABLE_EMBEDDED_OUTPOST=true \
|
||||
uv run ak spectacular --file schema.yml
|
||||
|
||||
gen-compose:
|
||||
uv run scripts/generate_docker_compose.py
|
||||
|
||||
gen-changelog: ## (Release) generate the changelog based from the commits since the last tag
|
||||
git log --pretty=format:" - %s" $(shell git describe --tags $(shell git rev-list --tags --max-count=1))...$(shell git branch --show-current) | sort > changelog.md
|
||||
npx prettier --write changelog.md
|
||||
@@ -184,7 +145,7 @@ gen-client-ts: gen-clean-ts ## Build and install the authentik API for Typescri
|
||||
docker run \
|
||||
--rm -v ${PWD}:/local \
|
||||
--user ${UID}:${GID} \
|
||||
docker.io/openapitools/openapi-generator-cli:v7.15.0 generate \
|
||||
docker.io/openapitools/openapi-generator-cli:v7.11.0 generate \
|
||||
-i /local/schema.yml \
|
||||
-g typescript-fetch \
|
||||
-o /local/${GEN_API_TS} \
|
||||
@@ -193,7 +154,6 @@ gen-client-ts: gen-clean-ts ## Build and install the authentik API for Typescri
|
||||
--git-repo-id authentik \
|
||||
--git-user-id goauthentik
|
||||
|
||||
cd ${PWD}/${GEN_API_TS} && npm i
|
||||
cd ${PWD}/${GEN_API_TS} && npm link
|
||||
cd ${PWD}/web && npm link @goauthentik/api
|
||||
|
||||
@@ -201,7 +161,7 @@ gen-client-py: gen-clean-py ## Build and install the authentik API for Python
|
||||
docker run \
|
||||
--rm -v ${PWD}:/local \
|
||||
--user ${UID}:${GID} \
|
||||
docker.io/openapitools/openapi-generator-cli:v7.15.0 generate \
|
||||
docker.io/openapitools/openapi-generator-cli:v7.11.0 generate \
|
||||
-i /local/schema.yml \
|
||||
-g python \
|
||||
-o /local/${GEN_API_PY} \
|
||||
@@ -239,30 +199,34 @@ node-install: ## Install the necessary libraries to build Node.js packages
|
||||
#########################
|
||||
|
||||
web-build: node-install ## Build the Authentik UI
|
||||
npm run --prefix web build
|
||||
cd web && npm run build
|
||||
|
||||
web: web-lint-fix web-lint web-check-compile ## Automatically fix formatting issues in the Authentik UI source code, lint the code, and compile it
|
||||
|
||||
web-test: ## Run tests for the Authentik UI
|
||||
npm run --prefix web test
|
||||
cd web && npm run test
|
||||
|
||||
web-watch: ## Build and watch the Authentik UI for changes, updating automatically
|
||||
npm run --prefix web watch
|
||||
rm -rf web/dist/
|
||||
mkdir web/dist/
|
||||
touch web/dist/.gitkeep
|
||||
cd web && npm run watch
|
||||
|
||||
web-storybook-watch: ## Build and run the storybook documentation server
|
||||
npm run --prefix web storybook
|
||||
cd web && npm run storybook
|
||||
|
||||
web-lint-fix:
|
||||
npm run --prefix web prettier
|
||||
cd web && npm run prettier
|
||||
|
||||
web-lint:
|
||||
npm run --prefix web lint
|
||||
npm run --prefix web lit-analyse
|
||||
cd web && npm run lint
|
||||
cd web && npm run lit-analyse
|
||||
|
||||
web-check-compile:
|
||||
npm run --prefix web tsc
|
||||
cd web && npm run tsc
|
||||
|
||||
web-i18n-extract:
|
||||
npm run --prefix web extract-locales
|
||||
cd web && npm run extract-locales
|
||||
|
||||
#########################
|
||||
## Docs
|
||||
@@ -274,31 +238,31 @@ docs-install:
|
||||
npm ci --prefix website
|
||||
|
||||
docs-lint-fix: lint-codespell
|
||||
npm run --prefix website prettier
|
||||
npm run prettier --prefix website
|
||||
|
||||
docs-build:
|
||||
npm run --prefix website build
|
||||
npm run build --prefix website
|
||||
|
||||
docs-watch: ## Build and watch the topics documentation
|
||||
npm run --prefix website start
|
||||
npm run start --prefix website
|
||||
|
||||
integrations: docs-lint-fix integrations-build ## Fix formatting issues in the integrations source code, lint the code, and compile it
|
||||
|
||||
integrations-build:
|
||||
npm run --prefix website -w integrations build
|
||||
npm run build --prefix website -w integrations
|
||||
|
||||
integrations-watch: ## Build and watch the Integrations documentation
|
||||
npm run --prefix website -w integrations start
|
||||
npm run start --prefix website -w integrations
|
||||
|
||||
docs-api-build:
|
||||
npm run --prefix website -w api build
|
||||
npm run build --prefix website -w api
|
||||
|
||||
docs-api-watch: ## Build and watch the API documentation
|
||||
npm run --prefix website -w api build:api
|
||||
npm run --prefix website -w api start
|
||||
npm run build:api --prefix website -w api
|
||||
npm run start --prefix website -w api
|
||||
|
||||
docs-api-clean: ## Clean generated API documentation
|
||||
npm run --prefix website -w api build:api:clean
|
||||
npm run build:api:clean --prefix website -w api
|
||||
|
||||
#########################
|
||||
## Docker
|
||||
@@ -321,9 +285,6 @@ ci--meta-debug:
|
||||
python -V
|
||||
node --version
|
||||
|
||||
ci-mypy: ci--meta-debug
|
||||
uv run mypy --strict $(PY_SOURCES)
|
||||
|
||||
ci-black: ci--meta-debug
|
||||
uv run black --check $(PY_SOURCES)
|
||||
|
||||
|
||||
30
README.md
30
README.md
@@ -9,21 +9,21 @@
|
||||
[](https://github.com/goauthentik/authentik/actions/workflows/ci-outpost.yml)
|
||||
[](https://github.com/goauthentik/authentik/actions/workflows/ci-web.yml)
|
||||
[](https://codecov.io/gh/goauthentik/authentik)
|
||||

|
||||

|
||||

|
||||
[](https://www.transifex.com/authentik/authentik/)
|
||||
|
||||
## What is authentik?
|
||||
|
||||
authentik is an open-source Identity Provider (IdP) for modern SSO. It supports SAML, OAuth2/OIDC, LDAP, RADIUS, and more, designed for self-hosting from small labs to large production clusters.
|
||||
authentik is an open-source Identity Provider that emphasizes flexibility and versatility, with support for a wide set of protocols.
|
||||
|
||||
Our [enterprise offering](https://goauthentik.io/pricing) is available for organizations to securely replace existing IdPs such as Okta, Auth0, Entra ID, and Ping Identity for robust, large-scale identity management.
|
||||
Our [enterprise offer](https://goauthentik.io/pricing) can also be used as a self-hosted replacement for large-scale deployments of Okta/Auth0, Entra ID, Ping Identity, or other legacy IdPs for employees and B2B2C use.
|
||||
|
||||
## Installation
|
||||
|
||||
- Docker Compose: recommended for small/test setups. See the [documentation](https://docs.goauthentik.io/docs/install-config/install/docker-compose/).
|
||||
- Kubernetes (Helm Chart): recommended for larger setups. See the [documentation](https://docs.goauthentik.io/docs/install-config/install/kubernetes/) and the Helm chart [repository](https://github.com/goauthentik/helm).
|
||||
- AWS CloudFormation: deploy on AWS using our official templates. See the [documentation](https://docs.goauthentik.io/docs/install-config/install/aws/).
|
||||
- DigitalOcean Marketplace: one-click deployment via the official Marketplace app. See the [app listing](https://marketplace.digitalocean.com/apps/authentik).
|
||||
For small/test setups it is recommended to use Docker Compose; refer to the [documentation](https://goauthentik.io/docs/installation/docker-compose/?utm_source=github).
|
||||
|
||||
For bigger setups, there is a Helm Chart [here](https://github.com/goauthentik/helm). This is documented [here](https://goauthentik.io/docs/installation/kubernetes/?utm_source=github).
|
||||
|
||||
## Screenshots
|
||||
|
||||
@@ -32,20 +32,14 @@ Our [enterprise offering](https://goauthentik.io/pricing) is available for organ
|
||||
|  |  |
|
||||
|  |  |
|
||||
|
||||
## Development and contributions
|
||||
## Development
|
||||
|
||||
See the [Developer Documentation](https://docs.goauthentik.io/docs/developer-docs/) for information about setting up local build environments, testing your contributions, and our contribution process.
|
||||
See [Developer Documentation](https://docs.goauthentik.io/docs/developer-docs/?utm_source=github)
|
||||
|
||||
## Security
|
||||
|
||||
Please see [SECURITY.md](SECURITY.md).
|
||||
See [SECURITY.md](SECURITY.md)
|
||||
|
||||
## Adoption
|
||||
## Adoption and Contributions
|
||||
|
||||
Using authentik? We'd love to hear your story and feature your logo. Email us at [hello@goauthentik.io](mailto:hello@goauthentik.io) or open a GitHub Issue/PR!
|
||||
|
||||
## License
|
||||
|
||||
[](LICENSE)
|
||||
[](website/LICENSE)
|
||||
[](authentik/enterprise/LICENSE)
|
||||
Your organization uses authentik? We'd love to add your logo to the readme and our website! Email us @ hello@goauthentik.io or open a GitHub Issue/PR! For more information on how to contribute to authentik, please refer to our [contribution guide](https://docs.goauthentik.io/docs/developer-docs?utm_source=github).
|
||||
|
||||
25
SECURITY.md
25
SECURITY.md
@@ -20,33 +20,12 @@ Even if the issue is not a CVE, we still greatly appreciate your help in hardeni
|
||||
|
||||
| Version | Supported |
|
||||
| --------- | --------- |
|
||||
| 2025.4.x | ✅ |
|
||||
| 2025.6.x | ✅ |
|
||||
| 2025.8.x | ✅ |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
If you discover a potential vulnerability, please report it responsibly through one of the following channels:
|
||||
|
||||
- **Email**: [security@goauthentik.io](mailto:security@goauthentik.io)
|
||||
- **GitHub**: Submit a private security advisory via our [repository’s advisory portal](https://github.com/goauthentik/authentik/security/advisories/new)
|
||||
|
||||
When submitting a report, please include as much detail as possible, such as:
|
||||
|
||||
- **Affected version(s)**: The version of authentik where the issue was identified.
|
||||
- **Steps to reproduce**: A clear description or proof of concept to help us verify the issue.
|
||||
- **Impact assessment**: How the vulnerability could be exploited and its potential effect.
|
||||
- **Additional information**: Logs, configuration details (if relevant), or any suggested mitigations.
|
||||
|
||||
We kindly ask that you do not disclose the vulnerability publicly until we have confirmed and addressed the issue.
|
||||
|
||||
Our team will:
|
||||
|
||||
- Acknowledge receipt of your report as quickly as possible.
|
||||
- Keep you updated on the investigation and resolution progress.
|
||||
|
||||
## Researcher Recognition
|
||||
|
||||
We value contributions from the security community. For each valid report, we will publish a dedicated entry on our Security Advisory page that optionally includes the reporter’s name (or preferred alias). Please note that while we do not currently offer monetary bounties, we are committed to giving researchers appropriate credit for their efforts in keeping authentik secure.
|
||||
To report a vulnerability, send an email to [security@goauthentik.io](mailto:security@goauthentik.io). Be sure to include relevant information like which version you've found the issue in, instructions on how to reproduce the issue, and anything else that might make it easier for us to find the issue.
|
||||
|
||||
## Severity levels
|
||||
|
||||
|
||||
@@ -1,28 +1,20 @@
|
||||
"""authentik root module"""
|
||||
|
||||
from functools import lru_cache
|
||||
from os import environ
|
||||
|
||||
VERSION = "2025.10.0-rc1"
|
||||
__version__ = "2025.6.4"
|
||||
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"
|
||||
|
||||
|
||||
@lru_cache
|
||||
def authentik_version() -> str:
|
||||
return VERSION
|
||||
|
||||
|
||||
@lru_cache
|
||||
def authentik_build_hash(fallback: str | None = None) -> str:
|
||||
def get_build_hash(fallback: str | None = None) -> str:
|
||||
"""Get build hash"""
|
||||
build_hash = environ.get(ENV_GIT_HASH_KEY, fallback if fallback else "")
|
||||
return fallback if build_hash == "" and fallback else build_hash
|
||||
|
||||
|
||||
@lru_cache
|
||||
def authentik_full_version() -> str:
|
||||
def get_full_version() -> str:
|
||||
"""Get full version, with build hash appended"""
|
||||
version = authentik_version()
|
||||
if (build_hash := authentik_build_hash()) != "":
|
||||
version = __version__
|
||||
if (build_hash := get_build_hash()) != "":
|
||||
return f"{version}+{build_hash}"
|
||||
return version
|
||||
|
||||
@@ -16,7 +16,7 @@ from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from authentik import authentik_full_version
|
||||
from authentik import get_full_version
|
||||
from authentik.core.api.utils import PassiveSerializer
|
||||
from authentik.enterprise.license import LicenseKey
|
||||
from authentik.lib.config import CONFIG
|
||||
@@ -78,7 +78,7 @@ class SystemInfoSerializer(PassiveSerializer):
|
||||
"""Get versions"""
|
||||
return {
|
||||
"architecture": platform.machine(),
|
||||
"authentik_version": authentik_full_version(),
|
||||
"authentik_version": get_full_version(),
|
||||
"environment": get_env(),
|
||||
"openssl_fips_enabled": (
|
||||
backend._fips_enabled if LicenseKey.get_total().status().is_valid else None
|
||||
|
||||
@@ -10,7 +10,7 @@ from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from authentik import authentik_build_hash, authentik_version
|
||||
from authentik import __version__, get_build_hash
|
||||
from authentik.admin.tasks import VERSION_CACHE_KEY, VERSION_NULL, update_latest_version
|
||||
from authentik.core.api.utils import PassiveSerializer
|
||||
from authentik.outposts.models import Outpost
|
||||
@@ -29,20 +29,20 @@ class VersionSerializer(PassiveSerializer):
|
||||
|
||||
def get_build_hash(self, _) -> str:
|
||||
"""Get build hash, if version is not latest or released"""
|
||||
return authentik_build_hash()
|
||||
return get_build_hash()
|
||||
|
||||
def get_version_current(self, _) -> str:
|
||||
"""Get current version"""
|
||||
return authentik_version()
|
||||
return __version__
|
||||
|
||||
def get_version_latest(self, _) -> str:
|
||||
"""Get latest version from cache"""
|
||||
if get_current_tenant().schema_name == get_public_schema_name():
|
||||
return authentik_version()
|
||||
return __version__
|
||||
version_in_cache = cache.get(VERSION_CACHE_KEY)
|
||||
if not version_in_cache: # pragma: no cover
|
||||
update_latest_version.send()
|
||||
return authentik_version()
|
||||
return __version__
|
||||
return version_in_cache
|
||||
|
||||
def get_version_latest_valid(self, _) -> bool:
|
||||
|
||||
@@ -8,7 +8,7 @@ from packaging.version import parse
|
||||
from requests import RequestException
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik import authentik_build_hash, authentik_version
|
||||
from authentik import __version__, get_build_hash
|
||||
from authentik.admin.apps import PROM_INFO
|
||||
from authentik.events.models import Event, EventAction
|
||||
from authentik.lib.config import CONFIG
|
||||
@@ -19,16 +19,16 @@ LOGGER = get_logger()
|
||||
VERSION_NULL = "0.0.0"
|
||||
VERSION_CACHE_KEY = "authentik_latest_version"
|
||||
VERSION_CACHE_TIMEOUT = 8 * 60 * 60 # 8 hours
|
||||
LOCAL_VERSION = parse(authentik_version())
|
||||
LOCAL_VERSION = parse(__version__)
|
||||
|
||||
|
||||
def _set_prom_info():
|
||||
"""Set prometheus info for version"""
|
||||
PROM_INFO.info(
|
||||
{
|
||||
"version": authentik_version(),
|
||||
"version": __version__,
|
||||
"latest": cache.get(VERSION_CACHE_KEY, ""),
|
||||
"build_hash": authentik_build_hash(),
|
||||
"build_hash": get_build_hash(),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ from json import loads
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
|
||||
from authentik import authentik_version
|
||||
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
|
||||
@@ -27,7 +27,7 @@ class TestAdminAPI(TestCase):
|
||||
response = self.client.get(reverse("authentik_api:admin_version"))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
body = loads(response.content)
|
||||
self.assertEqual(body["version_current"], authentik_version())
|
||||
self.assertEqual(body["version_current"], __version__)
|
||||
|
||||
def test_apps(self):
|
||||
"""Test apps API"""
|
||||
|
||||
@@ -104,68 +104,6 @@ def postprocess_schema_responses(result, generator: SchemaGenerator, **kwargs):
|
||||
return result
|
||||
|
||||
|
||||
def postprocess_schema_pagination(result, generator: SchemaGenerator, **kwargs):
|
||||
to_replace = {
|
||||
"ordering": create_component(
|
||||
generator,
|
||||
"QueryPaginationOrdering",
|
||||
{
|
||||
"name": "ordering",
|
||||
"required": False,
|
||||
"in": "query",
|
||||
"description": "Which field to use when ordering the results.",
|
||||
"schema": {"type": "string"},
|
||||
},
|
||||
ResolvedComponent.PARAMETER,
|
||||
),
|
||||
"page": create_component(
|
||||
generator,
|
||||
"QueryPaginationPage",
|
||||
{
|
||||
"name": "page",
|
||||
"required": False,
|
||||
"in": "query",
|
||||
"description": "A page number within the paginated result set.",
|
||||
"schema": {"type": "integer"},
|
||||
},
|
||||
ResolvedComponent.PARAMETER,
|
||||
),
|
||||
"page_size": create_component(
|
||||
generator,
|
||||
"QueryPaginationPageSize",
|
||||
{
|
||||
"name": "page_size",
|
||||
"required": False,
|
||||
"in": "query",
|
||||
"description": "Number of results to return per page.",
|
||||
"schema": {"type": "integer"},
|
||||
},
|
||||
ResolvedComponent.PARAMETER,
|
||||
),
|
||||
"search": create_component(
|
||||
generator,
|
||||
"QuerySearch",
|
||||
{
|
||||
"name": "search",
|
||||
"required": False,
|
||||
"in": "query",
|
||||
"description": "A search term.",
|
||||
"schema": {"type": "string"},
|
||||
},
|
||||
ResolvedComponent.PARAMETER,
|
||||
),
|
||||
}
|
||||
for path in result["paths"].values():
|
||||
for method in path.values():
|
||||
# print(method["parameters"])
|
||||
for idx, param in enumerate(method.get("parameters", [])):
|
||||
for replace_name, replace_ref in to_replace.items():
|
||||
if param["name"] == replace_name:
|
||||
method["parameters"][idx] = replace_ref.ref
|
||||
# print(method["parameters"])
|
||||
return result
|
||||
|
||||
|
||||
def preprocess_schema_exclude_non_api(endpoints, **kwargs):
|
||||
"""Filter out all API Views which are not mounted under /api"""
|
||||
return [
|
||||
|
||||
@@ -8,6 +8,8 @@ API Browser - {{ brand.branding_title }}
|
||||
|
||||
{% block head %}
|
||||
<script src="{% versioned_script 'dist/standalone/api-browser/index-%v.js' %}" type="module"></script>
|
||||
<meta name="theme-color" content="#151515" media="(prefers-color-scheme: light)">
|
||||
<meta name="theme-color" content="#151515" media="(prefers-color-scheme: dark)">
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
|
||||
@@ -11,7 +11,7 @@ from rest_framework.relations import PrimaryKeyRelatedField
|
||||
from rest_framework.serializers import Serializer
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik import authentik_version
|
||||
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
|
||||
@@ -48,7 +48,7 @@ class Command(BaseCommand):
|
||||
"$schema": "http://json-schema.org/draft-07/schema",
|
||||
"$id": "https://goauthentik.io/blueprints/schema.json",
|
||||
"type": "object",
|
||||
"title": f"authentik {authentik_version()} Blueprint schema",
|
||||
"title": f"authentik {__version__} Blueprint schema",
|
||||
"required": ["version", "entries"],
|
||||
"properties": {
|
||||
"version": {
|
||||
|
||||
@@ -76,7 +76,6 @@ 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.stages.consent.models import UserConsent
|
||||
from authentik.tasks.models import Task
|
||||
from authentik.tenants.models import Tenant
|
||||
|
||||
@@ -136,7 +135,6 @@ def excluded_models() -> list[type[Model]]:
|
||||
EndpointDeviceConnection,
|
||||
DeviceToken,
|
||||
StreamEvent,
|
||||
UserConsent,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -38,7 +38,6 @@ from authentik.blueprints.v1.oci import OCI_PREFIX
|
||||
from authentik.events.logs import capture_logs
|
||||
from authentik.events.utils import sanitize_dict
|
||||
from authentik.lib.config import CONFIG
|
||||
from authentik.tasks.apps import PRIORITY_HIGH
|
||||
from authentik.tasks.models import Task
|
||||
from authentik.tasks.schedules.models import Schedule
|
||||
from authentik.tenants.models import Tenant
|
||||
@@ -112,7 +111,6 @@ class BlueprintEventHandler(FileSystemEventHandler):
|
||||
@actor(
|
||||
description=_("Find blueprints as `blueprints_find` does, but return a safe dict."),
|
||||
throws=(DatabaseError, ProgrammingError, InternalError),
|
||||
priority=PRIORITY_HIGH,
|
||||
)
|
||||
def blueprints_find_dict():
|
||||
blueprints = []
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
from typing import Any
|
||||
|
||||
from django.db import models
|
||||
from drf_spectacular.utils import extend_schema, extend_schema_field
|
||||
from drf_spectacular.utils import extend_schema
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.exceptions import ValidationError
|
||||
from rest_framework.fields import CharField, ChoiceField, ListField, SerializerMethodField
|
||||
from rest_framework.fields import CharField, ChoiceField, ListField
|
||||
from rest_framework.filters import OrderingFilter, SearchFilter
|
||||
from rest_framework.permissions import AllowAny
|
||||
from rest_framework.request import Request
|
||||
@@ -18,8 +18,6 @@ from authentik.brands.models import Brand
|
||||
from authentik.core.api.used_by import UsedByMixin
|
||||
from authentik.core.api.utils import ModelSerializer, PassiveSerializer
|
||||
from authentik.rbac.filters import SecretKeyFilter
|
||||
from authentik.tenants.api.settings import FlagJSONField
|
||||
from authentik.tenants.flags import Flag
|
||||
from authentik.tenants.utils import get_current_tenant
|
||||
|
||||
|
||||
@@ -112,16 +110,6 @@ class CurrentBrandSerializer(PassiveSerializer):
|
||||
flow_device_code = CharField(source="flow_device_code.slug", required=False)
|
||||
|
||||
default_locale = CharField(read_only=True)
|
||||
flags = SerializerMethodField()
|
||||
|
||||
@extend_schema_field(field=FlagJSONField)
|
||||
def get_flags(self, _):
|
||||
values = {}
|
||||
for flag in Flag.available():
|
||||
_flag = flag()
|
||||
if _flag.visibility == "public":
|
||||
values[_flag.key] = _flag.get()
|
||||
return values
|
||||
|
||||
|
||||
class BrandViewSet(UsedByMixin, ModelViewSet):
|
||||
|
||||
@@ -113,7 +113,7 @@ class Brand(SerializerModel):
|
||||
try:
|
||||
return self.attributes.get("settings", {}).get("locale", "")
|
||||
|
||||
except Exception as exc: # noqa
|
||||
except Exception as exc:
|
||||
LOGGER.warning("Failed to get default locale", exc=exc)
|
||||
return ""
|
||||
|
||||
|
||||
@@ -10,20 +10,11 @@ from authentik.core.tests.utils import create_test_admin_user, create_test_brand
|
||||
from authentik.lib.generators import generate_id
|
||||
from authentik.providers.oauth2.models import OAuth2Provider
|
||||
from authentik.providers.saml.models import SAMLProvider
|
||||
from authentik.tenants.flags import Flag
|
||||
|
||||
|
||||
class TestBrands(APITestCase):
|
||||
"""Test brands"""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.default_flags = {}
|
||||
for flag in Flag.available():
|
||||
_flag = flag()
|
||||
if _flag.visibility == "public":
|
||||
self.default_flags[_flag.key] = _flag.get()
|
||||
|
||||
def test_current_brand(self):
|
||||
"""Test Current brand API"""
|
||||
brand = create_test_brand()
|
||||
@@ -38,7 +29,6 @@ class TestBrands(APITestCase):
|
||||
"ui_footer_links": [],
|
||||
"ui_theme": Themes.AUTOMATIC,
|
||||
"default_locale": "",
|
||||
"flags": self.default_flags,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -59,7 +49,27 @@ class TestBrands(APITestCase):
|
||||
"ui_footer_links": [],
|
||||
"ui_theme": Themes.AUTOMATIC,
|
||||
"default_locale": "",
|
||||
"flags": self.default_flags,
|
||||
},
|
||||
)
|
||||
|
||||
def test_brand_subdomain_same_suffix(self):
|
||||
"""Test Current brand API"""
|
||||
Brand.objects.all().delete()
|
||||
Brand.objects.create(domain="bar.baz", branding_title="custom")
|
||||
Brand.objects.create(domain="foo.bar.baz", branding_title="custom")
|
||||
self.assertJSONEqual(
|
||||
self.client.get(
|
||||
reverse("authentik_api:brand-current"), HTTP_HOST="foo.bar.baz"
|
||||
).content.decode(),
|
||||
{
|
||||
"branding_logo": "/static/dist/assets/icons/icon_left_brand.svg",
|
||||
"branding_favicon": "/static/dist/assets/icons/icon.png",
|
||||
"branding_title": "custom",
|
||||
"branding_custom_css": "",
|
||||
"matched_domain": "foo.bar.baz",
|
||||
"ui_footer_links": [],
|
||||
"ui_theme": Themes.AUTOMATIC,
|
||||
"default_locale": "",
|
||||
},
|
||||
)
|
||||
|
||||
@@ -77,7 +87,6 @@ class TestBrands(APITestCase):
|
||||
"ui_footer_links": [],
|
||||
"ui_theme": Themes.AUTOMATIC,
|
||||
"default_locale": "",
|
||||
"flags": self.default_flags,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -158,7 +167,6 @@ class TestBrands(APITestCase):
|
||||
"ui_footer_links": [],
|
||||
"ui_theme": Themes.AUTOMATIC,
|
||||
"default_locale": "",
|
||||
"flags": self.default_flags,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -4,11 +4,12 @@ from typing import Any
|
||||
|
||||
from django.db.models import F, Q
|
||||
from django.db.models import Value as V
|
||||
from django.db.models.functions import Length
|
||||
from django.http.request import HttpRequest
|
||||
from django.utils.html import _json_script_escapes
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
from authentik import authentik_full_version
|
||||
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
|
||||
@@ -20,9 +21,9 @@ DEFAULT_BRAND = Brand(domain="fallback")
|
||||
def get_brand_for_request(request: HttpRequest) -> Brand:
|
||||
"""Get brand object for current request"""
|
||||
db_brands = (
|
||||
Brand.objects.annotate(host_domain=V(request.get_host()))
|
||||
Brand.objects.annotate(host_domain=V(request.get_host()), match_length=Length("domain"))
|
||||
.filter(Q(host_domain__iendswith=F("domain")) | _q_default)
|
||||
.order_by("default")
|
||||
.order_by("-match_length", "default")
|
||||
)
|
||||
brands = list(db_brands.all())
|
||||
if len(brands) < 1:
|
||||
@@ -43,5 +44,5 @@ def context_processor(request: HttpRequest) -> dict[str, Any]:
|
||||
"brand_css": brand_css,
|
||||
"footer_links": tenant.footer_links,
|
||||
"html_meta": {**get_http_meta()},
|
||||
"version": authentik_full_version(),
|
||||
"version": get_full_version(),
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ from copy import copy
|
||||
from django.core.cache import cache
|
||||
from django.db.models import QuerySet
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.utils.translation import gettext as _
|
||||
from drf_spectacular.types import OpenApiTypes
|
||||
from drf_spectacular.utils import OpenApiParameter, OpenApiResponse, extend_schema
|
||||
from guardian.shortcuts import get_objects_for_user
|
||||
@@ -67,15 +66,6 @@ class ApplicationSerializer(ModelSerializer):
|
||||
user = self.context["request"].user
|
||||
return app.get_launch_url(user)
|
||||
|
||||
def validate_slug(self, slug: str) -> str:
|
||||
if slug in Application.reserved_slugs:
|
||||
raise ValidationError(
|
||||
_("The slug '{slug}' is reserved and cannot be used for applications.").format(
|
||||
slug=slug
|
||||
)
|
||||
)
|
||||
return slug
|
||||
|
||||
def __init__(self, *args, **kwargs) -> None:
|
||||
super().__init__(*args, **kwargs)
|
||||
if SERIALIZER_CONTEXT_BLUEPRINT in self.context:
|
||||
|
||||
@@ -295,7 +295,7 @@ class GroupViewSet(UsedByMixin, ModelViewSet):
|
||||
@extend_schema(
|
||||
request=UserAccountSerializer,
|
||||
responses={
|
||||
204: OpenApiResponse(description="User removed"),
|
||||
204: OpenApiResponse(description="User added"),
|
||||
404: OpenApiResponse(description="User not found"),
|
||||
},
|
||||
)
|
||||
@@ -307,7 +307,7 @@ class GroupViewSet(UsedByMixin, ModelViewSet):
|
||||
permission_classes=[],
|
||||
)
|
||||
def remove_user(self, request: Request, pk: str) -> Response:
|
||||
"""Remove user from group"""
|
||||
"""Add user to group"""
|
||||
group: Group = self.get_object()
|
||||
user: User = (
|
||||
get_objects_for_user(request.user, "authentik_core.view_user")
|
||||
|
||||
@@ -171,7 +171,7 @@ class PropertyMappingViewSet(
|
||||
except PropertyMappingExpressionException as exc:
|
||||
response_data["result"] = exception_to_string(exc.exc)
|
||||
response_data["successful"] = False
|
||||
except Exception as exc: # noqa
|
||||
except Exception as exc:
|
||||
response_data["result"] = exception_to_string(exc)
|
||||
response_data["successful"] = False
|
||||
response = PropertyMappingTestResultSerializer(response_data)
|
||||
|
||||
@@ -154,8 +154,7 @@ class UserSerializer(ModelSerializer):
|
||||
if SERIALIZER_CONTEXT_BLUEPRINT in self.context:
|
||||
self.fields["password"] = CharField(required=False, allow_null=True)
|
||||
self.fields["permissions"] = ListField(
|
||||
required=False,
|
||||
child=ChoiceField(choices=get_permission_choices()),
|
||||
required=False, child=ChoiceField(choices=get_permission_choices())
|
||||
)
|
||||
|
||||
def create(self, validated_data: dict) -> User:
|
||||
@@ -270,10 +269,7 @@ class UserSelfSerializer(ModelSerializer):
|
||||
ListSerializer(
|
||||
child=inline_serializer(
|
||||
"UserSelfGroups",
|
||||
{
|
||||
"name": CharField(read_only=True),
|
||||
"pk": CharField(read_only=True),
|
||||
},
|
||||
{"name": CharField(read_only=True), "pk": CharField(read_only=True)},
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -321,19 +317,12 @@ class UserSelfSerializer(ModelSerializer):
|
||||
|
||||
class SessionUserSerializer(PassiveSerializer):
|
||||
"""Response for the /user/me endpoint, returns the currently active user (as `user` property)
|
||||
and, if this user is being impersonated, the original user in the `original` property.
|
||||
"""
|
||||
and, if this user is being impersonated, the original user in the `original` property."""
|
||||
|
||||
user = UserSelfSerializer()
|
||||
original = UserSelfSerializer(required=False)
|
||||
|
||||
|
||||
class UserPasswordSetSerializer(PassiveSerializer):
|
||||
"""Payload to set a users' password directly"""
|
||||
|
||||
password = CharField(required=True)
|
||||
|
||||
|
||||
class UsersFilter(FilterSet):
|
||||
"""Filter for users"""
|
||||
|
||||
@@ -416,15 +405,21 @@ class UserViewSet(UsedByMixin, ModelViewSet):
|
||||
ordering = ["username", "date_joined", "last_updated"]
|
||||
serializer_class = UserSerializer
|
||||
filterset_class = UsersFilter
|
||||
search_fields = ["email", "name", "uuid", "username"]
|
||||
search_fields = [
|
||||
"username",
|
||||
"name",
|
||||
"is_active",
|
||||
"email",
|
||||
"uuid",
|
||||
"attributes",
|
||||
"date_joined",
|
||||
"last_updated",
|
||||
]
|
||||
|
||||
def get_ql_fields(self):
|
||||
from djangoql.schema import BoolField, StrField
|
||||
|
||||
from authentik.enterprise.search.fields import (
|
||||
ChoiceSearchField,
|
||||
JSONSearchField,
|
||||
)
|
||||
from authentik.enterprise.search.fields import ChoiceSearchField, JSONSearchField
|
||||
|
||||
return [
|
||||
StrField(User, "username"),
|
||||
@@ -519,12 +514,7 @@ class UserViewSet(UsedByMixin, ModelViewSet):
|
||||
)
|
||||
},
|
||||
)
|
||||
@action(
|
||||
detail=False,
|
||||
methods=["POST"],
|
||||
pagination_class=None,
|
||||
filter_backends=[],
|
||||
)
|
||||
@action(detail=False, methods=["POST"], pagination_class=None, filter_backends=[])
|
||||
def service_account(self, request: Request) -> Response:
|
||||
"""Create a new user account that is marked as a service account"""
|
||||
username = request.data.get("name")
|
||||
@@ -568,13 +558,7 @@ class UserViewSet(UsedByMixin, ModelViewSet):
|
||||
return Response(data={"non_field_errors": [str(exc)]}, status=400)
|
||||
|
||||
@extend_schema(responses={200: SessionUserSerializer(many=False)})
|
||||
@action(
|
||||
url_path="me",
|
||||
url_name="me",
|
||||
detail=False,
|
||||
pagination_class=None,
|
||||
filter_backends=[],
|
||||
)
|
||||
@action(url_path="me", url_name="me", detail=False, pagination_class=None, filter_backends=[])
|
||||
def user_me(self, request: Request) -> Response:
|
||||
"""Get information about current user"""
|
||||
context = {"request": request}
|
||||
@@ -591,7 +575,12 @@ class UserViewSet(UsedByMixin, ModelViewSet):
|
||||
|
||||
@permission_required("authentik_core.reset_user_password")
|
||||
@extend_schema(
|
||||
request=UserPasswordSetSerializer,
|
||||
request=inline_serializer(
|
||||
"UserPasswordSetSerializer",
|
||||
{
|
||||
"password": CharField(required=True),
|
||||
},
|
||||
),
|
||||
responses={
|
||||
204: OpenApiResponse(description="Successfully changed password"),
|
||||
400: OpenApiResponse(description="Bad request"),
|
||||
@@ -600,11 +589,9 @@ class UserViewSet(UsedByMixin, ModelViewSet):
|
||||
@action(detail=True, methods=["POST"], permission_classes=[])
|
||||
def set_password(self, request: Request, pk: int) -> Response:
|
||||
"""Set password for user"""
|
||||
data = UserPasswordSetSerializer(data=request.data)
|
||||
data.is_valid(raise_exception=True)
|
||||
user: User = self.get_object()
|
||||
try:
|
||||
user.set_password(data.validated_data["password"], request=request)
|
||||
user.set_password(request.data.get("password"), request=request)
|
||||
user.save()
|
||||
except (ValidationError, IntegrityError) as exc:
|
||||
LOGGER.debug("Failed to set password", exc=exc)
|
||||
@@ -623,7 +610,7 @@ class UserViewSet(UsedByMixin, ModelViewSet):
|
||||
)
|
||||
@action(detail=True, pagination_class=None, filter_backends=[], methods=["POST"])
|
||||
def recovery(self, request: Request, pk: int) -> Response:
|
||||
"""Create a temporary link that a user can use to recover their account"""
|
||||
"""Create a temporary link that a user can use to recover their accounts"""
|
||||
link, _ = self._create_recovery_link()
|
||||
return Response({"link": link})
|
||||
|
||||
@@ -644,7 +631,7 @@ class UserViewSet(UsedByMixin, ModelViewSet):
|
||||
)
|
||||
@action(detail=True, pagination_class=None, filter_backends=[], methods=["POST"])
|
||||
def recovery_email(self, request: Request, pk: int) -> Response:
|
||||
"""Send an email with a temporary link that a user can use to recover their account"""
|
||||
"""Create a temporary link that a user can use to recover their accounts"""
|
||||
for_user: User = self.get_object()
|
||||
if for_user.email == "":
|
||||
LOGGER.debug("User doesn't have an email address")
|
||||
@@ -681,7 +668,8 @@ class UserViewSet(UsedByMixin, ModelViewSet):
|
||||
},
|
||||
),
|
||||
responses={
|
||||
204: OpenApiResponse(description="Successfully started impersonation"),
|
||||
"204": OpenApiResponse(description="Successfully started impersonation"),
|
||||
"401": OpenApiResponse(description="Access denied"),
|
||||
},
|
||||
)
|
||||
@action(detail=True, methods=["POST"], permission_classes=[])
|
||||
@@ -696,32 +684,28 @@ class UserViewSet(UsedByMixin, ModelViewSet):
|
||||
if not request.user.has_perm(
|
||||
"authentik_core.impersonate", user_to_be
|
||||
) and not request.user.has_perm("authentik_core.impersonate"):
|
||||
LOGGER.debug(
|
||||
"User attempted to impersonate without permissions",
|
||||
user=request.user,
|
||||
)
|
||||
return Response(status=403)
|
||||
LOGGER.debug("User attempted to impersonate without permissions", user=request.user)
|
||||
return Response(status=401)
|
||||
if user_to_be.pk == self.request.user.pk:
|
||||
LOGGER.debug("User attempted to impersonate themselves", user=request.user)
|
||||
return Response(status=401)
|
||||
if not reason and request.tenant.impersonation_require_reason:
|
||||
LOGGER.debug(
|
||||
"User attempted to impersonate without providing a reason",
|
||||
user=request.user,
|
||||
"User attempted to impersonate without providing a reason", user=request.user
|
||||
)
|
||||
raise ValidationError({"reason": _("This field is required.")})
|
||||
return Response(status=401)
|
||||
|
||||
request.session[SESSION_KEY_IMPERSONATE_ORIGINAL_USER] = request.user
|
||||
request.session[SESSION_KEY_IMPERSONATE_USER] = user_to_be
|
||||
|
||||
Event.new(EventAction.IMPERSONATION_STARTED, reason=reason).from_http(request, user_to_be)
|
||||
|
||||
return Response(status=204)
|
||||
return Response(status=201)
|
||||
|
||||
@extend_schema(
|
||||
request=OpenApiTypes.NONE,
|
||||
responses={
|
||||
"204": OpenApiResponse(description="Successfully ended impersonation"),
|
||||
"204": OpenApiResponse(description="Successfully started impersonation"),
|
||||
},
|
||||
)
|
||||
@action(detail=False, methods=["GET"])
|
||||
@@ -746,8 +730,7 @@ class UserViewSet(UsedByMixin, ModelViewSet):
|
||||
@extend_schema(
|
||||
responses={
|
||||
200: inline_serializer(
|
||||
"UserPathSerializer",
|
||||
{"paths": ListField(child=CharField(), read_only=True)},
|
||||
"UserPathSerializer", {"paths": ListField(child=CharField(), read_only=True)}
|
||||
)
|
||||
},
|
||||
parameters=[
|
||||
|
||||
@@ -21,6 +21,8 @@ from rest_framework.serializers import (
|
||||
raise_errors_on_nested_writes,
|
||||
)
|
||||
|
||||
from authentik.rbac.permissions import assign_initial_permissions
|
||||
|
||||
|
||||
def is_dict(value: Any):
|
||||
"""Ensure a value is a dictionary, useful for JSONFields"""
|
||||
@@ -50,6 +52,15 @@ class ModelSerializer(BaseModelSerializer):
|
||||
serializer_field_mapping = BaseModelSerializer.serializer_field_mapping.copy()
|
||||
serializer_field_mapping[models.JSONField] = JSONDictField
|
||||
|
||||
def create(self, validated_data):
|
||||
instance = super().create(validated_data)
|
||||
|
||||
request = self.context.get("request")
|
||||
if request and hasattr(request, "user") and not request.user.is_anonymous:
|
||||
assign_initial_permissions(request.user, instance)
|
||||
|
||||
return instance
|
||||
|
||||
def update(self, instance: Model, validated_data):
|
||||
raise_errors_on_nested_writes("update", self, validated_data)
|
||||
info = model_meta.get_field_info(instance)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""custom runserver command"""
|
||||
|
||||
from io import StringIO
|
||||
from typing import TextIO
|
||||
|
||||
from daphne.management.commands.runserver import Command as RunServer
|
||||
from daphne.server import Server
|
||||
@@ -33,4 +33,4 @@ class Command(RunServer):
|
||||
super().__init__(*args, **kwargs)
|
||||
# Redirect standard stdout banner from Daphne into the void
|
||||
# as there are a couple more steps that happen before startup is fully done
|
||||
self.stdout = StringIO()
|
||||
self.stdout = TextIO()
|
||||
|
||||
@@ -11,7 +11,7 @@ from django.core.management.base import BaseCommand
|
||||
from django.db.models import Model
|
||||
from django.db.models.signals import post_save, pre_delete
|
||||
|
||||
from authentik import authentik_full_version
|
||||
from authentik import get_full_version
|
||||
from authentik.core.models import User
|
||||
from authentik.events.middleware import should_log_model
|
||||
from authentik.events.models import Event, EventAction
|
||||
@@ -19,7 +19,7 @@ from authentik.events.utils import model_to_dict
|
||||
|
||||
|
||||
def get_banner_text(shell_type="shell") -> str:
|
||||
return f"""### authentik {shell_type} ({authentik_full_version()})
|
||||
return f"""### authentik {shell_type} ({get_full_version()})
|
||||
### Node {platform.node()} | Arch {platform.machine()} | Python {platform.python_version()} """
|
||||
|
||||
|
||||
@@ -99,7 +99,7 @@ class Command(BaseCommand):
|
||||
else:
|
||||
try:
|
||||
hook()
|
||||
except Exception: # noqa
|
||||
except Exception:
|
||||
# Match the behavior of the cpython shell where an error in
|
||||
# sys.__interactivehook__ prints a warning and the exception
|
||||
# and continues.
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
# Generated by Django 5.1.12 on 2025-09-25 13:39
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_core", "0050_user_last_updated_and_more"),
|
||||
("authentik_rbac", "0006_alter_role_options"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddIndex(
|
||||
model_name="group",
|
||||
index=models.Index(fields=["is_superuser"], name="authentik_c_is_supe_1e5a97_idx"),
|
||||
),
|
||||
]
|
||||
@@ -114,21 +114,15 @@ class AttributesMixin(models.Model):
|
||||
|
||||
def update_attributes(self, properties: dict[str, Any]):
|
||||
"""Update fields and attributes, but correctly by merging dicts"""
|
||||
needs_update = False
|
||||
for key, value in properties.items():
|
||||
if key == "attributes":
|
||||
continue
|
||||
if getattr(self, key, None) != value:
|
||||
setattr(self, key, value)
|
||||
needs_update = True
|
||||
setattr(self, key, value)
|
||||
final_attributes = {}
|
||||
MERGE_LIST_UNIQUE.merge(final_attributes, self.attributes)
|
||||
MERGE_LIST_UNIQUE.merge(final_attributes, properties.get("attributes", {}))
|
||||
if self.attributes != final_attributes:
|
||||
self.attributes = final_attributes
|
||||
needs_update = True
|
||||
if needs_update:
|
||||
self.save()
|
||||
self.attributes = final_attributes
|
||||
self.save()
|
||||
|
||||
@classmethod
|
||||
def update_or_create_attributes(
|
||||
@@ -206,10 +200,7 @@ class Group(SerializerModel, AttributesMixin):
|
||||
"parent",
|
||||
),
|
||||
)
|
||||
indexes = (
|
||||
models.Index(fields=["name"]),
|
||||
models.Index(fields=["is_superuser"]),
|
||||
)
|
||||
indexes = [models.Index(fields=["name"])]
|
||||
verbose_name = _("Group")
|
||||
verbose_name_plural = _("Groups")
|
||||
permissions = [
|
||||
@@ -409,7 +400,7 @@ class User(SerializerModel, GuardianUserMixin, AttributesMixin, AbstractUser):
|
||||
try:
|
||||
return self.attributes.get("settings", {}).get("locale", "")
|
||||
|
||||
except Exception as exc: # noqa
|
||||
except Exception as exc:
|
||||
LOGGER.warning("Failed to get default locale", exc=exc)
|
||||
if request:
|
||||
return request.brand.locale
|
||||
@@ -557,9 +548,6 @@ class Application(SerializerModel, PolicyBindingModel):
|
||||
|
||||
objects = ApplicationQuerySet.as_manager()
|
||||
|
||||
# Reserved slugs that would clash with OAuth2 provider endpoints
|
||||
reserved_slugs = ["authorize", "token", "device", "userinfo", "introspect", "revoke"]
|
||||
|
||||
@property
|
||||
def serializer(self) -> Serializer:
|
||||
from authentik.core.api.applications import ApplicationSerializer
|
||||
@@ -590,7 +578,7 @@ class Application(SerializerModel, PolicyBindingModel):
|
||||
try:
|
||||
return url % user.__dict__
|
||||
|
||||
except Exception as exc: # noqa
|
||||
except Exception as exc:
|
||||
LOGGER.warning("Failed to format launch url", exc=exc)
|
||||
return url
|
||||
return url
|
||||
@@ -786,7 +774,7 @@ class Source(ManagedModel, SerializerModel, PolicyBindingModel):
|
||||
"slug": self.slug,
|
||||
}
|
||||
|
||||
except Exception as exc: # noqa
|
||||
except Exception as exc:
|
||||
LOGGER.warning("Failed to template user path", exc=exc, source=self)
|
||||
return User.default_path()
|
||||
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
|
||||
from django.contrib.auth.signals import user_logged_in
|
||||
from django.core.cache import cache
|
||||
from django.core.signals import Signal
|
||||
from django.db.models import Model
|
||||
from django.db.models.signals import post_delete, post_save, pre_save
|
||||
from django.dispatch import Signal, receiver
|
||||
from django.dispatch import receiver
|
||||
from django.http.request import HttpRequest
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@ from authentik.core.models import (
|
||||
ExpiringModel,
|
||||
User,
|
||||
)
|
||||
from authentik.lib.utils.db import chunked_queryset
|
||||
from authentik.tasks.models import Task
|
||||
|
||||
LOGGER = get_logger()
|
||||
@@ -29,7 +28,7 @@ def clean_expired_models():
|
||||
cls.objects.all().exclude(expiring=False).exclude(expiring=True, expires__gt=now())
|
||||
)
|
||||
amount = objects.count()
|
||||
for obj in chunked_queryset(objects):
|
||||
for obj in objects:
|
||||
obj.expire_action()
|
||||
LOGGER.debug("Expired models", model=cls, amount=amount)
|
||||
self.info(f"Expired {amount} {cls._meta.verbose_name_plural}")
|
||||
|
||||
@@ -15,11 +15,7 @@
|
||||
<link rel="shortcut icon" href="{{ brand.branding_favicon_url }}">
|
||||
{% block head_before %}
|
||||
{% endblock %}
|
||||
|
||||
{% include "base/theme.html" %}
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="{% static 'dist/authentik.css' %}">
|
||||
|
||||
<style>{{ brand_css }}</style>
|
||||
<script src="{% versioned_script 'dist/poly-%v.js' %}" type="module"></script>
|
||||
<script src="{% versioned_script 'dist/standalone/loading/index-%v.js' %}" type="module"></script>
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
{% if ui_theme == "dark" %}
|
||||
<meta name="color-scheme" content="dark" />
|
||||
<meta name="theme-color" content="#18191a">
|
||||
{% elif ui_theme == "light" %}
|
||||
<meta name="color-scheme" content="light" />
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
{% else %}
|
||||
<meta name="color-scheme" content="light dark" />
|
||||
<meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)">
|
||||
<meta name="theme-color" content="#18191a" media="(prefers-color-scheme: dark)">
|
||||
{% endif %}
|
||||
@@ -4,11 +4,12 @@
|
||||
|
||||
{% block head %}
|
||||
<script src="{% versioned_script 'dist/admin/AdminInterface-%v.js' %}" type="module"></script>
|
||||
<meta name="theme-color" content="#18191a" media="(prefers-color-scheme: dark)">
|
||||
<meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)">
|
||||
{% include "base/header_js.html" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<ak-skip-to-content></ak-skip-to-content>
|
||||
<ak-message-container alignment="bottom"></ak-message-container>
|
||||
<ak-interface-admin>
|
||||
<ak-loading></ak-loading>
|
||||
|
||||
@@ -4,11 +4,12 @@
|
||||
|
||||
{% block head %}
|
||||
<script src="{% versioned_script 'dist/user/UserInterface-%v.js' %}" type="module"></script>
|
||||
<meta name="theme-color" content="#1c1e21" media="(prefers-color-scheme: light)">
|
||||
<meta name="theme-color" content="#1c1e21" media="(prefers-color-scheme: dark)">
|
||||
{% include "base/header_js.html" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<ak-skip-to-content></ak-skip-to-content>
|
||||
<ak-message-container></ak-message-container>
|
||||
<ak-interface-user>
|
||||
<ak-loading></ak-loading>
|
||||
|
||||
@@ -45,7 +45,6 @@
|
||||
{% block body %}
|
||||
<div class="pf-c-background-image">
|
||||
</div>
|
||||
<ak-skip-to-content></ak-skip-to-content>
|
||||
<ak-message-container></ak-message-container>
|
||||
<div class="pf-c-login stacked">
|
||||
<div class="ak-login-container">
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
from django import template
|
||||
from django.templatetags.static import static as static_loader
|
||||
|
||||
from authentik import authentik_full_version
|
||||
from authentik import get_full_version
|
||||
|
||||
register = template.Library()
|
||||
|
||||
@@ -11,4 +11,4 @@ register = template.Library()
|
||||
@register.simple_tag()
|
||||
def versioned_script(path: str) -> str:
|
||||
"""Wrapper around {% static %} tag that supports setting the version"""
|
||||
return static_loader(path.replace("%v", authentik_full_version()))
|
||||
return static_loader(path.replace("%v", get_full_version()))
|
||||
|
||||
@@ -257,35 +257,3 @@ class TestApplicationsAPI(APITestCase):
|
||||
self.assertEqual(
|
||||
Application.objects.with_provider().get(slug=slug).get_provider(), provider
|
||||
)
|
||||
|
||||
def test_create_application_with_reserved_slug(self):
|
||||
"""Test creating an application with a reserved slug"""
|
||||
self.client.force_login(self.user)
|
||||
response = self.client.post(
|
||||
reverse("authentik_api:application-list"),
|
||||
{
|
||||
"name": "Test Application",
|
||||
"slug": Application.reserved_slugs[0],
|
||||
},
|
||||
)
|
||||
self.assertEqual(response.status_code, 400)
|
||||
self.assertIn("slug", response.data)
|
||||
self.assertIn("reserved", response.data["slug"][0])
|
||||
|
||||
def test_update_application_with_reserved_slug(self):
|
||||
"""Test updating an application to use a reserved slug"""
|
||||
self.client.force_login(self.user)
|
||||
app = Application.objects.create(
|
||||
name="Test Application",
|
||||
slug="valid-slug",
|
||||
)
|
||||
|
||||
response = self.client.patch(
|
||||
reverse("authentik_api:application-detail", kwargs={"slug": app.slug}),
|
||||
{
|
||||
"slug": Application.reserved_slugs[0],
|
||||
},
|
||||
)
|
||||
self.assertEqual(response.status_code, 400)
|
||||
self.assertIn("slug", response.data)
|
||||
self.assertIn("reserved", response.data["slug"][0])
|
||||
|
||||
@@ -59,7 +59,7 @@ class TestImpersonation(APITestCase):
|
||||
),
|
||||
data={"reason": "some reason"},
|
||||
)
|
||||
self.assertEqual(response.status_code, 204)
|
||||
self.assertEqual(response.status_code, 201)
|
||||
|
||||
response = self.client.get(reverse("authentik_api:user-me"))
|
||||
response_body = loads(response.content.decode())
|
||||
@@ -80,7 +80,7 @@ class TestImpersonation(APITestCase):
|
||||
),
|
||||
data={"reason": "some reason"},
|
||||
)
|
||||
self.assertEqual(response.status_code, 204)
|
||||
self.assertEqual(response.status_code, 201)
|
||||
|
||||
response = self.client.get(reverse("authentik_api:user-me"))
|
||||
response_body = loads(response.content.decode())
|
||||
@@ -137,10 +137,10 @@ class TestImpersonation(APITestCase):
|
||||
self.client.force_login(self.user)
|
||||
|
||||
response = self.client.post(
|
||||
reverse("authentik_api:user-impersonate", kwargs={"pk": self.other_user.pk}),
|
||||
reverse("authentik_api:user-impersonate", kwargs={"pk": self.user.pk}),
|
||||
data={"reason": ""},
|
||||
)
|
||||
self.assertEqual(response.status_code, 400)
|
||||
self.assertEqual(response.status_code, 401)
|
||||
|
||||
response = self.client.get(reverse("authentik_api:user-me"))
|
||||
response_body = loads(response.content.decode())
|
||||
|
||||
@@ -102,16 +102,6 @@ class TestUsersAPI(APITestCase):
|
||||
self.admin.refresh_from_db()
|
||||
self.assertTrue(self.admin.check_password(new_pw))
|
||||
|
||||
def test_set_password_blank(self):
|
||||
"""Test Direct password set"""
|
||||
self.client.force_login(self.admin)
|
||||
response = self.client.post(
|
||||
reverse("authentik_api:user-set-password", kwargs={"pk": self.admin.pk}),
|
||||
data={"password": ""},
|
||||
)
|
||||
self.assertEqual(response.status_code, 400)
|
||||
self.assertJSONEqual(response.content, {"password": ["This field may not be blank."]})
|
||||
|
||||
def test_recovery(self):
|
||||
"""Test user recovery link"""
|
||||
flow = create_test_flow(
|
||||
|
||||
@@ -10,7 +10,7 @@ from django.utils.translation import gettext as _
|
||||
from django.views.generic.base import RedirectView, TemplateView
|
||||
from rest_framework.request import Request
|
||||
|
||||
from authentik import authentik_build_hash
|
||||
from authentik import get_build_hash
|
||||
from authentik.admin.tasks import LOCAL_VERSION
|
||||
from authentik.api.v3.config import ConfigView
|
||||
from authentik.brands.api import CurrentBrandSerializer
|
||||
@@ -46,13 +46,11 @@ class InterfaceView(TemplateView):
|
||||
"""Base interface view"""
|
||||
|
||||
def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
|
||||
brand = CurrentBrandSerializer(self.request.brand)
|
||||
kwargs["config_json"] = dumps(ConfigView(request=Request(self.request)).get_config().data)
|
||||
kwargs["ui_theme"] = brand.data["ui_theme"]
|
||||
kwargs["brand_json"] = dumps(brand.data)
|
||||
kwargs["brand_json"] = dumps(CurrentBrandSerializer(self.request.brand).data)
|
||||
kwargs["version_family"] = f"{LOCAL_VERSION.major}.{LOCAL_VERSION.minor}"
|
||||
kwargs["version_subdomain"] = f"version-{LOCAL_VERSION.major}-{LOCAL_VERSION.minor}"
|
||||
kwargs["build"] = authentik_build_hash()
|
||||
kwargs["build"] = get_build_hash()
|
||||
kwargs["url_kwargs"] = self.kwargs
|
||||
kwargs["base_url"] = self.request.build_absolute_uri(CONFIG.get("web.path", "/"))
|
||||
kwargs["base_url_rel"] = CONFIG.get("web.path", "/")
|
||||
|
||||
@@ -12,7 +12,7 @@ from cryptography.x509.oid import NameOID
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from authentik import authentik_version
|
||||
from authentik import __version__
|
||||
from authentik.crypto.models import CertificateKeyPair
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ class CertificateBuilder:
|
||||
.issuer_name(
|
||||
x509.Name(
|
||||
[
|
||||
x509.NameAttribute(NameOID.COMMON_NAME, f"authentik {authentik_version()}"),
|
||||
x509.NameAttribute(NameOID.COMMON_NAME, f"authentik {__version__}"),
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
from django.utils.translation import gettext as _
|
||||
from rest_framework.exceptions import ValidationError
|
||||
|
||||
from authentik.crypto.models import CertificateKeyPair
|
||||
from authentik.enterprise.license import LicenseKey
|
||||
|
||||
|
||||
class RadiusProviderSerializerMixin:
|
||||
|
||||
def validate_certificate(self, cert: CertificateKeyPair) -> CertificateKeyPair:
|
||||
if cert:
|
||||
if not LicenseKey.cached_summary().status.is_valid:
|
||||
raise ValidationError(_("Enterprise is required to use EAP-TLS."))
|
||||
return cert
|
||||
@@ -1,9 +0,0 @@
|
||||
from authentik.enterprise.apps import EnterpriseConfig
|
||||
|
||||
|
||||
class AuthentikEnterpriseProviderRadiusConfig(EnterpriseConfig):
|
||||
|
||||
name = "authentik.enterprise.providers.radius"
|
||||
label = "authentik_enterprise_providers_radius"
|
||||
verbose_name = "authentik Enterprise.Providers.Radius"
|
||||
default = True
|
||||
@@ -1,14 +0,0 @@
|
||||
from django.utils.translation import gettext as _
|
||||
from rest_framework.exceptions import ValidationError
|
||||
|
||||
from authentik.enterprise.license import LicenseKey
|
||||
from authentik.providers.scim.models import SCIMAuthenticationMode
|
||||
|
||||
|
||||
class SCIMProviderSerializerMixin:
|
||||
|
||||
def validate_auth_mode(self, auth_mode: SCIMAuthenticationMode) -> SCIMAuthenticationMode:
|
||||
if auth_mode == SCIMAuthenticationMode.OAUTH:
|
||||
if not LicenseKey.cached_summary().status.is_valid:
|
||||
raise ValidationError(_("Enterprise is required to use the OAuth mode."))
|
||||
return auth_mode
|
||||
@@ -1,9 +0,0 @@
|
||||
from authentik.enterprise.apps import EnterpriseConfig
|
||||
|
||||
|
||||
class AuthentikEnterpriseProviderSCIMConfig(EnterpriseConfig):
|
||||
|
||||
name = "authentik.enterprise.providers.scim"
|
||||
label = "authentik_enterprise_providers_scim"
|
||||
verbose_name = "authentik Enterprise.Providers.SCIM"
|
||||
default = True
|
||||
@@ -1,80 +0,0 @@
|
||||
from datetime import timedelta
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from django.utils.timezone import now
|
||||
from requests import Request, RequestException
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.providers.scim.clients.exceptions import SCIMRequestException
|
||||
from authentik.sources.oauth.clients.oauth2 import OAuth2Client
|
||||
from authentik.sources.oauth.models import OAuthSource, UserOAuthSourceConnection
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from authentik.providers.scim.models import SCIMProvider
|
||||
|
||||
|
||||
class SCIMOAuthException(SCIMRequestException):
|
||||
"""Exceptions related to OAuth operations for SCIM requests"""
|
||||
|
||||
|
||||
class SCIMOAuthAuth:
|
||||
|
||||
def __init__(self, provider: "SCIMProvider"):
|
||||
self.provider = provider
|
||||
self.user = provider.auth_oauth_user
|
||||
self.connection = self.get_connection()
|
||||
self.logger = get_logger().bind()
|
||||
|
||||
def retrieve_token(self):
|
||||
if not self.provider.auth_oauth:
|
||||
return None
|
||||
source: OAuthSource = self.provider.auth_oauth
|
||||
client = OAuth2Client(source, None)
|
||||
access_token_url = source.source_type.access_token_url or ""
|
||||
if source.source_type.urls_customizable and source.access_token_url:
|
||||
access_token_url = source.access_token_url
|
||||
data = client.get_access_token_args(None, None)
|
||||
data["grant_type"] = "password"
|
||||
data.update(self.provider.auth_oauth_params)
|
||||
try:
|
||||
response = client.do_request(
|
||||
"POST",
|
||||
access_token_url,
|
||||
auth=client.get_access_token_auth(),
|
||||
data=data,
|
||||
headers=client._default_headers,
|
||||
)
|
||||
response.raise_for_status()
|
||||
body = response.json()
|
||||
if "error" in body:
|
||||
self.logger.info("Failed to get new OAuth token", error=body["error"])
|
||||
raise SCIMOAuthException(response, body["error"])
|
||||
return body
|
||||
except RequestException as exc:
|
||||
raise SCIMOAuthException(exc.response, message="Failed to get OAuth token") from exc
|
||||
|
||||
def get_connection(self):
|
||||
token = UserOAuthSourceConnection.objects.filter(
|
||||
source=self.provider.auth_oauth, user=self.user, expires__gt=now()
|
||||
).first()
|
||||
if token and token.access_token:
|
||||
return token
|
||||
token = self.retrieve_token()
|
||||
access_token = token["access_token"]
|
||||
expires_in = int(token.get("expires_in", 0))
|
||||
token, _ = UserOAuthSourceConnection.objects.update_or_create(
|
||||
source=self.provider.auth_oauth,
|
||||
user=self.user,
|
||||
defaults={
|
||||
"access_token": access_token,
|
||||
"expires": now() + timedelta(seconds=expires_in),
|
||||
},
|
||||
)
|
||||
return token
|
||||
|
||||
def __call__(self, request: Request) -> Request:
|
||||
if not self.connection.is_valid:
|
||||
self.logger.info("OAuth token expired, renewing token")
|
||||
self.connection = self.get_connection()
|
||||
request.headers["Authorization"] = f"Bearer {self.connection.access_token}"
|
||||
return request
|
||||
@@ -1,30 +0,0 @@
|
||||
from django.db.models import Model
|
||||
from django.db.models.signals import post_save
|
||||
from django.dispatch import receiver
|
||||
|
||||
from authentik.core.models import USER_PATH_SYSTEM_PREFIX, User, UserTypes
|
||||
from authentik.events.middleware import audit_ignore
|
||||
from authentik.providers.scim.models import SCIMAuthenticationMode, SCIMProvider
|
||||
|
||||
USER_PATH_PROVIDERS_SCIM = USER_PATH_SYSTEM_PREFIX + "/providers/scim"
|
||||
|
||||
|
||||
@receiver(post_save, sender=SCIMProvider)
|
||||
def scim_provider_post_save(sender: type[Model], instance: SCIMProvider, created: bool, **__):
|
||||
"""Create service account before provider is saved"""
|
||||
identifier = f"ak-providers-scim-{instance.pk}"
|
||||
with audit_ignore():
|
||||
if instance.auth_mode == SCIMAuthenticationMode.OAUTH:
|
||||
user, user_created = User.objects.update_or_create(
|
||||
username=identifier,
|
||||
defaults={
|
||||
"name": f"SCIM Provider {instance.name} Service-Account",
|
||||
"type": UserTypes.INTERNAL_SERVICE_ACCOUNT,
|
||||
"path": USER_PATH_PROVIDERS_SCIM,
|
||||
},
|
||||
)
|
||||
if created or user_created:
|
||||
instance.auth_oauth_user = user
|
||||
instance.save()
|
||||
elif instance.auth_mode == SCIMAuthenticationMode.TOKEN:
|
||||
User.objects.filter(username=identifier).delete()
|
||||
@@ -1,193 +0,0 @@
|
||||
"""SCIM OAuth tests"""
|
||||
|
||||
from base64 import b64encode
|
||||
from datetime import timedelta
|
||||
from unittest.mock import MagicMock, PropertyMock, patch
|
||||
|
||||
from django.urls import reverse
|
||||
from django.utils.timezone import now
|
||||
from requests_mock import Mocker
|
||||
from rest_framework.test import APITestCase
|
||||
|
||||
from authentik.blueprints.tests import apply_blueprint
|
||||
from authentik.core.models import Application, Group, User
|
||||
from authentik.core.tests.utils import create_test_admin_user
|
||||
from authentik.enterprise.license import LicenseKey
|
||||
from authentik.enterprise.models import License
|
||||
from authentik.enterprise.tests.test_license import expiry_valid
|
||||
from authentik.lib.generators import generate_id
|
||||
from authentik.providers.scim.models import SCIMAuthenticationMode, SCIMMapping, SCIMProvider
|
||||
from authentik.sources.oauth.models import OAuthSource, UserOAuthSourceConnection
|
||||
from authentik.tenants.models import Tenant
|
||||
|
||||
|
||||
class SCIMOAuthTests(APITestCase):
|
||||
"""SCIM User tests"""
|
||||
|
||||
@apply_blueprint("system/providers-scim.yaml")
|
||||
def setUp(self) -> None:
|
||||
# Delete all users and groups as the mocked HTTP responses only return one ID
|
||||
# which will cause errors with multiple users
|
||||
Tenant.objects.update(avatars="none")
|
||||
User.objects.all().exclude_anonymous().delete()
|
||||
Group.objects.all().delete()
|
||||
self.source = OAuthSource.objects.create(
|
||||
name=generate_id(),
|
||||
slug=generate_id(),
|
||||
access_token_url="http://localhost/token", # nosec
|
||||
consumer_key=generate_id(),
|
||||
consumer_secret=generate_id(),
|
||||
provider_type="openidconnect",
|
||||
)
|
||||
self.provider = SCIMProvider.objects.create(
|
||||
name=generate_id(),
|
||||
url="https://localhost",
|
||||
auth_mode=SCIMAuthenticationMode.OAUTH,
|
||||
auth_oauth=self.source,
|
||||
auth_oauth_params={
|
||||
"foo": "bar",
|
||||
},
|
||||
exclude_users_service_account=True,
|
||||
)
|
||||
self.app: Application = Application.objects.create(
|
||||
name=generate_id(),
|
||||
slug=generate_id(),
|
||||
)
|
||||
self.app.backchannel_providers.add(self.provider)
|
||||
self.provider.property_mappings.add(
|
||||
SCIMMapping.objects.get(managed="goauthentik.io/providers/scim/user")
|
||||
)
|
||||
self.provider.property_mappings_group.add(
|
||||
SCIMMapping.objects.get(managed="goauthentik.io/providers/scim/group")
|
||||
)
|
||||
|
||||
def test_retrieve_token(self):
|
||||
"""Test token retrieval"""
|
||||
with Mocker() as mocker:
|
||||
token = generate_id()
|
||||
mocker.post("http://localhost/token", json={"access_token": token, "expires_in": 3600})
|
||||
self.provider.scim_auth()
|
||||
conn = UserOAuthSourceConnection.objects.filter(
|
||||
source=self.source,
|
||||
user=self.provider.auth_oauth_user,
|
||||
).first()
|
||||
self.assertIsNotNone(conn)
|
||||
self.assertTrue(conn.is_valid)
|
||||
auth = (
|
||||
b64encode(
|
||||
b":".join((self.source.consumer_key.encode(), self.source.consumer_secret.encode()))
|
||||
)
|
||||
.strip()
|
||||
.decode()
|
||||
)
|
||||
self.assertEqual(
|
||||
mocker.request_history[0].headers["Authorization"],
|
||||
f"Basic {auth}",
|
||||
)
|
||||
self.assertEqual(mocker.request_history[0].body, "grant_type=password&foo=bar")
|
||||
|
||||
def test_existing_token(self):
|
||||
"""Test existing token"""
|
||||
UserOAuthSourceConnection.objects.create(
|
||||
source=self.source,
|
||||
user=self.provider.auth_oauth_user,
|
||||
access_token=generate_id(),
|
||||
expires=now() + timedelta(hours=3),
|
||||
)
|
||||
with Mocker() as mocker:
|
||||
self.provider.scim_auth()
|
||||
self.assertEqual(len(mocker.request_history), 0)
|
||||
|
||||
@Mocker()
|
||||
def test_user_create(self, mock: Mocker):
|
||||
"""Test user creation"""
|
||||
scim_id = generate_id()
|
||||
token = generate_id()
|
||||
mock.post("http://localhost/token", json={"access_token": token, "expires_in": 3600})
|
||||
mock.get(
|
||||
"https://localhost/ServiceProviderConfig",
|
||||
json={},
|
||||
)
|
||||
mock.post(
|
||||
"https://localhost/Users",
|
||||
json={
|
||||
"id": scim_id,
|
||||
},
|
||||
)
|
||||
uid = generate_id()
|
||||
user = User.objects.create(
|
||||
username=uid,
|
||||
name=f"{uid} {uid}",
|
||||
email=f"{uid}@goauthentik.io",
|
||||
)
|
||||
self.assertEqual(mock.call_count, 3)
|
||||
self.assertEqual(mock.request_history[1].method, "GET")
|
||||
self.assertEqual(mock.request_history[2].method, "POST")
|
||||
self.assertJSONEqual(
|
||||
mock.request_history[2].body,
|
||||
{
|
||||
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
|
||||
"active": True,
|
||||
"emails": [
|
||||
{
|
||||
"primary": True,
|
||||
"type": "other",
|
||||
"value": f"{uid}@goauthentik.io",
|
||||
}
|
||||
],
|
||||
"externalId": user.uid,
|
||||
"name": {
|
||||
"familyName": uid,
|
||||
"formatted": f"{uid} {uid}",
|
||||
"givenName": uid,
|
||||
},
|
||||
"displayName": f"{uid} {uid}",
|
||||
"userName": uid,
|
||||
},
|
||||
)
|
||||
|
||||
@patch(
|
||||
"authentik.enterprise.license.LicenseKey.validate",
|
||||
MagicMock(
|
||||
return_value=LicenseKey(
|
||||
aud="",
|
||||
exp=expiry_valid,
|
||||
name=generate_id(),
|
||||
internal_users=100,
|
||||
external_users=100,
|
||||
)
|
||||
),
|
||||
)
|
||||
def test_api_create(self):
|
||||
License.objects.create(key=generate_id())
|
||||
self.client.force_login(create_test_admin_user())
|
||||
res = self.client.post(
|
||||
reverse("authentik_api:scimprovider-list"),
|
||||
{
|
||||
"name": generate_id(),
|
||||
"url": "http://localhost",
|
||||
"auth_mode": "oauth",
|
||||
"auth_oauth": str(self.source.pk),
|
||||
},
|
||||
)
|
||||
self.assertEqual(res.status_code, 201)
|
||||
|
||||
@patch(
|
||||
"authentik.enterprise.models.LicenseUsageStatus.is_valid",
|
||||
PropertyMock(return_value=False),
|
||||
)
|
||||
def test_api_create_no_license(self):
|
||||
self.client.force_login(create_test_admin_user())
|
||||
res = self.client.post(
|
||||
reverse("authentik_api:scimprovider-list"),
|
||||
{
|
||||
"name": generate_id(),
|
||||
"url": "http://localhost",
|
||||
"auth_mode": "oauth",
|
||||
"auth_oauth": str(self.source.pk),
|
||||
},
|
||||
)
|
||||
self.assertEqual(res.status_code, 400)
|
||||
self.assertJSONEqual(
|
||||
res.content, {"auth_mode": ["Enterprise is required to use the OAuth mode."]}
|
||||
)
|
||||
@@ -1,7 +1,6 @@
|
||||
SPECTACULAR_SETTINGS = {
|
||||
"POSTPROCESSING_HOOKS": [
|
||||
"authentik.api.schema.postprocess_schema_responses",
|
||||
"authentik.api.schema.postprocess_schema_pagination",
|
||||
"authentik.enterprise.search.schema.postprocess_schema_search_autocomplete",
|
||||
"drf_spectacular.hooks.postprocess_schema_enums",
|
||||
],
|
||||
|
||||
@@ -5,8 +5,6 @@ TENANT_APPS = [
|
||||
"authentik.enterprise.policies.unique_password",
|
||||
"authentik.enterprise.providers.google_workspace",
|
||||
"authentik.enterprise.providers.microsoft_entra",
|
||||
"authentik.enterprise.providers.radius",
|
||||
"authentik.enterprise.providers.scim",
|
||||
"authentik.enterprise.providers.ssf",
|
||||
"authentik.enterprise.search",
|
||||
"authentik.enterprise.stages.authenticator_endpoint_gdtc",
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
# Generated by Django 5.1.12 on 2025-09-08 19:43
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_stages_authenticator_endpoint_gdtc", "0001_initial"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="authenticatorendpointgdtcstage",
|
||||
name="friendly_name",
|
||||
field=models.TextField(blank=True, default=""),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
||||
@@ -23,7 +23,6 @@ from authentik.events.models import (
|
||||
)
|
||||
from authentik.events.utils import get_user
|
||||
from authentik.rbac.decorators import permission_required
|
||||
from authentik.stages.email.models import get_template_choices
|
||||
|
||||
|
||||
class NotificationTransportSerializer(ModelSerializer):
|
||||
@@ -31,18 +30,6 @@ class NotificationTransportSerializer(ModelSerializer):
|
||||
|
||||
mode_verbose = SerializerMethodField()
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields["email_template"].choices = get_template_choices()
|
||||
|
||||
def validate_email_template(self, value: str) -> str:
|
||||
"""Check validity of email template"""
|
||||
choices = get_template_choices()
|
||||
for path, _ in choices:
|
||||
if path == value:
|
||||
return value
|
||||
raise ValidationError(f"Invalid template '{value}' specified.")
|
||||
|
||||
def get_mode_verbose(self, instance: NotificationTransport) -> str:
|
||||
"""Return selected mode with a UI Label"""
|
||||
return TransportMode(instance.mode).label
|
||||
@@ -65,8 +52,6 @@ class NotificationTransportSerializer(ModelSerializer):
|
||||
"webhook_url",
|
||||
"webhook_mapping_body",
|
||||
"webhook_mapping_headers",
|
||||
"email_subject_prefix",
|
||||
"email_template",
|
||||
"send_once",
|
||||
]
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ if TYPE_CHECKING:
|
||||
class ASNDict(TypedDict):
|
||||
"""ASN Details"""
|
||||
|
||||
asn: int | None
|
||||
asn: int
|
||||
as_org: str | None
|
||||
network: str | None
|
||||
|
||||
@@ -60,7 +60,7 @@ class ASNContextProcessor(MMDBContextProcessor):
|
||||
except (GeoIP2Error, ValueError):
|
||||
return None
|
||||
|
||||
def asn_to_dict(self, asn: ASN | None) -> ASNDict | dict:
|
||||
def asn_to_dict(self, asn: ASN | None) -> ASNDict:
|
||||
"""Convert ASN to dict"""
|
||||
if not asn:
|
||||
return {}
|
||||
|
||||
@@ -19,10 +19,10 @@ if TYPE_CHECKING:
|
||||
class GeoIPDict(TypedDict):
|
||||
"""GeoIP Details"""
|
||||
|
||||
continent: str | None
|
||||
country: str | None
|
||||
lat: float | None
|
||||
long: float | None
|
||||
continent: str
|
||||
country: str
|
||||
lat: float
|
||||
long: float
|
||||
city: str
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ class GeoIPContextProcessor(MMDBContextProcessor):
|
||||
except (GeoIP2Error, ValueError):
|
||||
return None
|
||||
|
||||
def city_to_dict(self, city: City | None) -> GeoIPDict | dict:
|
||||
def city_to_dict(self, city: City | None) -> GeoIPDict:
|
||||
"""Convert City to dict"""
|
||||
if not city:
|
||||
return {}
|
||||
|
||||
@@ -197,8 +197,7 @@ class AuditMiddleware:
|
||||
return
|
||||
if _CTX_IGNORE.get():
|
||||
return
|
||||
current_request = _CTX_REQUEST.get()
|
||||
if current_request is None or request.request_id != current_request.request_id:
|
||||
if request.request_id != _CTX_REQUEST.get().request_id:
|
||||
return
|
||||
user = self.get_user(request)
|
||||
|
||||
@@ -213,8 +212,7 @@ class AuditMiddleware:
|
||||
return
|
||||
if _CTX_IGNORE.get():
|
||||
return
|
||||
current_request = _CTX_REQUEST.get()
|
||||
if current_request is None or request.request_id != current_request.request_id:
|
||||
if request.request_id != _CTX_REQUEST.get().request_id:
|
||||
return
|
||||
user = self.get_user(request)
|
||||
|
||||
@@ -241,8 +239,7 @@ class AuditMiddleware:
|
||||
return
|
||||
if _CTX_IGNORE.get():
|
||||
return
|
||||
current_request = _CTX_REQUEST.get()
|
||||
if current_request is None or request.request_id != current_request.request_id:
|
||||
if request.request_id != _CTX_REQUEST.get().request_id:
|
||||
return
|
||||
user = self.get_user(request)
|
||||
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
# Generated by Django 5.1.11 on 2025-08-14 13:53
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_events", "0011_alter_systemtask_options"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="notificationtransport",
|
||||
name="email_subject_prefix",
|
||||
field=models.TextField(blank=True, default="authentik Notification: "),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="notificationtransport",
|
||||
name="email_template",
|
||||
field=models.TextField(default="email/event_notification.html"),
|
||||
),
|
||||
]
|
||||
@@ -1,16 +0,0 @@
|
||||
# Generated by Django 5.1.11 on 2025-07-28 15:05
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_events", "0012_notificationtransport_email_subject_prefix_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.DeleteModel(
|
||||
name="SystemTask",
|
||||
),
|
||||
]
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user