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:
Ken Sternberg
2024-07-15 08:07:38 -07:00
328 changed files with 19166 additions and 14136 deletions

View File

@ -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*))?

View File

@ -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.

View File

@ -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/*"

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 .

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -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)

View File

@ -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",

View File

@ -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")

View File

@ -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)

View File

@ -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):

View File

@ -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

View File

@ -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",

View File

@ -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", {

View File

@ -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>

View File

@ -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

View File

@ -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"

View File

@ -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()

View File

@ -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",
), ),

View File

@ -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,
} }
) )

View File

@ -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)

View File

@ -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)

View File

@ -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()

View File

@ -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):

View File

@ -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,
} }
) )

View 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>

View File

@ -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
)

View File

@ -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": ""},
} }

View File

@ -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": [],

View File

@ -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"),
} }
) )

View 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"]

View File

@ -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

View 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"""

View File

@ -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,

View File

@ -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"),

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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",

View File

@ -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:

View File

@ -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)

View File

@ -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"]

View File

@ -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": {

View File

@ -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)

View File

@ -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}",
}, },
) )

View File

@ -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,

View File

@ -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

View File

@ -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",
} }
) )

View File

@ -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",
} }
) )

View File

@ -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)

View File

@ -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,

View File

@ -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

View File

@ -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",

View File

@ -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"
) )

View File

@ -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,
} }
) )

View File

@ -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},

View File

@ -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,

View File

@ -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
) )

View File

@ -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},

View File

@ -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

View File

@ -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, ""),

View File

@ -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),

View File

@ -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,
} }
) )

View File

@ -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],
} }
) )

View File

@ -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)
), ),

View File

@ -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()

View File

@ -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

View File

@ -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)),
} }
) )

View File

@ -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,
} }
) )

View File

@ -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, []

View File

@ -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,
} }

View File

@ -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."),
} }
) )

View File

@ -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>

View File

@ -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()

View File

@ -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,

View File

@ -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",

View File

@ -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(

View File

@ -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,
}, },
) )

View File

@ -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:

View File

@ -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,
}, },
) )

View File

@ -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": [
{ {

View File

@ -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):

View File

@ -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"""

View File

@ -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},

View File

@ -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

View File

@ -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"

View File

@ -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
View File

@ -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
View File

@ -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