Merge branch 'main' into web/add-htmltagmaps-to-activate-lit-analyzer
* main: (213 commits) website/docs: configuration: fix typo in kubectl command (#10492) website/integrations: fix typo in minio instructions (#10500) web: bump @typescript-eslint/eslint-plugin from 7.5.0 to 7.16.0 in /tests/wdio (#10496) website: bump prettier from 3.3.2 to 3.3.3 in /website (#10493) core: bump ruff from 0.5.1 to 0.5.2 (#10494) web: bump @typescript-eslint/parser from 7.5.0 to 7.16.0 in /tests/wdio (#10495) web: bump eslint-plugin-sonarjs from 0.25.1 to 1.0.3 in /tests/wdio (#10498) web: bump prettier from 3.3.2 to 3.3.3 in /tests/wdio (#10497) web: bump pseudolocale from 2.0.0 to 2.1.0 in /web (#10499) core: bump goauthentik.io/api/v3 from 3.2024061.1 to 3.2024061.2 (#10491) web: bump API Client version (#10488) flows: remove stage challenge type (#10476) core: bump github.com/redis/go-redis/v9 from 9.5.3 to 9.5.4 (#10469) core: bump goauthentik.io/api/v3 from 3.2024060.6 to 3.2024061.1 (#10470) web: bump the babel group across 1 directory with 2 updates (#10471) web: bump the storybook group across 1 directory with 7 updates (#10472) core: bump coverage from 7.5.4 to 7.6.0 (#10473) website/docs: air gapped: clarify .env usage at the top for Kubernetes installations (#10447) website/docs: air gapped: update "see configuration" wording (#10448) website/docs: Add Kubernetes Bootstrap Instructions (#9541) ...
This commit is contained in:
@ -1,5 +1,5 @@
|
|||||||
[bumpversion]
|
[bumpversion]
|
||||||
current_version = 2024.4.2
|
current_version = 2024.6.1
|
||||||
tag = True
|
tag = True
|
||||||
commit = 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*))?
|
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(?:-(?P<rc_t>[a-zA-Z-]+)(?P<rc_n>[1-9]\\d*))?
|
||||||
|
|||||||
@ -54,9 +54,10 @@ runs:
|
|||||||
authentik:
|
authentik:
|
||||||
outposts:
|
outposts:
|
||||||
container_image_base: ghcr.io/goauthentik/dev-%(type)s:gh-%(build_hash)s
|
container_image_base: ghcr.io/goauthentik/dev-%(type)s:gh-%(build_hash)s
|
||||||
image:
|
global:
|
||||||
repository: ghcr.io/goauthentik/dev-server
|
image:
|
||||||
tag: ${{ inputs.tag }}
|
repository: ghcr.io/goauthentik/dev-server
|
||||||
|
tag: ${{ inputs.tag }}
|
||||||
```
|
```
|
||||||
|
|
||||||
For arm64, use these values:
|
For arm64, use these values:
|
||||||
@ -65,9 +66,10 @@ runs:
|
|||||||
authentik:
|
authentik:
|
||||||
outposts:
|
outposts:
|
||||||
container_image_base: ghcr.io/goauthentik/dev-%(type)s:gh-%(build_hash)s
|
container_image_base: ghcr.io/goauthentik/dev-%(type)s:gh-%(build_hash)s
|
||||||
image:
|
global:
|
||||||
repository: ghcr.io/goauthentik/dev-server
|
image:
|
||||||
tag: ${{ inputs.tag }}-arm64
|
repository: ghcr.io/goauthentik/dev-server
|
||||||
|
tag: ${{ inputs.tag }}-arm64
|
||||||
```
|
```
|
||||||
|
|
||||||
Afterwards, run the upgrade commands from the latest release notes.
|
Afterwards, run the upgrade commands from the latest release notes.
|
||||||
|
|||||||
38
.github/dependabot.yml
vendored
38
.github/dependabot.yml
vendored
@ -21,7 +21,10 @@ updates:
|
|||||||
labels:
|
labels:
|
||||||
- dependencies
|
- dependencies
|
||||||
- package-ecosystem: npm
|
- package-ecosystem: npm
|
||||||
directory: "/web"
|
directories:
|
||||||
|
- "/web"
|
||||||
|
- "/tests/wdio"
|
||||||
|
- "/web/sfe"
|
||||||
schedule:
|
schedule:
|
||||||
interval: daily
|
interval: daily
|
||||||
time: "04:00"
|
time: "04:00"
|
||||||
@ -30,7 +33,6 @@ updates:
|
|||||||
open-pull-requests-limit: 10
|
open-pull-requests-limit: 10
|
||||||
commit-message:
|
commit-message:
|
||||||
prefix: "web:"
|
prefix: "web:"
|
||||||
# TODO: deduplicate these groups
|
|
||||||
groups:
|
groups:
|
||||||
sentry:
|
sentry:
|
||||||
patterns:
|
patterns:
|
||||||
@ -56,38 +58,6 @@ updates:
|
|||||||
patterns:
|
patterns:
|
||||||
- "@rollup/*"
|
- "@rollup/*"
|
||||||
- "rollup-*"
|
- "rollup-*"
|
||||||
- package-ecosystem: npm
|
|
||||||
directory: "/tests/wdio"
|
|
||||||
schedule:
|
|
||||||
interval: daily
|
|
||||||
time: "04:00"
|
|
||||||
labels:
|
|
||||||
- dependencies
|
|
||||||
open-pull-requests-limit: 10
|
|
||||||
commit-message:
|
|
||||||
prefix: "web:"
|
|
||||||
# TODO: deduplicate these groups
|
|
||||||
groups:
|
|
||||||
sentry:
|
|
||||||
patterns:
|
|
||||||
- "@sentry/*"
|
|
||||||
- "@spotlightjs/*"
|
|
||||||
babel:
|
|
||||||
patterns:
|
|
||||||
- "@babel/*"
|
|
||||||
- "babel-*"
|
|
||||||
eslint:
|
|
||||||
patterns:
|
|
||||||
- "@typescript-eslint/*"
|
|
||||||
- "eslint"
|
|
||||||
- "eslint-*"
|
|
||||||
storybook:
|
|
||||||
patterns:
|
|
||||||
- "@storybook/*"
|
|
||||||
- "*storybook*"
|
|
||||||
esbuild:
|
|
||||||
patterns:
|
|
||||||
- "@esbuild/*"
|
|
||||||
wdio:
|
wdio:
|
||||||
patterns:
|
patterns:
|
||||||
- "@wdio/*"
|
- "@wdio/*"
|
||||||
|
|||||||
7
.github/workflows/api-ts-publish.yml
vendored
7
.github/workflows/api-ts-publish.yml
vendored
@ -31,7 +31,12 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }}
|
NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }}
|
||||||
- name: Upgrade /web
|
- name: Upgrade /web
|
||||||
working-directory: web/
|
working-directory: web
|
||||||
|
run: |
|
||||||
|
export VERSION=`node -e 'console.log(require("../gen-ts-api/package.json").version)'`
|
||||||
|
npm i @goauthentik/api@$VERSION
|
||||||
|
- name: Upgrade /web/sfe
|
||||||
|
working-directory: web/sfe
|
||||||
run: |
|
run: |
|
||||||
export VERSION=`node -e 'console.log(require("../gen-ts-api/package.json").version)'`
|
export VERSION=`node -e 'console.log(require("../gen-ts-api/package.json").version)'`
|
||||||
npm i @goauthentik/api@$VERSION
|
npm i @goauthentik/api@$VERSION
|
||||||
|
|||||||
2
.github/workflows/ci-main.yml
vendored
2
.github/workflows/ci-main.yml
vendored
@ -219,7 +219,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
ref: ${{ github.event.pull_request.head.sha }}
|
ref: ${{ github.event.pull_request.head.sha }}
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3.0.0
|
uses: docker/setup-qemu-action@v3.1.0
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
- name: prepare variables
|
- name: prepare variables
|
||||||
|
|||||||
2
.github/workflows/ci-outpost.yml
vendored
2
.github/workflows/ci-outpost.yml
vendored
@ -76,7 +76,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
ref: ${{ github.event.pull_request.head.sha }}
|
ref: ${{ github.event.pull_request.head.sha }}
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3.0.0
|
uses: docker/setup-qemu-action@v3.1.0
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
- name: prepare variables
|
- name: prepare variables
|
||||||
|
|||||||
120
.github/workflows/ci-web.yml
vendored
120
.github/workflows/ci-web.yml
vendored
@ -12,14 +12,36 @@ on:
|
|||||||
- version-*
|
- version-*
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
lint-eslint:
|
lint:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
|
command:
|
||||||
|
- lint
|
||||||
|
- lint:lockfile
|
||||||
|
- tsc
|
||||||
|
- prettier-check
|
||||||
project:
|
project:
|
||||||
- web
|
- web
|
||||||
- tests/wdio
|
- tests/wdio
|
||||||
|
include:
|
||||||
|
- command: tsc
|
||||||
|
project: web
|
||||||
|
extra_setup: |
|
||||||
|
cd sfe/ && npm ci
|
||||||
|
- command: lit-analyse
|
||||||
|
project: web
|
||||||
|
extra_setup: |
|
||||||
|
# lit-analyse doesn't understand path rewrites, so make it
|
||||||
|
# belive it's an actual module
|
||||||
|
cd node_modules/@goauthentik
|
||||||
|
ln -s ../../src/ web
|
||||||
|
exclude:
|
||||||
|
- command: lint:lockfile
|
||||||
|
project: tests/wdio
|
||||||
|
- command: tsc
|
||||||
|
project: tests/wdio
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
@ -28,85 +50,17 @@ jobs:
|
|||||||
cache: "npm"
|
cache: "npm"
|
||||||
cache-dependency-path: ${{ matrix.project }}/package-lock.json
|
cache-dependency-path: ${{ matrix.project }}/package-lock.json
|
||||||
- working-directory: ${{ matrix.project }}/
|
- working-directory: ${{ matrix.project }}/
|
||||||
run: npm ci
|
|
||||||
- name: Generate API
|
|
||||||
run: make gen-client-ts
|
|
||||||
- name: Eslint
|
|
||||||
working-directory: ${{ matrix.project }}/
|
|
||||||
run: npm run lint
|
|
||||||
lint-lockfile:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- working-directory: web/
|
|
||||||
run: |
|
|
||||||
[ -z "$(jq -r '.packages | to_entries[] | select((.key | startswith("node_modules")) and (.value | has("resolved") | not)) | .key' < package-lock.json)" ]
|
|
||||||
lint-build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version-file: web/package.json
|
|
||||||
cache: "npm"
|
|
||||||
cache-dependency-path: web/package-lock.json
|
|
||||||
- working-directory: web/
|
|
||||||
run: npm ci
|
|
||||||
- name: Generate API
|
|
||||||
run: make gen-client-ts
|
|
||||||
- name: TSC
|
|
||||||
working-directory: web/
|
|
||||||
run: npm run tsc
|
|
||||||
lint-prettier:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
project:
|
|
||||||
- web
|
|
||||||
- tests/wdio
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version-file: ${{ matrix.project }}/package.json
|
|
||||||
cache: "npm"
|
|
||||||
cache-dependency-path: ${{ matrix.project }}/package-lock.json
|
|
||||||
- working-directory: ${{ matrix.project }}/
|
|
||||||
run: npm ci
|
|
||||||
- name: Generate API
|
|
||||||
run: make gen-client-ts
|
|
||||||
- name: prettier
|
|
||||||
working-directory: ${{ matrix.project }}/
|
|
||||||
run: npm run prettier-check
|
|
||||||
lint-lit-analyse:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version-file: web/package.json
|
|
||||||
cache: "npm"
|
|
||||||
cache-dependency-path: web/package-lock.json
|
|
||||||
- working-directory: web/
|
|
||||||
run: |
|
run: |
|
||||||
npm ci
|
npm ci
|
||||||
# lit-analyse doesn't understand path rewrites, so make it
|
${{ matrix.extra_setup }}
|
||||||
# belive it's an actual module
|
|
||||||
cd node_modules/@goauthentik
|
|
||||||
ln -s ../../src/ web
|
|
||||||
- name: Generate API
|
- name: Generate API
|
||||||
run: make gen-client-ts
|
run: make gen-client-ts
|
||||||
- name: lit-analyse
|
- name: Lint
|
||||||
working-directory: web/
|
working-directory: ${{ matrix.project }}/
|
||||||
run: npm run lit-analyse
|
run: npm run ${{ matrix.command }}
|
||||||
ci-web-mark:
|
ci-web-mark:
|
||||||
needs:
|
needs:
|
||||||
- lint-lockfile
|
- lint
|
||||||
- lint-eslint
|
|
||||||
- lint-prettier
|
|
||||||
- lint-lit-analyse
|
|
||||||
- lint-build
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- run: echo mark
|
- run: echo mark
|
||||||
@ -128,3 +82,21 @@ jobs:
|
|||||||
- name: build
|
- name: build
|
||||||
working-directory: web/
|
working-directory: web/
|
||||||
run: npm run build
|
run: npm run build
|
||||||
|
test:
|
||||||
|
needs:
|
||||||
|
- ci-web-mark
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version-file: web/package.json
|
||||||
|
cache: "npm"
|
||||||
|
cache-dependency-path: web/package-lock.json
|
||||||
|
- working-directory: web/
|
||||||
|
run: npm ci
|
||||||
|
- name: Generate API
|
||||||
|
run: make gen-client-ts
|
||||||
|
- name: test
|
||||||
|
working-directory: web/
|
||||||
|
run: npm run test
|
||||||
|
|||||||
27
.github/workflows/ci-website.yml
vendored
27
.github/workflows/ci-website.yml
vendored
@ -12,27 +12,21 @@ on:
|
|||||||
- version-*
|
- version-*
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
lint-lockfile:
|
lint:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
command:
|
||||||
|
- lint:lockfile
|
||||||
|
- prettier-check
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- working-directory: website/
|
|
||||||
run: |
|
|
||||||
[ -z "$(jq -r '.packages | to_entries[] | select((.key | startswith("node_modules")) and (.value | has("resolved") | not)) | .key' < package-lock.json)" ]
|
|
||||||
lint-prettier:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version-file: website/package.json
|
|
||||||
cache: "npm"
|
|
||||||
cache-dependency-path: website/package-lock.json
|
|
||||||
- working-directory: website/
|
- working-directory: website/
|
||||||
run: npm ci
|
run: npm ci
|
||||||
- name: prettier
|
- name: Lint
|
||||||
working-directory: website/
|
working-directory: website/
|
||||||
run: npm run prettier-check
|
run: npm run ${{ matrix.command }}
|
||||||
test:
|
test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
@ -69,8 +63,7 @@ jobs:
|
|||||||
run: npm run ${{ matrix.job }}
|
run: npm run ${{ matrix.job }}
|
||||||
ci-website-mark:
|
ci-website-mark:
|
||||||
needs:
|
needs:
|
||||||
- lint-lockfile
|
- lint
|
||||||
- lint-prettier
|
|
||||||
- test
|
- test
|
||||||
- build
|
- build
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|||||||
8
.github/workflows/release-publish.yml
vendored
8
.github/workflows/release-publish.yml
vendored
@ -14,7 +14,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3.0.0
|
uses: docker/setup-qemu-action@v3.1.0
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
- name: prepare variables
|
- name: prepare variables
|
||||||
@ -68,7 +68,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
go-version-file: "go.mod"
|
go-version-file: "go.mod"
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3.0.0
|
uses: docker/setup-qemu-action@v3.1.0
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
- name: prepare variables
|
- name: prepare variables
|
||||||
@ -155,8 +155,8 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Run test suite in final docker images
|
- name: Run test suite in final docker images
|
||||||
run: |
|
run: |
|
||||||
echo "PG_PASS=$(openssl rand 32 | base64)" >> .env
|
echo "PG_PASS=$(openssl rand 32 | base64 -w 0)" >> .env
|
||||||
echo "AUTHENTIK_SECRET_KEY=$(openssl rand 32 | base64)" >> .env
|
echo "AUTHENTIK_SECRET_KEY=$(openssl rand 32 | base64 -w 0)" >> .env
|
||||||
docker compose pull -q
|
docker compose pull -q
|
||||||
docker compose up --no-start
|
docker compose up --no-start
|
||||||
docker compose start postgresql redis
|
docker compose start postgresql redis
|
||||||
|
|||||||
4
.github/workflows/release-tag.yml
vendored
4
.github/workflows/release-tag.yml
vendored
@ -14,8 +14,8 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Pre-release test
|
- name: Pre-release test
|
||||||
run: |
|
run: |
|
||||||
echo "PG_PASS=$(openssl rand 32 | base64)" >> .env
|
echo "PG_PASS=$(openssl rand 32 | base64 -w 0)" >> .env
|
||||||
echo "AUTHENTIK_SECRET_KEY=$(openssl rand 32 | base64)" >> .env
|
echo "AUTHENTIK_SECRET_KEY=$(openssl rand 32 | base64 -w 0)" >> .env
|
||||||
docker buildx install
|
docker buildx install
|
||||||
mkdir -p ./gen-ts-api
|
mkdir -p ./gen-ts-api
|
||||||
docker build -t testing:latest .
|
docker build -t testing:latest .
|
||||||
|
|||||||
@ -30,7 +30,12 @@ WORKDIR /work/web
|
|||||||
|
|
||||||
RUN --mount=type=bind,target=/work/web/package.json,src=./web/package.json \
|
RUN --mount=type=bind,target=/work/web/package.json,src=./web/package.json \
|
||||||
--mount=type=bind,target=/work/web/package-lock.json,src=./web/package-lock.json \
|
--mount=type=bind,target=/work/web/package-lock.json,src=./web/package-lock.json \
|
||||||
|
--mount=type=bind,target=/work/web/sfe/package.json,src=./web/sfe/package.json \
|
||||||
|
--mount=type=bind,target=/work/web/sfe/package-lock.json,src=./web/sfe/package-lock.json \
|
||||||
|
--mount=type=bind,target=/work/web/scripts,src=./web/scripts \
|
||||||
--mount=type=cache,id=npm-web,sharing=shared,target=/root/.npm \
|
--mount=type=cache,id=npm-web,sharing=shared,target=/root/.npm \
|
||||||
|
npm ci --include=dev && \
|
||||||
|
cd sfe && \
|
||||||
npm ci --include=dev
|
npm ci --include=dev
|
||||||
|
|
||||||
COPY ./package.json /work
|
COPY ./package.json /work
|
||||||
@ -38,7 +43,9 @@ COPY ./web /work/web/
|
|||||||
COPY ./website /work/website/
|
COPY ./website /work/website/
|
||||||
COPY ./gen-ts-api /work/web/node_modules/@goauthentik/api
|
COPY ./gen-ts-api /work/web/node_modules/@goauthentik/api
|
||||||
|
|
||||||
RUN npm run build
|
RUN npm run build && \
|
||||||
|
cd sfe && \
|
||||||
|
npm run build
|
||||||
|
|
||||||
# Stage 3: Build go proxy
|
# Stage 3: Build go proxy
|
||||||
FROM --platform=${BUILDPLATFORM} mcr.microsoft.com/oss/go/microsoft/golang:1.22-fips-bookworm AS go-builder
|
FROM --platform=${BUILDPLATFORM} mcr.microsoft.com/oss/go/microsoft/golang:1.22-fips-bookworm AS go-builder
|
||||||
|
|||||||
4
Makefile
4
Makefile
@ -47,8 +47,8 @@ test-go:
|
|||||||
go test -timeout 0 -v -race -cover ./...
|
go test -timeout 0 -v -race -cover ./...
|
||||||
|
|
||||||
test-docker: ## Run all tests in a docker-compose
|
test-docker: ## Run all tests in a docker-compose
|
||||||
echo "PG_PASS=$(shell openssl rand 32 | base64)" >> .env
|
echo "PG_PASS=$(shell openssl rand 32 | base64 -w 0)" >> .env
|
||||||
echo "AUTHENTIK_SECRET_KEY=$(shell openssl rand 32 | base64)" >> .env
|
echo "AUTHENTIK_SECRET_KEY=$(shell openssl rand 32 | base64 -w 0)" >> .env
|
||||||
docker compose pull -q
|
docker compose pull -q
|
||||||
docker compose up --no-start
|
docker compose up --no-start
|
||||||
docker compose start postgresql redis
|
docker compose start postgresql redis
|
||||||
|
|||||||
@ -18,10 +18,10 @@ Even if the issue is not a CVE, we still greatly appreciate your help in hardeni
|
|||||||
|
|
||||||
(.x being the latest patch release for each version)
|
(.x being the latest patch release for each version)
|
||||||
|
|
||||||
| Version | Supported |
|
| Version | Supported |
|
||||||
| --------- | --------- |
|
| -------- | --------- |
|
||||||
| 2023.10.x | ✅ |
|
| 2024.4.x | ✅ |
|
||||||
| 2024.2.x | ✅ |
|
| 2024.6.x | ✅ |
|
||||||
|
|
||||||
## Reporting a Vulnerability
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
from os import environ
|
from os import environ
|
||||||
|
|
||||||
__version__ = "2024.4.2"
|
__version__ = "2024.6.1"
|
||||||
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"
|
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -24,7 +24,7 @@ from authentik.tenants.utils import get_current_tenant
|
|||||||
class FooterLinkSerializer(PassiveSerializer):
|
class FooterLinkSerializer(PassiveSerializer):
|
||||||
"""Links returned in Config API"""
|
"""Links returned in Config API"""
|
||||||
|
|
||||||
href = CharField(read_only=True)
|
href = CharField(read_only=True, allow_null=True)
|
||||||
name = CharField(read_only=True)
|
name = CharField(read_only=True)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -103,7 +103,7 @@ class ApplicationSerializer(ModelSerializer):
|
|||||||
class ApplicationViewSet(UsedByMixin, ModelViewSet):
|
class ApplicationViewSet(UsedByMixin, ModelViewSet):
|
||||||
"""Application Viewset"""
|
"""Application Viewset"""
|
||||||
|
|
||||||
queryset = Application.objects.all().prefetch_related("provider")
|
queryset = Application.objects.all().prefetch_related("provider").prefetch_related("policies")
|
||||||
serializer_class = ApplicationSerializer
|
serializer_class = ApplicationSerializer
|
||||||
search_fields = [
|
search_fields = [
|
||||||
"name",
|
"name",
|
||||||
|
|||||||
@ -44,6 +44,13 @@ class TokenSerializer(ManagedSerializer, ModelSerializer):
|
|||||||
if SERIALIZER_CONTEXT_BLUEPRINT in self.context:
|
if SERIALIZER_CONTEXT_BLUEPRINT in self.context:
|
||||||
self.fields["key"] = CharField(required=False)
|
self.fields["key"] = CharField(required=False)
|
||||||
|
|
||||||
|
def validate_user(self, user: User):
|
||||||
|
"""Ensure user of token cannot be changed"""
|
||||||
|
if self.instance and self.instance.user_id:
|
||||||
|
if user.pk != self.instance.user_id:
|
||||||
|
raise ValidationError("User cannot be changed")
|
||||||
|
return user
|
||||||
|
|
||||||
def validate(self, attrs: dict[Any, str]) -> dict[Any, str]:
|
def validate(self, attrs: dict[Any, str]) -> dict[Any, str]:
|
||||||
"""Ensure only API or App password tokens are created."""
|
"""Ensure only API or App password tokens are created."""
|
||||||
request: Request = self.context.get("request")
|
request: Request = self.context.get("request")
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
"""authentik core exceptions"""
|
"""authentik core exceptions"""
|
||||||
|
|
||||||
|
from authentik.lib.expression.exceptions import ControlFlowException
|
||||||
from authentik.lib.sentry import SentryIgnoredException
|
from authentik.lib.sentry import SentryIgnoredException
|
||||||
|
|
||||||
|
|
||||||
@ -12,11 +13,7 @@ class PropertyMappingExpressionException(SentryIgnoredException):
|
|||||||
self.mapping = mapping
|
self.mapping = mapping
|
||||||
|
|
||||||
|
|
||||||
class SkipObjectException(PropertyMappingExpressionException):
|
class SkipObjectException(ControlFlowException):
|
||||||
"""Exception which can be raised in a property mapping to skip syncing an object.
|
"""Exception which can be raised in a property mapping to skip syncing an object.
|
||||||
Only applies to Property mappings which sync objects, and not on mappings which transitively
|
Only applies to Property mappings which sync objects, and not on mappings which transitively
|
||||||
apply to a single user"""
|
apply to a single user"""
|
||||||
|
|
||||||
def __init__(self) -> None:
|
|
||||||
# For this class only, both of these are set by the function evaluating the property mapping
|
|
||||||
super().__init__(exc=None, mapping=None)
|
|
||||||
|
|||||||
@ -7,12 +7,13 @@ from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
|||||||
|
|
||||||
|
|
||||||
def backport_is_backchannel(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
def backport_is_backchannel(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||||
|
db_alias = schema_editor.connection.alias
|
||||||
from authentik.providers.ldap.models import LDAPProvider
|
from authentik.providers.ldap.models import LDAPProvider
|
||||||
from authentik.providers.scim.models import SCIMProvider
|
from authentik.providers.scim.models import SCIMProvider
|
||||||
|
|
||||||
for model in [LDAPProvider, SCIMProvider]:
|
for model in [LDAPProvider, SCIMProvider]:
|
||||||
try:
|
try:
|
||||||
for obj in model.objects.only("is_backchannel"):
|
for obj in model.objects.using(db_alias).only("is_backchannel"):
|
||||||
obj.is_backchannel = True
|
obj.is_backchannel = True
|
||||||
obj.save()
|
obj.save()
|
||||||
except (DatabaseError, InternalError, ProgrammingError):
|
except (DatabaseError, InternalError, ProgrammingError):
|
||||||
|
|||||||
@ -26,6 +26,7 @@ from authentik.blueprints.models import ManagedModel
|
|||||||
from authentik.core.expression.exceptions import PropertyMappingExpressionException
|
from authentik.core.expression.exceptions import PropertyMappingExpressionException
|
||||||
from authentik.core.types import UILoginButton, UserSettingSerializer
|
from authentik.core.types import UILoginButton, UserSettingSerializer
|
||||||
from authentik.lib.avatars import get_avatar
|
from authentik.lib.avatars import get_avatar
|
||||||
|
from authentik.lib.expression.exceptions import ControlFlowException
|
||||||
from authentik.lib.generators import generate_id
|
from authentik.lib.generators import generate_id
|
||||||
from authentik.lib.models import (
|
from authentik.lib.models import (
|
||||||
CreatedUpdatedModel,
|
CreatedUpdatedModel,
|
||||||
@ -783,6 +784,8 @@ class PropertyMapping(SerializerModel, ManagedModel):
|
|||||||
evaluator = PropertyMappingEvaluator(self, user, request, **kwargs)
|
evaluator = PropertyMappingEvaluator(self, user, request, **kwargs)
|
||||||
try:
|
try:
|
||||||
return evaluator.evaluate(self.expression)
|
return evaluator.evaluate(self.expression)
|
||||||
|
except ControlFlowException as exc:
|
||||||
|
raise exc
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
raise PropertyMappingExpressionException(self, exc) from exc
|
raise PropertyMappingExpressionException(self, exc) from exc
|
||||||
|
|
||||||
|
|||||||
@ -212,7 +212,7 @@ class SourceFlowManager:
|
|||||||
|
|
||||||
def _prepare_flow(
|
def _prepare_flow(
|
||||||
self,
|
self,
|
||||||
flow: Flow,
|
flow: Flow | None,
|
||||||
connection: UserSourceConnection,
|
connection: UserSourceConnection,
|
||||||
stages: list[StageView] | None = None,
|
stages: list[StageView] | None = None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
@ -309,7 +309,9 @@ class SourceFlowManager:
|
|||||||
# When request isn't authenticated we jump straight to auth
|
# When request isn't authenticated we jump straight to auth
|
||||||
if not self.request.user.is_authenticated:
|
if not self.request.user.is_authenticated:
|
||||||
return self.handle_auth(connection)
|
return self.handle_auth(connection)
|
||||||
# Connection has already been saved
|
if SESSION_KEY_OVERRIDE_FLOW_TOKEN in self.request.session:
|
||||||
|
return self._prepare_flow(None, connection)
|
||||||
|
connection.save()
|
||||||
Event.new(
|
Event.new(
|
||||||
EventAction.SOURCE_LINKED,
|
EventAction.SOURCE_LINKED,
|
||||||
message="Linked Source",
|
message="Linked Source",
|
||||||
|
|||||||
@ -10,7 +10,7 @@
|
|||||||
versionSubdomain: "{{ version_subdomain }}",
|
versionSubdomain: "{{ version_subdomain }}",
|
||||||
build: "{{ build }}",
|
build: "{{ build }}",
|
||||||
};
|
};
|
||||||
window.addEventListener("DOMContentLoaded", () => {
|
window.addEventListener("DOMContentLoaded", function () {
|
||||||
{% for message in messages %}
|
{% for message in messages %}
|
||||||
window.dispatchEvent(
|
window.dispatchEvent(
|
||||||
new CustomEvent("ak-message", {
|
new CustomEvent("ak-message", {
|
||||||
|
|||||||
@ -71,9 +71,9 @@
|
|||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<li>
|
<li>
|
||||||
<a href="https://goauthentik.io?utm_source=authentik">
|
<span>
|
||||||
{% trans 'Powered by authentik' %}
|
{% trans 'Powered by authentik' %}
|
||||||
</a>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</footer>
|
</footer>
|
||||||
|
|||||||
@ -17,11 +17,5 @@ def versioned_script(path: str) -> str:
|
|||||||
f'<script src="{static_loader(path.replace("%v", get_full_version()))}'
|
f'<script src="{static_loader(path.replace("%v", get_full_version()))}'
|
||||||
'" type="module"></script>'
|
'" type="module"></script>'
|
||||||
),
|
),
|
||||||
# Legacy method of loading scripts used as a fallback, without the version in the filename
|
|
||||||
# TODO: Remove after 2024.6 or later
|
|
||||||
(
|
|
||||||
f'<script src="{static_loader(path.replace("-%v", ""))}?'
|
|
||||||
f'version={get_full_version()}" type="module"></script>'
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
return mark_safe("".join(returned_lines)) # nosec
|
return mark_safe("".join(returned_lines)) # nosec
|
||||||
|
|||||||
@ -3,7 +3,10 @@
|
|||||||
from django.test import RequestFactory, TestCase
|
from django.test import RequestFactory, TestCase
|
||||||
from guardian.shortcuts import get_anonymous_user
|
from guardian.shortcuts import get_anonymous_user
|
||||||
|
|
||||||
from authentik.core.expression.exceptions import PropertyMappingExpressionException
|
from authentik.core.expression.exceptions import (
|
||||||
|
PropertyMappingExpressionException,
|
||||||
|
SkipObjectException,
|
||||||
|
)
|
||||||
from authentik.core.models import PropertyMapping
|
from authentik.core.models import PropertyMapping
|
||||||
from authentik.core.tests.utils import create_test_admin_user
|
from authentik.core.tests.utils import create_test_admin_user
|
||||||
from authentik.events.models import Event, EventAction
|
from authentik.events.models import Event, EventAction
|
||||||
@ -42,6 +45,17 @@ class TestPropertyMappings(TestCase):
|
|||||||
self.assertTrue(events.exists())
|
self.assertTrue(events.exists())
|
||||||
self.assertEqual(len(events), 1)
|
self.assertEqual(len(events), 1)
|
||||||
|
|
||||||
|
def test_expression_skip(self):
|
||||||
|
"""Test expression error"""
|
||||||
|
expr = "raise SkipObject"
|
||||||
|
mapping = PropertyMapping.objects.create(name=generate_id(), expression=expr)
|
||||||
|
with self.assertRaises(SkipObjectException):
|
||||||
|
mapping.evaluate(None, None)
|
||||||
|
events = Event.objects.filter(
|
||||||
|
action=EventAction.PROPERTY_MAPPING_EXCEPTION, context__expression=expr
|
||||||
|
)
|
||||||
|
self.assertFalse(events.exists())
|
||||||
|
|
||||||
def test_expression_error_extended(self):
|
def test_expression_error_extended(self):
|
||||||
"""Test expression error (with user and http request"""
|
"""Test expression error (with user and http request"""
|
||||||
expr = "return aaa"
|
expr = "return aaa"
|
||||||
|
|||||||
@ -13,9 +13,8 @@ from authentik.core.models import (
|
|||||||
USER_ATTRIBUTE_TOKEN_MAXIMUM_LIFETIME,
|
USER_ATTRIBUTE_TOKEN_MAXIMUM_LIFETIME,
|
||||||
Token,
|
Token,
|
||||||
TokenIntents,
|
TokenIntents,
|
||||||
User,
|
|
||||||
)
|
)
|
||||||
from authentik.core.tests.utils import create_test_admin_user
|
from authentik.core.tests.utils import create_test_admin_user, create_test_user
|
||||||
from authentik.lib.generators import generate_id
|
from authentik.lib.generators import generate_id
|
||||||
|
|
||||||
|
|
||||||
@ -24,7 +23,7 @@ class TestTokenAPI(APITestCase):
|
|||||||
|
|
||||||
def setUp(self) -> None:
|
def setUp(self) -> None:
|
||||||
super().setUp()
|
super().setUp()
|
||||||
self.user = User.objects.create(username="testuser")
|
self.user = create_test_user()
|
||||||
self.admin = create_test_admin_user()
|
self.admin = create_test_admin_user()
|
||||||
self.client.force_login(self.user)
|
self.client.force_login(self.user)
|
||||||
|
|
||||||
@ -154,6 +153,24 @@ class TestTokenAPI(APITestCase):
|
|||||||
self.assertEqual(token.expiring, True)
|
self.assertEqual(token.expiring, True)
|
||||||
self.assertNotEqual(token.expires.timestamp(), expires.timestamp())
|
self.assertNotEqual(token.expires.timestamp(), expires.timestamp())
|
||||||
|
|
||||||
|
def test_token_change_user(self):
|
||||||
|
"""Test creating a token and then changing the user"""
|
||||||
|
ident = generate_id()
|
||||||
|
response = self.client.post(reverse("authentik_api:token-list"), {"identifier": ident})
|
||||||
|
self.assertEqual(response.status_code, 201)
|
||||||
|
token = Token.objects.get(identifier=ident)
|
||||||
|
self.assertEqual(token.user, self.user)
|
||||||
|
self.assertEqual(token.intent, TokenIntents.INTENT_API)
|
||||||
|
self.assertEqual(token.expiring, True)
|
||||||
|
self.assertTrue(self.user.has_perm("authentik_core.view_token_key", token))
|
||||||
|
response = self.client.put(
|
||||||
|
reverse("authentik_api:token-detail", kwargs={"identifier": ident}),
|
||||||
|
data={"identifier": "user_token_poc_v3", "intent": "api", "user": self.admin.pk},
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, 400)
|
||||||
|
token.refresh_from_db()
|
||||||
|
self.assertEqual(token.user, self.user)
|
||||||
|
|
||||||
def test_list(self):
|
def test_list(self):
|
||||||
"""Test Token List (Test normal authentication)"""
|
"""Test Token List (Test normal authentication)"""
|
||||||
Token.objects.all().delete()
|
Token.objects.all().delete()
|
||||||
|
|||||||
@ -20,8 +20,9 @@ from authentik.core.api.transactional_applications import TransactionalApplicati
|
|||||||
from authentik.core.api.users import UserViewSet
|
from authentik.core.api.users import UserViewSet
|
||||||
from authentik.core.views import apps
|
from authentik.core.views import apps
|
||||||
from authentik.core.views.debug import AccessDeniedView
|
from authentik.core.views.debug import AccessDeniedView
|
||||||
from authentik.core.views.interface import FlowInterfaceView, InterfaceView
|
from authentik.core.views.interface import InterfaceView
|
||||||
from authentik.core.views.session import EndSessionView
|
from authentik.core.views.session import EndSessionView
|
||||||
|
from authentik.flows.views.interface import FlowInterfaceView
|
||||||
from authentik.root.asgi_middleware import SessionMiddleware
|
from authentik.root.asgi_middleware import SessionMiddleware
|
||||||
from authentik.root.messages.consumer import MessageConsumer
|
from authentik.root.messages.consumer import MessageConsumer
|
||||||
from authentik.root.middleware import ChannelsLoggingMiddleware
|
from authentik.root.middleware import ChannelsLoggingMiddleware
|
||||||
@ -53,6 +54,8 @@ urlpatterns = [
|
|||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"if/flow/<slug:flow_slug>/",
|
"if/flow/<slug:flow_slug>/",
|
||||||
|
# FIXME: move this url to the flows app...also will cause all
|
||||||
|
# of the reverse calls to be adjusted
|
||||||
ensure_csrf_cookie(FlowInterfaceView.as_view()),
|
ensure_csrf_cookie(FlowInterfaceView.as_view()),
|
||||||
name="if-flow",
|
name="if-flow",
|
||||||
),
|
),
|
||||||
|
|||||||
@ -8,7 +8,6 @@ from django.views import View
|
|||||||
from authentik.core.models import Application
|
from authentik.core.models import Application
|
||||||
from authentik.flows.challenge import (
|
from authentik.flows.challenge import (
|
||||||
ChallengeResponse,
|
ChallengeResponse,
|
||||||
ChallengeTypes,
|
|
||||||
HttpChallengeResponse,
|
HttpChallengeResponse,
|
||||||
RedirectChallenge,
|
RedirectChallenge,
|
||||||
)
|
)
|
||||||
@ -74,7 +73,6 @@ class RedirectToAppStage(ChallengeStageView):
|
|||||||
raise Http404
|
raise Http404
|
||||||
return RedirectChallenge(
|
return RedirectChallenge(
|
||||||
instance={
|
instance={
|
||||||
"type": ChallengeTypes.REDIRECT.value,
|
|
||||||
"to": launch,
|
"to": launch,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@ -3,7 +3,6 @@
|
|||||||
from json import dumps
|
from json import dumps
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from django.shortcuts import get_object_or_404
|
|
||||||
from django.views.generic.base import TemplateView
|
from django.views.generic.base import TemplateView
|
||||||
from rest_framework.request import Request
|
from rest_framework.request import Request
|
||||||
|
|
||||||
@ -11,7 +10,6 @@ from authentik import get_build_hash
|
|||||||
from authentik.admin.tasks import LOCAL_VERSION
|
from authentik.admin.tasks import LOCAL_VERSION
|
||||||
from authentik.api.v3.config import ConfigView
|
from authentik.api.v3.config import ConfigView
|
||||||
from authentik.brands.api import CurrentBrandSerializer
|
from authentik.brands.api import CurrentBrandSerializer
|
||||||
from authentik.flows.models import Flow
|
|
||||||
|
|
||||||
|
|
||||||
class InterfaceView(TemplateView):
|
class InterfaceView(TemplateView):
|
||||||
@ -25,14 +23,3 @@ class InterfaceView(TemplateView):
|
|||||||
kwargs["build"] = get_build_hash()
|
kwargs["build"] = get_build_hash()
|
||||||
kwargs["url_kwargs"] = self.kwargs
|
kwargs["url_kwargs"] = self.kwargs
|
||||||
return super().get_context_data(**kwargs)
|
return super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
class FlowInterfaceView(InterfaceView):
|
|
||||||
"""Flow interface"""
|
|
||||||
|
|
||||||
template_name = "if/flow.html"
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
|
|
||||||
kwargs["flow"] = get_object_or_404(Flow, slug=self.kwargs.get("flow_slug"))
|
|
||||||
kwargs["inspector"] = "inspector" in self.request.GET
|
|
||||||
return super().get_context_data(**kwargs)
|
|
||||||
|
|||||||
@ -75,7 +75,10 @@ def on_login_failed(
|
|||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
"""Failed Login, authentik custom event"""
|
"""Failed Login, authentik custom event"""
|
||||||
Event.new(EventAction.LOGIN_FAILED, **credentials, stage=stage, **kwargs).from_http(request)
|
user = User.objects.filter(username=credentials.get("username")).first()
|
||||||
|
Event.new(EventAction.LOGIN_FAILED, **credentials, stage=stage, **kwargs).from_http(
|
||||||
|
request, user
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@receiver(invitation_used)
|
@receiver(invitation_used)
|
||||||
|
|||||||
@ -32,14 +32,6 @@ class FlowLayout(models.TextChoices):
|
|||||||
SIDEBAR_RIGHT = "sidebar_right"
|
SIDEBAR_RIGHT = "sidebar_right"
|
||||||
|
|
||||||
|
|
||||||
class ChallengeTypes(Enum):
|
|
||||||
"""Currently defined challenge types"""
|
|
||||||
|
|
||||||
NATIVE = "native"
|
|
||||||
SHELL = "shell"
|
|
||||||
REDIRECT = "redirect"
|
|
||||||
|
|
||||||
|
|
||||||
class ErrorDetailSerializer(PassiveSerializer):
|
class ErrorDetailSerializer(PassiveSerializer):
|
||||||
"""Serializer for rest_framework's error messages"""
|
"""Serializer for rest_framework's error messages"""
|
||||||
|
|
||||||
@ -60,9 +52,6 @@ class Challenge(PassiveSerializer):
|
|||||||
"""Challenge that gets sent to the client based on which stage
|
"""Challenge that gets sent to the client based on which stage
|
||||||
is currently active"""
|
is currently active"""
|
||||||
|
|
||||||
type = ChoiceField(
|
|
||||||
choices=[(x.value, x.name) for x in ChallengeTypes],
|
|
||||||
)
|
|
||||||
flow_info = ContextualFlowInfo(required=False)
|
flow_info = ContextualFlowInfo(required=False)
|
||||||
component = CharField(default="")
|
component = CharField(default="")
|
||||||
|
|
||||||
@ -96,7 +85,6 @@ class FlowErrorChallenge(Challenge):
|
|||||||
"""Challenge class when an unhandled error occurs during a stage. Normal users
|
"""Challenge class when an unhandled error occurs during a stage. Normal users
|
||||||
are shown an error message, superusers are shown a full stacktrace."""
|
are shown an error message, superusers are shown a full stacktrace."""
|
||||||
|
|
||||||
type = CharField(default=ChallengeTypes.NATIVE.value)
|
|
||||||
component = CharField(default="ak-stage-flow-error")
|
component = CharField(default="ak-stage-flow-error")
|
||||||
|
|
||||||
request_id = CharField()
|
request_id = CharField()
|
||||||
|
|||||||
@ -21,7 +21,9 @@ def set_oobe_flow_authentication(apps: Apps, schema_editor: BaseDatabaseSchemaEd
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
if users.exists():
|
if users.exists():
|
||||||
Flow.objects.filter(slug="initial-setup").update(authentication="require_superuser")
|
Flow.objects.using(db_alias).filter(slug="initial-setup").update(
|
||||||
|
authentication="require_superuser"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|||||||
@ -18,7 +18,6 @@ from authentik.flows.challenge import (
|
|||||||
AccessDeniedChallenge,
|
AccessDeniedChallenge,
|
||||||
Challenge,
|
Challenge,
|
||||||
ChallengeResponse,
|
ChallengeResponse,
|
||||||
ChallengeTypes,
|
|
||||||
ContextualFlowInfo,
|
ContextualFlowInfo,
|
||||||
HttpChallengeResponse,
|
HttpChallengeResponse,
|
||||||
RedirectChallenge,
|
RedirectChallenge,
|
||||||
@ -244,7 +243,6 @@ class AccessDeniedChallengeView(ChallengeStageView):
|
|||||||
return AccessDeniedChallenge(
|
return AccessDeniedChallenge(
|
||||||
data={
|
data={
|
||||||
"error_message": str(self.error_message or "Unknown error"),
|
"error_message": str(self.error_message or "Unknown error"),
|
||||||
"type": ChallengeTypes.NATIVE.value,
|
|
||||||
"component": "ak-stage-access-denied",
|
"component": "ak-stage-access-denied",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -264,7 +262,6 @@ class RedirectStage(ChallengeStageView):
|
|||||||
)
|
)
|
||||||
return RedirectChallenge(
|
return RedirectChallenge(
|
||||||
data={
|
data={
|
||||||
"type": ChallengeTypes.REDIRECT.value,
|
|
||||||
"to": destination,
|
"to": destination,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
54
authentik/flows/templates/if/flow-sfe.html
Normal file
54
authentik/flows/templates/if/flow-sfe.html
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
{% load static %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load authentik_core %}
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||||
|
<title>{% block title %}{% trans title|default:brand.branding_title %}{% endblock %}</title>
|
||||||
|
<link rel="icon" href="{{ brand.branding_favicon }}">
|
||||||
|
<link rel="shortcut icon" href="{{ brand.branding_favicon }}">
|
||||||
|
{% block head_before %}
|
||||||
|
{% endblock %}
|
||||||
|
<link rel="stylesheet" type="text/css" href="{% static 'dist/sfe/bootstrap.min.css' %}">
|
||||||
|
<meta name="sentry-trace" content="{{ sentry_trace }}" />
|
||||||
|
{% include "base/header_js.html" %}
|
||||||
|
<style>
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
background-image: url("{{ flow.background_url }}");
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: cover;
|
||||||
|
}
|
||||||
|
.card {
|
||||||
|
padding: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-signin {
|
||||||
|
max-width: 330px;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-signin .form-floating:focus-within {
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
.brand-icon {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body class="d-flex align-items-center py-4 bg-body-tertiary">
|
||||||
|
<div class="card m-auto">
|
||||||
|
<main class="form-signin w-100 m-auto" id="flow-sfe-container">
|
||||||
|
</main>
|
||||||
|
<span class="mt-3 mb-0 text-muted text-center">{% trans 'Powered by authentik' %}</span>
|
||||||
|
</div>
|
||||||
|
<script src="{% static 'dist/sfe/index.js' %}"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -8,7 +8,6 @@ from django.urls.base import reverse
|
|||||||
from rest_framework.test import APITestCase
|
from rest_framework.test import APITestCase
|
||||||
|
|
||||||
from authentik.core.models import User
|
from authentik.core.models import User
|
||||||
from authentik.flows.challenge import ChallengeTypes
|
|
||||||
from authentik.flows.models import Flow
|
from authentik.flows.models import Flow
|
||||||
|
|
||||||
|
|
||||||
@ -26,7 +25,6 @@ class FlowTestCase(APITestCase):
|
|||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
raw_response = loads(response.content.decode())
|
raw_response = loads(response.content.decode())
|
||||||
self.assertIsNotNone(raw_response["component"])
|
self.assertIsNotNone(raw_response["component"])
|
||||||
self.assertIsNotNone(raw_response["type"])
|
|
||||||
if flow:
|
if flow:
|
||||||
self.assertIn("flow_info", raw_response)
|
self.assertIn("flow_info", raw_response)
|
||||||
self.assertEqual(raw_response["flow_info"]["background"], flow.background_url)
|
self.assertEqual(raw_response["flow_info"]["background"], flow.background_url)
|
||||||
@ -46,6 +44,4 @@ class FlowTestCase(APITestCase):
|
|||||||
|
|
||||||
def assertStageRedirects(self, response: HttpResponse, to: str) -> dict[str, Any]:
|
def assertStageRedirects(self, response: HttpResponse, to: str) -> dict[str, Any]:
|
||||||
"""Wrapper around assertStageResponse that checks for a redirect"""
|
"""Wrapper around assertStageResponse that checks for a redirect"""
|
||||||
return self.assertStageResponse(
|
return self.assertStageResponse(response, component="xak-flow-redirect", to=to)
|
||||||
response, component="xak-flow-redirect", to=to, type=ChallengeTypes.REDIRECT.value
|
|
||||||
)
|
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
from authentik.flows.challenge import AutosubmitChallenge, ChallengeTypes
|
from authentik.flows.challenge import AutosubmitChallenge
|
||||||
|
|
||||||
|
|
||||||
class TestChallenges(TestCase):
|
class TestChallenges(TestCase):
|
||||||
@ -12,7 +12,6 @@ class TestChallenges(TestCase):
|
|||||||
"""Test blank autosubmit"""
|
"""Test blank autosubmit"""
|
||||||
challenge = AutosubmitChallenge(
|
challenge = AutosubmitChallenge(
|
||||||
data={
|
data={
|
||||||
"type": ChallengeTypes.NATIVE.value,
|
|
||||||
"url": "http://localhost",
|
"url": "http://localhost",
|
||||||
"attrs": {},
|
"attrs": {},
|
||||||
}
|
}
|
||||||
@ -21,7 +20,6 @@ class TestChallenges(TestCase):
|
|||||||
# Test with an empty value
|
# Test with an empty value
|
||||||
challenge = AutosubmitChallenge(
|
challenge = AutosubmitChallenge(
|
||||||
data={
|
data={
|
||||||
"type": ChallengeTypes.NATIVE.value,
|
|
||||||
"url": "http://localhost",
|
"url": "http://localhost",
|
||||||
"attrs": {"foo": ""},
|
"attrs": {"foo": ""},
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,7 +7,6 @@ from django.urls.base import reverse
|
|||||||
from rest_framework.test import APITestCase
|
from rest_framework.test import APITestCase
|
||||||
|
|
||||||
from authentik.core.tests.utils import create_test_admin_user, create_test_flow
|
from authentik.core.tests.utils import create_test_admin_user, create_test_flow
|
||||||
from authentik.flows.challenge import ChallengeTypes
|
|
||||||
from authentik.flows.models import FlowDesignation, FlowStageBinding, InvalidResponseAction
|
from authentik.flows.models import FlowDesignation, FlowStageBinding, InvalidResponseAction
|
||||||
from authentik.stages.dummy.models import DummyStage
|
from authentik.stages.dummy.models import DummyStage
|
||||||
from authentik.stages.identification.models import IdentificationStage, UserFields
|
from authentik.stages.identification.models import IdentificationStage, UserFields
|
||||||
@ -54,7 +53,6 @@ class TestFlowInspector(APITestCase):
|
|||||||
"layout": "stacked",
|
"layout": "stacked",
|
||||||
},
|
},
|
||||||
"flow_designation": "authentication",
|
"flow_designation": "authentication",
|
||||||
"type": ChallengeTypes.NATIVE.value,
|
|
||||||
"password_fields": False,
|
"password_fields": False,
|
||||||
"primary_action": "Log in",
|
"primary_action": "Log in",
|
||||||
"sources": [],
|
"sources": [],
|
||||||
|
|||||||
@ -30,7 +30,6 @@ from authentik.flows.apps import HIST_FLOW_EXECUTION_STAGE_TIME
|
|||||||
from authentik.flows.challenge import (
|
from authentik.flows.challenge import (
|
||||||
Challenge,
|
Challenge,
|
||||||
ChallengeResponse,
|
ChallengeResponse,
|
||||||
ChallengeTypes,
|
|
||||||
FlowErrorChallenge,
|
FlowErrorChallenge,
|
||||||
HttpChallengeResponse,
|
HttpChallengeResponse,
|
||||||
RedirectChallenge,
|
RedirectChallenge,
|
||||||
@ -552,7 +551,6 @@ def to_stage_response(request: HttpRequest, source: HttpResponse) -> HttpRespons
|
|||||||
return HttpChallengeResponse(
|
return HttpChallengeResponse(
|
||||||
RedirectChallenge(
|
RedirectChallenge(
|
||||||
{
|
{
|
||||||
"type": ChallengeTypes.REDIRECT,
|
|
||||||
"to": str(redirect_url),
|
"to": str(redirect_url),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -561,7 +559,6 @@ def to_stage_response(request: HttpRequest, source: HttpResponse) -> HttpRespons
|
|||||||
return HttpChallengeResponse(
|
return HttpChallengeResponse(
|
||||||
ShellChallenge(
|
ShellChallenge(
|
||||||
{
|
{
|
||||||
"type": ChallengeTypes.SHELL,
|
|
||||||
"body": source.render().content.decode("utf-8"),
|
"body": source.render().content.decode("utf-8"),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -571,7 +568,6 @@ def to_stage_response(request: HttpRequest, source: HttpResponse) -> HttpRespons
|
|||||||
return HttpChallengeResponse(
|
return HttpChallengeResponse(
|
||||||
ShellChallenge(
|
ShellChallenge(
|
||||||
{
|
{
|
||||||
"type": ChallengeTypes.SHELL,
|
|
||||||
"body": source.content.decode("utf-8"),
|
"body": source.content.decode("utf-8"),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
41
authentik/flows/views/interface.py
Normal file
41
authentik/flows/views/interface.py
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
"""Interface views"""
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from django.shortcuts import get_object_or_404
|
||||||
|
from ua_parser.user_agent_parser import Parse
|
||||||
|
|
||||||
|
from authentik.core.views.interface import InterfaceView
|
||||||
|
from authentik.flows.models import Flow
|
||||||
|
|
||||||
|
|
||||||
|
class FlowInterfaceView(InterfaceView):
|
||||||
|
"""Flow interface"""
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
|
||||||
|
kwargs["flow"] = get_object_or_404(Flow, slug=self.kwargs.get("flow_slug"))
|
||||||
|
kwargs["inspector"] = "inspector" in self.request.GET
|
||||||
|
return super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
def compat_needs_sfe(self) -> bool:
|
||||||
|
"""Check if we need to use the simplified flow executor for compatibility"""
|
||||||
|
ua = Parse(self.request.META.get("HTTP_USER_AGENT", ""))
|
||||||
|
if ua["user_agent"]["family"] == "IE":
|
||||||
|
return True
|
||||||
|
# Only use SFE for Edge 18 and older, after Edge 18 MS switched to chromium which supports
|
||||||
|
# the default flow executor
|
||||||
|
if (
|
||||||
|
ua["user_agent"]["family"] == "Edge"
|
||||||
|
and int(ua["user_agent"]["major"]) <= 18 # noqa: PLR2004
|
||||||
|
): # noqa: PLR2004
|
||||||
|
return True
|
||||||
|
# https://github.com/AzureAD/microsoft-authentication-library-for-objc
|
||||||
|
# Used by Microsoft Teams/Office on macOS, and also uses a very outdated browser engine
|
||||||
|
if "PKeyAuth" in ua["string"]:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_template_names(self) -> list[str]:
|
||||||
|
if self.compat_needs_sfe() or "sfe" in self.request.GET:
|
||||||
|
return ["if/flow-sfe.html"]
|
||||||
|
return ["if/flow.html"]
|
||||||
@ -19,6 +19,7 @@ from structlog.stdlib import get_logger
|
|||||||
|
|
||||||
from authentik.core.models import User
|
from authentik.core.models import User
|
||||||
from authentik.events.models import Event
|
from authentik.events.models import Event
|
||||||
|
from authentik.lib.expression.exceptions import ControlFlowException
|
||||||
from authentik.lib.utils.http import get_http_session
|
from authentik.lib.utils.http import get_http_session
|
||||||
from authentik.policies.models import Policy, PolicyBinding
|
from authentik.policies.models import Policy, PolicyBinding
|
||||||
from authentik.policies.process import PolicyProcess
|
from authentik.policies.process import PolicyProcess
|
||||||
@ -216,7 +217,8 @@ class BaseEvaluator:
|
|||||||
# so the user only sees information relevant to them
|
# so the user only sees information relevant to them
|
||||||
# and none of our surrounding error handling
|
# and none of our surrounding error handling
|
||||||
exc.__traceback__ = exc.__traceback__.tb_next
|
exc.__traceback__ = exc.__traceback__.tb_next
|
||||||
self.handle_error(exc, expression_source)
|
if not isinstance(exc, ControlFlowException):
|
||||||
|
self.handle_error(exc, expression_source)
|
||||||
raise exc
|
raise exc
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|||||||
6
authentik/lib/expression/exceptions.py
Normal file
6
authentik/lib/expression/exceptions.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
from authentik.lib.sentry import SentryIgnoredException
|
||||||
|
|
||||||
|
|
||||||
|
class ControlFlowException(SentryIgnoredException):
|
||||||
|
"""Exceptions used to control the flow from exceptions, not reported as a warning/
|
||||||
|
error in logs"""
|
||||||
@ -62,7 +62,7 @@ class DomainlessURLValidator(URLValidator):
|
|||||||
r"^(?:[a-z0-9.+-]*)://" # scheme is validated separately
|
r"^(?:[a-z0-9.+-]*)://" # scheme is validated separately
|
||||||
r"(?:[^\s:@/]+(?::[^\s:@/]*)?@)?" # user:pass authentication
|
r"(?:[^\s:@/]+(?::[^\s:@/]*)?@)?" # user:pass authentication
|
||||||
r"(?:" + self.ipv4_re + "|" + self.ipv6_re + "|" + self.host_re + ")"
|
r"(?:" + self.ipv4_re + "|" + self.ipv6_re + "|" + self.host_re + ")"
|
||||||
r"(?::\d{2,5})?" # port
|
r"(?::\d{1,5})?" # port
|
||||||
r"(?:[/?#][^\s]*)?" # resource path
|
r"(?:[/?#][^\s]*)?" # resource path
|
||||||
r"\Z",
|
r"\Z",
|
||||||
re.IGNORECASE,
|
re.IGNORECASE,
|
||||||
@ -88,7 +88,7 @@ class DomainlessFormattedURLValidator(DomainlessURLValidator):
|
|||||||
r"^(?:[a-z0-9.+-]*)://" # scheme is validated separately
|
r"^(?:[a-z0-9.+-]*)://" # scheme is validated separately
|
||||||
r"(?:[^\s:@/]+(?::[^\s:@/]*)?@)?" # user:pass authentication
|
r"(?:[^\s:@/]+(?::[^\s:@/]*)?@)?" # user:pass authentication
|
||||||
r"(?:" + self.ipv4_re + "|" + self.ipv6_re + "|" + self.host_re + ")"
|
r"(?:" + self.ipv4_re + "|" + self.ipv6_re + "|" + self.host_re + ")"
|
||||||
r"(?::\d{2,5})?" # port
|
r"(?::\d{1,5})?" # port
|
||||||
r"(?:[/?#][^\s]*)?" # resource path
|
r"(?:[/?#][^\s]*)?" # resource path
|
||||||
r"\Z",
|
r"\Z",
|
||||||
re.IGNORECASE,
|
re.IGNORECASE,
|
||||||
|
|||||||
@ -59,8 +59,9 @@ def sentry_init(**sentry_init_kwargs):
|
|||||||
"_experiments": {
|
"_experiments": {
|
||||||
"profiles_sample_rate": float(CONFIG.get("error_reporting.sample_rate", 0.1)),
|
"profiles_sample_rate": float(CONFIG.get("error_reporting.sample_rate", 0.1)),
|
||||||
},
|
},
|
||||||
|
**sentry_init_kwargs,
|
||||||
|
**CONFIG.get_dict_from_b64_json("error_reporting.extra_args", {}),
|
||||||
}
|
}
|
||||||
kwargs.update(**sentry_init_kwargs)
|
|
||||||
|
|
||||||
sentry_sdk_init(
|
sentry_sdk_init(
|
||||||
dsn=CONFIG.get("error_reporting.sentry_dsn"),
|
dsn=CONFIG.get("error_reporting.sentry_dsn"),
|
||||||
|
|||||||
@ -6,9 +6,9 @@ from django.http import HttpRequest
|
|||||||
from authentik.core.expression.evaluator import PropertyMappingEvaluator
|
from authentik.core.expression.evaluator import PropertyMappingEvaluator
|
||||||
from authentik.core.expression.exceptions import (
|
from authentik.core.expression.exceptions import (
|
||||||
PropertyMappingExpressionException,
|
PropertyMappingExpressionException,
|
||||||
SkipObjectException,
|
|
||||||
)
|
)
|
||||||
from authentik.core.models import PropertyMapping, User
|
from authentik.core.models import PropertyMapping, User
|
||||||
|
from authentik.lib.expression.exceptions import ControlFlowException
|
||||||
|
|
||||||
|
|
||||||
class PropertyMappingManager:
|
class PropertyMappingManager:
|
||||||
@ -60,11 +60,7 @@ class PropertyMappingManager:
|
|||||||
mapping.set_context(user, request, **kwargs)
|
mapping.set_context(user, request, **kwargs)
|
||||||
try:
|
try:
|
||||||
value = mapping.evaluate(mapping.model.expression)
|
value = mapping.evaluate(mapping.model.expression)
|
||||||
except SkipObjectException as exc:
|
except (PropertyMappingExpressionException, ControlFlowException) as exc:
|
||||||
exc.exc = exc
|
|
||||||
exc.mapping = mapping
|
|
||||||
raise exc from exc
|
|
||||||
except PropertyMappingExpressionException as exc:
|
|
||||||
raise exc from exc
|
raise exc from exc
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
raise PropertyMappingExpressionException(exc, mapping.model) from exc
|
raise PropertyMappingExpressionException(exc, mapping.model) from exc
|
||||||
|
|||||||
@ -9,9 +9,9 @@ from structlog.stdlib import get_logger
|
|||||||
|
|
||||||
from authentik.core.expression.exceptions import (
|
from authentik.core.expression.exceptions import (
|
||||||
PropertyMappingExpressionException,
|
PropertyMappingExpressionException,
|
||||||
SkipObjectException,
|
|
||||||
)
|
)
|
||||||
from authentik.events.models import Event, EventAction
|
from authentik.events.models import Event, EventAction
|
||||||
|
from authentik.lib.expression.exceptions import ControlFlowException
|
||||||
from authentik.lib.sync.mapper import PropertyMappingManager
|
from authentik.lib.sync.mapper import PropertyMappingManager
|
||||||
from authentik.lib.sync.outgoing.exceptions import NotFoundSyncException, StopSync
|
from authentik.lib.sync.outgoing.exceptions import NotFoundSyncException, StopSync
|
||||||
from authentik.lib.utils.errors import exception_to_string
|
from authentik.lib.utils.errors import exception_to_string
|
||||||
@ -92,7 +92,7 @@ class BaseOutgoingSyncClient[
|
|||||||
eval_kwargs.setdefault("user", None)
|
eval_kwargs.setdefault("user", None)
|
||||||
for value in self.mapper.iter_eval(**eval_kwargs):
|
for value in self.mapper.iter_eval(**eval_kwargs):
|
||||||
always_merger.merge(raw_final_object, value)
|
always_merger.merge(raw_final_object, value)
|
||||||
except SkipObjectException as exc:
|
except ControlFlowException as exc:
|
||||||
raise exc from exc
|
raise exc from exc
|
||||||
except PropertyMappingExpressionException as exc:
|
except PropertyMappingExpressionException as exc:
|
||||||
# Value error can be raised when assigning invalid data to an attribute
|
# Value error can be raised when assigning invalid data to an attribute
|
||||||
|
|||||||
@ -2,6 +2,7 @@ from collections.abc import Callable
|
|||||||
|
|
||||||
from django.core.paginator import Paginator
|
from django.core.paginator import Paginator
|
||||||
from django.db.models import Model
|
from django.db.models import Model
|
||||||
|
from django.db.models.query import Q
|
||||||
from django.db.models.signals import m2m_changed, post_save, pre_delete
|
from django.db.models.signals import m2m_changed, post_save, pre_delete
|
||||||
|
|
||||||
from authentik.core.models import Group, User
|
from authentik.core.models import Group, User
|
||||||
@ -34,7 +35,9 @@ def register_signals(
|
|||||||
|
|
||||||
def model_post_save(sender: type[Model], instance: User | Group, created: bool, **_):
|
def model_post_save(sender: type[Model], instance: User | Group, created: bool, **_):
|
||||||
"""Post save handler"""
|
"""Post save handler"""
|
||||||
if not provider_type.objects.filter(backchannel_application__isnull=False).exists():
|
if not provider_type.objects.filter(
|
||||||
|
Q(backchannel_application__isnull=False) | Q(application__isnull=False)
|
||||||
|
).exists():
|
||||||
return
|
return
|
||||||
task_sync_direct.delay(class_to_path(instance.__class__), instance.pk, Direction.add.value)
|
task_sync_direct.delay(class_to_path(instance.__class__), instance.pk, Direction.add.value)
|
||||||
|
|
||||||
@ -43,7 +46,9 @@ def register_signals(
|
|||||||
|
|
||||||
def model_pre_delete(sender: type[Model], instance: User | Group, **_):
|
def model_pre_delete(sender: type[Model], instance: User | Group, **_):
|
||||||
"""Pre-delete handler"""
|
"""Pre-delete handler"""
|
||||||
if not provider_type.objects.filter(backchannel_application__isnull=False).exists():
|
if not provider_type.objects.filter(
|
||||||
|
Q(backchannel_application__isnull=False) | Q(application__isnull=False)
|
||||||
|
).exists():
|
||||||
return
|
return
|
||||||
task_sync_direct.delay(
|
task_sync_direct.delay(
|
||||||
class_to_path(instance.__class__), instance.pk, Direction.remove.value
|
class_to_path(instance.__class__), instance.pk, Direction.remove.value
|
||||||
@ -58,7 +63,9 @@ def register_signals(
|
|||||||
"""Sync group membership"""
|
"""Sync group membership"""
|
||||||
if action not in ["post_add", "post_remove"]:
|
if action not in ["post_add", "post_remove"]:
|
||||||
return
|
return
|
||||||
if not provider_type.objects.filter(backchannel_application__isnull=False).exists():
|
if not provider_type.objects.filter(
|
||||||
|
Q(backchannel_application__isnull=False) | Q(application__isnull=False)
|
||||||
|
).exists():
|
||||||
return
|
return
|
||||||
# reverse: instance is a Group, pk_set is a list of user pks
|
# reverse: instance is a Group, pk_set is a list of user pks
|
||||||
# non-reverse: instance is a User, pk_set is a list of groups
|
# non-reverse: instance is a User, pk_set is a list of groups
|
||||||
|
|||||||
@ -5,6 +5,7 @@ from celery.exceptions import Retry
|
|||||||
from celery.result import allow_join_result
|
from celery.result import allow_join_result
|
||||||
from django.core.paginator import Paginator
|
from django.core.paginator import Paginator
|
||||||
from django.db.models import Model, QuerySet
|
from django.db.models import Model, QuerySet
|
||||||
|
from django.db.models.query import Q
|
||||||
from django.utils.text import slugify
|
from django.utils.text import slugify
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from structlog.stdlib import BoundLogger, get_logger
|
from structlog.stdlib import BoundLogger, get_logger
|
||||||
@ -37,7 +38,9 @@ class SyncTasks:
|
|||||||
self._provider_model = provider_model
|
self._provider_model = provider_model
|
||||||
|
|
||||||
def sync_all(self, single_sync: Callable[[int], None]):
|
def sync_all(self, single_sync: Callable[[int], None]):
|
||||||
for provider in self._provider_model.objects.filter(backchannel_application__isnull=False):
|
for provider in self._provider_model.objects.filter(
|
||||||
|
Q(backchannel_application__isnull=False) | Q(application__isnull=False)
|
||||||
|
):
|
||||||
self.trigger_single_task(provider, single_sync)
|
self.trigger_single_task(provider, single_sync)
|
||||||
|
|
||||||
def trigger_single_task(self, provider: OutgoingSyncProvider, sync_task: Callable[[int], None]):
|
def trigger_single_task(self, provider: OutgoingSyncProvider, sync_task: Callable[[int], None]):
|
||||||
@ -62,7 +65,8 @@ class SyncTasks:
|
|||||||
provider_pk=provider_pk,
|
provider_pk=provider_pk,
|
||||||
)
|
)
|
||||||
provider = self._provider_model.objects.filter(
|
provider = self._provider_model.objects.filter(
|
||||||
pk=provider_pk, backchannel_application__isnull=False
|
Q(backchannel_application__isnull=False) | Q(application__isnull=False),
|
||||||
|
pk=provider_pk,
|
||||||
).first()
|
).first()
|
||||||
if not provider:
|
if not provider:
|
||||||
return
|
return
|
||||||
@ -204,7 +208,9 @@ class SyncTasks:
|
|||||||
if not instance:
|
if not instance:
|
||||||
return
|
return
|
||||||
operation = Direction(raw_op)
|
operation = Direction(raw_op)
|
||||||
for provider in self._provider_model.objects.filter(backchannel_application__isnull=False):
|
for provider in self._provider_model.objects.filter(
|
||||||
|
Q(backchannel_application__isnull=False) | Q(application__isnull=False)
|
||||||
|
):
|
||||||
client = provider.client_for_model(instance.__class__)
|
client = provider.client_for_model(instance.__class__)
|
||||||
# Check if the object is allowed within the provider's restrictions
|
# Check if the object is allowed within the provider's restrictions
|
||||||
queryset = provider.get_object_qs(instance.__class__)
|
queryset = provider.get_object_qs(instance.__class__)
|
||||||
@ -233,7 +239,9 @@ class SyncTasks:
|
|||||||
group = Group.objects.filter(pk=group_pk).first()
|
group = Group.objects.filter(pk=group_pk).first()
|
||||||
if not group:
|
if not group:
|
||||||
return
|
return
|
||||||
for provider in self._provider_model.objects.filter(backchannel_application__isnull=False):
|
for provider in self._provider_model.objects.filter(
|
||||||
|
Q(backchannel_application__isnull=False) | Q(application__isnull=False)
|
||||||
|
):
|
||||||
# Check if the object is allowed within the provider's restrictions
|
# Check if the object is allowed within the provider's restrictions
|
||||||
queryset: QuerySet = provider.get_object_qs(Group)
|
queryset: QuerySet = provider.get_object_qs(Group)
|
||||||
# The queryset we get from the provider must include the instance we've got given
|
# The queryset we get from the provider must include the instance we've got given
|
||||||
|
|||||||
@ -20,6 +20,7 @@ from authentik.core.api.utils import JSONDictField, ModelSerializer, PassiveSeri
|
|||||||
from authentik.core.models import Provider
|
from authentik.core.models import Provider
|
||||||
from authentik.enterprise.license import LicenseKey
|
from authentik.enterprise.license import LicenseKey
|
||||||
from authentik.enterprise.providers.rac.models import RACProvider
|
from authentik.enterprise.providers.rac.models import RACProvider
|
||||||
|
from authentik.lib.utils.time import timedelta_from_string, timedelta_string_validator
|
||||||
from authentik.outposts.api.service_connections import ServiceConnectionSerializer
|
from authentik.outposts.api.service_connections import ServiceConnectionSerializer
|
||||||
from authentik.outposts.apps import MANAGED_OUTPOST, MANAGED_OUTPOST_NAME
|
from authentik.outposts.apps import MANAGED_OUTPOST, MANAGED_OUTPOST_NAME
|
||||||
from authentik.outposts.models import (
|
from authentik.outposts.models import (
|
||||||
@ -49,6 +50,10 @@ class OutpostSerializer(ModelSerializer):
|
|||||||
service_connection_obj = ServiceConnectionSerializer(
|
service_connection_obj = ServiceConnectionSerializer(
|
||||||
source="service_connection", read_only=True
|
source="service_connection", read_only=True
|
||||||
)
|
)
|
||||||
|
refresh_interval_s = SerializerMethodField()
|
||||||
|
|
||||||
|
def get_refresh_interval_s(self, obj: Outpost) -> int:
|
||||||
|
return int(timedelta_from_string(obj.config.refresh_interval).total_seconds())
|
||||||
|
|
||||||
def validate_name(self, name: str) -> str:
|
def validate_name(self, name: str) -> str:
|
||||||
"""Validate name (especially for embedded outpost)"""
|
"""Validate name (especially for embedded outpost)"""
|
||||||
@ -84,7 +89,8 @@ class OutpostSerializer(ModelSerializer):
|
|||||||
def validate_config(self, config) -> dict:
|
def validate_config(self, config) -> dict:
|
||||||
"""Check that the config has all required fields"""
|
"""Check that the config has all required fields"""
|
||||||
try:
|
try:
|
||||||
from_dict(OutpostConfig, config)
|
parsed = from_dict(OutpostConfig, config)
|
||||||
|
timedelta_string_validator(parsed.refresh_interval)
|
||||||
except DaciteError as exc:
|
except DaciteError as exc:
|
||||||
raise ValidationError(f"Failed to validate config: {str(exc)}") from exc
|
raise ValidationError(f"Failed to validate config: {str(exc)}") from exc
|
||||||
return config
|
return config
|
||||||
@ -99,6 +105,7 @@ class OutpostSerializer(ModelSerializer):
|
|||||||
"providers_obj",
|
"providers_obj",
|
||||||
"service_connection",
|
"service_connection",
|
||||||
"service_connection_obj",
|
"service_connection_obj",
|
||||||
|
"refresh_interval_s",
|
||||||
"token_identifier",
|
"token_identifier",
|
||||||
"config",
|
"config",
|
||||||
"managed",
|
"managed",
|
||||||
|
|||||||
@ -13,16 +13,17 @@ import authentik.outposts.models
|
|||||||
|
|
||||||
|
|
||||||
def fix_missing_token_identifier(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
def fix_missing_token_identifier(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||||
|
db_alias = schema_editor.connection.alias
|
||||||
User = apps.get_model("authentik_core", "User")
|
User = apps.get_model("authentik_core", "User")
|
||||||
Token = apps.get_model("authentik_core", "Token")
|
Token = apps.get_model("authentik_core", "Token")
|
||||||
from authentik.outposts.models import Outpost
|
from authentik.outposts.models import Outpost
|
||||||
|
|
||||||
for outpost in Outpost.objects.using(schema_editor.connection.alias).all().only("pk"):
|
for outpost in Outpost.objects.using(db_alias).all().only("pk"):
|
||||||
user_identifier = outpost.user_identifier
|
user_identifier = outpost.user_identifier
|
||||||
users = User.objects.filter(username=user_identifier)
|
users = User.objects.using(db_alias).filter(username=user_identifier)
|
||||||
if not users.exists():
|
if not users.exists():
|
||||||
continue
|
continue
|
||||||
tokens = Token.objects.filter(user=users.first())
|
tokens = Token.objects.using(db_alias).filter(user=users.first())
|
||||||
for token in tokens:
|
for token in tokens:
|
||||||
if token.identifier != outpost.token_identifier:
|
if token.identifier != outpost.token_identifier:
|
||||||
token.identifier = outpost.token_identifier
|
token.identifier = outpost.token_identifier
|
||||||
@ -37,8 +38,8 @@ def migrate_to_service_connection(apps: Apps, schema_editor: BaseDatabaseSchemaE
|
|||||||
"authentik_outposts", "KubernetesServiceConnection"
|
"authentik_outposts", "KubernetesServiceConnection"
|
||||||
)
|
)
|
||||||
|
|
||||||
docker = DockerServiceConnection.objects.filter(local=True).first()
|
docker = DockerServiceConnection.objects.using(db_alias).filter(local=True).first()
|
||||||
k8s = KubernetesServiceConnection.objects.filter(local=True).first()
|
k8s = KubernetesServiceConnection.objects.using(db_alias).filter(local=True).first()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
for outpost in Outpost.objects.using(db_alias).all().exclude(deployment_type="custom"):
|
for outpost in Outpost.objects.using(db_alias).all().exclude(deployment_type="custom"):
|
||||||
@ -54,21 +55,21 @@ def migrate_to_service_connection(apps: Apps, schema_editor: BaseDatabaseSchemaE
|
|||||||
|
|
||||||
|
|
||||||
def remove_pb_prefix_users(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
def remove_pb_prefix_users(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||||
alias = schema_editor.connection.alias
|
db_alias = schema_editor.connection.alias
|
||||||
User = apps.get_model("authentik_core", "User")
|
User = apps.get_model("authentik_core", "User")
|
||||||
Outpost = apps.get_model("authentik_outposts", "Outpost")
|
Outpost = apps.get_model("authentik_outposts", "Outpost")
|
||||||
|
|
||||||
for outpost in Outpost.objects.using(alias).all():
|
for outpost in Outpost.objects.using(db_alias).all():
|
||||||
matching = User.objects.using(alias).filter(username=f"pb-outpost-{outpost.uuid.hex}")
|
matching = User.objects.using(db_alias).filter(username=f"pb-outpost-{outpost.uuid.hex}")
|
||||||
if matching.exists():
|
if matching.exists():
|
||||||
matching.delete()
|
matching.delete()
|
||||||
|
|
||||||
|
|
||||||
def update_config_prefix(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
def update_config_prefix(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||||
alias = schema_editor.connection.alias
|
db_alias = schema_editor.connection.alias
|
||||||
Outpost = apps.get_model("authentik_outposts", "Outpost")
|
Outpost = apps.get_model("authentik_outposts", "Outpost")
|
||||||
|
|
||||||
for outpost in Outpost.objects.using(alias).all():
|
for outpost in Outpost.objects.using(db_alias).all():
|
||||||
config = outpost._config
|
config = outpost._config
|
||||||
for key in list(config):
|
for key in list(config):
|
||||||
if "passbook" in key:
|
if "passbook" in key:
|
||||||
|
|||||||
@ -61,6 +61,7 @@ class OutpostConfig:
|
|||||||
|
|
||||||
log_level: str = CONFIG.get("log_level")
|
log_level: str = CONFIG.get("log_level")
|
||||||
object_naming_template: str = field(default="ak-outpost-%(name)s")
|
object_naming_template: str = field(default="ak-outpost-%(name)s")
|
||||||
|
refresh_interval: str = "minutes=5"
|
||||||
|
|
||||||
container_image: str | None = field(default=None)
|
container_image: str | None = field(default=None)
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
"""Reputation policy API Views"""
|
"""Reputation policy API Views"""
|
||||||
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from django_filters.filters import BaseInFilter, CharFilter
|
||||||
|
from django_filters.filterset import FilterSet
|
||||||
from rest_framework import mixins
|
from rest_framework import mixins
|
||||||
from rest_framework.exceptions import ValidationError
|
from rest_framework.exceptions import ValidationError
|
||||||
from rest_framework.viewsets import GenericViewSet, ModelViewSet
|
from rest_framework.viewsets import GenericViewSet, ModelViewSet
|
||||||
@ -11,6 +13,10 @@ from authentik.policies.api.policies import PolicySerializer
|
|||||||
from authentik.policies.reputation.models import Reputation, ReputationPolicy
|
from authentik.policies.reputation.models import Reputation, ReputationPolicy
|
||||||
|
|
||||||
|
|
||||||
|
class CharInFilter(BaseInFilter, CharFilter):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ReputationPolicySerializer(PolicySerializer):
|
class ReputationPolicySerializer(PolicySerializer):
|
||||||
"""Reputation Policy Serializer"""
|
"""Reputation Policy Serializer"""
|
||||||
|
|
||||||
@ -38,6 +44,16 @@ class ReputationPolicyViewSet(UsedByMixin, ModelViewSet):
|
|||||||
ordering = ["name"]
|
ordering = ["name"]
|
||||||
|
|
||||||
|
|
||||||
|
class ReputationFilter(FilterSet):
|
||||||
|
"""Filter for reputation"""
|
||||||
|
|
||||||
|
identifier_in = CharInFilter(field_name="identifier", lookup_expr="in")
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Reputation
|
||||||
|
fields = ["identifier", "ip", "score"]
|
||||||
|
|
||||||
|
|
||||||
class ReputationSerializer(ModelSerializer):
|
class ReputationSerializer(ModelSerializer):
|
||||||
"""Reputation Serializer"""
|
"""Reputation Serializer"""
|
||||||
|
|
||||||
@ -66,5 +82,5 @@ class ReputationViewSet(
|
|||||||
queryset = Reputation.objects.all()
|
queryset = Reputation.objects.all()
|
||||||
serializer_class = ReputationSerializer
|
serializer_class = ReputationSerializer
|
||||||
search_fields = ["identifier", "ip", "score"]
|
search_fields = ["identifier", "ip", "score"]
|
||||||
filterset_fields = ["identifier", "ip", "score"]
|
filterset_class = ReputationFilter
|
||||||
ordering = ["ip"]
|
ordering = ["ip"]
|
||||||
|
|||||||
@ -10,7 +10,6 @@ from authentik.blueprints.tests import apply_blueprint
|
|||||||
from authentik.core.models import Application
|
from authentik.core.models import Application
|
||||||
from authentik.core.tests.utils import create_test_admin_user, create_test_flow
|
from authentik.core.tests.utils import create_test_admin_user, create_test_flow
|
||||||
from authentik.events.models import Event, EventAction
|
from authentik.events.models import Event, EventAction
|
||||||
from authentik.flows.challenge import ChallengeTypes
|
|
||||||
from authentik.lib.generators import generate_id
|
from authentik.lib.generators import generate_id
|
||||||
from authentik.lib.utils.time import timedelta_from_string
|
from authentik.lib.utils.time import timedelta_from_string
|
||||||
from authentik.providers.oauth2.constants import TOKEN_TYPE
|
from authentik.providers.oauth2.constants import TOKEN_TYPE
|
||||||
@ -327,7 +326,6 @@ class TestAuthorize(OAuthTestCase):
|
|||||||
response.content.decode(),
|
response.content.decode(),
|
||||||
{
|
{
|
||||||
"component": "xak-flow-redirect",
|
"component": "xak-flow-redirect",
|
||||||
"type": ChallengeTypes.REDIRECT.value,
|
|
||||||
"to": f"foo://localhost?code={code.code}&state={state}",
|
"to": f"foo://localhost?code={code.code}&state={state}",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -397,7 +395,6 @@ class TestAuthorize(OAuthTestCase):
|
|||||||
response.content.decode(),
|
response.content.decode(),
|
||||||
{
|
{
|
||||||
"component": "xak-flow-redirect",
|
"component": "xak-flow-redirect",
|
||||||
"type": ChallengeTypes.REDIRECT.value,
|
|
||||||
"to": (
|
"to": (
|
||||||
f"http://localhost#access_token={token.token}"
|
f"http://localhost#access_token={token.token}"
|
||||||
f"&id_token={provider.encode(token.id_token.to_dict())}"
|
f"&id_token={provider.encode(token.id_token.to_dict())}"
|
||||||
@ -460,7 +457,6 @@ class TestAuthorize(OAuthTestCase):
|
|||||||
response.content.decode(),
|
response.content.decode(),
|
||||||
{
|
{
|
||||||
"component": "xak-flow-redirect",
|
"component": "xak-flow-redirect",
|
||||||
"type": ChallengeTypes.REDIRECT.value,
|
|
||||||
"to": (f"http://localhost#code={code.code}" f"&state={state}"),
|
"to": (f"http://localhost#code={code.code}" f"&state={state}"),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -516,7 +512,6 @@ class TestAuthorize(OAuthTestCase):
|
|||||||
response.content.decode(),
|
response.content.decode(),
|
||||||
{
|
{
|
||||||
"component": "ak-stage-autosubmit",
|
"component": "ak-stage-autosubmit",
|
||||||
"type": ChallengeTypes.NATIVE.value,
|
|
||||||
"url": "http://localhost",
|
"url": "http://localhost",
|
||||||
"title": f"Redirecting to {app.name}...",
|
"title": f"Redirecting to {app.name}...",
|
||||||
"attrs": {
|
"attrs": {
|
||||||
@ -564,7 +559,6 @@ class TestAuthorize(OAuthTestCase):
|
|||||||
response.content.decode(),
|
response.content.decode(),
|
||||||
{
|
{
|
||||||
"component": "ak-stage-autosubmit",
|
"component": "ak-stage-autosubmit",
|
||||||
"type": ChallengeTypes.NATIVE.value,
|
|
||||||
"url": "http://localhost",
|
"url": "http://localhost",
|
||||||
"title": f"Redirecting to {app.name}...",
|
"title": f"Redirecting to {app.name}...",
|
||||||
"attrs": {
|
"attrs": {
|
||||||
|
|||||||
@ -4,9 +4,10 @@ from urllib.parse import urlencode
|
|||||||
|
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
from authentik.core.models import Application
|
from authentik.core.models import Application, Group
|
||||||
from authentik.core.tests.utils import create_test_admin_user, create_test_brand, create_test_flow
|
from authentik.core.tests.utils import create_test_admin_user, create_test_brand, create_test_flow
|
||||||
from authentik.lib.generators import generate_id
|
from authentik.lib.generators import generate_id
|
||||||
|
from authentik.policies.models import PolicyBinding
|
||||||
from authentik.providers.oauth2.models import DeviceToken, OAuth2Provider
|
from authentik.providers.oauth2.models import DeviceToken, OAuth2Provider
|
||||||
from authentik.providers.oauth2.tests.utils import OAuthTestCase
|
from authentik.providers.oauth2.tests.utils import OAuthTestCase
|
||||||
from authentik.providers.oauth2.views.device_init import QS_KEY_CODE
|
from authentik.providers.oauth2.views.device_init import QS_KEY_CODE
|
||||||
@ -77,3 +78,23 @@ class TesOAuth2DeviceInit(OAuthTestCase):
|
|||||||
+ "?"
|
+ "?"
|
||||||
+ urlencode({QS_KEY_CODE: token.user_code}),
|
+ urlencode({QS_KEY_CODE: token.user_code}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_device_init_denied(self):
|
||||||
|
"""Test device init"""
|
||||||
|
group = Group.objects.create(name="foo")
|
||||||
|
PolicyBinding.objects.create(
|
||||||
|
group=group,
|
||||||
|
target=self.application,
|
||||||
|
order=0,
|
||||||
|
)
|
||||||
|
token = DeviceToken.objects.create(
|
||||||
|
user_code="foo",
|
||||||
|
provider=self.provider,
|
||||||
|
)
|
||||||
|
res = self.client.get(
|
||||||
|
reverse("authentik_providers_oauth2_root:device-login")
|
||||||
|
+ "?"
|
||||||
|
+ urlencode({QS_KEY_CODE: token.user_code})
|
||||||
|
)
|
||||||
|
self.assertEqual(res.status_code, 200)
|
||||||
|
self.assertIn(b"Permission denied", res.content)
|
||||||
|
|||||||
@ -8,7 +8,6 @@ from django.urls import reverse
|
|||||||
|
|
||||||
from authentik.core.models import Application
|
from authentik.core.models import Application
|
||||||
from authentik.core.tests.utils import create_test_admin_user, create_test_flow
|
from authentik.core.tests.utils import create_test_admin_user, create_test_flow
|
||||||
from authentik.flows.challenge import ChallengeTypes
|
|
||||||
from authentik.lib.generators import generate_id
|
from authentik.lib.generators import generate_id
|
||||||
from authentik.providers.oauth2.constants import GRANT_TYPE_AUTHORIZATION_CODE
|
from authentik.providers.oauth2.constants import GRANT_TYPE_AUTHORIZATION_CODE
|
||||||
from authentik.providers.oauth2.models import AuthorizationCode, OAuth2Provider
|
from authentik.providers.oauth2.models import AuthorizationCode, OAuth2Provider
|
||||||
@ -60,7 +59,6 @@ class TestTokenPKCE(OAuthTestCase):
|
|||||||
response.content.decode(),
|
response.content.decode(),
|
||||||
{
|
{
|
||||||
"component": "xak-flow-redirect",
|
"component": "xak-flow-redirect",
|
||||||
"type": ChallengeTypes.REDIRECT.value,
|
|
||||||
"to": f"foo://localhost?code={code.code}&state={state}",
|
"to": f"foo://localhost?code={code.code}&state={state}",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -123,7 +121,6 @@ class TestTokenPKCE(OAuthTestCase):
|
|||||||
response.content.decode(),
|
response.content.decode(),
|
||||||
{
|
{
|
||||||
"component": "xak-flow-redirect",
|
"component": "xak-flow-redirect",
|
||||||
"type": ChallengeTypes.REDIRECT.value,
|
|
||||||
"to": f"foo://localhost?code={code.code}&state={state}",
|
"to": f"foo://localhost?code={code.code}&state={state}",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -191,7 +188,6 @@ class TestTokenPKCE(OAuthTestCase):
|
|||||||
response.content.decode(),
|
response.content.decode(),
|
||||||
{
|
{
|
||||||
"component": "xak-flow-redirect",
|
"component": "xak-flow-redirect",
|
||||||
"type": ChallengeTypes.REDIRECT.value,
|
|
||||||
"to": f"foo://localhost?code={code.code}&state={state}",
|
"to": f"foo://localhost?code={code.code}&state={state}",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -242,7 +238,6 @@ class TestTokenPKCE(OAuthTestCase):
|
|||||||
response.content.decode(),
|
response.content.decode(),
|
||||||
{
|
{
|
||||||
"component": "xak-flow-redirect",
|
"component": "xak-flow-redirect",
|
||||||
"type": ChallengeTypes.REDIRECT.value,
|
|
||||||
"to": f"foo://localhost?code={code.code}&state={state}",
|
"to": f"foo://localhost?code={code.code}&state={state}",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
@ -22,7 +22,6 @@ from authentik.events.signals import get_login_event
|
|||||||
from authentik.flows.challenge import (
|
from authentik.flows.challenge import (
|
||||||
PLAN_CONTEXT_TITLE,
|
PLAN_CONTEXT_TITLE,
|
||||||
AutosubmitChallenge,
|
AutosubmitChallenge,
|
||||||
ChallengeTypes,
|
|
||||||
HttpChallengeResponse,
|
HttpChallengeResponse,
|
||||||
)
|
)
|
||||||
from authentik.flows.exceptions import FlowNonApplicableException
|
from authentik.flows.exceptions import FlowNonApplicableException
|
||||||
@ -484,7 +483,6 @@ class OAuthFulfillmentStage(StageView):
|
|||||||
|
|
||||||
challenge = AutosubmitChallenge(
|
challenge = AutosubmitChallenge(
|
||||||
data={
|
data={
|
||||||
"type": ChallengeTypes.NATIVE.value,
|
|
||||||
"component": "ak-stage-autosubmit",
|
"component": "ak-stage-autosubmit",
|
||||||
"title": self.executor.plan.context.get(
|
"title": self.executor.plan.context.get(
|
||||||
PLAN_CONTEXT_TITLE,
|
PLAN_CONTEXT_TITLE,
|
||||||
|
|||||||
@ -11,10 +11,11 @@ from django.views.decorators.csrf import csrf_exempt
|
|||||||
from rest_framework.throttling import AnonRateThrottle
|
from rest_framework.throttling import AnonRateThrottle
|
||||||
from structlog.stdlib import get_logger
|
from structlog.stdlib import get_logger
|
||||||
|
|
||||||
|
from authentik.core.models import Application
|
||||||
from authentik.lib.config import CONFIG
|
from authentik.lib.config import CONFIG
|
||||||
from authentik.lib.utils.time import timedelta_from_string
|
from authentik.lib.utils.time import timedelta_from_string
|
||||||
from authentik.providers.oauth2.models import DeviceToken, OAuth2Provider
|
from authentik.providers.oauth2.models import DeviceToken, OAuth2Provider
|
||||||
from authentik.providers.oauth2.views.device_init import QS_KEY_CODE, get_application
|
from authentik.providers.oauth2.views.device_init import QS_KEY_CODE
|
||||||
|
|
||||||
LOGGER = get_logger()
|
LOGGER = get_logger()
|
||||||
|
|
||||||
@ -37,7 +38,9 @@ class DeviceView(View):
|
|||||||
).first()
|
).first()
|
||||||
if not provider:
|
if not provider:
|
||||||
return HttpResponseBadRequest()
|
return HttpResponseBadRequest()
|
||||||
if not get_application(provider):
|
try:
|
||||||
|
_ = provider.application
|
||||||
|
except Application.DoesNotExist:
|
||||||
return HttpResponseBadRequest()
|
return HttpResponseBadRequest()
|
||||||
self.provider = provider
|
self.provider = provider
|
||||||
self.client_id = client_id
|
self.client_id = client_id
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
from rest_framework.fields import CharField
|
from rest_framework.fields import CharField
|
||||||
|
|
||||||
from authentik.flows.challenge import Challenge, ChallengeResponse, ChallengeTypes
|
from authentik.flows.challenge import Challenge, ChallengeResponse
|
||||||
from authentik.flows.planner import FlowPlan
|
from authentik.flows.planner import FlowPlan
|
||||||
from authentik.flows.stage import ChallengeStageView
|
from authentik.flows.stage import ChallengeStageView
|
||||||
from authentik.flows.views.executor import SESSION_KEY_PLAN
|
from authentik.flows.views.executor import SESSION_KEY_PLAN
|
||||||
@ -38,7 +38,6 @@ class OAuthDeviceCodeFinishStage(ChallengeStageView):
|
|||||||
token.save()
|
token.save()
|
||||||
return OAuthDeviceCodeFinishChallenge(
|
return OAuthDeviceCodeFinishChallenge(
|
||||||
data={
|
data={
|
||||||
"type": ChallengeTypes.NATIVE.value,
|
|
||||||
"component": "ak-provider-oauth2-device-code-finish",
|
"component": "ak-provider-oauth2-device-code-finish",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,22 +1,24 @@
|
|||||||
"""Device flow views"""
|
"""Device flow views"""
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from django.http import HttpRequest, HttpResponse
|
from django.http import HttpRequest, HttpResponse
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
from django.views import View
|
|
||||||
from rest_framework.exceptions import ValidationError
|
from rest_framework.exceptions import ValidationError
|
||||||
from rest_framework.fields import CharField, IntegerField
|
from rest_framework.fields import CharField, IntegerField
|
||||||
from structlog.stdlib import get_logger
|
from structlog.stdlib import get_logger
|
||||||
|
|
||||||
from authentik.brands.models import Brand
|
from authentik.brands.models import Brand
|
||||||
from authentik.core.models import Application
|
from authentik.core.models import Application
|
||||||
from authentik.flows.challenge import Challenge, ChallengeResponse, ChallengeTypes
|
from authentik.flows.challenge import Challenge, ChallengeResponse
|
||||||
from authentik.flows.exceptions import FlowNonApplicableException
|
from authentik.flows.exceptions import FlowNonApplicableException
|
||||||
from authentik.flows.models import in_memory_stage
|
from authentik.flows.models import in_memory_stage
|
||||||
from authentik.flows.planner import PLAN_CONTEXT_APPLICATION, PLAN_CONTEXT_SSO, FlowPlanner
|
from authentik.flows.planner import PLAN_CONTEXT_APPLICATION, PLAN_CONTEXT_SSO, FlowPlanner
|
||||||
from authentik.flows.stage import ChallengeStageView
|
from authentik.flows.stage import ChallengeStageView
|
||||||
from authentik.flows.views.executor import SESSION_KEY_PLAN
|
from authentik.flows.views.executor import SESSION_KEY_PLAN
|
||||||
from authentik.lib.utils.urls import redirect_with_qs
|
from authentik.lib.utils.urls import redirect_with_qs
|
||||||
from authentik.providers.oauth2.models import DeviceToken, OAuth2Provider
|
from authentik.policies.views import PolicyAccessView
|
||||||
|
from authentik.providers.oauth2.models import DeviceToken
|
||||||
from authentik.providers.oauth2.views.device_finish import (
|
from authentik.providers.oauth2.views.device_finish import (
|
||||||
PLAN_CONTEXT_DEVICE,
|
PLAN_CONTEXT_DEVICE,
|
||||||
OAuthDeviceCodeFinishStage,
|
OAuthDeviceCodeFinishStage,
|
||||||
@ -31,60 +33,52 @@ LOGGER = get_logger()
|
|||||||
QS_KEY_CODE = "code" # nosec
|
QS_KEY_CODE = "code" # nosec
|
||||||
|
|
||||||
|
|
||||||
def get_application(provider: OAuth2Provider) -> Application | None:
|
class CodeValidatorView(PolicyAccessView):
|
||||||
"""Get application from provider"""
|
"""Helper to validate frontside token"""
|
||||||
try:
|
|
||||||
app = provider.application
|
def __init__(self, code: str, **kwargs: Any) -> None:
|
||||||
if not app:
|
super().__init__(**kwargs)
|
||||||
|
self.code = code
|
||||||
|
|
||||||
|
def resolve_provider_application(self):
|
||||||
|
self.token = DeviceToken.objects.filter(user_code=self.code).first()
|
||||||
|
if not self.token:
|
||||||
|
raise Application.DoesNotExist
|
||||||
|
self.provider = self.token.provider
|
||||||
|
self.application = self.token.provider.application
|
||||||
|
|
||||||
|
def get(self, request: HttpRequest, *args, **kwargs):
|
||||||
|
scope_descriptions = UserInfoView().get_scope_descriptions(self.token.scope, self.provider)
|
||||||
|
planner = FlowPlanner(self.provider.authorization_flow)
|
||||||
|
planner.allow_empty_flows = True
|
||||||
|
planner.use_cache = False
|
||||||
|
try:
|
||||||
|
plan = planner.plan(
|
||||||
|
request,
|
||||||
|
{
|
||||||
|
PLAN_CONTEXT_SSO: True,
|
||||||
|
PLAN_CONTEXT_APPLICATION: self.application,
|
||||||
|
# OAuth2 related params
|
||||||
|
PLAN_CONTEXT_DEVICE: self.token,
|
||||||
|
# Consent related params
|
||||||
|
PLAN_CONTEXT_CONSENT_HEADER: _("You're about to sign into %(application)s.")
|
||||||
|
% {"application": self.application.name},
|
||||||
|
PLAN_CONTEXT_CONSENT_PERMISSIONS: scope_descriptions,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
except FlowNonApplicableException:
|
||||||
|
LOGGER.warning("Flow not applicable to user")
|
||||||
return None
|
return None
|
||||||
return app
|
plan.insert_stage(in_memory_stage(OAuthDeviceCodeFinishStage))
|
||||||
except Application.DoesNotExist:
|
request.session[SESSION_KEY_PLAN] = plan
|
||||||
return None
|
return redirect_with_qs(
|
||||||
|
"authentik_core:if-flow",
|
||||||
|
request.GET,
|
||||||
def validate_code(code: int, request: HttpRequest) -> HttpResponse | None:
|
flow_slug=self.token.provider.authorization_flow.slug,
|
||||||
"""Validate user token"""
|
|
||||||
token = DeviceToken.objects.filter(
|
|
||||||
user_code=code,
|
|
||||||
).first()
|
|
||||||
if not token:
|
|
||||||
return None
|
|
||||||
|
|
||||||
app = get_application(token.provider)
|
|
||||||
if not app:
|
|
||||||
return None
|
|
||||||
|
|
||||||
scope_descriptions = UserInfoView().get_scope_descriptions(token.scope, token.provider)
|
|
||||||
planner = FlowPlanner(token.provider.authorization_flow)
|
|
||||||
planner.allow_empty_flows = True
|
|
||||||
planner.use_cache = False
|
|
||||||
try:
|
|
||||||
plan = planner.plan(
|
|
||||||
request,
|
|
||||||
{
|
|
||||||
PLAN_CONTEXT_SSO: True,
|
|
||||||
PLAN_CONTEXT_APPLICATION: app,
|
|
||||||
# OAuth2 related params
|
|
||||||
PLAN_CONTEXT_DEVICE: token,
|
|
||||||
# Consent related params
|
|
||||||
PLAN_CONTEXT_CONSENT_HEADER: _("You're about to sign into %(application)s.")
|
|
||||||
% {"application": app.name},
|
|
||||||
PLAN_CONTEXT_CONSENT_PERMISSIONS: scope_descriptions,
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
except FlowNonApplicableException:
|
|
||||||
LOGGER.warning("Flow not applicable to user")
|
|
||||||
return None
|
|
||||||
plan.insert_stage(in_memory_stage(OAuthDeviceCodeFinishStage))
|
|
||||||
request.session[SESSION_KEY_PLAN] = plan
|
|
||||||
return redirect_with_qs(
|
|
||||||
"authentik_core:if-flow",
|
|
||||||
request.GET,
|
|
||||||
flow_slug=token.provider.authorization_flow.slug,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class DeviceEntryView(View):
|
class DeviceEntryView(PolicyAccessView):
|
||||||
"""View used to initiate the device-code flow, url entered by endusers"""
|
"""View used to initiate the device-code flow, url entered by endusers"""
|
||||||
|
|
||||||
def dispatch(self, request: HttpRequest) -> HttpResponse:
|
def dispatch(self, request: HttpRequest) -> HttpResponse:
|
||||||
@ -94,7 +88,9 @@ class DeviceEntryView(View):
|
|||||||
LOGGER.info("Brand has no device code flow configured", brand=brand)
|
LOGGER.info("Brand has no device code flow configured", brand=brand)
|
||||||
return HttpResponse(status=404)
|
return HttpResponse(status=404)
|
||||||
if QS_KEY_CODE in request.GET:
|
if QS_KEY_CODE in request.GET:
|
||||||
validation = validate_code(request.GET[QS_KEY_CODE], request)
|
validation = CodeValidatorView(request.GET[QS_KEY_CODE], request=request).dispatch(
|
||||||
|
request
|
||||||
|
)
|
||||||
if validation:
|
if validation:
|
||||||
return validation
|
return validation
|
||||||
LOGGER.info("Got code from query parameter but no matching token found")
|
LOGGER.info("Got code from query parameter but no matching token found")
|
||||||
@ -131,7 +127,7 @@ class OAuthDeviceCodeChallengeResponse(ChallengeResponse):
|
|||||||
|
|
||||||
def validate_code(self, code: int) -> HttpResponse | None:
|
def validate_code(self, code: int) -> HttpResponse | None:
|
||||||
"""Validate code and save the returned http response"""
|
"""Validate code and save the returned http response"""
|
||||||
response = validate_code(code, self.stage.request)
|
response = CodeValidatorView(code, request=self.stage.request).dispatch(self.stage.request)
|
||||||
if not response:
|
if not response:
|
||||||
raise ValidationError(_("Invalid code"), "invalid")
|
raise ValidationError(_("Invalid code"), "invalid")
|
||||||
return response
|
return response
|
||||||
@ -145,7 +141,6 @@ class OAuthDeviceCodeStage(ChallengeStageView):
|
|||||||
def get_challenge(self, *args, **kwargs) -> Challenge:
|
def get_challenge(self, *args, **kwargs) -> Challenge:
|
||||||
return OAuthDeviceCodeChallenge(
|
return OAuthDeviceCodeChallenge(
|
||||||
data={
|
data={
|
||||||
"type": ChallengeTypes.NATIVE.value,
|
|
||||||
"component": "ak-provider-oauth2-device-code",
|
"component": "ak-provider-oauth2-device-code",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@ -268,7 +268,7 @@ class SAMLProviderViewSet(UsedByMixin, ModelViewSet):
|
|||||||
except ValueError as exc: # pragma: no cover
|
except ValueError as exc: # pragma: no cover
|
||||||
LOGGER.warning(str(exc))
|
LOGGER.warning(str(exc))
|
||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
_("Failed to import Metadata: {messages}".format_map({"message": str(exc)})),
|
_("Failed to import Metadata: {messages}".format_map({"messages": str(exc)})),
|
||||||
) from None
|
) from None
|
||||||
return Response(status=204)
|
return Response(status=204)
|
||||||
|
|
||||||
|
|||||||
@ -16,7 +16,6 @@ from authentik.flows.challenge import (
|
|||||||
AutoSubmitChallengeResponse,
|
AutoSubmitChallengeResponse,
|
||||||
Challenge,
|
Challenge,
|
||||||
ChallengeResponse,
|
ChallengeResponse,
|
||||||
ChallengeTypes,
|
|
||||||
)
|
)
|
||||||
from authentik.flows.planner import PLAN_CONTEXT_APPLICATION
|
from authentik.flows.planner import PLAN_CONTEXT_APPLICATION
|
||||||
from authentik.flows.stage import ChallengeStageView
|
from authentik.flows.stage import ChallengeStageView
|
||||||
@ -81,7 +80,6 @@ class SAMLFlowFinalView(ChallengeStageView):
|
|||||||
return super().get(
|
return super().get(
|
||||||
self.request,
|
self.request,
|
||||||
**{
|
**{
|
||||||
"type": ChallengeTypes.NATIVE.value,
|
|
||||||
"component": "ak-stage-autosubmit",
|
"component": "ak-stage-autosubmit",
|
||||||
"title": self.executor.plan.context.get(
|
"title": self.executor.plan.context.get(
|
||||||
PLAN_CONTEXT_TITLE,
|
PLAN_CONTEXT_TITLE,
|
||||||
|
|||||||
@ -89,6 +89,6 @@ class SCIMClient[TModel: "Model", TConnection: "Model", TSchema: "BaseModel"](
|
|||||||
return ServiceProviderConfiguration.model_validate(
|
return ServiceProviderConfiguration.model_validate(
|
||||||
self._request("GET", "/ServiceProviderConfig")
|
self._request("GET", "/ServiceProviderConfig")
|
||||||
)
|
)
|
||||||
except (ValidationError, SCIMRequestException) as exc:
|
except (ValidationError, SCIMRequestException, NotFoundSyncException) as exc:
|
||||||
self.logger.warning("failed to get ServiceProviderConfig", exc=exc)
|
self.logger.warning("failed to get ServiceProviderConfig", exc=exc)
|
||||||
return default_config
|
return default_config
|
||||||
|
|||||||
@ -5,6 +5,7 @@ from collections import OrderedDict
|
|||||||
from hashlib import sha512
|
from hashlib import sha512
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
import orjson
|
||||||
from celery.schedules import crontab
|
from celery.schedules import crontab
|
||||||
from django.conf import ImproperlyConfigured
|
from django.conf import ImproperlyConfigured
|
||||||
from sentry_sdk import set_tag
|
from sentry_sdk import set_tag
|
||||||
@ -147,7 +148,6 @@ SPECTACULAR_SETTINGS = {
|
|||||||
},
|
},
|
||||||
"ENUM_NAME_OVERRIDES": {
|
"ENUM_NAME_OVERRIDES": {
|
||||||
"EventActions": "authentik.events.models.EventAction",
|
"EventActions": "authentik.events.models.EventAction",
|
||||||
"ChallengeChoices": "authentik.flows.challenge.ChallengeTypes",
|
|
||||||
"FlowDesignationEnum": "authentik.flows.models.FlowDesignation",
|
"FlowDesignationEnum": "authentik.flows.models.FlowDesignation",
|
||||||
"FlowLayoutEnum": "authentik.flows.models.FlowLayout",
|
"FlowLayoutEnum": "authentik.flows.models.FlowLayout",
|
||||||
"PolicyEngineMode": "authentik.policies.models.PolicyEngineMode",
|
"PolicyEngineMode": "authentik.policies.models.PolicyEngineMode",
|
||||||
@ -178,16 +178,20 @@ REST_FRAMEWORK = {
|
|||||||
"rest_framework.filters.OrderingFilter",
|
"rest_framework.filters.OrderingFilter",
|
||||||
"rest_framework.filters.SearchFilter",
|
"rest_framework.filters.SearchFilter",
|
||||||
],
|
],
|
||||||
"DEFAULT_PARSER_CLASSES": [
|
|
||||||
"rest_framework.parsers.JSONParser",
|
|
||||||
],
|
|
||||||
"DEFAULT_PERMISSION_CLASSES": ("authentik.rbac.permissions.ObjectPermissions",),
|
"DEFAULT_PERMISSION_CLASSES": ("authentik.rbac.permissions.ObjectPermissions",),
|
||||||
"DEFAULT_AUTHENTICATION_CLASSES": (
|
"DEFAULT_AUTHENTICATION_CLASSES": (
|
||||||
"authentik.api.authentication.TokenAuthentication",
|
"authentik.api.authentication.TokenAuthentication",
|
||||||
"rest_framework.authentication.SessionAuthentication",
|
"rest_framework.authentication.SessionAuthentication",
|
||||||
),
|
),
|
||||||
"DEFAULT_RENDERER_CLASSES": [
|
"DEFAULT_RENDERER_CLASSES": [
|
||||||
"rest_framework.renderers.JSONRenderer",
|
"drf_orjson_renderer.renderers.ORJSONRenderer",
|
||||||
|
],
|
||||||
|
"ORJSON_RENDERER_OPTIONS": [
|
||||||
|
orjson.OPT_NON_STR_KEYS,
|
||||||
|
orjson.OPT_UTC_Z,
|
||||||
|
],
|
||||||
|
"DEFAULT_PARSER_CLASSES": [
|
||||||
|
"drf_orjson_renderer.parsers.ORJSONParser",
|
||||||
],
|
],
|
||||||
"DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
|
"DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
|
||||||
"TEST_REQUEST_DEFAULT_FORMAT": "json",
|
"TEST_REQUEST_DEFAULT_FORMAT": "json",
|
||||||
|
|||||||
@ -31,9 +31,9 @@ def set_default_group_mappings(apps: Apps, schema_editor):
|
|||||||
db_alias = schema_editor.connection.alias
|
db_alias = schema_editor.connection.alias
|
||||||
|
|
||||||
for source in LDAPSource.objects.using(db_alias).all():
|
for source in LDAPSource.objects.using(db_alias).all():
|
||||||
if source.property_mappings_group.exists():
|
if source.property_mappings_group.using(db_alias).exists():
|
||||||
continue
|
continue
|
||||||
source.property_mappings_group.set(
|
source.property_mappings_group.using(db_alias).set(
|
||||||
LDAPPropertyMapping.objects.using(db_alias).filter(
|
LDAPPropertyMapping.objects.using(db_alias).filter(
|
||||||
managed="goauthentik.io/sources/ldap/default-name"
|
managed="goauthentik.io/sources/ldap/default-name"
|
||||||
)
|
)
|
||||||
|
|||||||
@ -9,7 +9,7 @@ from jwt import decode, encode
|
|||||||
from rest_framework.fields import CharField
|
from rest_framework.fields import CharField
|
||||||
from structlog.stdlib import get_logger
|
from structlog.stdlib import get_logger
|
||||||
|
|
||||||
from authentik.flows.challenge import Challenge, ChallengeResponse, ChallengeTypes
|
from authentik.flows.challenge import Challenge, ChallengeResponse
|
||||||
from authentik.sources.oauth.clients.oauth2 import OAuth2Client
|
from authentik.sources.oauth.clients.oauth2 import OAuth2Client
|
||||||
from authentik.sources.oauth.models import OAuthSource
|
from authentik.sources.oauth.models import OAuthSource
|
||||||
from authentik.sources.oauth.types.registry import SourceType, registry
|
from authentik.sources.oauth.types.registry import SourceType, registry
|
||||||
@ -130,6 +130,5 @@ class AppleType(SourceType):
|
|||||||
"scope": "name email",
|
"scope": "name email",
|
||||||
"redirect_uri": args["redirect_uri"],
|
"redirect_uri": args["redirect_uri"],
|
||||||
"state": args["state"],
|
"state": args["state"],
|
||||||
"type": ChallengeTypes.NATIVE.value,
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@ -8,7 +8,7 @@ from django.templatetags.static import static
|
|||||||
from django.urls.base import reverse
|
from django.urls.base import reverse
|
||||||
from structlog.stdlib import get_logger
|
from structlog.stdlib import get_logger
|
||||||
|
|
||||||
from authentik.flows.challenge import Challenge, ChallengeTypes, RedirectChallenge
|
from authentik.flows.challenge import Challenge, RedirectChallenge
|
||||||
from authentik.sources.oauth.models import OAuthSource
|
from authentik.sources.oauth.models import OAuthSource
|
||||||
from authentik.sources.oauth.views.callback import OAuthCallback
|
from authentik.sources.oauth.views.callback import OAuthCallback
|
||||||
from authentik.sources.oauth.views.redirect import OAuthRedirect
|
from authentik.sources.oauth.views.redirect import OAuthRedirect
|
||||||
@ -48,7 +48,6 @@ class SourceType:
|
|||||||
"""Allow types to return custom challenges"""
|
"""Allow types to return custom challenges"""
|
||||||
return RedirectChallenge(
|
return RedirectChallenge(
|
||||||
data={
|
data={
|
||||||
"type": ChallengeTypes.REDIRECT.value,
|
|
||||||
"to": reverse(
|
"to": reverse(
|
||||||
"authentik_sources_oauth:oauth-client-login",
|
"authentik_sources_oauth:oauth-client-login",
|
||||||
kwargs={"source_slug": source.slug},
|
kwargs={"source_slug": source.slug},
|
||||||
|
|||||||
@ -10,7 +10,7 @@ from rest_framework.serializers import BaseSerializer, Serializer
|
|||||||
|
|
||||||
from authentik.core.models import Source, UserSourceConnection
|
from authentik.core.models import Source, UserSourceConnection
|
||||||
from authentik.core.types import UILoginButton, UserSettingSerializer
|
from authentik.core.types import UILoginButton, UserSettingSerializer
|
||||||
from authentik.flows.challenge import Challenge, ChallengeResponse, ChallengeTypes
|
from authentik.flows.challenge import Challenge, ChallengeResponse
|
||||||
from authentik.lib.generators import generate_id
|
from authentik.lib.generators import generate_id
|
||||||
|
|
||||||
|
|
||||||
@ -71,7 +71,6 @@ class PlexSource(Source):
|
|||||||
return UILoginButton(
|
return UILoginButton(
|
||||||
challenge=PlexAuthenticationChallenge(
|
challenge=PlexAuthenticationChallenge(
|
||||||
data={
|
data={
|
||||||
"type": ChallengeTypes.NATIVE.value,
|
|
||||||
"component": "ak-source-plex",
|
"component": "ak-source-plex",
|
||||||
"client_id": self.client_id,
|
"client_id": self.client_id,
|
||||||
"slug": self.slug,
|
"slug": self.slug,
|
||||||
|
|||||||
@ -10,6 +10,8 @@ from authentik.sources.saml.processors import constants
|
|||||||
|
|
||||||
|
|
||||||
def update_algorithms(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
def update_algorithms(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||||
|
db_alias = schema_editor.connection.alias
|
||||||
|
|
||||||
SAMLSource = apps.get_model("authentik_sources_saml", "SAMLSource")
|
SAMLSource = apps.get_model("authentik_sources_saml", "SAMLSource")
|
||||||
signature_translation_map = {
|
signature_translation_map = {
|
||||||
"rsa-sha1": constants.RSA_SHA1,
|
"rsa-sha1": constants.RSA_SHA1,
|
||||||
@ -22,7 +24,7 @@ def update_algorithms(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
|||||||
"sha256": constants.SHA256,
|
"sha256": constants.SHA256,
|
||||||
}
|
}
|
||||||
|
|
||||||
for source in SAMLSource.objects.all():
|
for source in SAMLSource.objects.using(db_alias).all():
|
||||||
source.signature_algorithm = signature_translation_map.get(
|
source.signature_algorithm = signature_translation_map.get(
|
||||||
source.signature_algorithm, constants.RSA_SHA256
|
source.signature_algorithm, constants.RSA_SHA256
|
||||||
)
|
)
|
||||||
|
|||||||
@ -10,7 +10,7 @@ from rest_framework.serializers import Serializer
|
|||||||
from authentik.core.models import Source, UserSourceConnection
|
from authentik.core.models import Source, UserSourceConnection
|
||||||
from authentik.core.types import UILoginButton, UserSettingSerializer
|
from authentik.core.types import UILoginButton, UserSettingSerializer
|
||||||
from authentik.crypto.models import CertificateKeyPair
|
from authentik.crypto.models import CertificateKeyPair
|
||||||
from authentik.flows.challenge import ChallengeTypes, RedirectChallenge
|
from authentik.flows.challenge import RedirectChallenge
|
||||||
from authentik.flows.models import Flow
|
from authentik.flows.models import Flow
|
||||||
from authentik.lib.utils.time import timedelta_string_validator
|
from authentik.lib.utils.time import timedelta_string_validator
|
||||||
from authentik.sources.saml.processors.constants import (
|
from authentik.sources.saml.processors.constants import (
|
||||||
@ -204,7 +204,6 @@ class SAMLSource(Source):
|
|||||||
return UILoginButton(
|
return UILoginButton(
|
||||||
challenge=RedirectChallenge(
|
challenge=RedirectChallenge(
|
||||||
data={
|
data={
|
||||||
"type": ChallengeTypes.REDIRECT.value,
|
|
||||||
"to": reverse(
|
"to": reverse(
|
||||||
"authentik_sources_saml:login",
|
"authentik_sources_saml:login",
|
||||||
kwargs={"source_slug": self.slug},
|
kwargs={"source_slug": self.slug},
|
||||||
|
|||||||
@ -10,6 +10,7 @@ from django.core.cache import cache
|
|||||||
from django.core.exceptions import SuspiciousOperation
|
from django.core.exceptions import SuspiciousOperation
|
||||||
from django.http import HttpRequest
|
from django.http import HttpRequest
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
|
from lxml import etree # nosec
|
||||||
from structlog.stdlib import get_logger
|
from structlog.stdlib import get_logger
|
||||||
|
|
||||||
from authentik.core.models import (
|
from authentik.core.models import (
|
||||||
@ -240,7 +241,7 @@ class ResponseProcessor:
|
|||||||
name_id.text,
|
name_id.text,
|
||||||
delete_none_values(self.get_attributes()),
|
delete_none_values(self.get_attributes()),
|
||||||
)
|
)
|
||||||
flow_manager.policy_context["saml_response"] = self._root
|
flow_manager.policy_context["saml_response"] = etree.tostring(self._root)
|
||||||
return flow_manager
|
return flow_manager
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -21,7 +21,6 @@ from authentik.flows.challenge import (
|
|||||||
AutosubmitChallenge,
|
AutosubmitChallenge,
|
||||||
Challenge,
|
Challenge,
|
||||||
ChallengeResponse,
|
ChallengeResponse,
|
||||||
ChallengeTypes,
|
|
||||||
)
|
)
|
||||||
from authentik.flows.exceptions import FlowNonApplicableException
|
from authentik.flows.exceptions import FlowNonApplicableException
|
||||||
from authentik.flows.models import in_memory_stage
|
from authentik.flows.models import in_memory_stage
|
||||||
@ -52,7 +51,6 @@ class AutosubmitStageView(ChallengeStageView):
|
|||||||
def get_challenge(self, *args, **kwargs) -> Challenge:
|
def get_challenge(self, *args, **kwargs) -> Challenge:
|
||||||
return AutosubmitChallenge(
|
return AutosubmitChallenge(
|
||||||
data={
|
data={
|
||||||
"type": ChallengeTypes.NATIVE.value,
|
|
||||||
"component": "ak-stage-autosubmit",
|
"component": "ak-stage-autosubmit",
|
||||||
"title": self.executor.plan.context.get(PLAN_CONTEXT_TITLE, ""),
|
"title": self.executor.plan.context.get(PLAN_CONTEXT_TITLE, ""),
|
||||||
"url": self.executor.plan.context.get(PLAN_CONTEXT_URL, ""),
|
"url": self.executor.plan.context.get(PLAN_CONTEXT_URL, ""),
|
||||||
|
|||||||
@ -8,7 +8,6 @@ from authentik.events.models import Event, EventAction
|
|||||||
from authentik.flows.challenge import (
|
from authentik.flows.challenge import (
|
||||||
Challenge,
|
Challenge,
|
||||||
ChallengeResponse,
|
ChallengeResponse,
|
||||||
ChallengeTypes,
|
|
||||||
WithUserInfoChallenge,
|
WithUserInfoChallenge,
|
||||||
)
|
)
|
||||||
from authentik.flows.stage import ChallengeStageView
|
from authentik.flows.stage import ChallengeStageView
|
||||||
@ -61,7 +60,6 @@ class AuthenticatorDuoStageView(ChallengeStageView):
|
|||||||
enroll = self.request.session[SESSION_KEY_DUO_ENROLL]
|
enroll = self.request.session[SESSION_KEY_DUO_ENROLL]
|
||||||
return AuthenticatorDuoChallenge(
|
return AuthenticatorDuoChallenge(
|
||||||
data={
|
data={
|
||||||
"type": ChallengeTypes.NATIVE.value,
|
|
||||||
"activation_barcode": enroll["activation_barcode"],
|
"activation_barcode": enroll["activation_barcode"],
|
||||||
"activation_code": enroll["activation_code"],
|
"activation_code": enroll["activation_code"],
|
||||||
"stage_uuid": str(stage.stage_uuid),
|
"stage_uuid": str(stage.stage_uuid),
|
||||||
|
|||||||
@ -10,7 +10,6 @@ from rest_framework.fields import BooleanField, CharField, IntegerField
|
|||||||
from authentik.flows.challenge import (
|
from authentik.flows.challenge import (
|
||||||
Challenge,
|
Challenge,
|
||||||
ChallengeResponse,
|
ChallengeResponse,
|
||||||
ChallengeTypes,
|
|
||||||
WithUserInfoChallenge,
|
WithUserInfoChallenge,
|
||||||
)
|
)
|
||||||
from authentik.flows.stage import ChallengeStageView
|
from authentik.flows.stage import ChallengeStageView
|
||||||
@ -90,7 +89,6 @@ class AuthenticatorSMSStageView(ChallengeStageView):
|
|||||||
def get_challenge(self, *args, **kwargs) -> Challenge:
|
def get_challenge(self, *args, **kwargs) -> Challenge:
|
||||||
return AuthenticatorSMSChallenge(
|
return AuthenticatorSMSChallenge(
|
||||||
data={
|
data={
|
||||||
"type": ChallengeTypes.NATIVE.value,
|
|
||||||
"phone_number_required": self._has_phone_number() is None,
|
"phone_number_required": self._has_phone_number() is None,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
from django.http import HttpRequest, HttpResponse
|
from django.http import HttpRequest, HttpResponse
|
||||||
from rest_framework.fields import CharField, ListField
|
from rest_framework.fields import CharField, ListField
|
||||||
|
|
||||||
from authentik.flows.challenge import ChallengeResponse, ChallengeTypes, WithUserInfoChallenge
|
from authentik.flows.challenge import ChallengeResponse, WithUserInfoChallenge
|
||||||
from authentik.flows.stage import ChallengeStageView
|
from authentik.flows.stage import ChallengeStageView
|
||||||
from authentik.lib.generators import generate_id
|
from authentik.lib.generators import generate_id
|
||||||
from authentik.stages.authenticator_static.models import (
|
from authentik.stages.authenticator_static.models import (
|
||||||
@ -38,7 +38,6 @@ class AuthenticatorStaticStageView(ChallengeStageView):
|
|||||||
tokens: list[StaticToken] = self.request.session[SESSION_STATIC_TOKENS]
|
tokens: list[StaticToken] = self.request.session[SESSION_STATIC_TOKENS]
|
||||||
return AuthenticatorStaticChallenge(
|
return AuthenticatorStaticChallenge(
|
||||||
data={
|
data={
|
||||||
"type": ChallengeTypes.NATIVE.value,
|
|
||||||
"codes": [token.token for token in tokens],
|
"codes": [token.token for token in tokens],
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@ -11,7 +11,6 @@ from rest_framework.serializers import ValidationError
|
|||||||
from authentik.flows.challenge import (
|
from authentik.flows.challenge import (
|
||||||
Challenge,
|
Challenge,
|
||||||
ChallengeResponse,
|
ChallengeResponse,
|
||||||
ChallengeTypes,
|
|
||||||
WithUserInfoChallenge,
|
WithUserInfoChallenge,
|
||||||
)
|
)
|
||||||
from authentik.flows.stage import ChallengeStageView
|
from authentik.flows.stage import ChallengeStageView
|
||||||
@ -55,7 +54,6 @@ class AuthenticatorTOTPStageView(ChallengeStageView):
|
|||||||
device: TOTPDevice = self.request.session[SESSION_TOTP_DEVICE]
|
device: TOTPDevice = self.request.session[SESSION_TOTP_DEVICE]
|
||||||
return AuthenticatorTOTPChallenge(
|
return AuthenticatorTOTPChallenge(
|
||||||
data={
|
data={
|
||||||
"type": ChallengeTypes.NATIVE.value,
|
|
||||||
"config_url": device.config_url.replace(
|
"config_url": device.config_url.replace(
|
||||||
OTP_TOTP_ISSUER, quote(self.request.brand.branding_title)
|
OTP_TOTP_ISSUER, quote(self.request.brand.branding_title)
|
||||||
),
|
),
|
||||||
|
|||||||
@ -13,7 +13,7 @@ def migrate_configuration_stage(apps: Apps, schema_editor: BaseDatabaseSchemaEdi
|
|||||||
|
|
||||||
for stage in AuthenticatorValidateStage.objects.using(db_alias).all():
|
for stage in AuthenticatorValidateStage.objects.using(db_alias).all():
|
||||||
if stage.configuration_stage:
|
if stage.configuration_stage:
|
||||||
stage.configuration_stages.set([stage.configuration_stage])
|
stage.configuration_stages.using(db_alias).set([stage.configuration_stage])
|
||||||
stage.save()
|
stage.save()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -13,7 +13,7 @@ from rest_framework.serializers import ValidationError
|
|||||||
from authentik.core.api.utils import JSONDictField, PassiveSerializer
|
from authentik.core.api.utils import JSONDictField, PassiveSerializer
|
||||||
from authentik.core.models import User
|
from authentik.core.models import User
|
||||||
from authentik.events.models import Event, EventAction
|
from authentik.events.models import Event, EventAction
|
||||||
from authentik.flows.challenge import ChallengeResponse, ChallengeTypes, WithUserInfoChallenge
|
from authentik.flows.challenge import ChallengeResponse, WithUserInfoChallenge
|
||||||
from authentik.flows.exceptions import FlowSkipStageException, StageInvalidException
|
from authentik.flows.exceptions import FlowSkipStageException, StageInvalidException
|
||||||
from authentik.flows.models import FlowDesignation, NotConfiguredAction, Stage
|
from authentik.flows.models import FlowDesignation, NotConfiguredAction, Stage
|
||||||
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER
|
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER
|
||||||
@ -325,7 +325,7 @@ class AuthenticatorValidateStageView(ChallengeStageView):
|
|||||||
serializer = SelectableStageSerializer(
|
serializer = SelectableStageSerializer(
|
||||||
data={
|
data={
|
||||||
"pk": stage.pk,
|
"pk": stage.pk,
|
||||||
"name": stage.friendly_name or stage.name,
|
"name": getattr(stage, "friendly_name", stage.name),
|
||||||
"verbose_name": str(stage._meta.verbose_name)
|
"verbose_name": str(stage._meta.verbose_name)
|
||||||
.replace("Setup Stage", "")
|
.replace("Setup Stage", "")
|
||||||
.strip(),
|
.strip(),
|
||||||
@ -337,7 +337,6 @@ class AuthenticatorValidateStageView(ChallengeStageView):
|
|||||||
return AuthenticatorValidationChallenge(
|
return AuthenticatorValidationChallenge(
|
||||||
data={
|
data={
|
||||||
"component": "ak-stage-authenticator-validate",
|
"component": "ak-stage-authenticator-validate",
|
||||||
"type": ChallengeTypes.NATIVE.value,
|
|
||||||
"device_challenges": challenges,
|
"device_challenges": challenges,
|
||||||
"configuration_stages": stage_challenges,
|
"configuration_stages": stage_challenges,
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@ -30,7 +30,6 @@ from authentik.core.models import User
|
|||||||
from authentik.flows.challenge import (
|
from authentik.flows.challenge import (
|
||||||
Challenge,
|
Challenge,
|
||||||
ChallengeResponse,
|
ChallengeResponse,
|
||||||
ChallengeTypes,
|
|
||||||
WithUserInfoChallenge,
|
WithUserInfoChallenge,
|
||||||
)
|
)
|
||||||
from authentik.flows.stage import ChallengeStageView
|
from authentik.flows.stage import ChallengeStageView
|
||||||
@ -144,7 +143,6 @@ class AuthenticatorWebAuthnStageView(ChallengeStageView):
|
|||||||
self.request.session.save()
|
self.request.session.save()
|
||||||
return AuthenticatorWebAuthnChallenge(
|
return AuthenticatorWebAuthnChallenge(
|
||||||
data={
|
data={
|
||||||
"type": ChallengeTypes.NATIVE.value,
|
|
||||||
"registration": loads(options_to_json(registration_options)),
|
"registration": loads(options_to_json(registration_options)),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@ -9,7 +9,6 @@ from rest_framework.serializers import ValidationError
|
|||||||
from authentik.flows.challenge import (
|
from authentik.flows.challenge import (
|
||||||
Challenge,
|
Challenge,
|
||||||
ChallengeResponse,
|
ChallengeResponse,
|
||||||
ChallengeTypes,
|
|
||||||
WithUserInfoChallenge,
|
WithUserInfoChallenge,
|
||||||
)
|
)
|
||||||
from authentik.flows.stage import ChallengeStageView
|
from authentik.flows.stage import ChallengeStageView
|
||||||
@ -80,7 +79,6 @@ class CaptchaStageView(ChallengeStageView):
|
|||||||
return CaptchaChallenge(
|
return CaptchaChallenge(
|
||||||
data={
|
data={
|
||||||
"js_url": self.executor.current_stage.js_url,
|
"js_url": self.executor.current_stage.js_url,
|
||||||
"type": ChallengeTypes.NATIVE.value,
|
|
||||||
"site_key": self.executor.current_stage.public_key,
|
"site_key": self.executor.current_stage.public_key,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@ -10,7 +10,6 @@ from authentik.core.api.utils import PassiveSerializer
|
|||||||
from authentik.flows.challenge import (
|
from authentik.flows.challenge import (
|
||||||
Challenge,
|
Challenge,
|
||||||
ChallengeResponse,
|
ChallengeResponse,
|
||||||
ChallengeTypes,
|
|
||||||
WithUserInfoChallenge,
|
WithUserInfoChallenge,
|
||||||
)
|
)
|
||||||
from authentik.flows.planner import PLAN_CONTEXT_APPLICATION, PLAN_CONTEXT_PENDING_USER
|
from authentik.flows.planner import PLAN_CONTEXT_APPLICATION, PLAN_CONTEXT_PENDING_USER
|
||||||
@ -58,7 +57,6 @@ class ConsentStageView(ChallengeStageView):
|
|||||||
token = str(uuid4())
|
token = str(uuid4())
|
||||||
self.request.session[SESSION_KEY_CONSENT_TOKEN] = token
|
self.request.session[SESSION_KEY_CONSENT_TOKEN] = token
|
||||||
data = {
|
data = {
|
||||||
"type": ChallengeTypes.NATIVE.value,
|
|
||||||
"permissions": self.executor.plan.context.get(PLAN_CONTEXT_CONSENT_PERMISSIONS, []),
|
"permissions": self.executor.plan.context.get(PLAN_CONTEXT_CONSENT_PERMISSIONS, []),
|
||||||
"additional_permissions": self.executor.plan.context.get(
|
"additional_permissions": self.executor.plan.context.get(
|
||||||
PLAN_CONTEXT_CONSENT_EXTRA_PERMISSIONS, []
|
PLAN_CONTEXT_CONSENT_EXTRA_PERMISSIONS, []
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
from django.http.response import HttpResponse
|
from django.http.response import HttpResponse
|
||||||
from rest_framework.fields import CharField
|
from rest_framework.fields import CharField
|
||||||
|
|
||||||
from authentik.flows.challenge import Challenge, ChallengeResponse, ChallengeTypes
|
from authentik.flows.challenge import Challenge, ChallengeResponse
|
||||||
from authentik.flows.stage import ChallengeStageView
|
from authentik.flows.stage import ChallengeStageView
|
||||||
from authentik.lib.sentry import SentryIgnoredException
|
from authentik.lib.sentry import SentryIgnoredException
|
||||||
|
|
||||||
@ -34,7 +34,6 @@ class DummyStageView(ChallengeStageView):
|
|||||||
raise SentryIgnoredException("Test error")
|
raise SentryIgnoredException("Test error")
|
||||||
return DummyChallenge(
|
return DummyChallenge(
|
||||||
data={
|
data={
|
||||||
"type": ChallengeTypes.NATIVE.value,
|
|
||||||
"title": self.executor.current_stage.name,
|
"title": self.executor.current_stage.name,
|
||||||
"name": self.executor.current_stage.name,
|
"name": self.executor.current_stage.name,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,7 +15,7 @@ from rest_framework.fields import CharField
|
|||||||
from rest_framework.serializers import ValidationError
|
from rest_framework.serializers import ValidationError
|
||||||
|
|
||||||
from authentik.events.models import Event, EventAction
|
from authentik.events.models import Event, EventAction
|
||||||
from authentik.flows.challenge import Challenge, ChallengeResponse, ChallengeTypes
|
from authentik.flows.challenge import Challenge, ChallengeResponse
|
||||||
from authentik.flows.exceptions import StageInvalidException
|
from authentik.flows.exceptions import StageInvalidException
|
||||||
from authentik.flows.models import FlowDesignation, FlowToken
|
from authentik.flows.models import FlowDesignation, FlowToken
|
||||||
from authentik.flows.planner import PLAN_CONTEXT_IS_RESTORED, PLAN_CONTEXT_PENDING_USER
|
from authentik.flows.planner import PLAN_CONTEXT_IS_RESTORED, PLAN_CONTEXT_PENDING_USER
|
||||||
@ -160,7 +160,6 @@ class EmailStageView(ChallengeStageView):
|
|||||||
def get_challenge(self) -> Challenge:
|
def get_challenge(self) -> Challenge:
|
||||||
challenge = EmailChallenge(
|
challenge = EmailChallenge(
|
||||||
data={
|
data={
|
||||||
"type": ChallengeTypes.NATIVE.value,
|
|
||||||
"title": _("Email sent."),
|
"title": _("Email sent."),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@ -120,7 +120,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center">
|
<td align="center">
|
||||||
Powered by <a href="https://goauthentik.io?utm_source=authentik&utm_medium=email">authentik</a>.
|
Powered by <a rel="noopener noreferrer" target="_blank" href="https://goauthentik.io?utm_source=authentik&utm_medium=email">authentik</a>.
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@ -13,9 +13,9 @@ def assign_sources(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
|||||||
IdentificationStage = apps.get_model("authentik_stages_identification", "identificationstage")
|
IdentificationStage = apps.get_model("authentik_stages_identification", "identificationstage")
|
||||||
Source = apps.get_model("authentik_core", "source")
|
Source = apps.get_model("authentik_core", "source")
|
||||||
|
|
||||||
sources = Source.objects.all()
|
sources = Source.objects.using(db_alias).all()
|
||||||
for stage in IdentificationStage.objects.all().using(db_alias):
|
for stage in IdentificationStage.objects.using(db_alias).all():
|
||||||
stage.sources.set(sources)
|
stage.sources.using(db_alias).set(sources)
|
||||||
stage.save()
|
stage.save()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -20,7 +20,6 @@ from authentik.events.utils import sanitize_item
|
|||||||
from authentik.flows.challenge import (
|
from authentik.flows.challenge import (
|
||||||
Challenge,
|
Challenge,
|
||||||
ChallengeResponse,
|
ChallengeResponse,
|
||||||
ChallengeTypes,
|
|
||||||
RedirectChallenge,
|
RedirectChallenge,
|
||||||
)
|
)
|
||||||
from authentik.flows.models import FlowDesignation
|
from authentik.flows.models import FlowDesignation
|
||||||
@ -194,7 +193,6 @@ class IdentificationStageView(ChallengeStageView):
|
|||||||
current_stage: IdentificationStage = self.executor.current_stage
|
current_stage: IdentificationStage = self.executor.current_stage
|
||||||
challenge = IdentificationChallenge(
|
challenge = IdentificationChallenge(
|
||||||
data={
|
data={
|
||||||
"type": ChallengeTypes.NATIVE.value,
|
|
||||||
"component": "ak-stage-identification",
|
"component": "ak-stage-identification",
|
||||||
"primary_action": self.get_primary_action(),
|
"primary_action": self.get_primary_action(),
|
||||||
"user_fields": current_stage.user_fields,
|
"user_fields": current_stage.user_fields,
|
||||||
|
|||||||
@ -4,7 +4,6 @@ from django.urls import reverse
|
|||||||
from rest_framework.exceptions import ValidationError
|
from rest_framework.exceptions import ValidationError
|
||||||
|
|
||||||
from authentik.core.tests.utils import create_test_admin_user, create_test_flow
|
from authentik.core.tests.utils import create_test_admin_user, create_test_flow
|
||||||
from authentik.flows.challenge import ChallengeTypes
|
|
||||||
from authentik.flows.models import FlowDesignation, FlowStageBinding
|
from authentik.flows.models import FlowDesignation, FlowStageBinding
|
||||||
from authentik.flows.tests import FlowTestCase
|
from authentik.flows.tests import FlowTestCase
|
||||||
from authentik.lib.generators import generate_id
|
from authentik.lib.generators import generate_id
|
||||||
@ -90,7 +89,6 @@ class TestIdentificationStage(FlowTestCase):
|
|||||||
"challenge": {
|
"challenge": {
|
||||||
"component": "xak-flow-redirect",
|
"component": "xak-flow-redirect",
|
||||||
"to": "/source/oauth/login/test/",
|
"to": "/source/oauth/login/test/",
|
||||||
"type": ChallengeTypes.REDIRECT.value,
|
|
||||||
},
|
},
|
||||||
"icon_url": "/static/authentik/sources/default.svg",
|
"icon_url": "/static/authentik/sources/default.svg",
|
||||||
"name": "test",
|
"name": "test",
|
||||||
@ -126,7 +124,6 @@ class TestIdentificationStage(FlowTestCase):
|
|||||||
"challenge": {
|
"challenge": {
|
||||||
"component": "xak-flow-redirect",
|
"component": "xak-flow-redirect",
|
||||||
"to": "/source/oauth/login/test/",
|
"to": "/source/oauth/login/test/",
|
||||||
"type": ChallengeTypes.REDIRECT.value,
|
|
||||||
},
|
},
|
||||||
"icon_url": "/static/authentik/sources/default.svg",
|
"icon_url": "/static/authentik/sources/default.svg",
|
||||||
"name": "test",
|
"name": "test",
|
||||||
@ -189,7 +186,6 @@ class TestIdentificationStage(FlowTestCase):
|
|||||||
"challenge": {
|
"challenge": {
|
||||||
"component": "xak-flow-redirect",
|
"component": "xak-flow-redirect",
|
||||||
"to": "/source/oauth/login/test/",
|
"to": "/source/oauth/login/test/",
|
||||||
"type": ChallengeTypes.REDIRECT.value,
|
|
||||||
},
|
},
|
||||||
"icon_url": "/static/authentik/sources/default.svg",
|
"icon_url": "/static/authentik/sources/default.svg",
|
||||||
"name": "test",
|
"name": "test",
|
||||||
@ -240,7 +236,6 @@ class TestIdentificationStage(FlowTestCase):
|
|||||||
"challenge": {
|
"challenge": {
|
||||||
"component": "xak-flow-redirect",
|
"component": "xak-flow-redirect",
|
||||||
"to": "/source/oauth/login/test/",
|
"to": "/source/oauth/login/test/",
|
||||||
"type": ChallengeTypes.REDIRECT.value,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@ -276,7 +271,6 @@ class TestIdentificationStage(FlowTestCase):
|
|||||||
"challenge": {
|
"challenge": {
|
||||||
"component": "xak-flow-redirect",
|
"component": "xak-flow-redirect",
|
||||||
"to": "/source/oauth/login/test/",
|
"to": "/source/oauth/login/test/",
|
||||||
"type": ChallengeTypes.REDIRECT.value,
|
|
||||||
},
|
},
|
||||||
"icon_url": "/static/authentik/sources/default.svg",
|
"icon_url": "/static/authentik/sources/default.svg",
|
||||||
"name": "test",
|
"name": "test",
|
||||||
@ -304,7 +298,6 @@ class TestIdentificationStage(FlowTestCase):
|
|||||||
"challenge": {
|
"challenge": {
|
||||||
"component": "xak-flow-redirect",
|
"component": "xak-flow-redirect",
|
||||||
"to": "/source/oauth/login/test/",
|
"to": "/source/oauth/login/test/",
|
||||||
"type": ChallengeTypes.REDIRECT.value,
|
|
||||||
},
|
},
|
||||||
"icon_url": "/static/authentik/sources/default.svg",
|
"icon_url": "/static/authentik/sources/default.svg",
|
||||||
"name": "test",
|
"name": "test",
|
||||||
|
|||||||
@ -18,7 +18,6 @@ from authentik.core.signals import login_failed
|
|||||||
from authentik.flows.challenge import (
|
from authentik.flows.challenge import (
|
||||||
Challenge,
|
Challenge,
|
||||||
ChallengeResponse,
|
ChallengeResponse,
|
||||||
ChallengeTypes,
|
|
||||||
WithUserInfoChallenge,
|
WithUserInfoChallenge,
|
||||||
)
|
)
|
||||||
from authentik.flows.exceptions import StageInvalidException
|
from authentik.flows.exceptions import StageInvalidException
|
||||||
@ -135,11 +134,7 @@ class PasswordStageView(ChallengeStageView):
|
|||||||
response_class = PasswordChallengeResponse
|
response_class = PasswordChallengeResponse
|
||||||
|
|
||||||
def get_challenge(self) -> Challenge:
|
def get_challenge(self) -> Challenge:
|
||||||
challenge = PasswordChallenge(
|
challenge = PasswordChallenge(data={})
|
||||||
data={
|
|
||||||
"type": ChallengeTypes.NATIVE.value,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
recovery_flow = Flow.objects.filter(designation=FlowDesignation.RECOVERY)
|
recovery_flow = Flow.objects.filter(designation=FlowDesignation.RECOVERY)
|
||||||
if recovery_flow.exists():
|
if recovery_flow.exists():
|
||||||
recover_url = reverse(
|
recover_url = reverse(
|
||||||
|
|||||||
@ -11,7 +11,7 @@ from rest_framework.viewsets import ModelViewSet
|
|||||||
from authentik.core.api.used_by import UsedByMixin
|
from authentik.core.api.used_by import UsedByMixin
|
||||||
from authentik.core.expression.exceptions import PropertyMappingExpressionException
|
from authentik.core.expression.exceptions import PropertyMappingExpressionException
|
||||||
from authentik.flows.api.stages import StageSerializer
|
from authentik.flows.api.stages import StageSerializer
|
||||||
from authentik.flows.challenge import ChallengeTypes, HttpChallengeResponse
|
from authentik.flows.challenge import HttpChallengeResponse
|
||||||
from authentik.flows.planner import FlowPlan
|
from authentik.flows.planner import FlowPlan
|
||||||
from authentik.flows.views.executor import FlowExecutorView
|
from authentik.flows.views.executor import FlowExecutorView
|
||||||
from authentik.lib.generators import generate_id
|
from authentik.lib.generators import generate_id
|
||||||
@ -115,7 +115,6 @@ class PromptViewSet(UsedByMixin, ModelViewSet):
|
|||||||
)
|
)
|
||||||
challenge = PromptChallenge(
|
challenge = PromptChallenge(
|
||||||
data={
|
data={
|
||||||
"type": ChallengeTypes.NATIVE.value,
|
|
||||||
"fields": fields,
|
"fields": fields,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
@ -12,7 +12,7 @@ def set_generated_name(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
|||||||
|
|
||||||
for prompt in Prompt.objects.using(db_alias).all():
|
for prompt in Prompt.objects.using(db_alias).all():
|
||||||
name = prompt.field_key
|
name = prompt.field_key
|
||||||
stage = prompt.promptstage_set.order_by("name").first()
|
stage = prompt.promptstage_set.using(db_alias).order_by("name").first()
|
||||||
if stage:
|
if stage:
|
||||||
name += "_" + stage.name
|
name += "_" + stage.name
|
||||||
else:
|
else:
|
||||||
|
|||||||
@ -21,7 +21,7 @@ from rest_framework.serializers import ValidationError
|
|||||||
|
|
||||||
from authentik.core.api.utils import PassiveSerializer
|
from authentik.core.api.utils import PassiveSerializer
|
||||||
from authentik.core.models import User
|
from authentik.core.models import User
|
||||||
from authentik.flows.challenge import Challenge, ChallengeResponse, ChallengeTypes
|
from authentik.flows.challenge import Challenge, ChallengeResponse
|
||||||
from authentik.flows.planner import FlowPlan
|
from authentik.flows.planner import FlowPlan
|
||||||
from authentik.flows.stage import ChallengeStageView
|
from authentik.flows.stage import ChallengeStageView
|
||||||
from authentik.policies.engine import PolicyEngine
|
from authentik.policies.engine import PolicyEngine
|
||||||
@ -227,7 +227,6 @@ class PromptStageView(ChallengeStageView):
|
|||||||
serializers = self.get_prompt_challenge_fields(fields, context_prompt)
|
serializers = self.get_prompt_challenge_fields(fields, context_prompt)
|
||||||
challenge = PromptChallenge(
|
challenge = PromptChallenge(
|
||||||
data={
|
data={
|
||||||
"type": ChallengeTypes.NATIVE.value,
|
|
||||||
"fields": serializers,
|
"fields": serializers,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
@ -7,7 +7,6 @@ from django.urls import reverse
|
|||||||
from rest_framework.exceptions import ErrorDetail, ValidationError
|
from rest_framework.exceptions import ErrorDetail, ValidationError
|
||||||
|
|
||||||
from authentik.core.tests.utils import create_test_admin_user, create_test_flow
|
from authentik.core.tests.utils import create_test_admin_user, create_test_flow
|
||||||
from authentik.flows.challenge import ChallengeTypes
|
|
||||||
from authentik.flows.markers import StageMarker
|
from authentik.flows.markers import StageMarker
|
||||||
from authentik.flows.models import FlowStageBinding
|
from authentik.flows.models import FlowStageBinding
|
||||||
from authentik.flows.planner import FlowPlan
|
from authentik.flows.planner import FlowPlan
|
||||||
@ -596,7 +595,6 @@ class TestPromptStage(FlowTestCase):
|
|||||||
self.assertJSONEqual(
|
self.assertJSONEqual(
|
||||||
response.content.decode(),
|
response.content.decode(),
|
||||||
{
|
{
|
||||||
"type": ChallengeTypes.NATIVE.value,
|
|
||||||
"component": "ak-stage-prompt",
|
"component": "ak-stage-prompt",
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,10 +1,9 @@
|
|||||||
"""Sessions bound to ASN/Network and GeoIP/Continent/etc"""
|
"""Sessions bound to ASN/Network and GeoIP/Continent/etc"""
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from django.contrib.auth.middleware import AuthenticationMiddleware
|
from django.contrib.auth.middleware import AuthenticationMiddleware
|
||||||
from django.contrib.auth.signals import user_logged_out
|
from django.contrib.auth.signals import user_logged_out
|
||||||
|
from django.contrib.auth.views import redirect_to_login
|
||||||
from django.http.request import HttpRequest
|
from django.http.request import HttpRequest
|
||||||
from django.shortcuts import redirect
|
|
||||||
from structlog.stdlib import get_logger
|
from structlog.stdlib import get_logger
|
||||||
|
|
||||||
from authentik.core.models import AuthenticatedSession
|
from authentik.core.models import AuthenticatedSession
|
||||||
@ -87,7 +86,7 @@ class BoundSessionMiddleware(SessionMiddleware):
|
|||||||
AuthenticationMiddleware(lambda request: request).process_request(request)
|
AuthenticationMiddleware(lambda request: request).process_request(request)
|
||||||
logout_extra(request, exc)
|
logout_extra(request, exc)
|
||||||
request.session.clear()
|
request.session.clear()
|
||||||
return redirect(settings.LOGIN_URL)
|
return redirect_to_login(request.get_full_path())
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def recheck_session(self, request: HttpRequest):
|
def recheck_session(self, request: HttpRequest):
|
||||||
|
|||||||
@ -10,7 +10,7 @@ from rest_framework.fields import BooleanField, CharField
|
|||||||
|
|
||||||
from authentik.core.models import AuthenticatedSession, User
|
from authentik.core.models import AuthenticatedSession, User
|
||||||
from authentik.events.middleware import audit_ignore
|
from authentik.events.middleware import audit_ignore
|
||||||
from authentik.flows.challenge import ChallengeResponse, ChallengeTypes, WithUserInfoChallenge
|
from authentik.flows.challenge import ChallengeResponse, WithUserInfoChallenge
|
||||||
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, PLAN_CONTEXT_SOURCE
|
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, PLAN_CONTEXT_SOURCE
|
||||||
from authentik.flows.stage import ChallengeStageView
|
from authentik.flows.stage import ChallengeStageView
|
||||||
from authentik.lib.utils.time import timedelta_from_string
|
from authentik.lib.utils.time import timedelta_from_string
|
||||||
@ -45,11 +45,7 @@ class UserLoginStageView(ChallengeStageView):
|
|||||||
response_class = UserLoginChallengeResponse
|
response_class = UserLoginChallengeResponse
|
||||||
|
|
||||||
def get_challenge(self, *args, **kwargs) -> UserLoginChallenge:
|
def get_challenge(self, *args, **kwargs) -> UserLoginChallenge:
|
||||||
return UserLoginChallenge(
|
return UserLoginChallenge(data={})
|
||||||
data={
|
|
||||||
"type": ChallengeTypes.NATIVE.value,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
def dispatch(self, request: HttpRequest) -> HttpResponse:
|
def dispatch(self, request: HttpRequest) -> HttpResponse:
|
||||||
"""Check for remember_me, and do login"""
|
"""Check for remember_me, and do login"""
|
||||||
|
|||||||
@ -129,6 +129,11 @@ class TestUserLoginStage(FlowTestCase):
|
|||||||
session[SESSION_KEY_PLAN] = plan
|
session[SESSION_KEY_PLAN] = plan
|
||||||
session.save()
|
session.save()
|
||||||
|
|
||||||
|
response = self.client.get(
|
||||||
|
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
|
||||||
|
)
|
||||||
|
self.assertStageResponse(response, component="ak-stage-user-login")
|
||||||
|
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
|
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
|
||||||
data={"remember_me": True},
|
data={"remember_me": True},
|
||||||
|
|||||||
@ -6,6 +6,7 @@ from django.contrib.auth import update_session_auth_hash
|
|||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.db.utils import IntegrityError, InternalError
|
from django.db.utils import IntegrityError, InternalError
|
||||||
from django.http import HttpRequest, HttpResponse
|
from django.http import HttpRequest, HttpResponse
|
||||||
|
from django.utils.functional import SimpleLazyObject
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
from rest_framework.exceptions import ValidationError
|
from rest_framework.exceptions import ValidationError
|
||||||
|
|
||||||
@ -118,6 +119,14 @@ class UserWriteStageView(StageView):
|
|||||||
UserWriteStageView.write_attribute(user, key, value)
|
UserWriteStageView.write_attribute(user, key, value)
|
||||||
# User has this key already
|
# User has this key already
|
||||||
elif hasattr(user, key):
|
elif hasattr(user, key):
|
||||||
|
if isinstance(user, SimpleLazyObject):
|
||||||
|
user._setup()
|
||||||
|
user = user._wrapped
|
||||||
|
attr = getattr(type(user), key)
|
||||||
|
if isinstance(attr, property):
|
||||||
|
if not attr.fset:
|
||||||
|
self.logger.info("discarding key", key=key)
|
||||||
|
continue
|
||||||
setattr(user, key, value)
|
setattr(user, key, value)
|
||||||
# If none of the cases above matched, we have an attribute that the user doesn't have,
|
# If none of the cases above matched, we have an attribute that the user doesn't have,
|
||||||
# has no setter for, is not a nested attributes value and as such is invalid
|
# has no setter for, is not a nested attributes value and as such is invalid
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
"$schema": "http://json-schema.org/draft-07/schema",
|
"$schema": "http://json-schema.org/draft-07/schema",
|
||||||
"$id": "https://goauthentik.io/blueprints/schema.json",
|
"$id": "https://goauthentik.io/blueprints/schema.json",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"title": "authentik 2024.4.2 Blueprint schema",
|
"title": "authentik 2024.6.1 Blueprint schema",
|
||||||
"required": [
|
"required": [
|
||||||
"version",
|
"version",
|
||||||
"entries"
|
"entries"
|
||||||
|
|||||||
@ -31,7 +31,7 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- redis:/data
|
- redis:/data
|
||||||
server:
|
server:
|
||||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2024.4.2}
|
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2024.6.1}
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
command: server
|
command: server
|
||||||
environment:
|
environment:
|
||||||
@ -52,7 +52,7 @@ services:
|
|||||||
- postgresql
|
- postgresql
|
||||||
- redis
|
- redis
|
||||||
worker:
|
worker:
|
||||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2024.4.2}
|
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2024.6.1}
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
command: worker
|
command: worker
|
||||||
environment:
|
environment:
|
||||||
|
|||||||
18
go.mod
18
go.mod
@ -4,7 +4,7 @@ go 1.22.2
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
beryju.io/ldap v0.1.0
|
beryju.io/ldap v0.1.0
|
||||||
github.com/coreos/go-oidc v2.2.1+incompatible
|
github.com/coreos/go-oidc/v3 v3.11.0
|
||||||
github.com/getsentry/sentry-go v0.28.1
|
github.com/getsentry/sentry-go v0.28.1
|
||||||
github.com/go-http-utils/etag v0.0.0-20161124023236-513ea8f21eb1
|
github.com/go-http-utils/etag v0.0.0-20161124023236-513ea8f21eb1
|
||||||
github.com/go-ldap/ldap/v3 v3.4.8
|
github.com/go-ldap/ldap/v3 v3.4.8
|
||||||
@ -22,13 +22,13 @@ require (
|
|||||||
github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484
|
github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484
|
||||||
github.com/pires/go-proxyproto v0.7.0
|
github.com/pires/go-proxyproto v0.7.0
|
||||||
github.com/prometheus/client_golang v1.19.1
|
github.com/prometheus/client_golang v1.19.1
|
||||||
github.com/redis/go-redis/v9 v9.5.3
|
github.com/redis/go-redis/v9 v9.5.4
|
||||||
github.com/sethvargo/go-envconfig v1.0.3
|
github.com/sethvargo/go-envconfig v1.1.0
|
||||||
github.com/sirupsen/logrus v1.9.3
|
github.com/sirupsen/logrus v1.9.3
|
||||||
github.com/spf13/cobra v1.8.1
|
github.com/spf13/cobra v1.8.1
|
||||||
github.com/stretchr/testify v1.9.0
|
github.com/stretchr/testify v1.9.0
|
||||||
github.com/wwt/guac v1.3.2
|
github.com/wwt/guac v1.3.2
|
||||||
goauthentik.io/api/v3 v3.2024042.13
|
goauthentik.io/api/v3 v3.2024061.2
|
||||||
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab
|
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab
|
||||||
golang.org/x/oauth2 v0.21.0
|
golang.org/x/oauth2 v0.21.0
|
||||||
golang.org/x/sync v0.7.0
|
golang.org/x/sync v0.7.0
|
||||||
@ -47,6 +47,7 @@ require (
|
|||||||
github.com/go-asn1-ber/asn1-ber v1.5.5 // indirect
|
github.com/go-asn1-ber/asn1-ber v1.5.5 // indirect
|
||||||
github.com/go-http-utils/fresh v0.0.0-20161124030543-7231e26a4b27 // indirect
|
github.com/go-http-utils/fresh v0.0.0-20161124030543-7231e26a4b27 // indirect
|
||||||
github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a // indirect
|
github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a // indirect
|
||||||
|
github.com/go-jose/go-jose/v4 v4.0.2 // indirect
|
||||||
github.com/go-logr/logr v1.4.1 // indirect
|
github.com/go-logr/logr v1.4.1 // indirect
|
||||||
github.com/go-logr/stdr v1.2.2 // indirect
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
github.com/go-openapi/analysis v0.23.0 // indirect
|
github.com/go-openapi/analysis v0.23.0 // indirect
|
||||||
@ -63,7 +64,6 @@ require (
|
|||||||
github.com/oklog/ulid v1.3.1 // indirect
|
github.com/oklog/ulid v1.3.1 // indirect
|
||||||
github.com/opentracing/opentracing-go v1.2.0 // indirect
|
github.com/opentracing/opentracing-go v1.2.0 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/pquerna/cachecontrol v0.0.0-20201205024021-ac21108117ac // indirect
|
|
||||||
github.com/prometheus/client_model v0.5.0 // indirect
|
github.com/prometheus/client_model v0.5.0 // indirect
|
||||||
github.com/prometheus/common v0.48.0 // indirect
|
github.com/prometheus/common v0.48.0 // indirect
|
||||||
github.com/prometheus/procfs v0.12.0 // indirect
|
github.com/prometheus/procfs v0.12.0 // indirect
|
||||||
@ -72,11 +72,9 @@ require (
|
|||||||
go.opentelemetry.io/otel v1.24.0 // indirect
|
go.opentelemetry.io/otel v1.24.0 // indirect
|
||||||
go.opentelemetry.io/otel/metric v1.24.0 // indirect
|
go.opentelemetry.io/otel/metric v1.24.0 // indirect
|
||||||
go.opentelemetry.io/otel/trace v1.24.0 // indirect
|
go.opentelemetry.io/otel/trace v1.24.0 // indirect
|
||||||
golang.org/x/crypto v0.21.0 // indirect
|
golang.org/x/crypto v0.25.0 // indirect
|
||||||
golang.org/x/net v0.23.0 // indirect
|
golang.org/x/sys v0.22.0 // indirect
|
||||||
golang.org/x/sys v0.18.0 // indirect
|
golang.org/x/text v0.16.0 // indirect
|
||||||
golang.org/x/text v0.14.0 // indirect
|
|
||||||
google.golang.org/protobuf v1.33.0 // indirect
|
google.golang.org/protobuf v1.33.0 // indirect
|
||||||
gopkg.in/square/go-jose.v2 v2.5.1 // indirect
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
36
go.sum
36
go.sum
@ -55,8 +55,8 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P
|
|||||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||||
github.com/coreos/go-oidc v2.2.1+incompatible h1:mh48q/BqXqgjVHpy2ZY7WnWAbenxRjsz9N1i1YxjHAk=
|
github.com/coreos/go-oidc/v3 v3.11.0 h1:Ia3MxdwpSw702YW0xgfmP1GVCMA9aEFWu12XUZ3/OtI=
|
||||||
github.com/coreos/go-oidc v2.2.1+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
|
github.com/coreos/go-oidc/v3 v3.11.0/go.mod h1:gE3LgjOgFoHi9a4ce4/tJczr0Ai2/BoDhf0r5lltWI0=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
@ -84,6 +84,8 @@ github.com/go-http-utils/fresh v0.0.0-20161124030543-7231e26a4b27 h1:O6yi4xa9b2D
|
|||||||
github.com/go-http-utils/fresh v0.0.0-20161124030543-7231e26a4b27/go.mod h1:AYvN8omj7nKLmbcXS2dyABYU6JB1Lz1bHmkkq1kf4I4=
|
github.com/go-http-utils/fresh v0.0.0-20161124030543-7231e26a4b27/go.mod h1:AYvN8omj7nKLmbcXS2dyABYU6JB1Lz1bHmkkq1kf4I4=
|
||||||
github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a h1:v6zMvHuY9yue4+QkG/HQ/W67wvtQmWJ4SDo9aK/GIno=
|
github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a h1:v6zMvHuY9yue4+QkG/HQ/W67wvtQmWJ4SDo9aK/GIno=
|
||||||
github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a/go.mod h1:I79BieaU4fxrw4LMXby6q5OS9XnoR9UIKLOzDFjUmuw=
|
github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a/go.mod h1:I79BieaU4fxrw4LMXby6q5OS9XnoR9UIKLOzDFjUmuw=
|
||||||
|
github.com/go-jose/go-jose/v4 v4.0.2 h1:R3l3kkBds16bO7ZFAEEcofK0MkrAJt3jlJznWZG0nvk=
|
||||||
|
github.com/go-jose/go-jose/v4 v4.0.2/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY=
|
||||||
github.com/go-ldap/ldap/v3 v3.4.8 h1:loKJyspcRezt2Q3ZRMq2p/0v8iOurlmeXDPw6fikSvQ=
|
github.com/go-ldap/ldap/v3 v3.4.8 h1:loKJyspcRezt2Q3ZRMq2p/0v8iOurlmeXDPw6fikSvQ=
|
||||||
github.com/go-ldap/ldap/v3 v3.4.8/go.mod h1:qS3Sjlu76eHfHGpUdWkAXQTw4beih+cHsco2jXlIXrk=
|
github.com/go-ldap/ldap/v3 v3.4.8/go.mod h1:qS3Sjlu76eHfHGpUdWkAXQTw4beih+cHsco2jXlIXrk=
|
||||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
@ -231,8 +233,6 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
|||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/pquerna/cachecontrol v0.0.0-20201205024021-ac21108117ac h1:jWKYCNlX4J5s8M0nHYkh7Y7c9gRVDEb3mq51j5J0F5M=
|
|
||||||
github.com/pquerna/cachecontrol v0.0.0-20201205024021-ac21108117ac/go.mod h1:hoLfEwdY11HjRfKFH6KqnPsfxlo3BP6bJehpDv8t6sQ=
|
|
||||||
github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
|
github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
|
||||||
github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
|
github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
|
||||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
@ -242,14 +242,14 @@ github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSz
|
|||||||
github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc=
|
github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc=
|
||||||
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
|
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
|
||||||
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
|
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
|
||||||
github.com/redis/go-redis/v9 v9.5.3 h1:fOAp1/uJG+ZtcITgZOfYFmTKPE7n4Vclj1wZFgRciUU=
|
github.com/redis/go-redis/v9 v9.5.4 h1:vOFYDKKVgrI5u++QvnMT7DksSMYg7Aw/Np4vLJLKLwY=
|
||||||
github.com/redis/go-redis/v9 v9.5.3/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
|
github.com/redis/go-redis/v9 v9.5.4/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
|
||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/sethvargo/go-envconfig v1.0.3 h1:ZDxFGT1M7RPX0wgDOCdZMidrEB+NrayYr6fL0/+pk4I=
|
github.com/sethvargo/go-envconfig v1.1.0 h1:cWZiJxeTm7AlCvzGXrEXaSTCNgip5oJepekh/BOQuog=
|
||||||
github.com/sethvargo/go-envconfig v1.0.3/go.mod h1:JLd0KFWQYzyENqnEPWWZ49i4vzZo/6nRidxI8YvGiHw=
|
github.com/sethvargo/go-envconfig v1.1.0/go.mod h1:JLd0KFWQYzyENqnEPWWZ49i4vzZo/6nRidxI8YvGiHw=
|
||||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
@ -264,7 +264,6 @@ github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpE
|
|||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
@ -294,8 +293,8 @@ go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y
|
|||||||
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
|
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
|
||||||
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
|
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
|
||||||
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
|
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
|
||||||
goauthentik.io/api/v3 v3.2024042.13 h1:eklVXXLH0tV+02puhxzWJZ8l6HhxmeVMYp/M6sdaji8=
|
goauthentik.io/api/v3 v3.2024061.2 h1:9NHK2wriMENQHUmbYN3uxsdZZIV0QoEEEaGM0JS8XRY=
|
||||||
goauthentik.io/api/v3 v3.2024042.13/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
|
goauthentik.io/api/v3 v3.2024061.2/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
@ -305,8 +304,9 @@ golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPh
|
|||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
||||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||||
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
|
|
||||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||||
|
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
|
||||||
|
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||||
@ -374,8 +374,8 @@ golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
|||||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||||
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||||
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
|
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
|
||||||
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
@ -430,8 +430,9 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
|
||||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
|
||||||
|
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
@ -446,8 +447,9 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
|
||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
|
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||||
|
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
@ -577,8 +579,6 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8
|
|||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w=
|
|
||||||
gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user