mirror of
https://github.com/goauthentik/authentik
synced 2026-05-06 07:02:51 +02:00
Compare commits
27 Commits
version/20
...
version/20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f1b7a9f934 | ||
|
|
4af75d0979 | ||
|
|
af0a314e0b | ||
|
|
6c7f901220 | ||
|
|
3570bfa39d | ||
|
|
35ab51e4c5 | ||
|
|
22eaf97d62 | ||
|
|
764b211bd4 | ||
|
|
7afc59d691 | ||
|
|
349572bfe4 | ||
|
|
bef55bc3a5 | ||
|
|
d86da24c01 | ||
|
|
25d0ee02a8 | ||
|
|
bccfb0b48c | ||
|
|
4b14eca2da | ||
|
|
7d5cfb6356 | ||
|
|
7ce46ccbe0 | ||
|
|
d0217c9135 | ||
|
|
d82ba344d9 | ||
|
|
01959132e8 | ||
|
|
9d81f0598c | ||
|
|
cbe429f3fa | ||
|
|
1cf0f57608 | ||
|
|
052da72acf | ||
|
|
9a1c76efe7 | ||
|
|
96b5bee912 | ||
|
|
09b3a1d0bd |
@@ -1,5 +1,5 @@
|
||||
[bumpversion]
|
||||
current_version = 2024.12.1
|
||||
current_version = 2024.12.3
|
||||
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*))?
|
||||
|
||||
@@ -35,14 +35,6 @@ runs:
|
||||
AUTHENTIK_OUTPOSTS__CONTAINER_IMAGE_BASE=ghcr.io/goauthentik/dev-%(type)s:gh-%(build_hash)s
|
||||
```
|
||||
|
||||
For arm64, use these values:
|
||||
|
||||
```shell
|
||||
AUTHENTIK_IMAGE=ghcr.io/goauthentik/dev-server
|
||||
AUTHENTIK_TAG=${{ inputs.tag }}-arm64
|
||||
AUTHENTIK_OUTPOSTS__CONTAINER_IMAGE_BASE=ghcr.io/goauthentik/dev-%(type)s:gh-%(build_hash)s
|
||||
```
|
||||
|
||||
Afterwards, run the upgrade commands from the latest release notes.
|
||||
</details>
|
||||
<details>
|
||||
@@ -60,18 +52,6 @@ runs:
|
||||
tag: ${{ inputs.tag }}
|
||||
```
|
||||
|
||||
For arm64, use these values:
|
||||
|
||||
```yaml
|
||||
authentik:
|
||||
outposts:
|
||||
container_image_base: ghcr.io/goauthentik/dev-%(type)s:gh-%(build_hash)s
|
||||
global:
|
||||
image:
|
||||
repository: ghcr.io/goauthentik/dev-server
|
||||
tag: ${{ inputs.tag }}-arm64
|
||||
```
|
||||
|
||||
Afterwards, run the upgrade commands from the latest release notes.
|
||||
</details>
|
||||
edit-mode: replace
|
||||
|
||||
14
.github/actions/docker-push-variables/action.yml
vendored
14
.github/actions/docker-push-variables/action.yml
vendored
@@ -9,6 +9,9 @@ inputs:
|
||||
image-arch:
|
||||
required: false
|
||||
description: "Docker image arch"
|
||||
release:
|
||||
required: true
|
||||
description: "True if this is a release build, false if this is a dev/PR build"
|
||||
|
||||
outputs:
|
||||
shouldPush:
|
||||
@@ -29,15 +32,24 @@ outputs:
|
||||
imageTags:
|
||||
description: "Docker image tags"
|
||||
value: ${{ steps.ev.outputs.imageTags }}
|
||||
imageTagsJSON:
|
||||
description: "Docker image tags, as a JSON array"
|
||||
value: ${{ steps.ev.outputs.imageTagsJSON }}
|
||||
attestImageNames:
|
||||
description: "Docker image names used for attestation"
|
||||
value: ${{ steps.ev.outputs.attestImageNames }}
|
||||
cacheTo:
|
||||
description: "cache-to value for the docker build step"
|
||||
value: ${{ steps.ev.outputs.cacheTo }}
|
||||
imageMainTag:
|
||||
description: "Docker image main tag"
|
||||
value: ${{ steps.ev.outputs.imageMainTag }}
|
||||
imageMainName:
|
||||
description: "Docker image main name"
|
||||
value: ${{ steps.ev.outputs.imageMainName }}
|
||||
imageBuildArgs:
|
||||
description: "Docker image build args"
|
||||
value: ${{ steps.ev.outputs.imageBuildArgs }}
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
@@ -48,6 +60,8 @@ runs:
|
||||
env:
|
||||
IMAGE_NAME: ${{ inputs.image-name }}
|
||||
IMAGE_ARCH: ${{ inputs.image-arch }}
|
||||
RELEASE: ${{ inputs.release }}
|
||||
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
||||
REF: ${{ github.ref }}
|
||||
run: |
|
||||
python3 ${{ github.action_path }}/push_vars.py
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import configparser
|
||||
import os
|
||||
from json import dumps
|
||||
from time import time
|
||||
|
||||
parser = configparser.ConfigParser()
|
||||
@@ -48,7 +49,7 @@ if is_release:
|
||||
]
|
||||
else:
|
||||
suffix = ""
|
||||
if image_arch and image_arch != "amd64":
|
||||
if image_arch:
|
||||
suffix = f"-{image_arch}"
|
||||
for name in image_names:
|
||||
image_tags += [
|
||||
@@ -70,12 +71,31 @@ def get_attest_image_names(image_with_tags: list[str]):
|
||||
return ",".join(set(image_tags))
|
||||
|
||||
|
||||
# Generate `cache-to` param
|
||||
cache_to = ""
|
||||
if should_push:
|
||||
_cache_tag = "buildcache"
|
||||
if image_arch:
|
||||
_cache_tag += f"-{image_arch}"
|
||||
cache_to = f"type=registry,ref={get_attest_image_names(image_tags)}:{_cache_tag},mode=max"
|
||||
|
||||
|
||||
image_build_args = []
|
||||
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)
|
||||
print(f"sha={sha}", file=_output)
|
||||
print(f"version={version}", file=_output)
|
||||
print(f"prerelease={prerelease}", file=_output)
|
||||
print(f"imageTags={','.join(image_tags)}", file=_output)
|
||||
print(f"imageTagsJSON={dumps(image_tags)}", file=_output)
|
||||
print(f"attestImageNames={get_attest_image_names(image_tags)}", file=_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={image_build_args}", file=_output)
|
||||
|
||||
11
.github/actions/docker-push-variables/test.sh
vendored
11
.github/actions/docker-push-variables/test.sh
vendored
@@ -1,7 +1,18 @@
|
||||
#!/bin/bash -x
|
||||
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
||||
# Non-pushing PR
|
||||
GITHUB_OUTPUT=/dev/stdout \
|
||||
GITHUB_REF=ref \
|
||||
GITHUB_SHA=sha \
|
||||
IMAGE_NAME=ghcr.io/goauthentik/server,beryju/authentik \
|
||||
GITHUB_REPOSITORY=goauthentik/authentik \
|
||||
python $SCRIPT_DIR/push_vars.py
|
||||
|
||||
# Pushing PR/main
|
||||
GITHUB_OUTPUT=/dev/stdout \
|
||||
GITHUB_REF=ref \
|
||||
GITHUB_SHA=sha \
|
||||
IMAGE_NAME=ghcr.io/goauthentik/server,beryju/authentik \
|
||||
GITHUB_REPOSITORY=goauthentik/authentik \
|
||||
DOCKER_USERNAME=foo \
|
||||
python $SCRIPT_DIR/push_vars.py
|
||||
|
||||
95
.github/workflows/_reusable-docker-build-single.yaml
vendored
Normal file
95
.github/workflows/_reusable-docker-build-single.yaml
vendored
Normal file
@@ -0,0 +1,95 @@
|
||||
# Re-usable workflow for a single-architecture build
|
||||
name: Single-arch Container build
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
image_name:
|
||||
required: true
|
||||
type: string
|
||||
image_arch:
|
||||
required: true
|
||||
type: string
|
||||
runs-on:
|
||||
required: true
|
||||
type: string
|
||||
registry_dockerhub:
|
||||
default: false
|
||||
type: boolean
|
||||
registry_ghcr:
|
||||
default: false
|
||||
type: boolean
|
||||
release:
|
||||
default: false
|
||||
type: boolean
|
||||
outputs:
|
||||
image-digest:
|
||||
value: ${{ jobs.build.outputs.image-digest }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build ${{ inputs.image_arch }}
|
||||
runs-on: ${{ inputs.runs-on }}
|
||||
outputs:
|
||||
image-digest: ${{ steps.push.outputs.digest }}
|
||||
permissions:
|
||||
# Needed to upload container images to ghcr.io
|
||||
packages: write
|
||||
# Needed for attestation
|
||||
id-token: write
|
||||
attestations: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: docker/setup-qemu-action@v3.3.0
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
- name: prepare variables
|
||||
uses: ./.github/actions/docker-push-variables
|
||||
id: ev
|
||||
env:
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
with:
|
||||
image-name: ${{ inputs.image_name }}
|
||||
image-arch: ${{ inputs.image_arch }}
|
||||
release: ${{ inputs.release }}
|
||||
- name: Login to Docker Hub
|
||||
if: ${{ inputs.registry_dockerhub }}
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
- name: Login to GitHub Container Registry
|
||||
if: ${{ inputs.registry_ghcr }}
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: make empty clients
|
||||
if: ${{ inputs.release }}
|
||||
run: |
|
||||
mkdir -p ./gen-ts-api
|
||||
mkdir -p ./gen-go-api
|
||||
- name: generate ts client
|
||||
if: ${{ !inputs.release }}
|
||||
run: make gen-client-ts
|
||||
- name: Build Docker Image
|
||||
uses: docker/build-push-action@v6
|
||||
id: push
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
secrets: |
|
||||
GEOIPUPDATE_ACCOUNT_ID=${{ secrets.GEOIPUPDATE_ACCOUNT_ID }}
|
||||
GEOIPUPDATE_LICENSE_KEY=${{ secrets.GEOIPUPDATE_LICENSE_KEY }}
|
||||
build-args: |
|
||||
${{ steps.ev.outputs.imageBuildArgs }}
|
||||
tags: ${{ steps.ev.outputs.imageTags }}
|
||||
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@v2
|
||||
id: attest
|
||||
with:
|
||||
subject-name: ${{ steps.ev.outputs.attestImageNames }}
|
||||
subject-digest: ${{ steps.push.outputs.digest }}
|
||||
push-to-registry: true
|
||||
102
.github/workflows/_reusable-docker-build.yaml
vendored
Normal file
102
.github/workflows/_reusable-docker-build.yaml
vendored
Normal file
@@ -0,0 +1,102 @@
|
||||
# Re-usable workflow for a multi-architecture build
|
||||
name: Multi-arch container build
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
image_name:
|
||||
required: true
|
||||
type: string
|
||||
registry_dockerhub:
|
||||
default: false
|
||||
type: boolean
|
||||
registry_ghcr:
|
||||
default: true
|
||||
type: boolean
|
||||
release:
|
||||
default: false
|
||||
type: boolean
|
||||
outputs: {}
|
||||
|
||||
jobs:
|
||||
build-server-amd64:
|
||||
uses: ./.github/workflows/_reusable-docker-build-single.yaml
|
||||
secrets: inherit
|
||||
with:
|
||||
image_name: ${{ inputs.image_name }}
|
||||
image_arch: amd64
|
||||
runs-on: ubuntu-latest
|
||||
registry_dockerhub: ${{ inputs.registry_dockerhub }}
|
||||
registry_ghcr: ${{ inputs.registry_ghcr }}
|
||||
release: ${{ inputs.release }}
|
||||
build-server-arm64:
|
||||
uses: ./.github/workflows/_reusable-docker-build-single.yaml
|
||||
secrets: inherit
|
||||
with:
|
||||
image_name: ${{ inputs.image_name }}
|
||||
image_arch: arm64
|
||||
runs-on: ubuntu-22.04-arm
|
||||
registry_dockerhub: ${{ inputs.registry_dockerhub }}
|
||||
registry_ghcr: ${{ inputs.registry_ghcr }}
|
||||
release: ${{ inputs.release }}
|
||||
get-tags:
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- build-server-amd64
|
||||
- build-server-arm64
|
||||
outputs:
|
||||
tags: ${{ steps.ev.outputs.imageTagsJSON }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: prepare variables
|
||||
uses: ./.github/actions/docker-push-variables
|
||||
id: ev
|
||||
env:
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
with:
|
||||
image-name: ${{ inputs.image_name }}
|
||||
merge-server:
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- get-tags
|
||||
- build-server-amd64
|
||||
- build-server-arm64
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
tag: ${{ fromJson(needs.get-tags.outputs.tags) }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: prepare variables
|
||||
uses: ./.github/actions/docker-push-variables
|
||||
id: ev
|
||||
env:
|
||||
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_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
- name: Login to GitHub Container Registry
|
||||
if: ${{ inputs.registry_ghcr }}
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- uses: int128/docker-manifest-create-action@v2
|
||||
id: build
|
||||
with:
|
||||
tags: ${{ matrix.tag }}
|
||||
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@v2
|
||||
id: attest
|
||||
with:
|
||||
subject-name: ${{ steps.ev.outputs.attestImageNames }}
|
||||
subject-digest: ${{ steps.build.outputs.digest }}
|
||||
push-to-registry: true
|
||||
64
.github/workflows/ci-main.yml
vendored
64
.github/workflows/ci-main.yml
vendored
@@ -223,68 +223,18 @@ jobs:
|
||||
with:
|
||||
jobs: ${{ toJSON(needs) }}
|
||||
build:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
arch:
|
||||
- amd64
|
||||
- arm64
|
||||
needs: ci-core-mark
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
# Needed to upload contianer images to ghcr.io
|
||||
# Needed to upload container images to ghcr.io
|
||||
packages: write
|
||||
# Needed for attestation
|
||||
id-token: write
|
||||
attestations: write
|
||||
timeout-minutes: 120
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3.2.0
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: prepare variables
|
||||
uses: ./.github/actions/docker-push-variables
|
||||
id: ev
|
||||
env:
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
with:
|
||||
image-name: ghcr.io/goauthentik/dev-server
|
||||
image-arch: ${{ matrix.arch }}
|
||||
- name: Login to Container Registry
|
||||
if: ${{ steps.ev.outputs.shouldPush == 'true' }}
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: generate ts client
|
||||
run: make gen-client-ts
|
||||
- name: Build Docker Image
|
||||
uses: docker/build-push-action@v6
|
||||
id: push
|
||||
with:
|
||||
context: .
|
||||
secrets: |
|
||||
GEOIPUPDATE_ACCOUNT_ID=${{ secrets.GEOIPUPDATE_ACCOUNT_ID }}
|
||||
GEOIPUPDATE_LICENSE_KEY=${{ secrets.GEOIPUPDATE_LICENSE_KEY }}
|
||||
tags: ${{ steps.ev.outputs.imageTags }}
|
||||
push: ${{ steps.ev.outputs.shouldPush == 'true' }}
|
||||
build-args: |
|
||||
GIT_BUILD_HASH=${{ steps.ev.outputs.sha }}
|
||||
cache-from: type=registry,ref=ghcr.io/goauthentik/dev-server:buildcache
|
||||
cache-to: ${{ steps.ev.outputs.shouldPush == 'true' && 'type=registry,ref=ghcr.io/goauthentik/dev-server:buildcache,mode=max' || '' }}
|
||||
platforms: linux/${{ matrix.arch }}
|
||||
- uses: actions/attest-build-provenance@v2
|
||||
id: attest
|
||||
if: ${{ steps.ev.outputs.shouldPush == 'true' }}
|
||||
with:
|
||||
subject-name: ${{ steps.ev.outputs.attestImageNames }}
|
||||
subject-digest: ${{ steps.push.outputs.digest }}
|
||||
push-to-registry: true
|
||||
needs: ci-core-mark
|
||||
uses: ./.github/workflows/_reusable-docker-build.yaml
|
||||
secrets: inherit
|
||||
with:
|
||||
image_name: ghcr.io/goauthentik/dev-server
|
||||
release: false
|
||||
pr-comment:
|
||||
needs:
|
||||
- build
|
||||
|
||||
2
.github/workflows/ci-outpost.yml
vendored
2
.github/workflows/ci-outpost.yml
vendored
@@ -72,7 +72,7 @@ jobs:
|
||||
- rac
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
# Needed to upload contianer images to ghcr.io
|
||||
# Needed to upload container images to ghcr.io
|
||||
packages: write
|
||||
# Needed for attestation
|
||||
id-token: write
|
||||
|
||||
63
.github/workflows/release-publish.yml
vendored
63
.github/workflows/release-publish.yml
vendored
@@ -7,64 +7,23 @@ on:
|
||||
|
||||
jobs:
|
||||
build-server:
|
||||
runs-on: ubuntu-latest
|
||||
uses: ./.github/workflows/_reusable-docker-build.yaml
|
||||
secrets: inherit
|
||||
permissions:
|
||||
# Needed to upload contianer images to ghcr.io
|
||||
# Needed to upload container images to ghcr.io
|
||||
packages: write
|
||||
# Needed for attestation
|
||||
id-token: write
|
||||
attestations: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3.2.0
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: prepare variables
|
||||
uses: ./.github/actions/docker-push-variables
|
||||
id: ev
|
||||
env:
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
with:
|
||||
image-name: ghcr.io/goauthentik/server,beryju/authentik
|
||||
- name: Docker Login Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: make empty clients
|
||||
run: |
|
||||
mkdir -p ./gen-ts-api
|
||||
mkdir -p ./gen-go-api
|
||||
- name: Build Docker Image
|
||||
uses: docker/build-push-action@v6
|
||||
id: push
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
secrets: |
|
||||
GEOIPUPDATE_ACCOUNT_ID=${{ secrets.GEOIPUPDATE_ACCOUNT_ID }}
|
||||
GEOIPUPDATE_LICENSE_KEY=${{ secrets.GEOIPUPDATE_LICENSE_KEY }}
|
||||
build-args: |
|
||||
VERSION=${{ github.ref }}
|
||||
tags: ${{ steps.ev.outputs.imageTags }}
|
||||
platforms: linux/amd64,linux/arm64
|
||||
- uses: actions/attest-build-provenance@v2
|
||||
id: attest
|
||||
with:
|
||||
subject-name: ${{ steps.ev.outputs.attestImageNames }}
|
||||
subject-digest: ${{ steps.push.outputs.digest }}
|
||||
push-to-registry: true
|
||||
with:
|
||||
image_name: ghcr.io/goauthentik/server,beryju/authentik
|
||||
release: true
|
||||
registry_dockerhub: true
|
||||
registry_ghcr: true
|
||||
build-outpost:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
# Needed to upload contianer images to ghcr.io
|
||||
# Needed to upload container images to ghcr.io
|
||||
packages: write
|
||||
# Needed for attestation
|
||||
id-token: write
|
||||
@@ -188,8 +147,8 @@ jobs:
|
||||
aws-region: ${{ env.AWS_REGION }}
|
||||
- name: Upload template
|
||||
run: |
|
||||
aws s3 cp website/docs/install-config/install/aws/template.yaml s3://authentik-cloudformation-templates/authentik.ecs.${{ github.ref }}.yaml
|
||||
aws s3 cp website/docs/install-config/install/aws/template.yaml s3://authentik-cloudformation-templates/authentik.ecs.latest.yaml
|
||||
aws s3 cp --acl=public-read website/docs/install-config/install/aws/template.yaml s3://authentik-cloudformation-templates/authentik.ecs.${{ github.ref }}.yaml
|
||||
aws s3 cp --acl=public-read website/docs/install-config/install/aws/template.yaml s3://authentik-cloudformation-templates/authentik.ecs.latest.yaml
|
||||
test-release:
|
||||
needs:
|
||||
- build-server
|
||||
|
||||
11
.github/workflows/release-tag.yml
vendored
11
.github/workflows/release-tag.yml
vendored
@@ -14,16 +14,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Pre-release test
|
||||
run: |
|
||||
echo "PG_PASS=$(openssl rand 32 | base64 -w 0)" >> .env
|
||||
echo "AUTHENTIK_SECRET_KEY=$(openssl rand 32 | base64 -w 0)" >> .env
|
||||
docker buildx install
|
||||
mkdir -p ./gen-ts-api
|
||||
docker build -t testing:latest .
|
||||
echo "AUTHENTIK_IMAGE=testing" >> .env
|
||||
echo "AUTHENTIK_TAG=latest" >> .env
|
||||
docker compose up --no-start
|
||||
docker compose start postgresql redis
|
||||
docker compose run -u root server test-all
|
||||
make test-docker
|
||||
- id: generate_token
|
||||
uses: tibdex/github-app-token@v2
|
||||
with:
|
||||
|
||||
32
Dockerfile
32
Dockerfile
@@ -94,7 +94,7 @@ RUN --mount=type=secret,id=GEOIPUPDATE_ACCOUNT_ID \
|
||||
/bin/sh -c "/usr/bin/entry.sh || echo 'Failed to get GeoIP database, disabling'; exit 0"
|
||||
|
||||
# Stage 5: Python dependencies
|
||||
FROM ghcr.io/goauthentik/fips-python:3.12.7-slim-bookworm-fips-full AS python-deps
|
||||
FROM ghcr.io/goauthentik/fips-python:3.12.8-slim-bookworm-fips AS python-deps
|
||||
|
||||
ARG TARGETARCH
|
||||
ARG TARGETVARIANT
|
||||
@@ -116,15 +116,30 @@ RUN --mount=type=bind,target=./pyproject.toml,src=./pyproject.toml \
|
||||
--mount=type=bind,target=./poetry.lock,src=./poetry.lock \
|
||||
--mount=type=cache,target=/root/.cache/pip \
|
||||
--mount=type=cache,target=/root/.cache/pypoetry \
|
||||
pip install --no-cache cffi && \
|
||||
apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
build-essential libffi-dev \
|
||||
# Required for cryptography
|
||||
curl pkg-config \
|
||||
# Required for lxml
|
||||
libxslt-dev zlib1g-dev \
|
||||
# Required for xmlsec
|
||||
libltdl-dev \
|
||||
# Required for kadmin
|
||||
sccache clang && \
|
||||
curl https://sh.rustup.rs -sSf | sh -s -- -y && \
|
||||
. "$HOME/.cargo/env" && \
|
||||
python -m venv /ak-root/venv/ && \
|
||||
bash -c "source ${VENV_PATH}/bin/activate && \
|
||||
pip3 install --upgrade pip && \
|
||||
pip3 install poetry && \
|
||||
pip3 install --upgrade pip poetry && \
|
||||
poetry config --local installer.no-binary cryptography,xmlsec,lxml,python-kadmin-rs && \
|
||||
poetry install --only=main --no-ansi --no-interaction --no-root && \
|
||||
pip install --force-reinstall /wheels/*"
|
||||
pip uninstall cryptography -y && \
|
||||
poetry install --only=main --no-ansi --no-interaction --no-root"
|
||||
|
||||
# Stage 6: Run
|
||||
FROM ghcr.io/goauthentik/fips-python:3.12.7-slim-bookworm-fips-full AS final-image
|
||||
FROM ghcr.io/goauthentik/fips-python:3.12.8-slim-bookworm-fips AS final-image
|
||||
|
||||
ARG VERSION
|
||||
ARG GIT_BUILD_HASH
|
||||
@@ -141,7 +156,7 @@ WORKDIR /
|
||||
# We cannot cache this layer otherwise we'll end up with a bigger image
|
||||
RUN apt-get update && \
|
||||
# Required for runtime
|
||||
apt-get install -y --no-install-recommends libpq5 libmaxminddb0 ca-certificates libkrb5-3 libkadm5clnt-mit12 libkdb5-10 && \
|
||||
apt-get install -y --no-install-recommends libpq5 libmaxminddb0 ca-certificates libkrb5-3 libkadm5clnt-mit12 libkdb5-10 libltdl7 libxslt1.1 && \
|
||||
# Required for bootstrap & healtcheck
|
||||
apt-get install -y --no-install-recommends runit && \
|
||||
apt-get clean && \
|
||||
@@ -176,9 +191,8 @@ ENV TMPDIR=/dev/shm/ \
|
||||
PYTHONUNBUFFERED=1 \
|
||||
PATH="/ak-root/venv/bin:/lifecycle:$PATH" \
|
||||
VENV_PATH="/ak-root/venv" \
|
||||
POETRY_VIRTUALENVS_CREATE=false
|
||||
|
||||
ENV GOFIPS=1
|
||||
POETRY_VIRTUALENVS_CREATE=false \
|
||||
GOFIPS=1
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=30s --start-period=60s --retries=3 CMD [ "ak", "healthcheck" ]
|
||||
|
||||
|
||||
12
Makefile
12
Makefile
@@ -45,15 +45,6 @@ help: ## Show this help
|
||||
go-test:
|
||||
go test -timeout 0 -v -race -cover ./...
|
||||
|
||||
test-docker: ## Run all tests in a docker-compose
|
||||
echo "PG_PASS=$(shell openssl rand 32 | base64 -w 0)" >> .env
|
||||
echo "AUTHENTIK_SECRET_KEY=$(shell openssl rand 32 | base64 -w 0)" >> .env
|
||||
docker compose pull -q
|
||||
docker compose up --no-start
|
||||
docker compose start postgresql redis
|
||||
docker compose run -u root server test-all
|
||||
rm -f .env
|
||||
|
||||
test: ## Run the server tests and produce a coverage report (locally)
|
||||
coverage run manage.py test --keepdb authentik
|
||||
coverage html
|
||||
@@ -263,6 +254,9 @@ docker: ## Build a docker image of the current source tree
|
||||
mkdir -p ${GEN_API_TS}
|
||||
DOCKER_BUILDKIT=1 docker build . --progress plain --tag ${DOCKER_IMAGE}
|
||||
|
||||
test-docker:
|
||||
./scripts/test_docker.sh
|
||||
|
||||
#########################
|
||||
## CI
|
||||
#########################
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
from os import environ
|
||||
|
||||
__version__ = "2024.12.1"
|
||||
__version__ = "2024.12.3"
|
||||
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"
|
||||
|
||||
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
"""Application Roles API Viewset"""
|
||||
|
||||
from django.http import HttpRequest
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from rest_framework.exceptions import ValidationError
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
|
||||
from authentik.blueprints.v1.importer import SERIALIZER_CONTEXT_BLUEPRINT
|
||||
from authentik.core.api.used_by import UsedByMixin
|
||||
from authentik.core.api.utils import ModelSerializer
|
||||
from authentik.core.models import (
|
||||
Application,
|
||||
ApplicationEntitlement,
|
||||
User,
|
||||
)
|
||||
|
||||
|
||||
@@ -18,7 +19,10 @@ class ApplicationEntitlementSerializer(ModelSerializer):
|
||||
|
||||
def validate_app(self, app: Application) -> Application:
|
||||
"""Ensure user has permission to view"""
|
||||
user: User = self._context["request"].user
|
||||
request: HttpRequest = self.context.get("request")
|
||||
if not request and SERIALIZER_CONTEXT_BLUEPRINT in self.context:
|
||||
return app
|
||||
user = request.user
|
||||
if user.has_perm("view_application", app) or user.has_perm(
|
||||
"authentik_core.view_application"
|
||||
):
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from drf_spectacular.types import OpenApiTypes
|
||||
from drf_spectacular.utils import OpenApiParameter, extend_schema
|
||||
from guardian.shortcuts import get_objects_for_user
|
||||
from rest_framework.fields import (
|
||||
BooleanField,
|
||||
CharField,
|
||||
@@ -16,7 +17,6 @@ from rest_framework.viewsets import ViewSet
|
||||
|
||||
from authentik.core.api.utils import MetaNameSerializer
|
||||
from authentik.enterprise.stages.authenticator_endpoint_gdtc.models import EndpointDevice
|
||||
from authentik.rbac.decorators import permission_required
|
||||
from authentik.stages.authenticator import device_classes, devices_for_user
|
||||
from authentik.stages.authenticator.models import Device
|
||||
from authentik.stages.authenticator_webauthn.models import WebAuthnDevice
|
||||
@@ -73,7 +73,9 @@ class AdminDeviceViewSet(ViewSet):
|
||||
def get_devices(self, **kwargs):
|
||||
"""Get all devices in all child classes"""
|
||||
for model in device_classes():
|
||||
device_set = model.objects.filter(**kwargs)
|
||||
device_set = get_objects_for_user(
|
||||
self.request.user, f"{model._meta.app_label}.view_{model._meta.model_name}", model
|
||||
).filter(**kwargs)
|
||||
yield from device_set
|
||||
|
||||
@extend_schema(
|
||||
@@ -86,10 +88,6 @@ class AdminDeviceViewSet(ViewSet):
|
||||
],
|
||||
responses={200: DeviceSerializer(many=True)},
|
||||
)
|
||||
@permission_required(
|
||||
None,
|
||||
[f"{model._meta.app_label}.view_{model._meta.model_name}" for model in device_classes()],
|
||||
)
|
||||
def list(self, request: Request) -> Response:
|
||||
"""Get all devices for current user"""
|
||||
kwargs = {}
|
||||
|
||||
@@ -159,9 +159,9 @@ class ConnectionToken(ExpiringModel):
|
||||
default_settings["port"] = str(port)
|
||||
else:
|
||||
default_settings["hostname"] = self.endpoint.host
|
||||
default_settings["client-name"] = "authentik"
|
||||
# default_settings["enable-drive"] = "true"
|
||||
# default_settings["drive-name"] = "authentik"
|
||||
if self.endpoint.protocol == Protocols.RDP:
|
||||
default_settings["resize-method"] = "display-update"
|
||||
default_settings["client-name"] = f"authentik - {self.session.user}"
|
||||
settings = {}
|
||||
always_merger.merge(settings, default_settings)
|
||||
always_merger.merge(settings, self.endpoint.provider.settings)
|
||||
|
||||
@@ -50,9 +50,10 @@ class TestModels(TransactionTestCase):
|
||||
{
|
||||
"hostname": self.endpoint.host.split(":")[0],
|
||||
"port": "1324",
|
||||
"client-name": "authentik",
|
||||
"client-name": f"authentik - {self.user}",
|
||||
"drive-path": path,
|
||||
"create-drive-path": "true",
|
||||
"resize-method": "display-update",
|
||||
},
|
||||
)
|
||||
# Set settings in provider
|
||||
@@ -63,10 +64,11 @@ class TestModels(TransactionTestCase):
|
||||
{
|
||||
"hostname": self.endpoint.host.split(":")[0],
|
||||
"port": "1324",
|
||||
"client-name": "authentik",
|
||||
"client-name": f"authentik - {self.user}",
|
||||
"drive-path": path,
|
||||
"create-drive-path": "true",
|
||||
"level": "provider",
|
||||
"resize-method": "display-update",
|
||||
},
|
||||
)
|
||||
# Set settings in endpoint
|
||||
@@ -79,10 +81,11 @@ class TestModels(TransactionTestCase):
|
||||
{
|
||||
"hostname": self.endpoint.host.split(":")[0],
|
||||
"port": "1324",
|
||||
"client-name": "authentik",
|
||||
"client-name": f"authentik - {self.user}",
|
||||
"drive-path": path,
|
||||
"create-drive-path": "true",
|
||||
"level": "endpoint",
|
||||
"resize-method": "display-update",
|
||||
},
|
||||
)
|
||||
# Set settings in token
|
||||
@@ -95,10 +98,11 @@ class TestModels(TransactionTestCase):
|
||||
{
|
||||
"hostname": self.endpoint.host.split(":")[0],
|
||||
"port": "1324",
|
||||
"client-name": "authentik",
|
||||
"client-name": f"authentik - {self.user}",
|
||||
"drive-path": path,
|
||||
"create-drive-path": "true",
|
||||
"level": "token",
|
||||
"resize-method": "display-update",
|
||||
},
|
||||
)
|
||||
# Set settings in property mapping (provider)
|
||||
@@ -114,10 +118,11 @@ class TestModels(TransactionTestCase):
|
||||
{
|
||||
"hostname": self.endpoint.host.split(":")[0],
|
||||
"port": "1324",
|
||||
"client-name": "authentik",
|
||||
"client-name": f"authentik - {self.user}",
|
||||
"drive-path": path,
|
||||
"create-drive-path": "true",
|
||||
"level": "property_mapping_provider",
|
||||
"resize-method": "display-update",
|
||||
},
|
||||
)
|
||||
# Set settings in property mapping (endpoint)
|
||||
@@ -135,11 +140,12 @@ class TestModels(TransactionTestCase):
|
||||
{
|
||||
"hostname": self.endpoint.host.split(":")[0],
|
||||
"port": "1324",
|
||||
"client-name": "authentik",
|
||||
"client-name": f"authentik - {self.user}",
|
||||
"drive-path": path,
|
||||
"create-drive-path": "true",
|
||||
"level": "property_mapping_endpoint",
|
||||
"foo": "true",
|
||||
"bar": "6",
|
||||
"resize-method": "display-update",
|
||||
},
|
||||
)
|
||||
|
||||
@@ -109,6 +109,8 @@ class FlowPlan:
|
||||
|
||||
def pop(self):
|
||||
"""Pop next pending stage from bottom of list"""
|
||||
if not self.markers and not self.bindings:
|
||||
return
|
||||
self.markers.pop(0)
|
||||
self.bindings.pop(0)
|
||||
|
||||
@@ -156,8 +158,13 @@ class FlowPlan:
|
||||
final_stage: type[StageView] = self.bindings[-1].stage.view
|
||||
temp_exec = FlowExecutorView(flow=flow, request=request, plan=self)
|
||||
temp_exec.current_stage = self.bindings[-1].stage
|
||||
temp_exec.current_stage_view = final_stage
|
||||
temp_exec.setup(request, flow.slug)
|
||||
stage = final_stage(request=request, executor=temp_exec)
|
||||
return stage.dispatch(request)
|
||||
response = stage.dispatch(request)
|
||||
# Ensure we clean the flow state we have in the session before we redirect away
|
||||
temp_exec.stage_ok()
|
||||
return response
|
||||
|
||||
return redirect_with_qs(
|
||||
"authentik_core:if-flow",
|
||||
|
||||
@@ -103,7 +103,7 @@ class FlowExecutorView(APIView):
|
||||
|
||||
permission_classes = [AllowAny]
|
||||
|
||||
flow: Flow
|
||||
flow: Flow = None
|
||||
|
||||
plan: FlowPlan | None = None
|
||||
current_binding: FlowStageBinding | None = None
|
||||
@@ -114,7 +114,8 @@ class FlowExecutorView(APIView):
|
||||
|
||||
def setup(self, request: HttpRequest, flow_slug: str):
|
||||
super().setup(request, flow_slug=flow_slug)
|
||||
self.flow = get_object_or_404(Flow.objects.select_related(), slug=flow_slug)
|
||||
if not self.flow:
|
||||
self.flow = get_object_or_404(Flow.objects.select_related(), slug=flow_slug)
|
||||
self._logger = get_logger().bind(flow_slug=flow_slug)
|
||||
set_tag("authentik.flow", self.flow.slug)
|
||||
|
||||
|
||||
37
authentik/lib/api.py
Normal file
37
authentik/lib/api.py
Normal file
@@ -0,0 +1,37 @@
|
||||
from collections.abc import Callable, Sequence
|
||||
from typing import Self
|
||||
from uuid import UUID
|
||||
|
||||
from django.db.models import Model, Q, QuerySet, UUIDField
|
||||
from django.shortcuts import get_object_or_404
|
||||
|
||||
|
||||
class MultipleFieldLookupMixin:
|
||||
"""Helper mixin class to add support for multiple lookup_fields.
|
||||
`lookup_fields` needs to be set which specifies the actual fields to query, `lookup_field`
|
||||
is only used to generate the URL."""
|
||||
|
||||
lookup_field: str
|
||||
lookup_fields: str | Sequence[str]
|
||||
|
||||
get_queryset: Callable[[Self], QuerySet]
|
||||
filter_queryset: Callable[[Self, QuerySet], QuerySet]
|
||||
|
||||
def get_object(self):
|
||||
queryset: QuerySet = self.get_queryset()
|
||||
queryset = self.filter_queryset(queryset)
|
||||
if isinstance(self.lookup_fields, str):
|
||||
self.lookup_fields = [self.lookup_fields]
|
||||
query = Q()
|
||||
model: Model = queryset.model
|
||||
for field in self.lookup_fields:
|
||||
field_inst = model._meta.get_field(field)
|
||||
# Sanity check, if the field we're filtering again, only apply the filter if
|
||||
# our value looks like a UUID
|
||||
if isinstance(field_inst, UUIDField):
|
||||
try:
|
||||
UUID(self.kwargs[self.lookup_field])
|
||||
except ValueError:
|
||||
continue
|
||||
query |= Q(**{field: self.kwargs[self.lookup_field]})
|
||||
return get_object_or_404(queryset, query)
|
||||
@@ -283,7 +283,8 @@ class ConfigLoader:
|
||||
def get_optional_int(self, path: str, default=None) -> int | None:
|
||||
"""Wrapper for get that converts value into int or None if set"""
|
||||
value = self.get(path, default)
|
||||
|
||||
if value is UNSET:
|
||||
return default
|
||||
try:
|
||||
return int(value)
|
||||
except (ValueError, TypeError) as exc:
|
||||
|
||||
@@ -499,11 +499,11 @@ class OAuthFulfillmentStage(StageView):
|
||||
)
|
||||
|
||||
challenge.is_valid()
|
||||
|
||||
self.executor.stage_ok()
|
||||
return HttpChallengeResponse(
|
||||
challenge=challenge,
|
||||
)
|
||||
|
||||
self.executor.stage_ok()
|
||||
return HttpResponseRedirectScheme(uri, allowed_schemes=[parsed.scheme])
|
||||
|
||||
def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
||||
|
||||
@@ -256,7 +256,7 @@ class AssertionProcessor:
|
||||
assertion.attrib["IssueInstant"] = self._issue_instant
|
||||
assertion.append(self.get_issuer())
|
||||
|
||||
if self.provider.signing_kp:
|
||||
if self.provider.signing_kp and self.provider.sign_assertion:
|
||||
sign_algorithm_transform = SIGN_ALGORITHM_TRANSFORM_MAP.get(
|
||||
self.provider.signature_algorithm, xmlsec.constants.TransformRsaSha1
|
||||
)
|
||||
@@ -295,6 +295,18 @@ class AssertionProcessor:
|
||||
|
||||
response.append(self.get_issuer())
|
||||
|
||||
if self.provider.signing_kp and self.provider.sign_response:
|
||||
sign_algorithm_transform = SIGN_ALGORITHM_TRANSFORM_MAP.get(
|
||||
self.provider.signature_algorithm, xmlsec.constants.TransformRsaSha1
|
||||
)
|
||||
signature = xmlsec.template.create(
|
||||
response,
|
||||
xmlsec.constants.TransformExclC14N,
|
||||
sign_algorithm_transform,
|
||||
ns=xmlsec.constants.DSigNs,
|
||||
)
|
||||
response.append(signature)
|
||||
|
||||
status = SubElement(response, f"{{{NS_SAML_PROTOCOL}}}Status")
|
||||
status_code = SubElement(status, f"{{{NS_SAML_PROTOCOL}}}StatusCode")
|
||||
status_code.attrib["Value"] = "urn:oasis:names:tc:SAML:2.0:status:Success"
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
|
||||
from base64 import b64encode
|
||||
|
||||
from defusedxml.lxml import fromstring
|
||||
from django.http.request import QueryDict
|
||||
from django.test import TestCase
|
||||
from lxml import etree # nosec
|
||||
|
||||
from authentik.blueprints.tests import apply_blueprint
|
||||
from authentik.core.tests.utils import create_test_admin_user, create_test_cert, create_test_flow
|
||||
@@ -11,12 +13,14 @@ from authentik.crypto.models import CertificateKeyPair
|
||||
from authentik.events.models import Event, EventAction
|
||||
from authentik.lib.generators import generate_id
|
||||
from authentik.lib.tests.utils import get_request
|
||||
from authentik.lib.xml import lxml_from_string
|
||||
from authentik.providers.saml.models import SAMLPropertyMapping, SAMLProvider
|
||||
from authentik.providers.saml.processors.assertion import AssertionProcessor
|
||||
from authentik.providers.saml.processors.authn_request_parser import AuthNRequestParser
|
||||
from authentik.sources.saml.exceptions import MismatchedRequestID
|
||||
from authentik.sources.saml.models import SAMLSource
|
||||
from authentik.sources.saml.processors.constants import (
|
||||
NS_MAP,
|
||||
SAML_BINDING_REDIRECT,
|
||||
SAML_NAME_ID_FORMAT_EMAIL,
|
||||
SAML_NAME_ID_FORMAT_UNSPECIFIED,
|
||||
@@ -185,6 +189,19 @@ class TestAuthNRequest(TestCase):
|
||||
self.assertEqual(response.count(response_proc._assertion_id), 2)
|
||||
self.assertEqual(response.count(response_proc._response_id), 2)
|
||||
|
||||
schema = etree.XMLSchema(
|
||||
etree.parse("schemas/saml-schema-protocol-2.0.xsd", parser=etree.XMLParser()) # nosec
|
||||
)
|
||||
self.assertTrue(schema.validate(lxml_from_string(response)))
|
||||
|
||||
response_xml = fromstring(response)
|
||||
self.assertEqual(
|
||||
len(response_xml.xpath("//saml:Assertion/ds:Signature", namespaces=NS_MAP)), 1
|
||||
)
|
||||
self.assertEqual(
|
||||
len(response_xml.xpath("//samlp:Response/ds:Signature", namespaces=NS_MAP)), 1
|
||||
)
|
||||
|
||||
# Now parse the response (source)
|
||||
http_request.POST = QueryDict(mutable=True)
|
||||
http_request.POST["SAMLResponse"] = b64encode(response.encode()).decode()
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
|
||||
from django.apps import apps
|
||||
from django.contrib.auth.models import Permission
|
||||
from django.db.models import QuerySet
|
||||
from django.db.models import Q, QuerySet
|
||||
from django_filters.filters import ModelChoiceFilter
|
||||
from django_filters.filterset import FilterSet
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from rest_framework.exceptions import ValidationError
|
||||
from rest_framework.fields import (
|
||||
CharField,
|
||||
@@ -13,8 +14,11 @@ from rest_framework.fields import (
|
||||
ReadOnlyField,
|
||||
SerializerMethodField,
|
||||
)
|
||||
from rest_framework.filters import OrderingFilter, SearchFilter
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.viewsets import ReadOnlyModelViewSet
|
||||
|
||||
from authentik.blueprints.v1.importer import excluded_models
|
||||
from authentik.core.api.utils import ModelSerializer, PassiveSerializer
|
||||
from authentik.core.models import User
|
||||
from authentik.lib.validators import RequiredTogetherValidator
|
||||
@@ -92,7 +96,9 @@ class RBACPermissionViewSet(ReadOnlyModelViewSet):
|
||||
queryset = Permission.objects.none()
|
||||
serializer_class = PermissionSerializer
|
||||
ordering = ["name"]
|
||||
filter_backends = [DjangoFilterBackend, OrderingFilter, SearchFilter]
|
||||
filterset_class = PermissionFilter
|
||||
permission_classes = [IsAuthenticated]
|
||||
search_fields = [
|
||||
"codename",
|
||||
"content_type__model",
|
||||
@@ -100,13 +106,13 @@ class RBACPermissionViewSet(ReadOnlyModelViewSet):
|
||||
]
|
||||
|
||||
def get_queryset(self) -> QuerySet:
|
||||
return (
|
||||
Permission.objects.all()
|
||||
.select_related("content_type")
|
||||
.filter(
|
||||
content_type__app_label__startswith="authentik",
|
||||
query = Q()
|
||||
for model in excluded_models():
|
||||
query |= Q(
|
||||
content_type__app_label=model._meta.app_label,
|
||||
content_type__model=model._meta.model_name,
|
||||
)
|
||||
)
|
||||
return Permission.objects.all().select_related("content_type").exclude(query)
|
||||
|
||||
|
||||
class PermissionAssignSerializer(PassiveSerializer):
|
||||
|
||||
@@ -13,6 +13,7 @@ from authentik.core.api.sources import SourceSerializer
|
||||
from authentik.core.api.used_by import UsedByMixin
|
||||
from authentik.core.api.utils import PassiveSerializer
|
||||
from authentik.events.api.tasks import SystemTaskSerializer
|
||||
from authentik.lib.api import MultipleFieldLookupMixin
|
||||
from authentik.sources.kerberos.models import KerberosSource
|
||||
from authentik.sources.kerberos.tasks import CACHE_KEY_STATUS
|
||||
|
||||
@@ -59,12 +60,13 @@ class KerberosSyncStatusSerializer(PassiveSerializer):
|
||||
tasks = SystemTaskSerializer(many=True, read_only=True)
|
||||
|
||||
|
||||
class KerberosSourceViewSet(UsedByMixin, ModelViewSet):
|
||||
class KerberosSourceViewSet(MultipleFieldLookupMixin, UsedByMixin, ModelViewSet):
|
||||
"""Kerberos Source Viewset"""
|
||||
|
||||
queryset = KerberosSource.objects.all()
|
||||
serializer_class = KerberosSourceSerializer
|
||||
lookup_field = "slug"
|
||||
lookup_fields = ["slug", "pbm_uuid"]
|
||||
filterset_fields = [
|
||||
"name",
|
||||
"slug",
|
||||
|
||||
@@ -38,7 +38,9 @@ class KerberosBackend(InbuiltBackend):
|
||||
self, username: str, realm: str | None, password: str, **filters
|
||||
) -> tuple[User | None, KerberosSource | None]:
|
||||
sources = KerberosSource.objects.filter(enabled=True)
|
||||
user = User.objects.filter(usersourceconnection__source__in=sources, **filters).first()
|
||||
user = User.objects.filter(
|
||||
usersourceconnection__source__in=sources, username=username, **filters
|
||||
).first()
|
||||
|
||||
if user is not None:
|
||||
# User found, let's get its connections for the sources that are available
|
||||
@@ -77,7 +79,7 @@ class KerberosBackend(InbuiltBackend):
|
||||
password, sender=user_source_connection.source
|
||||
)
|
||||
user_source_connection.user.save()
|
||||
return user, user_source_connection.source
|
||||
return user_source_connection.user, user_source_connection.source
|
||||
# Password doesn't match, onto next source
|
||||
LOGGER.debug(
|
||||
"failed to kinit, password invalid",
|
||||
|
||||
@@ -18,6 +18,7 @@ from authentik.core.api.property_mappings import PropertyMappingFilterSet, Prope
|
||||
from authentik.core.api.sources import SourceSerializer
|
||||
from authentik.core.api.used_by import UsedByMixin
|
||||
from authentik.crypto.models import CertificateKeyPair
|
||||
from authentik.lib.api import MultipleFieldLookupMixin
|
||||
from authentik.lib.sync.outgoing.api import SyncStatusSerializer
|
||||
from authentik.sources.ldap.models import LDAPSource, LDAPSourcePropertyMapping
|
||||
from authentik.sources.ldap.tasks import CACHE_KEY_STATUS, SYNC_CLASSES
|
||||
@@ -103,12 +104,13 @@ class LDAPSourceSerializer(SourceSerializer):
|
||||
extra_kwargs = {"bind_password": {"write_only": True}}
|
||||
|
||||
|
||||
class LDAPSourceViewSet(UsedByMixin, ModelViewSet):
|
||||
class LDAPSourceViewSet(MultipleFieldLookupMixin, UsedByMixin, ModelViewSet):
|
||||
"""LDAP Source Viewset"""
|
||||
|
||||
queryset = LDAPSource.objects.all()
|
||||
serializer_class = LDAPSourceSerializer
|
||||
lookup_field = "slug"
|
||||
lookup_fields = ["slug", "pbm_uuid"]
|
||||
filterset_fields = [
|
||||
"name",
|
||||
"slug",
|
||||
|
||||
@@ -16,6 +16,7 @@ from rest_framework.viewsets import ModelViewSet
|
||||
from authentik.core.api.sources import SourceSerializer
|
||||
from authentik.core.api.used_by import UsedByMixin
|
||||
from authentik.core.api.utils import PassiveSerializer
|
||||
from authentik.lib.api import MultipleFieldLookupMixin
|
||||
from authentik.lib.utils.http import get_http_session
|
||||
from authentik.sources.oauth.models import OAuthSource
|
||||
from authentik.sources.oauth.types.registry import SourceType, registry
|
||||
@@ -170,12 +171,13 @@ class OAuthSourceFilter(FilterSet):
|
||||
]
|
||||
|
||||
|
||||
class OAuthSourceViewSet(UsedByMixin, ModelViewSet):
|
||||
class OAuthSourceViewSet(MultipleFieldLookupMixin, UsedByMixin, ModelViewSet):
|
||||
"""Source Viewset"""
|
||||
|
||||
queryset = OAuthSource.objects.all()
|
||||
serializer_class = OAuthSourceSerializer
|
||||
lookup_field = "slug"
|
||||
lookup_fields = ["slug", "pbm_uuid"]
|
||||
filterset_class = OAuthSourceFilter
|
||||
search_fields = ["name", "slug"]
|
||||
ordering = ["name"]
|
||||
|
||||
@@ -18,6 +18,7 @@ from authentik.core.api.used_by import UsedByMixin
|
||||
from authentik.core.api.utils import PassiveSerializer
|
||||
from authentik.flows.challenge import RedirectChallenge
|
||||
from authentik.flows.views.executor import to_stage_response
|
||||
from authentik.lib.api import MultipleFieldLookupMixin
|
||||
from authentik.rbac.decorators import permission_required
|
||||
from authentik.sources.plex.models import PlexSource, UserPlexSourceConnection
|
||||
from authentik.sources.plex.plex import PlexAuth, PlexSourceFlowManager
|
||||
@@ -45,12 +46,13 @@ class PlexTokenRedeemSerializer(PassiveSerializer):
|
||||
plex_token = CharField()
|
||||
|
||||
|
||||
class PlexSourceViewSet(UsedByMixin, ModelViewSet):
|
||||
class PlexSourceViewSet(MultipleFieldLookupMixin, UsedByMixin, ModelViewSet):
|
||||
"""Plex source Viewset"""
|
||||
|
||||
queryset = PlexSource.objects.all()
|
||||
serializer_class = PlexSourceSerializer
|
||||
lookup_field = "slug"
|
||||
lookup_fields = ["slug", "pbm_uuid"]
|
||||
filterset_fields = [
|
||||
"name",
|
||||
"slug",
|
||||
|
||||
@@ -9,6 +9,7 @@ from rest_framework.viewsets import ModelViewSet
|
||||
|
||||
from authentik.core.api.sources import SourceSerializer
|
||||
from authentik.core.api.used_by import UsedByMixin
|
||||
from authentik.lib.api import MultipleFieldLookupMixin
|
||||
from authentik.providers.saml.api.providers import SAMLMetadataSerializer
|
||||
from authentik.sources.saml.models import SAMLSource
|
||||
from authentik.sources.saml.processors.metadata import MetadataProcessor
|
||||
@@ -37,12 +38,13 @@ class SAMLSourceSerializer(SourceSerializer):
|
||||
]
|
||||
|
||||
|
||||
class SAMLSourceViewSet(UsedByMixin, ModelViewSet):
|
||||
class SAMLSourceViewSet(MultipleFieldLookupMixin, UsedByMixin, ModelViewSet):
|
||||
"""SAMLSource Viewset"""
|
||||
|
||||
queryset = SAMLSource.objects.all()
|
||||
serializer_class = SAMLSourceSerializer
|
||||
lookup_field = "slug"
|
||||
lookup_fields = ["slug", "pbm_uuid"]
|
||||
filterset_fields = [
|
||||
"name",
|
||||
"slug",
|
||||
|
||||
@@ -7,6 +7,7 @@ from rest_framework.viewsets import ModelViewSet
|
||||
from authentik.core.api.sources import SourceSerializer
|
||||
from authentik.core.api.tokens import TokenSerializer
|
||||
from authentik.core.api.used_by import UsedByMixin
|
||||
from authentik.lib.api import MultipleFieldLookupMixin
|
||||
from authentik.sources.scim.models import SCIMSource
|
||||
|
||||
|
||||
@@ -47,12 +48,13 @@ class SCIMSourceSerializer(SourceSerializer):
|
||||
]
|
||||
|
||||
|
||||
class SCIMSourceViewSet(UsedByMixin, ModelViewSet):
|
||||
class SCIMSourceViewSet(MultipleFieldLookupMixin, UsedByMixin, ModelViewSet):
|
||||
"""SCIMSource Viewset"""
|
||||
|
||||
queryset = SCIMSource.objects.all()
|
||||
serializer_class = SCIMSourceSerializer
|
||||
lookup_field = "slug"
|
||||
lookup_fields = ["slug", "pbm_uuid"]
|
||||
filterset_fields = ["name", "slug"]
|
||||
search_fields = ["name", "slug", "token__identifier", "token__user__username"]
|
||||
ordering = ["name"]
|
||||
|
||||
@@ -20,7 +20,7 @@ from authentik.flows.planner import (
|
||||
FlowPlanner,
|
||||
)
|
||||
from authentik.flows.stage import ChallengeStageView
|
||||
from authentik.flows.views.executor import SESSION_KEY_PLAN, InvalidStageError
|
||||
from authentik.flows.views.executor import SESSION_KEY_GET, SESSION_KEY_PLAN, InvalidStageError
|
||||
from authentik.lib.utils.urls import reverse_with_qs
|
||||
from authentik.stages.redirect.models import RedirectMode, RedirectStage
|
||||
|
||||
@@ -72,7 +72,9 @@ class RedirectStageView(ChallengeStageView):
|
||||
self.request.session[SESSION_KEY_PLAN] = plan
|
||||
kwargs = self.executor.kwargs
|
||||
kwargs.update({"flow_slug": flow.slug})
|
||||
return reverse_with_qs("authentik_core:if-flow", self.request.GET, kwargs=kwargs)
|
||||
return reverse_with_qs(
|
||||
"authentik_core:if-flow", self.request.session[SESSION_KEY_GET], kwargs=kwargs
|
||||
)
|
||||
|
||||
def get_challenge(self, *args, **kwargs) -> Challenge:
|
||||
"""Get the redirect target. Prioritize `redirect_stage_target` if present."""
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
"""Test Redirect stage"""
|
||||
|
||||
from urllib.parse import urlencode
|
||||
|
||||
from django.urls.base import reverse
|
||||
from rest_framework.exceptions import ValidationError
|
||||
|
||||
@@ -58,6 +60,23 @@ class TestRedirectStage(FlowTestCase):
|
||||
response, reverse("authentik_core:if-flow", kwargs={"flow_slug": self.target_flow.slug})
|
||||
)
|
||||
|
||||
def test_flow_query(self):
|
||||
self.stage.mode = RedirectMode.FLOW
|
||||
self.stage.save()
|
||||
|
||||
response = self.client.get(
|
||||
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
|
||||
+ "?"
|
||||
+ urlencode({"query": urlencode({"test": "foo"})})
|
||||
)
|
||||
|
||||
self.assertStageRedirects(
|
||||
response,
|
||||
reverse("authentik_core:if-flow", kwargs={"flow_slug": self.target_flow.slug})
|
||||
+ "?"
|
||||
+ urlencode({"test": "foo"}),
|
||||
)
|
||||
|
||||
def test_override_static(self):
|
||||
policy = ExpressionPolicy.objects.create(
|
||||
name=generate_id(),
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "http://json-schema.org/draft-07/schema",
|
||||
"$id": "https://goauthentik.io/blueprints/schema.json",
|
||||
"type": "object",
|
||||
"title": "authentik 2024.12.1 Blueprint schema",
|
||||
"title": "authentik 2024.12.3 Blueprint schema",
|
||||
"required": [
|
||||
"version",
|
||||
"entries"
|
||||
|
||||
@@ -31,7 +31,7 @@ services:
|
||||
volumes:
|
||||
- redis:/data
|
||||
server:
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2024.12.1}
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2024.12.3}
|
||||
restart: unless-stopped
|
||||
command: server
|
||||
environment:
|
||||
@@ -54,7 +54,7 @@ services:
|
||||
redis:
|
||||
condition: service_healthy
|
||||
worker:
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2024.12.1}
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2024.12.3}
|
||||
restart: unless-stopped
|
||||
command: worker
|
||||
environment:
|
||||
|
||||
@@ -29,4 +29,4 @@ func UserAgent() string {
|
||||
return fmt.Sprintf("authentik@%s", FullVersion())
|
||||
}
|
||||
|
||||
const VERSION = "2024.12.1"
|
||||
const VERSION = "2024.12.3"
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"maps"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
@@ -16,13 +17,16 @@ import (
|
||||
"goauthentik.io/internal/constants"
|
||||
)
|
||||
|
||||
func (ac *APIController) getWebsocketURL(akURL url.URL, outpostUUID string) *url.URL {
|
||||
func (ac *APIController) getWebsocketURL(akURL url.URL, outpostUUID string, query url.Values) *url.URL {
|
||||
wsUrl := &url.URL{}
|
||||
wsUrl.Scheme = strings.ReplaceAll(akURL.Scheme, "http", "ws")
|
||||
wsUrl.Host = akURL.Host
|
||||
_p, _ := url.JoinPath(akURL.Path, "ws/outpost/", outpostUUID)
|
||||
_p, _ := url.JoinPath(akURL.Path, "ws/outpost/", outpostUUID, "/")
|
||||
wsUrl.Path = _p
|
||||
wsUrl.RawQuery = akURL.Query().Encode()
|
||||
v := url.Values{}
|
||||
maps.Insert(v, maps.All(akURL.Query()))
|
||||
maps.Insert(v, maps.All(query))
|
||||
wsUrl.RawQuery = v.Encode()
|
||||
return wsUrl
|
||||
}
|
||||
|
||||
@@ -45,7 +49,9 @@ func (ac *APIController) initWS(akURL url.URL, outpostUUID string) error {
|
||||
},
|
||||
}
|
||||
|
||||
ws, _, err := dialer.Dial(ac.getWebsocketURL(akURL, outpostUUID).String(), header)
|
||||
wsu := ac.getWebsocketURL(akURL, outpostUUID, query).String()
|
||||
ac.logger.WithField("url", wsu).Debug("connecting to websocket")
|
||||
ws, _, err := dialer.Dial(wsu, header)
|
||||
if err != nil {
|
||||
ac.logger.WithError(err).Warning("failed to connect websocket")
|
||||
return err
|
||||
|
||||
@@ -19,14 +19,24 @@ func TestWebsocketURL(t *testing.T) {
|
||||
u := URLMustParse("http://localhost:9000?foo=bar")
|
||||
uuid := "23470845-7263-4fe3-bd79-ec1d7bf77d77"
|
||||
ac := &APIController{}
|
||||
nu := ac.getWebsocketURL(*u, uuid)
|
||||
assert.Equal(t, "ws://localhost:9000/ws/outpost/23470845-7263-4fe3-bd79-ec1d7bf77d77?foo=bar", nu.String())
|
||||
nu := ac.getWebsocketURL(*u, uuid, url.Values{})
|
||||
assert.Equal(t, "ws://localhost:9000/ws/outpost/23470845-7263-4fe3-bd79-ec1d7bf77d77/?foo=bar", nu.String())
|
||||
}
|
||||
|
||||
func TestWebsocketURL_Query(t *testing.T) {
|
||||
u := URLMustParse("http://localhost:9000?foo=bar")
|
||||
uuid := "23470845-7263-4fe3-bd79-ec1d7bf77d77"
|
||||
ac := &APIController{}
|
||||
v := url.Values{}
|
||||
v.Set("bar", "baz")
|
||||
nu := ac.getWebsocketURL(*u, uuid, v)
|
||||
assert.Equal(t, "ws://localhost:9000/ws/outpost/23470845-7263-4fe3-bd79-ec1d7bf77d77/?bar=baz&foo=bar", nu.String())
|
||||
}
|
||||
|
||||
func TestWebsocketURL_Subpath(t *testing.T) {
|
||||
u := URLMustParse("http://localhost:9000/foo/bar/")
|
||||
uuid := "23470845-7263-4fe3-bd79-ec1d7bf77d77"
|
||||
ac := &APIController{}
|
||||
nu := ac.getWebsocketURL(*u, uuid)
|
||||
assert.Equal(t, "ws://localhost:9000/foo/bar/ws/outpost/23470845-7263-4fe3-bd79-ec1d7bf77d77", nu.String())
|
||||
nu := ac.getWebsocketURL(*u, uuid, url.Values{})
|
||||
assert.Equal(t, "ws://localhost:9000/foo/bar/ws/outpost/23470845-7263-4fe3-bd79-ec1d7bf77d77/", nu.String())
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env -S bash -e
|
||||
#!/usr/bin/env -S bash
|
||||
set -e -o pipefail
|
||||
MODE_FILE="${TMPDIR}/authentik-mode"
|
||||
|
||||
function log {
|
||||
@@ -87,7 +88,6 @@ elif [[ "$1" == "bash" ]]; then
|
||||
elif [[ "$1" == "test-all" ]]; then
|
||||
prepare_debug
|
||||
chmod 777 /root
|
||||
pip install --force-reinstall /wheels/*
|
||||
check_if_root "python -m manage test authentik"
|
||||
elif [[ "$1" == "healthcheck" ]]; then
|
||||
run_authentik healthcheck $(cat $MODE_FILE)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "@goauthentik/authentik",
|
||||
"version": "2024.12.1",
|
||||
"version": "2024.12.3",
|
||||
"private": true
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "authentik"
|
||||
version = "2024.12.1"
|
||||
version = "2024.12.3"
|
||||
description = ""
|
||||
authors = ["authentik Team <hello@goauthentik.io>"]
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
openapi: 3.0.3
|
||||
info:
|
||||
title: authentik
|
||||
version: 2024.12.1
|
||||
version: 2024.12.3
|
||||
description: Making authentication simple.
|
||||
contact:
|
||||
email: hello@goauthentik.io
|
||||
|
||||
29
scripts/test_docker.sh
Executable file
29
scripts/test_docker.sh
Executable file
@@ -0,0 +1,29 @@
|
||||
#!/bin/bash
|
||||
set -e -x -o pipefail
|
||||
hash="$(git rev-parse HEAD || openssl rand -base64 36)"
|
||||
|
||||
AUTHENTIK_IMAGE="xghcr.io/goauthentik/server"
|
||||
AUTHENTIK_TAG="$(echo "$hash" | cut -c1-15)"
|
||||
|
||||
if [ -f .env ]; then
|
||||
echo "Existing .env file, aborting"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo PG_PASS="$(openssl rand -base64 36 | tr -d '\n')" >.env
|
||||
echo AUTHENTIK_SECRET_KEY="$(openssl rand -base64 60 | tr -d '\n')" >>.env
|
||||
echo AUTHENTIK_IMAGE="${AUTHENTIK_IMAGE}" >>.env
|
||||
echo AUTHENTIK_TAG="${AUTHENTIK_TAG}" >>.env
|
||||
export COMPOSE_PROJECT_NAME="authentik-test-${AUTHENTIK_TAG}"
|
||||
|
||||
# Ensure buildx is installed
|
||||
docker buildx install
|
||||
# For release builds we have an empty client here as we use the NPM package
|
||||
mkdir -p ./gen-ts-api
|
||||
touch .env
|
||||
|
||||
docker build -t "${AUTHENTIK_IMAGE}:${AUTHENTIK_TAG}" .
|
||||
docker compose up --no-start
|
||||
docker compose start postgresql redis
|
||||
docker compose run -u root server test-all
|
||||
docker compose down -v
|
||||
@@ -46,7 +46,7 @@ export class OutpostServiceConnectionListPage extends TablePage<ServiceConnectio
|
||||
const connections = await new OutpostsApi(DEFAULT_CONFIG).outpostsServiceConnectionsAllList(
|
||||
await this.defaultEndpointConfig(),
|
||||
);
|
||||
Promise.all(
|
||||
await Promise.all(
|
||||
connections.results.map((connection) => {
|
||||
return new OutpostsApi(DEFAULT_CONFIG)
|
||||
.outpostsServiceConnectionsAllStateRetrieve({
|
||||
|
||||
@@ -291,7 +291,7 @@ export function renderForm(
|
||||
|
||||
${showHttpBasic ? renderHttpBasic(provider) : nothing}
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Trusted OIDC Sources")}
|
||||
label=${msg("Federated OIDC Sources")}
|
||||
name="jwtFederationSources"
|
||||
>
|
||||
<ak-dual-select-dynamic-selected
|
||||
|
||||
@@ -131,9 +131,10 @@ export class UserListPage extends WithBrandConfig(WithCapabilitiesConfig(TablePa
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.activePath = getURLParam<string>("path", "/");
|
||||
const defaultPath = new DefaultUIConfig().defaults.userPath;
|
||||
this.activePath = getURLParam<string>("path", defaultPath);
|
||||
uiConfig().then((c) => {
|
||||
if (c.defaults.userPath !== new DefaultUIConfig().defaults.userPath) {
|
||||
if (c.defaults.userPath !== defaultPath) {
|
||||
this.activePath = c.defaults.userPath;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -3,7 +3,7 @@ export const SUCCESS_CLASS = "pf-m-success";
|
||||
export const ERROR_CLASS = "pf-m-danger";
|
||||
export const PROGRESS_CLASS = "pf-m-in-progress";
|
||||
export const CURRENT_CLASS = "pf-m-current";
|
||||
export const VERSION = "2024.12.1";
|
||||
export const VERSION = "2024.12.3";
|
||||
export const TITLE_DEFAULT = "authentik";
|
||||
export const ROUTE_SEPARATOR = ";";
|
||||
|
||||
|
||||
@@ -18,6 +18,12 @@ export class LoadingOverlay extends AKElement implements ILoadingOverlay {
|
||||
@property({ type: Boolean, attribute: "topmost" })
|
||||
topmost = false;
|
||||
|
||||
@property({ type: Boolean })
|
||||
loading = true;
|
||||
|
||||
@property({ type: String })
|
||||
icon = "";
|
||||
|
||||
static get styles() {
|
||||
return [
|
||||
PFBase,
|
||||
@@ -40,7 +46,7 @@ export class LoadingOverlay extends AKElement implements ILoadingOverlay {
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`<ak-empty-state loading header="">
|
||||
return html`<ak-empty-state ?loading=${this.loading} header="" icon=${this.icon}>
|
||||
<span slot="body"><slot></slot></span>
|
||||
</ak-empty-state>`;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { AKElement } from "@goauthentik/elements/Base";
|
||||
import { WithBrandConfig } from "@goauthentik/elements/Interface/brandProvider";
|
||||
import { themeImage } from "@goauthentik/elements/utils/images";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { CSSResult, TemplateResult, css, html } from "lit";
|
||||
import { customElement } from "lit/decorators.js";
|
||||
|
||||
@@ -86,7 +87,7 @@ export class SidebarBrand extends WithBrandConfig(AKElement) {
|
||||
<div class="pf-c-brand ak-brand">
|
||||
<img
|
||||
src=${themeImage(this.brand?.brandingLogo ?? DefaultBrand.brandingLogo)}
|
||||
alt="authentik Logo"
|
||||
alt="${msg("authentik Logo")}"
|
||||
loading="lazy"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -4,7 +4,7 @@ import "@goauthentik/elements/LoadingOverlay";
|
||||
import Guacamole from "guacamole-common-js";
|
||||
|
||||
import { msg, str } from "@lit/localize";
|
||||
import { CSSResult, TemplateResult, css, html } from "lit";
|
||||
import { CSSResult, TemplateResult, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators.js";
|
||||
|
||||
import AKGlobal from "@goauthentik/common/styles/authentik.css";
|
||||
@@ -21,6 +21,23 @@ enum GuacClientState {
|
||||
DISCONNECTED = 5,
|
||||
}
|
||||
|
||||
export function GuacStateToString(state: GuacClientState): string {
|
||||
switch (state) {
|
||||
case GuacClientState.IDLE:
|
||||
return msg("Idle");
|
||||
case GuacClientState.CONNECTING:
|
||||
return msg("Connecting");
|
||||
case GuacClientState.WAITING:
|
||||
return msg("Waiting");
|
||||
case GuacClientState.CONNECTED:
|
||||
return msg("Connected");
|
||||
case GuacClientState.DISCONNECTING:
|
||||
return msg("Disconnecting");
|
||||
case GuacClientState.DISCONNECTED:
|
||||
return msg("Disconnected");
|
||||
}
|
||||
}
|
||||
|
||||
const AUDIO_INPUT_MIMETYPE = "audio/L16;rate=44100,channels=2";
|
||||
const RECONNECT_ATTEMPTS_INITIAL = 5;
|
||||
const RECONNECT_ATTEMPTS = 5;
|
||||
@@ -64,6 +81,9 @@ export class RacInterface extends Interface {
|
||||
@state()
|
||||
clientState?: GuacClientState;
|
||||
|
||||
@state()
|
||||
clientStatus?: Guacamole.Status;
|
||||
|
||||
@state()
|
||||
reconnectingMessage = "";
|
||||
|
||||
@@ -138,6 +158,7 @@ export class RacInterface extends Interface {
|
||||
};
|
||||
this.client = new Guacamole.Client(this.tunnel);
|
||||
this.client.onerror = (err) => {
|
||||
this.clientStatus = err;
|
||||
console.debug("authentik/rac: error: ", err);
|
||||
this.reconnect();
|
||||
};
|
||||
@@ -175,6 +196,10 @@ export class RacInterface extends Interface {
|
||||
const params = new URLSearchParams();
|
||||
params.set("screen_width", Math.floor(RacInterface.domSize().width).toString());
|
||||
params.set("screen_height", Math.floor(RacInterface.domSize().height).toString());
|
||||
// https://github.com/goauthentik/authentik/pull/11757
|
||||
// there are DPI issues when using SSH on HiDPi screens
|
||||
// but if we're not setting DPI at all the resolution is not respected at all
|
||||
params.set("screen_dpi", "96");
|
||||
this.client.connect(params.toString());
|
||||
}
|
||||
|
||||
@@ -221,6 +246,7 @@ export class RacInterface extends Interface {
|
||||
return;
|
||||
}
|
||||
this.hasConnected = true;
|
||||
this.clientStatus = undefined;
|
||||
this.container = this.client.getDisplay().getElement();
|
||||
this.initMouse(this.container);
|
||||
this.client?.sendSize(
|
||||
@@ -310,19 +336,35 @@ export class RacInterface extends Interface {
|
||||
console.debug("authentik/rac: Sent clipboard");
|
||||
}
|
||||
|
||||
renderOverlay() {
|
||||
if (!this.clientState || this.clientState == GuacClientState.CONNECTED) {
|
||||
return nothing;
|
||||
}
|
||||
let message = html`${GuacStateToString(this.clientState)}`;
|
||||
if (this.clientState == GuacClientState.WAITING) {
|
||||
message = html`${msg("Connecting...")}`;
|
||||
}
|
||||
if (this.hasConnected) {
|
||||
message = html`${this.reconnectingMessage}`;
|
||||
}
|
||||
if (this.clientStatus?.message) {
|
||||
message = html`${message}<br />${this.clientStatus.message}`;
|
||||
}
|
||||
const isLoading = [
|
||||
GuacClientState.CONNECTING,
|
||||
GuacClientState.DISCONNECTING,
|
||||
GuacClientState.WAITING,
|
||||
].includes(this.clientState);
|
||||
return html`
|
||||
<ak-loading-overlay ?loading=${isLoading} icon="fa fa-times">
|
||||
<span> ${message} </span>
|
||||
</ak-loading-overlay>
|
||||
`;
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
return html`
|
||||
${this.clientState !== GuacClientState.CONNECTED
|
||||
? html`
|
||||
<ak-loading-overlay>
|
||||
<span>
|
||||
${this.hasConnected
|
||||
? html`${this.reconnectingMessage}`
|
||||
: html`${msg("Connecting...")}`}
|
||||
</span>
|
||||
</ak-loading-overlay>
|
||||
`
|
||||
: html``}
|
||||
${this.renderOverlay()}
|
||||
<div class="container">${this.container}</div>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -511,7 +511,7 @@ export class FlowExecutor extends Interface implements StageHost {
|
||||
DefaultBrand.brandingLogo,
|
||||
),
|
||||
)}"
|
||||
alt="authentik Logo"
|
||||
alt="${msg("authentik Logo")}"
|
||||
/>
|
||||
</div>
|
||||
${until(this.renderChallenge())}
|
||||
|
||||
@@ -8,6 +8,7 @@ import { DefaultBrand } from "@goauthentik/elements/sidebar/SidebarBrand";
|
||||
import { themeImage } from "@goauthentik/elements/utils/images";
|
||||
import "rapidoc";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { CSSResult, TemplateResult, css, html } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators.js";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
@@ -102,7 +103,7 @@ export class APIBrowser extends Interface {
|
||||
>
|
||||
<div slot="nav-logo">
|
||||
<img
|
||||
alt="authentik Logo"
|
||||
alt="${msg("authentik Logo")}"
|
||||
class="logo"
|
||||
src="${themeImage(
|
||||
first(this.brand?.brandingLogo, DefaultBrand.brandingLogo),
|
||||
|
||||
@@ -27,8 +27,6 @@ AUTHENTIK_TAG=gh-next
|
||||
AUTHENTIK_OUTPOSTS__CONTAINER_IMAGE_BASE=ghcr.io/goauthentik/dev-%(type)s:gh-%(build_hash)s
|
||||
```
|
||||
|
||||
The Beta image is amd64 only. For arm64 platforms, append `-arm64` to the tag name (no spaces).
|
||||
|
||||
Next, run the upgrade commands below.
|
||||
|
||||
</TabItem>
|
||||
@@ -47,8 +45,6 @@ image:
|
||||
pullPolicy: Always
|
||||
```
|
||||
|
||||
The Beta image is amd64 only. For arm64 platforms, append `-arm64` to the tag name (no spaces).
|
||||
|
||||
Next, run the upgrade commands below.
|
||||
|
||||
</TabItem>
|
||||
|
||||
@@ -24,7 +24,7 @@ Parameters:
|
||||
Description: authentik server memory in MiB
|
||||
Type: Number
|
||||
AuthentikVersion:
|
||||
Default: 2024.12.1
|
||||
Default: 2024.12.3
|
||||
Description: authentik Docker image tag
|
||||
Type: String
|
||||
AuthentikWorkerCPU:
|
||||
|
||||
Reference in New Issue
Block a user