Compare commits

..

117 Commits

Author SHA1 Message Date
Teffen Ellis
c8d5832018 web: Flesh out module driven tag names. 2026-02-06 07:23:00 +01:00
Ken Sternberg
52eef39e7e web/flow: refactor FlowExecutor so that client-side stage selection is separate from stage execution
# What

Extracts and normalizes the *massive* switch/case statement into a table, eliminating as much repetition as possible. Where the server-side stage token and the client-side component have the same tag, only one is required. There were three different patterns for prop definitions, and those have been regularized into an expression with a compile-time type check, and the most common one can be omitted from the stage definition table.

# Why

1.  Because it’s hella cleaner. Stages are clear and easy to spot in the table (especially when it’s alphabetically ordered, OMG). Stages that disagree in name with their components, stages that take props different from the “standard” set, and stages that need `import` statements, are all easy to identify.
2.  Because identifying what we *do* with our web components is critical to their success, and to the success of the styling system the authentik web team envisions. FlowExecutor provides selection and execution of stages, but it also provides the inspector, the locale selector, headers, footers, customizations, and branding. Clearing away clutter to make that easier to see makes future refactoring for compatibility mode and dark theme handling much easier.
2026-02-03 17:22:07 -08:00
Ken Sternberg
a2f5ad00a9 Merge branch 'main' into dev
* main: (26 commits)
  providers/saml: auto pull signature algorithm options (#17614)
  core, web: bump @isaacs/brace-expansion from 5.0.0 to 5.0.1 in /packages/prettier-config (#19990)
  web: bump @isaacs/brace-expansion from 5.0.0 to 5.0.1 in /web (#19989)
  stages/authenticator_webauthn: fix double JSON encoding of webauthn options (#19952)
  core: bump django from 5.2.10 to 5.2.11 (#19988)
  ci: allow setting assignee to fail (#19985)
  root: revert enterprise loading behaviour (#19485)
  web/flows: update flow background (#19974)
  providers/oauth2: use compare_digest for client_secret comparison (#19979)
  recovery: consume token in transaction (#19967)
  core: ask for token duration on recovery link/email by admin (#19875)
  core: bump aws-cdk-lib from 2.236.0 to 2.237.0 (#19958)
  web: bump the storybook group across 1 directory with 5 updates (#19960)
  core: bump library/nginx from `c881927` to `7fe5dda` in /website (#19961)
  core: bump gunicorn from 25.0.0 to 25.0.1 (#19959)
  core: bump goauthentik.io/api/v3 to 3.2026.2.0-rc1-1770129730 (#19973)
  lifecycle: bump shm size (#19369)
  crypto: Add ED25519 and ED448 support to the certificate builder (#19465)
  web/admin: Register stage elements. Fix linter warnings (#19948)
  web: bump knip from 5.82.1 to 5.83.0 in /web (#19962)
  ...
2026-02-03 13:56:03 -08:00
Ken Sternberg
86a689ae05 Merge branch 'main' into dev
* main: (30 commits)
  web/admin: fix default binding order (#19943)
  ci: fix test_docker.sh (#19944)
  lib: Add ssh/sftp schemas in to DomainlessFormattedURLValidator (#19881)
  core: fix non-expiring service accounts and app passwords (#19913)
  lifecycle/ak: make sure /data has the correct permissions (#19935)
  lifecycle/aws: add /data volume (#19936)
  website/docs: Update location of media storage and outdated references (#19885)
  web: bump @types/node from 25.1.0 to 25.2.0 in /web (#19923)
  web: bump @playwright/test from 1.58.0 to 1.58.1 in /web (#19926)
  web: bump the rollup group across 1 directory with 4 updates (#19922)
  ci: always generate API clients (#19906)
  providers/scim: add configuration warning for migration (#19859)
  core, web: update translations (#19868)
  core: bump gunicorn from 24.1.1 to 25.0.0 (#19916)
  core: bump pyjwt from 2.10.1 to 2.11.0 (#19920)
  core: bump cachetools from 6.2.6 to 7.0.0 (#19919)
  web: bump @formatjs/intl-listformat from 8.2.0 to 8.2.1 in /web (#19924)
  web: bump the storybook group across 1 directory with 5 updates (#19921)
  core: bump axllent/mailpit from v1.28.4 to v1.29.0 in /tests/e2e (#19918)
  core: bump goauthentik/selenium from 144.0-ak-0.35.9 to 144.0-ak-0.40.0 in /tests/e2e (#19917)
  ...
2026-02-02 09:26:54 -08:00
Ken Sternberg
50092fef98 Merge branch 'main' into dev
* main: (52 commits)
  website: QL Search keyboard interactions docs, examples. (#16259)
  website/integrations: immich: add signing algorithm (#19187)
  website/docs: endpoint devices: add version command (#19767)
  common: introduce common (#19852)
  web: bump @sentry/browser from 10.37.0 to 10.38.0 in /web in the sentry group across 1 directory (#19871)
  core: bump debugpy from 1.8.19 to 1.8.20 (#19872)
  ci: bump actions/cache from 5.0.2 to 5.0.3 (#19873)
  web: bump chromedriver from 144.0.1 to 145.0.0 in /web (#19874)
  web: Captcha Refinements, Part 2  (#19757)
  root: assign cherry-pick PRs to original author (#19858)
  web: Lit Development Mode, performance fixes. (#19825)
  web: Fix development theme overrides (#19826)
  website/docs: add tip for recovering from accidental main branch work (#19865)
  web: bump API Client version (#19857)
  rbac: clean up roles and permissions (#19588)
  web: bump API Client version (#19851)
  website/docs: add more info to entra id scim doc (#19849)
  sources/oauth: Fix an issue where wechat may crash duing login. (#18973)
  providers/scim: fix email validation mismatch (#19848)
  providers/scim: modify user- and group syncing behavior (#13947)
  ...
2026-01-30 09:17:50 -08:00
Ken Sternberg
ea188eeac3 Merge branch 'main' into dev
* main: (45 commits)
  sources/saml: Add testcases for PR #19593 (#19647)
  revert: website/integrations: wazuh: Change exchange key generation to 64 bytes (#19759)
  web: bump API Client version (#19760)
  core: bump djangoql from 0.18.2 to 0.19.1 (#19780)
  web: Vendor SFE Bootstrap (#19766)
  core, web: update translations (#19717)
  web: bump the eslint group across 1 directory with 3 updates (#19782)
  web: bump the react group across 1 directory with 2 updates (#19784)
  web: bump country-flag-icons from 1.6.8 to 1.6.9 in /web (#19785)
  providers/oauth2: Support login_hint (#19498)
  admin/files: add centralized theme variable support for file URLs (#19657)
  core: bump github.com/pires/go-proxyproto from 0.9.1 to 0.9.2 (#19778)
  core: bump openapitools/openapi-diff from 2.1.6 to 2.1.7 in /scripts/api (#19779)
  core: bump gssapi from 1.10.1 to 1.11.1 (#19781)
  ci: bump actions/attest-build-provenance from 3.1.0 to 3.2.0 (#19783)
  website/docs: endpoint devices: fix local device login (#19698)
  web: Enforce `challenge` nullish types. (#19768)
  web/elements: stabilize dual-select status height (#19734)
  web/a11y: CAPTCHA Stage Form (#19670)
  web/table: align row action icons and tooltip color (#19736)
  ...
2026-01-27 10:45:44 -08:00
Ken Sternberg
50bb59b84c Merge branch 'main' into dev
* main:
  endpoints: FleetDM connector (#18589)
  web/admin: fix impersonation form requesting data without being opened (#19673)
  core: return bad request when user is authenticated and not active (#19706)
2026-01-23 13:53:00 -08:00
Ken Sternberg
e6ca150a21 Merge branch 'main' into dev
* main: (115 commits)
  internal: fix incorrect metric calculation (#19701)
  core, web: update translations (#19684)
  core: bump goauthentik.io/api/v3 from 3.2026020.12 to 3.2026020.14 (#19686)
  lifecycle/aws: bump aws-cdk from 2.1101.0 to 2.1102.0 in /lifecycle/aws (#19687)
  core: bump goauthentik/selenium from 143.0-ak-0.35.3 to 144.0-ak-0.35.7 in /tests/e2e (#19688)
  core: bump msgraph-sdk from 1.52.0 to 1.53.0 (#19689)
  core: bump ruff from 0.14.13 to 0.14.14 (#19690)
  core: bump twilio from 9.9.1 to 9.10.0 (#19691)
  core: bump gunicorn from 23.0.0 to 24.0.0 (#19692)
  web: bump the bundler group across 1 directory with 3 updates (#19693)
  web: bump unist-util-visit from 5.0.0 to 5.1.0 in /web (#19694)
  web: bump globals from 17.0.0 to 17.1.0 in /web (#19695)
  ci: bump actions/checkout from 6.0.1 to 6.0.2 (#19696)
  web: Form Modal Independence: Part 1 (#19395)
  web/common: add dev middleware to show warnings for consecutive identical requests (#19671)
  web/admin: fix file upload not preserving extension for custom names with dots (#19548)
  web/admin: fix brand form sending "undefined" string for blank default application (#19658)
  providers/proxy: Fix incorrect comparison of redirect URL and CookieDomain (#15686)
  core: add bulk session revocation (#18564)
  website/docs: endpoint devices: add serial number note (#19677)
  ...
2026-01-23 08:13:07 -08:00
Ken Sternberg
3cd4afb06a Merge branch 'main' into dev
* main: (47 commits)
  endpoints/connectors/agent: add tests for IA endpoint stage (#19487)
  website/docs: limiting permissions of AD service account (#19483)
  endpoints/connectors/agent: Skip Endpoint stage on device IA & fix confusing identification subtext (#19482)
  root: adjust makefile for non-brew macos (#19479)
  providers/oauth2: allow property mappings to override scope claim in access tokens (#19226)
  revert: lib: use orjson for structlog json (#19478)
  stages/authenticator_webauthn: Update FIDO MDS3 & Passkey aaguid blobs (#19464)
  core: bump library/nginx from `e3a22a7` to `c881927` in /website (#19469)
  core: bump library/node from 25.2.1-trixie to 25.3.0-trixie in /website (#19468)
  web: bump prettier from 3.7.4 to 3.8.0 in /web (#19471)
  website/integrations: add MinIO AIStor configuration via environment variables (#19337)
  root: upgrade ruff lint for 3.14 (#19461)
  lib: use orjson for structlog json (#19462)
  ci: fix checkout stable (for 2025.12) (#19448)
  root: Python 3.14 (#17313)
  core: bump channels from 4.3.1 to 4.3.2 (#19458)
  core: bump mypy from 1.18.2 to 1.19.1 (#19457)
  core: bump google-api-python-client from 2.177.0 to 2.188.0 (#19443)
  core: bump selenium from 4.32.0 to 4.39.0 (#19455)
  core: bump msgraph-sdk from 1.39.0 to 1.52.0 (#19454)
  ...
2026-01-15 10:24:07 -08:00
Ken Sternberg
42ad526636 Merge branch 'main' into dev
* main: (87 commits)
  core: bump importlib-metadata from 8.6.1 to 8.7.1 (#19430)
  core: bump geoip2 from 5.1.0 to 5.2.0 (#19429)
  core: bump debugpy from 1.8.14 to 1.8.19 (#19414)
  core: remove session migration (#14568)
  website/docs: add 2026.2 release notes draft page (#19418)
  core: bump wsproto from 1.2.0 to 1.3.2 (#19417)
  core: bump bpython from 0.25 to 0.26 (#19408)
  core: bump pdoc from 15.0.3 to 16.0.0 (#19413)
  core: bump ruff from 0.11.9 to 0.14.11 (#19410)
  core: bump python-kadmin-rs from 0.6.1 to 0.6.3 (#19416)
  core: bump drf-orjson-renderer from 1.7.3 to 1.8.0 (#19415)
  core: bump black from 25.1.0 to 25.12.0 (#19412)
  core: bump lxml from 6.0.0 to 6.0.2 (#19409)
  core: bump xmlsec from 1.3.16 to 1.3.17 (#19411)
  core: bump library/nginx from `06eb0c8` to `e3a22a7` in /website (#19394)
  core: bump library/node from `03729a7` to `6222695` in /website (#19393)
  website/docs: remove "beta" tag from 2025.12 (#19404)
  website/docs: add import to discord policy (#19397)
  web: bump @types/node from 25.0.7 to 25.0.8 in /web (#19392)
  website/docs: mention dynamic overrides in redirect stage documentation (#19368)
  ...
2026-01-14 08:24:16 -08:00
Ken Sternberg
f921eefdde Merge branch 'main' into dev
* main:
  website/docs: update unique email policy (#19305)
  core: bump library/nginx from `ca871a8` to `7272239` in /website (#19334)
  web: bump @types/node from 25.0.3 to 25.0.6 in /web (#19331)
  core: bump axllent/mailpit from v1.28.1 to v1.28.2 in /tests/e2e (#19329)
  web: bump knip from 5.80.1 to 5.80.2 in /web (#19332)
  web: bump pino from 10.1.0 to 10.1.1 in /web (#19333)
  website/docs: add flow import warnings (#19307)
  website/docs: Fix documentation example for `app_entitlements_attributes`. (#19316)
  website/docs: update m2m doc (#18963)
  website/docs: Fix typo in GitHub OAuth Source instructions (#18936)
  website/docs: deprecate GCDT auth stage (#19306)
  core, web: update translations (#19237)
2026-01-12 08:20:33 -08:00
Ken Sternberg
dd9eb4274d Merge branch 'main' into dev
* main: (44 commits)
  web: Fix flow inspector advancement event. (#19309)
  web: bump knip from 5.80.0 to 5.80.1 in /web (#19301)
  core: bump urllib3 from 2.5.0 to v2.6.3 (#19287)
  endpoints: show agent version (#19239)
  core: bump django from v5.2.9 to 5.2.10 (#19290)
  web/admin: add banner to flow import form (#19288)
  web: bump chromedriver from 143.0.3 to 143.0.4 in /web (#19244)
  stages/password: replace session-based retries with reputation (#18643)
  website/integations: fix aws spelling (#19253)
  website/docs: update entra id provider docs (#18366)
  stages/prompt: optimize API endpoints (#19251)
  web: bump the rollup group across 1 directory with 4 updates (#19206)
  web: bump vite from 7.3.0 to 7.3.1 in /web (#19245)
  website/docs: update github social login script example (#19246)
  website/integrations: update AWS (#17861)
  core: bump goauthentik.io/api/v3 from 3.2026020.8 to 3.2026020.10 (#19242)
  website: Fix typos. (#19243)
  core: fix read replica routing during transactions (#19086)
  website/glossary: improve (#18969)
  stages/authenticator_static: set max token length to 100 chars (#19162)
  ...
2026-01-09 09:34:48 -08:00
Ken Sternberg
ac0c1c9df3 Merge branch 'main' into dev
* main: (24 commits)
  web/maintenance: no missing element type definitions (#18950)
  core: add prettier failure on duplicate group names (#18941)
  website/integrations: make grafana terraform section expand (#19192)
  lib: update error logging (#18628)
  core, web: update translations (#19179)
  web: bump @formatjs/intl-listformat from 8.1.0 to 8.1.1 in /web (#19182)
  ci: bump getsentry/action-release from 3.4.0 to 3.5.0 (#19183)
  web: bump knip from 5.78.0 to 5.79.0 in /web (#19181)
  lifecycle: fix migration conn_options for psycopg connection (#19134)
  website/docs: remove duplicates in slo docs (#19170)
  web/admin: adjust sync threshold, add tooltip (#19131)
  web: Fix user library colors, modal z-indexes, table progress bars (#19152)
  web: fix slug auto-updating when editing existing applications (#19169)
  core: handle deserialization errors from FileField migration (#19067)
  stages/authenticator_webauthn: Update FIDO MDS3 & Passkey aaguid blobs (#19137)
  website/integrations: vaultwarden: add custom email scope (#19160)
  ci: bump int128/docker-manifest-create-action from 2.10.0 to 2.12.0 (#19138)
  core, web: update translations (#19135)
  web: bump globals from 16.5.0 to 17.0.0 in /web (#19154)
  web/user: fix consent delete form missing details (#19147)
  ...
2026-01-06 10:27:49 -08:00
Ken Sternberg
634f1f754d Merge branch 'main' into dev
* main: (48 commits)
  website/integrations: karakeep: don't hardcode wellknown's slug (#19127)
  core, web: bump qs from 6.14.0 to 6.14.1 in /packages/docusaurus-config (#19130)
  core: bump library/node from `ccfd9da` to `03729a7` in /website (#19125)
  core: bump github.com/jackc/pgx/v5 from 5.7.6 to 5.8.0 (#19088)
  web: bump the swc group across 1 directory with 11 updates (#19124)
  core: bump library/nginx from `ad85427` to `ca871a8` in /website (#19126)
  web: Capitalize language display names, code owner fix (#19119)
  web: Fix Impersonation, Lit Reactive Controller Contexts (#19114)
  web: bump the eslint group across 1 directory with 3 updates (#19110)
  core: bump library/nginx from `fb01117` to `ad85427` in /website (#19112)
  web: bump the storybook group across 1 directory with 5 updates (#19111)
  website/docs: release notes: Add more integrations (#19109)
  website/integrations: Add Audiobookshelf (#19104)
  website/integrations: Add Pulse (#19105)
  web/maintenance/no unknown attributes (part 1) (#18970)
  Update Vaultwarden documentation by removing warning (#19102)
  web: Fix stale flow background (#19015)
  web: fix promoted source button hover losing blue color (#19048)
  web: bump knip from 5.77.1 to 5.78.0 in /web (#19090)
  website/docs: endpoint devices: add path to macos setup (#19093)
  ...
2025-12-31 10:23:08 -08:00
Ken Sternberg
1620155f32 Merge branch 'main' into dev
* main:
  website/docs: Prioritize "Release Candidate" over "Current Release" (#18975)
  core: bump goauthentik.io/api/v3 from 3.2026020.4 to 3.2026020.5 (#19017)
  web: bump the eslint group across 1 directory with 3 updates (#19019)
  web/admin: prevent file upload attempt when backend not managed (#18646)
  api: rework schema generation (#18977)
  web: bump globby from 16.0.0 to 16.1.0 in /web (#18995)
  core: bump openapitools/openapi-generator-cli from v7.16.0 to v7.18.0 in /scripts/api (#19018)
  web: bump the rollup group across 1 directory with 4 updates (#18994)
2025-12-23 08:10:24 -08:00
Ken Sternberg
8d29ae4dd8 Merge branch 'main' into dev
* main:
  website/docs: improve endpoint devices docs (#19007)
  enterprise/search: add static autocomplete structure (#19008)
  enterprise/reports: improve export list, confirmation (#18981)
  providers/oauth2: Automated OpenID Conformance tests (#14785)
  ci: bump docker/setup-buildx-action from 3.11.1 to 3.12.0 (#18999)
  blueprints: fix flaky tests (#19002)
  web: fix Open button selecting row instead of navigating (#18992)
  events: notifications live update (#18980)
  web/admin: Fix haveibeenpwned link in PasswordPolicyForm (#18984)
  web/admin: fix dark theme on map (#18985)
  blueprints: add InternallyManagedMixin instead of large list (#18983)
  website/integrations: Fix path for Cloudflare Access (#18979)
2025-12-22 13:43:32 -08:00
Ken Sternberg
1521ade889 Merge branch 'main' into dev
* main: (60 commits)
  web/maintenance: no unknown tag names (#18944)
  web/maintenance: fix missing custom web component imports (#18942)
  website/docs: add note to active directory source doc (#18787)
  ci: bump actions/attest-build-provenance from 3.0.0 to 3.1.0 (#18960)
  web: bump @sentry/browser from 10.31.0 to 10.32.0 in /web in the sentry group across 1 directory (#18957)
  web: bump the swc group across 1 directory with 11 updates (#18958)
  web: bump chromedriver from 143.0.2 to 143.0.3 in /web (#18959)
  core: bump goauthentik.io/api/v3 from 3.2026020.3 to 3.2026020.4 (#18956)
  root: move docker files to lifecycle/containers and change docker-compose to compose (#16624)
  flows/executor: fix KeyError when session has no existing plan (#18951)
  web/admin: fix endpoints user binding (#18935)
  website/docs: Fix version parsing. (#18948)
  website/docs: release notes: add endpoint device links to 2025.12 notes (#18940)
  website/docs: Fix labels, Pre-Release detection (#18945)
  website/docs: endpoint devices (#18634)
  stages/identification: replace sleep with make_password (#18883)
  web/elements: progress-bar and table loading header (#18934)
  crypto: fix extra cert data in db migration (#18937)
  website/integrations: Add launch URL for Immich (#18921)
  web/flow: Fix spurious double submit  on ak-stage-autosubmit (#18727)
  ...
2025-12-19 08:55:37 -08:00
Ken Sternberg
9a4c56e6b2 Merge branch 'main' into dev
* main: (54 commits)
  website/docs: 2025.10.3 release notes (#18868)
  website/docs: Add docs for passkey autofill (WebauthN Conditional UI) (#18805)
  website/docs: adjust RBAC-related details in 2025.12 release notes (#18863)
  outposts: fix permission errors for related certificates (#18861)
  web/admin/rbac: misc object permission fixes (#18859)
  core: bump library/golang from `5d35fb8` to `8e8f9c8` (#18855)
  rbac: alter migrated direct permission roles (#18860)
  core: add skip s3_test_server_available to TestResolveFileUrlS3Backend (#18858)
  ci: replace codecov test-results action (#18862)
  core: bump goauthentik/fips-debian from `c10cd2c` to `2f19fc1` (#18856)
  admin/files: fix get_objects_for_user queryset argument in FileUsedByView (#18845)
  core: skip s3 tests if endpoint isn't available (#18841)
  crypto: Store details parsed from includeDetails in database instead (#18013)
  website/docs: add jellyseer integration doc (#18812)
  admin/files: revert add check for /media existence (#18636) (#18829)
  core: bump goauthentik.io/api/v3 from 3.2025120.26 to 3.2026020.1 (#18815)
  packages/django-dramatiq-postgres: broker: close django connections on consumer close (#18833)
  core: remove superuser check from `Token` list (#18684)
  website/docs: add icon info to style guide (#18832)
  core: list applications fix (#18798)
  ...
2025-12-16 08:05:51 -08:00
Ken Sternberg
4f1126ea99 Merge branch 'main' into dev
* main: (69 commits)
  website/docs: fix incorrect menu reference in data exports doc (#18752)
  translate: Updates for project authentik and language zh-Hans (#18756)
  translate: Updates for project authentik and language tr_TR (#18758)
  translate: Updates for project authentik and language fi_FI (#18759)
  translate: Updates for project authentik and language pl_PL (#18754)
  translate: Updates for project authentik and language ru_RU (#18745)
  translate: Updates for project authentik and language ko_KR (#18760)
  translate: Updates for project authentik and language ja_JP (#18755)
  translate: Updates for project authentik and language de_DE (#18749)
  translate: Updates for project authentik and language nl_NL (#18751)
  translate: Updates for project authentik and language pt_BR (#18746)
  translate: Updates for project authentik and language es_ES (#18748)
  translate: Updates for project authentik and language it_IT (#18750)
  translate: Updates for project authentik and language cs_CZ (#18753)
  translate: Updates for project authentik and language fr_FR (#18747)
  stages/identification: Add WebAuthn conditional UI (passkey autofill) support (#18377)
  api: allow configuring default page_size and max_page_size (#18165)
  root: do not require backend approval for npm workspace dependencies (#18738)
  outpost/proxyv2: more tests, fix pg password with spaces, and existing session on restart (#18211)
  web: bump @types/guacamole-common-js from 1.5.4 to 1.5.5 in /web (#18717)
  ...
2025-12-11 13:08:08 -08:00
Ken Sternberg
d15e360a74 Merge branch 'main' into dev
* main: (23 commits)
  *: Auto compress images (#18673)
  website/integrations: update kimai doc (#18629)
  root: skip current tab when refreshing others (#18674)
  core: add digraph group hierarchy (#17050)
  core: bump astral-sh/uv from 0.9.15 to 0.9.16 (#18668)
  core: bump goauthentik.io/api/v3 from 3.2025120.16 to 3.2025120.18 (#18661)
  web: bump type-fest from 5.3.0 to 5.3.1 in /web (#18663)
  ci: bump peter-evans/create-pull-request from 7.0.9 to 7.0.11 (#18666)
  web: bump vite from 7.2.6 to 7.2.7 in /web (#18662)
  core: bump goauthentik/fips-debian from `a80dbbd` to `10c8086` (#18665)
  ci: bump actions/create-github-app-token from 2.2.0 to 2.2.1 (#18664)
  ci: bump astral-sh/setup-uv from 7.1.4 to 7.1.5 in /.github/actions/setup (#18667)
  website/docs: background tasks: add more detail about "next run" (#18660)
  website/docs: install-config: fix dump_config command (#18659)
  website/integrations: wordpress: fix redirect uri (#18658)
  stages/mtls: always include cert in flow plan (#18657)
  endpoints: fix UI bugs, add user binding, etc (#18609)
  sources/ldap: make server info optional (#18648)
  web/admin: fix event volume chart not updating with query (#18649)
  web: Bump types, fix ESLint errors (#17546)
  ...
2025-12-08 08:35:20 -08:00
Ken Sternberg
2cd358d5e1 Merge branch 'main' into dev
* main: (40 commits)
  enterprise/stages/mtls: fix traefik certificate parsing (#18607)
  wed/admin: change s to S in "Stage" (#18632)
  flows: refresh unauthenticated tabs (#18621)
  flows: keep ?next url when using cancel (#18619)
  core, web: update translations (#18620)
  ci: bump actions/setup-node from 6.0.0 to 6.1.0 (#18552)
  core: bump goauthentik/fips-debian from `cf233be` to `a80dbbd` (#18594)
  web: bump @sentry/browser from 10.28.0 to 10.29.0 in /web in the sentry group across 1 directory (#18623)
  website/docs: adds note about ak_create_jwt function (#18614)
  api: fix IPC auth (#18612)
  web: bump mermaid from 11.12.1 to 11.12.2 in /web (#18602)
  web: Codemirror fixes (#18610)
  web: bump packages in /web (#18604)
  website/docs: expressions: fix markdown (#18613)
  website/docs: add missing API sidebar entry (#18586)
  web: bump yaml from 2.8.1 to 2.8.2 in /web (#18605)
  web/elements: update AppIcon story with files change (#18608)
  api: test action decorator (#18583)
  crypto: separate permissions for certificate and private keydownload (#18588)
  core: bump github.com/spf13/cobra from 1.10.1 to 1.10.2 (#18592)
  ...
2025-12-05 15:58:42 -08:00
Ken Sternberg
9fbc76098a Merge branch 'main' into dev
* main: (44 commits)
  build(deps): bump django from 5.2.8 to 5.2.9 (#18566)
  web: Adjust colors (#18427)
  admin/files: delete applications cache on migration (#18565)
  core: bump astral-sh/uv from 0.9.14 to 0.9.15 (#18555)
  core: bump goauthentik.io/api/v3 from 3.2025120.11 to 3.2025120.15 (#18551)
  core: bump goauthentik/fips-debian from `c718f60` to `cf233be` (#18553)
  ci: bump actions/checkout from 6.0.0 to 6.0.1 (#18554)
  ci: bump actions/stale from 10.1.0 to 10.1.1 (#18556)
  ci: bump golangci/golangci-lint-action from 9.1.0 to 9.2.0 (#18557)
  ci: bump actions/setup-node from 6.0.0 to 6.1.0 in /.github/actions/setup (#18559)
  core: bump library/golang from 1.25.4-trixie to 1.25.5-trixie (#18558)
  providers/scim: cache ServiceProviderConfig (#18047)
  web/i18n: Locale Context Merge Branch (#18426)
  website: Glossary (#16007)
  endpoints/stage: v2, better error handling, more settings (#18545)
  website: Docusaurus 3.9.2 (#18506)
  website/integrations: add hoop.dev (#17868)
  web/flows: update default background image (#18540)
  endpoints: implement endpoint stage (#18468)
  website/integrations: add salesforce (#18516)
  ...
2025-12-03 10:42:52 -08:00
Ken Sternberg
be3f35c58f Merge branch 'main' into dev
* main: (53 commits)
  core, web: update translations (#18380)
  web: re-add en.xlf locale (#18469)
  stages/user_write: Fix user attributes are not sanitized under certains conditions (#17890)
  providers/scim: compare users/groups before sending update request (#18456)
  enterprise/endpoints/connectors/agent: fix Apple JWE encryption when FIPS is enabled (#18464)
  website: bump @types/react from 19.2.6 to 19.2.7 in /website (#18357)
  core: bump goauthentik/fips-debian from `ac4c80b` to `de70579` (#18419)
  core: bump github.com/getsentry/sentry-go from 0.39.0 to 0.40.0 (#18416)
  website: bump prettier-plugin-packagejson from 2.5.19 to 2.5.20 in /website (#18460)
  core: bump goauthentik.io/api/v3 from 3.2025120.7 to 3.2025120.11 (#18461)
  website/integrations: add GLPI (#17937)
  website/integrations: small fixes (#18423)
  enterprise: Apple Platform SSO (#15318)
  crypto: only generate managed keypair if non-existent (#18457)
  ci: remove translation-rename (#18444)
  translate: Updates for project authentik and language tr (#18438)
  translate: Updates for project authentik and language fr (#18431)
  translate: Updates for project authentik and language ru (#18442)
  translate: Updates for project authentik and language cs_CZ (#18443)
  translate: Updates for project authentik and language pt (#18437)
  ...
2025-12-01 08:31:12 -08:00
Ken Sternberg
ac31137c97 Merge branch 'main' into dev
* main: (58 commits)
  core: bump goauthentik.io/api/v3 from 3.2025120.5 to 3.2025120.7 (#18381)
  web/admin: add entitlement search (#18291)
  core: bump goauthentik/fips-debian from `8b7e8d0` to `8c4ec98` (#18361)
  website: bump the build group in /website with 3 updates (#18382)
  core: bump astral-sh/uv from 0.9.11 to 0.9.12 (#18383)
  root: improve testing helpers (#18379)
  website: bump the goauthentik group across 1 directory with 4 updates (#18378)
  website: bump the eslint group in /website with 3 updates (#18356)
  policies: use flow planner directly in PolicyAccessView to directly set flow context (#18372)
  providers/scim: fix PATCH for AWS (#18230)
  enterprise/providers/scim: fix OAuth (#18358)
  web: Fix stale table rows (#17940)
  web: Bump packages. (#18371)
  *: convert slugfields to textfields (#17411)
  outposts: set container healthcheck inline (#18298)
  web:  ESLint Typing Fixes  (#18362)
  core: bump golang.org/x/crypto from 0.43.0 to 0.45.0 (#18275)
  lifecycle/aws: bump aws-cdk from 2.1032.0 to 2.1033.0 in /lifecycle/aws (#18278)
  core: bump github.com/getsentry/sentry-go from 0.38.0 to 0.39.0 (#18353)
  ci: bump actions/setup-python from 6.0.0 to 6.1.0 in /.github/actions/setup (#18360)
  ...
2025-11-26 07:47:43 -08:00
Ken Sternberg
8509056e21 Merge branch 'main' into dev
* main: (55 commits)
  Makefile: Fix kerberos tests for brew users (#17223)
  website/docs: add 2025.8.5 and 2025.10.2 release notes (#18268)
  internal: Automated internal backport: 5000-sidebar.sec.patch to authentik-main (#18266)
  internal: Automated internal backport: 1498-oauth2-cc-user-active.sec.patch to authentik-main (#18265)
  internal: Automated internal backport: 1487-invitation-expiry.sec.patch to authentik-main (#18264)
  core, web: update translations (#18241)
  web: bump ts-pattern from 5.8.0 to 5.9.0 in /web (#18247)
  web: bump the react group across 2 directories with 1 update (#18244)
  web: bump knip from 5.66.2 to 5.70.0 in /web (#18245)
  core: bump library/nginx from `b5b9e01` to `553f64a` in /website (#18253)
  core: bump library/golang from `27e1c92` to `728cbef` (#18252)
  core: bump goauthentik/fips-debian from `65a9f1f` to `55c1514` (#18251)
  web: Bump Vitest, TypeScript config (#18238)
  web: bump js-yaml from 4.1.0 to 4.1.1 in /packages/esbuild-plugin-live-reload (#18237)
  web/i18n: Remove English Locale (#18164)
  web: bump js-yaml from 3.14.1 to 3.14.2 in /packages/docusaurus-config (#18239)
  web/i18n: Clean up locale scripts (#18163)
  stages/prompt: fix choices with labels causing error on submit (#18183)
  web: Patternfly 5 Prep: Part 2 (#18085)
  lifecycle/aws: bump aws-cdk from 2.1031.2 to 2.1032.0 in /lifecycle/aws (#18218)
  ...
2025-11-20 09:18:17 -08:00
Ken Sternberg
63e188773f Merge branch 'main' into dev
* main:
  website/integrations: FortiMail (#17900)
  web/sfe: downgrade bootstrap that was accidentally upgraded (#18157)
  web: Fix ESBuild hanging process (#18162)
  website/integrations: macmon NAC (#17898)
2025-11-17 08:51:59 -08:00
Ken Sternberg
8585c646a2 Merge branch 'main' into dev
* main: (74 commits)
  packages/django-channels-postgres/layer: fix query when subscribed to multiple channels (#18152)
  core: deduplicate user attribute constant definitions (#18138)
  web: bump @trivago/prettier-plugin-sort-imports from 5.2.2 to 6.0.0 in /web (#18146)
  crypto: update certificates on fs event (#18129)
  github: converts issue templates to forms (#18133)
  core: bump github.com/getsentry/sentry-go from 0.36.2 to 0.37.0 (#18140)
  web: bump type-fest from 5.1.0 to 5.2.0 in /web (#18144)
  web: bump vite from 7.1.12 to 7.2.2 in /web (#18143)
  website: bump the build group in /website with 3 updates (#18141)
  web: bump globals from 16.4.0 to 16.5.0 in /web (#18145)
  core: bump astral-sh/uv from 0.9.8 to 0.9.9 (#18148)
  core: bump goauthentik/fips-debian from `5017d65` to `40a1f32` (#18149)
  website/integrations: Add ezBookkeeping integration (#18040)
  website/integrations: Add Joplin (#18042)
  web: Disable library `<datalist>` on Firefox. (#18103)
  web/admin: link to user on invitation list page (#18132)
  web/admin: update stage descriptions (#18118)
  website/integrations: add SeaTable (#18115)
  website/integrations: stripe: fix markdown (#18126)
  web/flows: improvements for hCaptcha (#16882)
  ...
2025-11-14 08:06:43 -08:00
Ken Sternberg
5c66e50205 website: fix bad escaping of URLs in release notes
## What

Fixes bad escaping of URLs in the release notes that resulted in mangled output.

v2024.6.4 had entries that looked like this:

```
##### `GET` /providers/google_workspace/{#123;id}#125;/
```

v2025.4.md had entries that looked like this:

```
##### `GET` /policies/unique_password/{#125;#123;policy_uuid}/
```

A couple of straightforward search-and-replaces has fixed the issue.

## Notes

Two of the release notes had bad escaping of URLs. I'm not sure how the error was made or got past,
but it was obvious when visiting the page.

@Beryju suggested that the bug is due to our using `{...}` to symbolize parameters in a URL while
Docusaurus wants to interpret `{...}` as an internal template instruction, resulting in odd
behavior. In either case, docusarus interpreted the hashtagged entries as links to unrelated issues
in Github (the same two issues, which were "bump version of pylint" and "bump version of sentry"),
which could be very confusing.

The inconsistencies between the two releases, and the working releases, suggests that the error was
introduced manually.
2025-11-10 09:20:22 -08:00
Ken Sternberg
2c658e2ee6 Merge branch 'main' into dev
* main: (42 commits)
  core, web: update translations (#17943)
  web: bump @types/node from 24.9.1 to 24.10.0 in /packages/prettier-config (#17949)
  core: bump library/nginx from `f547e3d` to `1beed3c` in /website (#17955)
  core: bump goauthentik.io/api/v3 from 3.2025120.2 to 3.2025120.3 (#17945)
  web: bump @types/node from 22.15.19 to 24.10.0 in /web (#17950)
  ci: bump docker/setup-qemu-action from 3.6.0 to 3.7.0 (#17999)
  lifecycle/aws: bump aws-cdk from 2.1031.1 to 2.1031.2 in /lifecycle/aws (#18014)
  core: bump golang.org/x/sync from 0.17.0 to 0.18.0 (#18033)
  core: bump astral-sh/uv from 0.9.7 to 0.9.8 (#18037)
  core: bump golang.org/x/oauth2 from 0.32.0 to 0.33.0 (#18034)
  core: bump axllent/mailpit from v1.27.10 to v1.27.11 in /tests/e2e (#18035)
  ci: bump golangci/golangci-lint-action from 8.0.0 to 9.0.0 (#18036)
  core: bump library/golang from `a13297b` to `27e1c92` (#18038)
  ci: fix migrate-from-stable for old versions (#18019)
  core: bump library/golang from 1.25.3-trixie to 1.25.4-trixie (#18000)
  website/docs: updates img-src csp (#18010)
  providers/saml: move sp binding location and default value (#17609)
  core: Add example invitation blueprint (#17661)
  root: settings.py: fix comment (#18006)
  core: bump google-auth-httplib2 from 0.2.0 to v0.2.1 (#17978)
  ...
2025-11-10 08:42:07 -08:00
Ken Sternberg
2c52a19a44 Merge branch 'main' into dev
* main:
  webiste/docs: remove broken info box and fix sentence (#17963)
  web/admin: fixes capitalization in application wizard title (#17959)
  website/docs: added Note about email_verified scope mapping is set to false by default (#17942)
  crypto: update certificate api and component (#17921)
  core: bump openapitools/openapi-diff from 2.1.4 to 2.1.5 in /scripts/api (#17929)
  ci: bump getsentry/action-release from 3.3.0 to 3.4.0 (#17931)
  ci: bump helm/kind-action from 1.12.0 to 1.13.0 (#17930)
  tasks/schedules: fix rel obj not being associated or updated (#17934)
  core, web: update translations (#17807)
  brands: sort matched brand by match length (#17920)
  web: bump the storybook group across 1 directory with 5 updates (#17787)
  brands: add more matching tests (#16185)
2025-11-05 08:42:36 -08:00
Ken Sternberg
bc2a07156b Merge branch 'main' into dev
* main: (32 commits)
  website/docs: 2025.10.1 release notes (#17918)
  providers/oauth2: fix kid always required for federation (#17914)
  providers/radius: revert fix inverted message authenticator validation (#17855) (#17915)
  website: bump @types/node from 24.9.1 to 24.9.2 in /website (#17786)
  web: bump @rollup/plugin-commonjs from 28.0.8 to 28.0.9 in /web in the rollup group across 1 directory (#17788)
  web: bump validator from 13.15.15 to 13.15.20 in /packages/docusaurus-config (#17866)
  internal: add default go http server timeouts (#17858)
  providers/radius: fix inverted message authenticator validation (#17855)
  stages/authenticator_webauthn: Update FIDO MDS3 & Passkey aaguid blobs (#17871)
  web: fix package-lock.json (#17809)
  website/integrations: oracle cloud: cleanup (#17808)
  website/integrations: Add Keycloak integration (#17813)
  website: bump the build group across 1 directory with 9 updates (#17849)
  lifecycle/aws: bump aws-cdk from 2.1031.0 to 2.1031.1 in /lifecycle/aws (#17850)
  core: bump astral-sh/uv from 0.9.6 to 0.9.7 (#17851)
  internal: full openssl path (#17856)
  outpost: revert breaking signals change (#17847)
  web/a11y: Isolated Outpost Error Page (#17683)
  provider/saml: make signing kp singleton (#17703)
  tasks: sanitize log attributes (#17833)
  ...
2025-11-03 08:23:26 -08:00
Ken Sternberg
0845cc400c Merge branch 'main' into dev
* main: (28 commits)
  ci: use hashes for actions everywhere (#17803)
  website/integrations: fixed paperless-ngx yml syntax issue and added additional info (#17739)
  core, web: update translations (#17782)
  ci: rework internal repo (#17797)
  root: use hashes for dockerfile FROM (#17795)
  web: bump validator from 13.15.15 to 13.15.20 in /packages/prettier-config (#17776)
  tasks: delay startup signals (#17769)
  website: bump the build group in /website with 6 updates (#17712)
  core, web: update translations (#17660)
  web: bump vite from 7.1.11 to 7.1.12 in /web (#17689)
  website: bump validator from 13.15.15 to 13.15.20 in /website (#17741)
  web: bump eslint-plugin-react-hooks from 7.0.0 to 7.0.1 in /packages/eslint-config in the eslint group across 1 directory (#17714)
  web: bump validator from 13.15.15 to 13.15.20 in /packages/eslint-config (#17742)
  packages/django-postgres-cache: use upsert instead of select/update in a transaction (#17760)
  providers/radius: fix panic when no cert is configured (#17762)
  sources/oauth: Make PKCE verifier 128 characters (#17763)
  providers/proxy: fix missing JWT/claims header (#17759)
  providers/proxy: add gorm logging (#17758)
  web: bump the sentry group across 1 directory with 2 updates (#17743)
  root: Add Dockerfile label org.opencontainers.image.source (#17756)
  ...
2025-10-29 10:07:10 -07:00
Ken Sternberg
91bbf0e449 Merge branch 'main' into dev
* main: (25 commits)
  ci: bump astral-sh/setup-uv from 7.1.1 to 7.1.2 in /.github/actions/setup (#17718)
  web: bump the storybook group across 1 directory with 5 updates (#17715)
  ci: bump actions/upload-artifact from 4.6.2 to 5.0.0 (#17720)
  ci: bump actions/download-artifact from 5.0.0 to 6.0.0 (#17719)
  website/integrations: grafana: replace deprecated redirect_uris usage by allowed_redirect_uris (#17710)
  web: bump @types/codemirror from 5.60.16 to 5.60.17 in /web (#17685)
  web: bump @types/node from 22.15.19 to 24.9.1 in /web (#17687)
  web: bump hono from 4.10.2 to 4.10.3 in /web (#17698)
  website/docs: blueprints: add a bit more info (#17704)
  website/docs: release notes: Add Zot integration (#17700)
  website/integrations: zot oci registry integration (#17682)
  website/integrations: sonarr: clarify reverse proxy setup (#17485)
  website/docs: eap add info about custom validation (#17642)
  web: Fix table row click handler. (#17697)
  root: Fix transifex link (#17696)
  translate: add cs_CZ (#17632)
  web: bump @goauthentik/prettier-config from 1.0.5 to 3.1.0 in /web in the goauthentik group across 1 directory (#17684)
  web: Make action field search case insensitive in Event Matcher Policy Form (#17680)
  website/docs: add note about invite link not bound (#17657)
  web:  Abstract Wizard Lifecycle (#17658)
  ...
2025-10-27 10:33:33 -07:00
Ken Sternberg
11f500c670 Merge branch 'main' into dev
* main:
  website: bump the eslint group in /website with 3 updates (#17601)
  web: bump hono from 4.9.12 to 4.10.2 in /web (#17653)
  web: bump @types/node from 24.9.0 to 24.9.1 in /packages/esbuild-plugin-live-reload (#17616)
  core: bump goauthentik.io/api/v3 from 3.2025100.25 to 3.2025120.1 (#17613)
  website: bump @types/node from 24.9.0 to 24.9.1 in /website (#17612)
  web: bump vite from 7.1.10 to 7.1.11 in /web (#17604)
  lib/sync/outgoing: store sync settings in database (#17630)
2025-10-22 10:00:52 -07:00
Ken Sternberg
eaf9185e73 Merge branch 'main' into dev
* main: (213 commits)
  web: bump @types/node from 24.9.0 to 24.9.1 in /packages/prettier-config (#17617)
  web: bump @types/node from 22.15.19 to 24.9.1 in /web (#17618)
  web: bump knip from 5.66.1 to 5.66.2 in /web (#17619)
  translate: Updates for file web/xliff/en.xlf in pt_BR (#17639)
  core, web: update translations (#17643)
  website/docs: rel notes 2025.10: add 3 more integration guides (#17641)
  providers/proxy: drop headers with underscores (#17650)
  core: bump astral-sh/uv from 0.9.4 to 0.9.5 (#17645)
  web: bump style-mod from 4.1.2 to 4.1.3 in /web (#17647)
  core: bump github.com/getsentry/sentry-go from 0.36.0 to 0.36.1 (#17646)
  website/integrations: add terraform cloud (#17610)
  website/integrations: add zendesk (#17541)
  core: bump djangorestframework from 3.16.0 (our fork) to v3.16.1 (official package) (#16594)
  enterprise: add prometheus metrics for license usage and expiry (#17606)
  ci: link to next. for pre-release docs (#17634)
  web: sync web/package-lock.json (#17611)
  website/integrations: random fixes (#17631)
  website/docs: add short-lived certificate recommendation (#17628)
  core, web: update translations (#17605)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in pt_BR (#17622)
  ...
2025-10-22 08:11:04 -07:00
Ken Sternberg
47ed22b57d Merge branch 'main' into dev
* main: (56 commits)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in fr (#17361)
  website/docs: add entra id scim source (#17357)
  outpost: proxyv2: Use Postgres for the Embedded Outpost (#16628)
  tasks: set uid early (#17356)
  tasks: only set tenant on task creation (#17358)
  core: bump golang.org/x/oauth2 from 0.31.0 to 0.32.0 (#17346)
  web: bump eslint-plugin-react-hooks from 6.1.1 to 7.0.0 in /packages/eslint-config in the eslint group across 1 directory (#17347)
  web: bump chromedriver from 141.0.0 to 141.0.1 in /web (#17348)
  web: bump knip from 5.64.1 to 5.64.2 in /web (#17349)
  web: bump @formatjs/intl-listformat from 7.7.11 to 7.7.12 in /web (#17350)
  web: bump pino from 9.13.0 to 10.0.0 in /web (#17351)
  core: bump axllent/mailpit from v1.27.9 to v1.27.10 in /tests/e2e (#17352)
  enterprise/providers/gws+entra: fix group integrity error during discovery (#17355)
  core, web: update translations (#17342)
  ci: bump snok/container-retention-policy from 2.2.1 to 3.0.1 (#17344)
  core: bump goauthentik.io/api/v3 from 3.2025100.18 to 3.2025100.20 (#17345)
  packages/django-dramatiq-postgres: broker: task retrieval fixes and improvements (#17335)
  enterprise/providers/gws+entra: fix integrity error during discovery (#17341)
  web: bump API Client version (#17340)
  api: Clean schema up more (#17055)
  ...
2025-10-09 10:50:39 -07:00
Ken Sternberg
542a605f9a Merge branch 'main' into dev
* main: (118 commits)
  tasks: add preprocess, running and postprocess statuses (#17297)
  web: Fix behavior for modals configured with closeAfterSuccessfulSubmit (#17277)
  web: Responsive toolbar flow (#17278)
  website/integrations: update dokuwiki (#17292)
  core, web: update translations (#17275)
  core: bump astral-sh/uv from 0.8.23 to 0.8.24 (#17281)
  website: bump @types/node from 24.6.2 to 24.7.0 in /website (#17283)
  web: bump the eslint group across 2 directories with 3 updates (#17285)
  web: bump @types/node from 24.6.2 to 24.7.0 in /packages/esbuild-plugin-live-reload (#17287)
  web: bump @types/node from 24.6.2 to 24.7.0 in /packages/prettier-config (#17288)
  web: bump @types/node from 22.15.19 to 24.7.0 in /web (#17289)
  website/integrations: sssd: Updating config template to include default shell (#17274)
  web/a11y: Flow Stages (#17273)
  web/a11y: Flow inspector. (#17271)
  packages/django-channels-postgres/layer: fix connection deadlock (#17270)
  core: bump django to 5.2.7 (#16324)
  core: fix absolute and relative path file uploads (#17269)
  web/a11y: Accessible scrollbars. (#17253)
  web: Fix table column updates, template parsing (#17254)
  website/integrations: add launch url info to dokuwiki (#17268)
  ...
2025-10-07 07:41:29 -07:00
Ken Sternberg
5b4a921b1a Merge branch 'main' into dev
* main: (29 commits)
  web/a11y: Notifications drawer (#17031)
  web: Clean up render interfaces. (#16031)
  web/a11y: Status label (#17148)
  web: Additional text field properties, ARIA fixes (#17115)
  web/e2e: User creation (#17149)
  web/a11y: Tree view (#17147)
  web/a11y: Fix dark theme color contrast (#17144)
  web: Table refresh timestamp. (#17145)
  providers/oauth2: add ui_locales support for OIDC (#17140)
  website/integrations: convert all note boxes to info boxes (#17139)
  website/docs: replaces all note boxes with info boxes (#17138)
  website/docs: developer docs: adjust sentence for writing docs (#17137)
  core: Add input validation for service account creation (#16964)
  website: bump @types/node from 24.5.2 to 24.6.0 in /website (#17126)
  ci: bump actions/setup-node from 4 to 5 (#17123)
  website: bump the build group in /website with 6 updates (#17124)
  website: bump the eslint group in /website with 3 updates (#17125)
  web: bump @sentry/browser from 10.15.0 to 10.16.0 in /web in the sentry group across 1 directory (#17127)
  web: bump the eslint group across 2 directories with 3 updates (#17128)
  lifecycle/aws: bump cross-env from 10.0.0 to 10.1.0 in /lifecycle/aws (#17130)
  ...
2025-09-30 14:26:21 -07:00
Ken Sternberg
08218dcd1a Merge branch 'main' into dev
* main: (38 commits)
  web: Apply consistent background color when input is disabled or readonly. (#17105)
  website/docs: 2025.8.4 release notes (#17119)
  web: revert bump the swc group across 1 directory with 11 updates (#17113)
  ci: fix node version in docker image build (#17110)
  translate: Updates for file web/xliff/en.xlf in pt_BR (#17111)
  tasks: reduce default number of retries and max backoff (#17107)
  packages/django-dramatiq-postgres: broker: fix new messages not being picked up when too many messages are waiting (#17106)
  website/docs: additional documentation for ak_user_by (#17098)
  stages/identification: fix mismatched error messages (#17090)
  providers/oauth2: fix authentication error with identical app passwords (#17100)
  translate: Updates for file web/xliff/en.xlf in de (#17099)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in de (#17096)
  core: bump goauthentik.io/api/v3 from 3.2025100.11 to 3.2025100.14 (#17071)
  website: bump @types/react from 19.1.13 to 19.1.15 in /website (#17075)
  website/integrations: add cloudflare access redirect (#17094)
  cmd/server/healthcheck: info log success instead of debug (#17093)
  website/integrations: cloudflare (#17039)
  rbac: optimize rbac assigned by users query (#17015)
  web: Fix layout class for 'row' in LibraryPage (#16752)
  *: Auto compress images (#16733)
  ...
2025-09-29 15:33:06 -07:00
Ken Sternberg
2c1fd70808 Merge branch 'main' into dev
* main: (70 commits)
  core, web: update translations (#17036)
  web: bump API Client version (#17048)
  tests/e2e: less hardcoded names (#17047)
  core/api: Better naming for partial user/group serializer, optimise bindings (#17022)
  core: bump goauthentik.io/api/v3 from 3.2025100.10 to 3.2025100.11 (#17040)
  website: bump the build group in /website with 6 updates (#17042)
  web: bump the swc group across 1 directory with 11 updates (#17043)
  website/integrations: Move Cloudflare Access Documentation. (#17038)
  web: Fix skip-to-content element target, order. (#17030)
  web: Add disabled radio styles. (#17026)
  web: Report unregistered elements. (#17025)
  website/docs: Update Github expression to handle non-OAuth sources gracefully (#17014)
  tests/e2e: fix ldap tests following #17010 (#17021)
  web: bump @sentry/browser from 10.13.0 to 10.14.0 in /web in the sentry group across 1 directory (#16966)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in pt_BR (#17001)
  lib/config: fix listen settings (#17005)
  core: bump goauthentik.io/api/v3 from 3.2025100.8 to 3.2025100.10 (#17019)
  core: add index on Group.is_superuser (#17011)
  lib: match exception_to_dict locals behaviour (#17006)
  outposts/ldap: add pwdChangeTime attribute (#17010)
  ...
2025-09-26 08:24:17 -07:00
Ken Sternberg
5603fe193e Merge branch 'main' into dev
* main: (24 commits)
  root: add mypy (#16904)
  website: Remove duplicate sidebar entries. (#16922)
  web: Remove CSS constructor polyfill. (#16920)
  web: Replace Github Slugger package with change-case. (#16921)
  website: Fix broken schema links v2 (#16919)
  website: bump the build group in /website with 3 updates (#16908)
  core: bump astral-sh/uv from 0.8.18 to 0.8.19 (#16906)
  core: bump goauthentik.io/api/v3 from 3.2025100.6 to 3.2025100.8 (#16907)
  website: bump the eslint group in /website with 2 updates (#16909)
  web: bump the eslint group across 2 directories with 2 updates (#16911)
  web: bump the rollup group across 1 directory with 4 updates (#16912)
  web: bump typedoc-plugin-markdown from 4.8.1 to 4.9.0 in /packages/esbuild-plugin-live-reload (#16913)
  web: bump pino from 9.10.0 to 9.11.0 in /packages/esbuild-plugin-live-reload (#16914)
  web: bump pino from 9.10.0 to 9.11.0 in /web (#16915)
  website: add hierarchy line to sidebar (#16565)
  events: remove deprecated models (#15823)
  core: update_attributes: only update the model if attributes changed (#16322)
  Revert "website: Fix broken schema links, non-relative paths, unapplied redirect aliases" (#16902)
  website: Fix broken schema links, non-relative paths, unapplied redirect aliases (#16900)
  website/integrations: adds termix (#16889)
  ...
2025-09-22 11:15:33 -07:00
Ken Sternberg
6a882f22fa Merge branch 'main' into dev
* main:
  core, web: update translations (#16885)
  web: bump the storybook group across 1 directory with 5 updates (#16886)
  api: optimise schemas' common query parameters (#16884)
2025-09-19 08:19:10 -07:00
Ken Sternberg
442d42f850 Merge branch 'main' into dev
* main: (58 commits)
  web: bump the esbuild group across 2 directories with 4 updates (#16868)
  core, web: update translations (#16864)
  core: bump astral-sh/uv from 0.8.17 to 0.8.18 (#16866)
  website: bump @types/node from 24.5.1 to 24.5.2 in /website (#16867)
  web: bump @types/node from 24.5.1 to 24.5.2 in /packages/esbuild-plugin-live-reload (#16869)
  web: bump pino from 9.9.5 to 9.10.0 in /packages/esbuild-plugin-live-reload (#16870)
  web: bump @types/node from 24.5.1 to 24.5.2 in /packages/prettier-config (#16871)
  web: bump @types/node from 22.15.19 to 24.5.2 in /web (#16872)
  web: bump dompurify from 3.2.6 to 3.2.7 in /web (#16873)
  web: bump pino from 9.9.5 to 9.10.0 in /web (#16874)
  web: bump vite from 7.1.5 to 7.1.6 in /web (#16875)
  web: bump chromedriver from 140.0.2 to 140.0.3 in /web (#16876)
  lifecycle/aws: bump aws-cdk from 2.1029.1 to 2.1029.2 in /lifecycle/aws (#16877)
  web: Fix docs links, a11y input descriptors (#16671)
  website: bump the eslint group in /website with 3 updates (#16788)
  website: bump the build group in /website with 3 updates (#16787)
  web: bump the eslint group across 2 directories with 3 updates (#16790)
  website/docs: extends the example to include `jwt_config` for matrix/synapse (#16860)
  web/a11y: Flow Search (#15876)
  web: bump API Client version, remove Webdriver dependencies (#16836)
  ...
2025-09-18 16:39:55 -07:00
Ken Sternberg
d0daec4aa1 Merge branch 'main' into dev
* main: (81 commits)
  translate: Updates for file web/xliff/en.xlf in de (#16808)
  stages: update friendly_name model from null to blank (#16672)
  sources/saml: add default error messages to exceptions (#15562)
  website/docs: 2025.8.3 release notes (#16809)
  core, web: update translations (#16783)
  stages/email_authenticator: Fix email mfa loop (#16579)
  website/docs: updated Frontend development environment contributor docs (#16731)
  webiste/integrations: update roundcube doc (#16753)
  website/docs: update create oauth provider page (#16617)
  website: bump @types/node from 24.4.0 to 24.5.0 in /website (#16789)
  web: bump the rollup group across 1 directory with 4 updates (#16792)
  core: bump github.com/getsentry/sentry-go from 0.35.2 to 0.35.3 (#16786)
  web: bump the storybook group across 1 directory with 5 updates (#16791)
  web: bump @types/node from 24.4.0 to 24.5.0 in /packages/esbuild-plugin-live-reload (#16794)
  web: bump @goauthentik/prettier-config from 1.0.5 to 3.1.0 in /web in the goauthentik group across 1 directory (#16793)
  web: bump @types/node from 24.4.0 to 24.5.0 in /packages/prettier-config (#16795)
  web: bump @types/node from 22.15.19 to 24.5.0 in /web (#16796)
  web: Use curated dictionary for e2e fixtures. (#16750)
  website/integrations: fix wekan redirect URL (#16801)
  website/docs: fix docker tabs not rendering properly (#16799)
  ...
2025-09-16 12:10:58 -07:00
Ken Sternberg
951da48f81 Merge branch 'main' into dev
* main: (121 commits)
  website: bump the eslint group in /website with 3 updates (#16674)
  web: bump the eslint group across 2 directories with 3 updates (#16675)
  web: bump vite from 7.1.4 to 7.1.5 in /web (#16676)
  website/docs: fix typo (#16681)
  core: Include region comments in VSCode Minimap. (#16667)
  tasks: fix status and healthcheck breaking with connection issues (#16504)
  website/docs: add period on very last sentence. (#16669)
  core: bump golang.org/x/sync from 0.16.0 to 0.17.0 (#16657)
  web: bump the eslint group across 3 directories with 2 updates (#16661)
  web: bump the storybook group across 1 directory with 5 updates (#16662)
  core: bump golang.org/x/oauth2 from 0.30.0 to 0.31.0 (#16658)
  core: bump github.com/prometheus/client_golang from 1.23.1 to 1.23.2 (#16659)
  website: bump the eslint group in /website with 2 updates (#16660)
  web: bump the rollup group across 1 directory with 4 updates (#16663)
  web: bump pino from 9.9.2 to 9.9.4 in /web (#16664)
  lifecycle/aws: bump aws-cdk from 2.1028.0 to 2.1029.0 in /lifecycle/aws (#16665)
  core: bump selenium/standalone-chrome from 139.0 to 140.0 in /tests/e2e (#16666)
  website/integrations: fix missing space after comma (#16650)
  website/integrations: add missing comma paperless-ngx (#16651)
  root: bump to debian trixie (#16626)
  ...
2025-09-09 08:12:24 -07:00
Ken Sternberg
7f353ac1b8 Merge branch 'main' into dev
* main: (71 commits)
  website: Redirect Azure to Entra. Add tags for search indexing. (#16474)
  website: Page redirect guide, documentation (#16466)
  root: bump openapi-generator-cli to v7.15.0 (#16440)
  ci: bump actions/attest-build-provenance from 2 to 3 (#16462)
  core: bump astral-sh/uv from 0.8.13 to 0.8.14 (#16461)
  ci: remove Python client API publication (#16468)
  website: Unify Netlify redirects with Docusaurus's client-side router. (#16430)
  core: fix client-side only validation allowing admin to set blank user password (#16467)
  website/integrations: Update Issuer URL for Immich (#16460)
  providers/oauth2: include scope in JWT (#16454)
  lib/sync/outgoing: fix single object sync timeout (#16447)
  website/docs: capitalized proper name of stages, removed old version references. (#16414)
  web: bump pino-pretty from 13.0.0 to 13.1.1 in /web (#16411)
  core: bump h2 from 4.2.0 to 4.3.0 (#16446)
  web: bump @playwright/test from 1.54.1 to 1.55.0 in /web (#16413)
  web: bump the react group across 2 directories with 1 update (#16448)
  web: bump bootstrap from 5.3.7 to 5.3.8 in /web (#16416)
  web: bump @sentry/browser from 10.6.0 to 10.7.0 in /web in the sentry group across 1 directory (#16433)
  root: check for brew install of libxml2 before updating path (#16422)
  core: bump github.com/stretchr/testify from 1.11.0 to 1.11.1 (#16434)
  ...
2025-08-29 08:38:11 -07:00
Ken Sternberg
7080053510 Merge branch 'main' into dev
* main:
  web: Automatic reload during server start up. (#16030)
  website/docs: Add steps for fixing xml python errors, clean up (#16223)
2025-08-26 08:21:54 -07:00
Ken Sternberg
f55207b6ef Merge branch 'main' into dev
* main:
  providers/oauth2: avoid deadlock during session migration (#16361)
  lifecycle/aws: bump aws-cdk from 2.1025.0 to 2.1026.0 in /lifecycle/aws (#16352)
  core: bump github.com/stretchr/testify from 1.10.0 to 1.11.0 (#16357)
  core: bump axllent/mailpit from v1.27.5 to v1.27.6 in /tests/e2e (#16358)
  website/docs: fix missing trailing slash in vaultwarden documentation (#16348)
  root: fix security.md (#16345)
  root: update security.md with github reporting link (#16332)
  website/docs: 2025.8.1 release notes (#16343)
  packages/django-dramatiq-postgres: broker: fix various timing issues (#16340)
  website/docs: adds details to certificates doc (#16335)
  outposts: allow ingress path type configuration (#16339)
  core, web: update translations (#16321)
  outposts: fix service connection update task arguments (#16312)
  core: use email backend for test_email management command (#16311)
  core: bump astral-sh/uv from 0.8.12 to 0.8.13 (#16325)
  website: Move docs netlify.toml (#16320)
  website/docs: add link in 2025.8 rel notes to back-channel logout docs (#16306)
  packages/django-dramatiq-postgres: middleware: fix listening on hosts where ipv6 is not supported (#16308)
  website: Fix version origin detection, build-time URLs  (#15774)
  web/a11y: Associating labels with inputs (#16119)
2025-08-25 14:09:52 -07:00
Ken Sternberg
7347577436 Merge branch 'main' into dev
* main: (210 commits)
  web: Username truncation, field alignment. (#16283)
  website/docs: adds a webhook header mapping example (#16301)
  web: Fix issue where form group uses unknown slot. (#16276)
  lifecycle: set PROMETHEUS_MULTIPROC_DIR as early as possible (#16298)
  providers/oauth2: fix logout token missing sid, fix wrong sub mode used (#16295)
  web: bump core-js from 3.45.0 to 3.45.1 in /web (#16290)
  root: Remove CODEOWNERS entries from docs/ directory (#16287)
  *: Fix dead doc link (#16288)
  web: saml provider view: fix state refresh issues (#14474)
  web: fix "Explore integrations" link in Quick actions (#16274)
  website/integrations: fix dead links to external docs (#16273)
  tasks: add rel_obj to system task exception event (#16270)
  website/docs: update 2025.8 release notes (#16269)
  web: bump @patternfly/elements from 4.1.0 to 4.2.0 in /web (#16265)
  web: bump mermaid from 11.9.0 to 11.10.0 in /web (#16263)
  web: bump @types/guacamole-common-js from 1.5.3 to 1.5.4 in /web (#16262)
  security: Bump supported versions (#16261)
  core: bump channels from 4.3.0 to v4.3.1 (#16260)
  translate: Updates for file web/xliff/en.xlf in cs_CZ (#16264)
  website: bump the eslint group in /website with 3 updates (#16248)
  ...
2025-08-21 09:50:14 -07:00
Ken Sternberg
4963dde699 Merge branch 'main' into dev
* main: (32 commits)
  core: bump goauthentik.io/api/v3 from 3.2025064.6 to 3.2025064.7 (#16024)
  core, web: update translations (#16021)
  ci: move images from beryju/* to authentik/* (#15321)
  core, web: update translations (#15985)
  core: bump cattrs from 24.1.3 to v25.1.1 (#15981)
  web: bump API Client version (#16002)
  ci: bump actions/download-artifact from 4 to 5 (#15995)
  core: bump certifi from 2025.7.14 to v2025.8.3 (#15982)
  core: bump anyio from 4.9.0 to v4.10.0 (#15979)
  core: bump boto3 from 1.40.1 to v1.40.2 (#15980)
  core: bump astral-sh/uv from 0.8.4 to 0.8.5 (#15998)
  core: bump goauthentik.io/api/v3 from 3.2025064.5 to 3.2025064.6 (#15997)
  stages/email: implement rate limiting for account verification (#15531)
  web: Fix stale application slug, missing error state. (#15941)
  website/docs: change azure ad to entra id (#15691)
  website/docs: add tips for image optimization (#15978)
  web: bump API Client version (#15976)
  providers/oauth2: backchannel logout (#15401)
  web: bump API Client version (#15953)
  translate: Updates for file web/xliff/en.xlf in fr (#15974)
  ...
2025-08-07 08:31:56 -07:00
Ken Sternberg
b60bfadaaf Merge branch 'main' into dev
* main: (77 commits)
  website/integrations: add hass-openid instructions (#14672)
  core: add updated_at field to user (#15571)
  root: Add more opencontainer labels to Dockerfiles (#15923)
  core: bump goauthentik.io/api/v3 from 3.2025064.2 to 3.2025064.3 (#15949)
  core, providers/ldap: add parent/child groups to api and ldap results (#14974)
  web: Make Webdriver optional during install. (#15952)
  core, web: update translations (#15945)
  packages/django-dramatiq-postgres: fix typo (#15932)
  web: bump API Client version (#15942)
  core: fix flow planner checking against wrong user when creating recovery link (#15390)
  providers/saml: configuration for default NameID Policy (#15109)
  core: bump boto3 from 1.39.15 to v1.40.1 (#15926)
  core: bump jsii from 1.112.0 to v1.113.0 (#15927)
  core: bump argon2-cffi-bindings from 21.2.0 to v25.1.0 (#15925)
  core: bump aiohttp from 3.12.14 to v3.12.15 (#15924)
  core: bump opentelemetry-api from 1.35.0 to v1.36.0 (#15928)
  web/admin: fix variable name (#15934)
  policies: fix typo (#15933)
  web: bump @sentry/browser from 9.43.0 to 10.0.0 in /web in the sentry group across 1 directory (#15911)
  core: bump github.com/prometheus/client_golang from 1.22.0 to 1.23.0 (#15908)
  ...
2025-08-04 10:42:32 -07:00
Ken Sternberg
0fc0783e7e Merge branch 'main' into dev
* main: (91 commits)
  *: replace Celery with Dramatiq (#13492)
  website/docs: stages/mtls: Clean up stage configuration section (#15753)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in es (#15818)
  website: bump the eslint group in /website with 2 updates (#15805)
  web: bump typedoc from 0.28.7 to 0.28.8 in /packages/esbuild-plugin-live-reload (#15809)
  core: bump axllent/mailpit from v1.27.1 to v1.27.2 in /tests/e2e (#15813)
  web: bump the rollup group across 1 directory with 4 updates (#15806)
  web: bump the eslint group across 3 directories with 2 updates (#15808)
  lifecycle/aws: bump cross-env from 7.0.3 to 10.0.0 in /lifecycle/aws (#15807)
  web: bump ts-pattern from 5.7.1 to 5.8.0 in /web (#15810)
  web: bump @sentry/browser from 9.41.0 to 9.42.0 in /web in the sentry group across 1 directory (#15811)
  web: Add support for placeholder in <ak-text-input> (#15795)
  providers/rac: fix incorrect caching (#15779)
  root: support for custom postgresql connection options (#15577)
  website: bump the build group in /website with 3 updates (#15784)
  web: bump @sentry/browser from 9.40.0 to 9.41.0 in /web in the sentry group across 1 directory (#15785)
  core: bump astral-sh/uv from 0.8.2 to 0.8.3 (#15786)
  lifecycle/aws: bump aws-cdk from 2.1021.0 to 2.1022.0 in /lifecycle/aws (#15787)
  core: bump twilio from 9.6.5 to 9.7.0 (#15788)
  revert: web: Font fixes (#15581) (#15789)
  ...
2025-07-28 08:06:15 -07:00
Ken Sternberg
aa73014f6a Merge branch 'main' into dev
* main: (131 commits)
  website/docs: add notification rule expression policy examples (#15333)
  website/docs: add force password reset guide (#15654)
  website: bump prettier-plugin-packagejson from 2.5.18 to 2.5.19 in /website (#15672)
  website: Flesh out Makefile commands, usage. (#15576)
  website/integrations: fix duplicate guacamole section (#15684)
  core: bump goauthentik.io/api/v3 from 3.2025063.5 to 3.2025063.6 (#15671)
  web: bump typedoc-plugin-markdown from 4.7.0 to 4.7.1 in /packages/esbuild-plugin-live-reload (#15681)
  web: bump the esbuild group across 2 directories with 4 updates (#15674)
  web: bump @types/node from 24.0.14 to 24.0.15 in /packages/prettier-config (#15676)
  website: bump @types/node from 24.0.14 to 24.0.15 in /website (#15675)
  web: bump @types/node from 24.0.14 to 24.0.15 in /packages/esbuild-plugin-live-reload (#15677)
  web: bump prettier-plugin-packagejson from 2.5.18 to 2.5.19 in /packages/prettier-config (#15678)
  web: bump chart.js and @types/chart.js in /web (#15679)
  web: bump the swc group across 1 directory with 11 updates (#15680)
  web: bump prettier-plugin-packagejson from 2.5.18 to 2.5.19 in /packages/esbuild-plugin-live-reload (#15682)
  web: bump @types/node from 22.15.19 to 24.0.15 in /web (#15683)
  website/dev docs: FDE e2e: fix useless markdown lini (#15658)
  providers/radius: set message authenticator (#15635)
  web: bump @eslint/plugin-kit from 0.3.1 to 0.3.3 in /packages/eslint-config (#15661)
  website/docs: add e2e testing steps (#15656)
  ...
2025-07-21 09:29:57 -07:00
Ken Sternberg
a95d3abe83 Merge branch 'main' into dev
* main: (280 commits)
  providers/proxy: fix ingress-nginx proxy buffer size annotations (#15506)
  website/docs: troubleshooting: Fix variable for postgres database in k8s (#15503)
  web: bump @sentry/browser from 9.35.0 to 9.36.0 in /web in the sentry group across 1 directory (#15492)
  core: bump golang.org/x/sync from 0.15.0 to 0.16.0 (#15493)
  core: bump maxmind/geoipupdate from v7.1.0 to v7.1.1 (#15495)
  core: bump astral-sh/uv from 0.7.19 to 0.7.20 (#15496)
  Docusaurus 3.8 prep integrations (#15483)
  web: Fix dangling div. (#15478)
  core: bump google-api-python-client from 2.175.0 to 2.176.0 (#15471)
  core, web: update translations (#15468)
  website: bump the build group in /website with 3 updates (#15469)
  website: bump @types/node from 24.0.10 to 24.0.12 in /website (#15470)
  core: bump msgraph-sdk from 1.36.0 to 1.37.0 (#15472)
  web/flows: more padding fixes (#15467)
  events: fix ak_client_ip not set in notification rule policy context (#15464)
  website/docs: edits to latest Events docs (#15457)
  website: bump the eslint group in /website with 3 updates (#15452)
  website/docs: fix small typos (#15403)
  root: monitoring: force db connection reload before healthcheck (#9970)
  core: bump microsoft-kiota-serialization-form from 1.9.3 to v1.9.4 (#15441)
  ...
2025-07-10 10:59:25 -07:00
Ken Sternberg
6948146faa Merge branch 'main' into dev
* main: (39 commits)
  website/docs: Add steps to troubleshoot /initial-setup/ (#15011)
  core, web: update translations (#15084)
  website: bump the eslint group in /website with 3 updates (#15085)
  website: bump @types/node from 24.0.1 to 24.0.3 in /website (#15086)
  website: bump postcss from 8.5.5 to 8.5.6 in /website (#15087)
  core: bump webauthn from 2.5.2 to 2.6.0 (#15089)
  core: bump goauthentik.io/api/v3 from 3.2025061.2 to 3.2025062.1 (#15090)
  web: bump the eslint group across 2 directories with 3 updates (#15091)
  translate: Updates for file web/xliff/en.xlf in zh_CN (#15074)
  translate: Updates for file web/xliff/en.xlf in zh-Hans (#15075)
  ci: fix post-release e2e builds failing (#15082)
  web: bump API Client version (#15079)
  web/common: fix uiConfig not merged correctly (#15080)
  root: backport version bump `2025.6.2` (#15078)
  website/integrations: add note to nextcloud OIDC config (#15073)
  web/admin: remove all special cases of slug handling, replace with a "smart slug" component (#14983)
  Web/cleanup/empty state better slot handling (#14289)
  website/docs: release notes for `2025.6.2` (#15065)
  website/docs: remove commented out config options (#15064)
  website/docs: postgres troubleshooting: get PGPASSWORD from POSTGRES_PASSWORD_FILE (#15039)
  ...
2025-06-17 08:00:48 -07:00
Ken Sternberg
0ea5f10e5a Merge branch 'main' into dev
* main:
  website/docs: also hide the postgres pool_options setting (#15023)
  blueprints: sort schema items (#15022)
  website: bump the build group in /website with 6 updates (#15027)
  core: bump astral-sh/uv from 0.7.12 to 0.7.13 (#15028)
  core: bump twilio from 9.6.2 to 9.6.3 (#15029)
  core: bump sentry-sdk from 2.29.1 to 2.30.0 (#15030)
  core: bump kubernetes from 32.0.1 to 33.1.0 (#15031)
  core, web: update translations (#15026)
  web: bump the sentry group across 1 directory with 2 updates (#15025)
  translate: Updates for file web/xliff/en.xlf in zh_CN (#15018)
  lifecycle/aws: bump aws-cdk from 2.1018.0 to 2.1018.1 in /lifecycle/aws (#15016)
  website: bump postcss from 8.5.4 to 8.5.5 in /website (#15013)
  website: bump @types/node from 24.0.0 to 24.0.1 in /website (#15014)
  core: fix transaction test case (#15021)
  translate: Updates for file web/xliff/en.xlf in zh-Hans (#15019)
  website/docs: correct minor version in release notes (#15012)
  root: test label handling and error reporting in PytestTestRunner (#14000)
  outposts/ldap: Handle comma-separated attributes in LDAP search requests (#15000)
  website/integrations: standardize application slug placeholder in docs (#15007)
  core: bump django from 5.1.10 to 5.1.11 (#14997)
2025-06-13 08:33:02 -07:00
Ken Sternberg
d78e459dcb Merge branch 'main' into dev
* main: (30 commits)
  web/elements: Add light mode custom css handling (#14944)
  website/docs: add host header dynamic property mapping (#15006)
  core, web: update translations (#14999)
  website/docs: fixes misplaced sentence (#14998)
  website/docs: note usage of `is_restored` by source stage (#13422)
  website: bump the build group in /website with 6 updates (#15001)
  web: bump @sentry/browser from 9.27.0 to 9.28.0 in /web in the sentry group across 1 directory (#15002)
  core: bump msgraph-sdk from 1.32.0 to 1.33.0 (#15003)
  core: bump google-api-python-client from 2.171.0 to 2.172.0 (#15004)
  web/admin: fix language in certificate import  (#14953)
  website/integrations: add new categories and update sidebar info (#14995)
  brands: fix custom_css being escaped (#14994)
  web/admin: show selected policy engine mode on bindings pages, allow setting it on sources (#12963)
  website/integrations: add bitwarden (#14922)
  core: bump goauthentik.io/api/v3 from 3.2025061.1 to 3.2025061.2 (#14986)
  website: bump @types/node from 22.15.30 to 24.0.0 in /website (#14988)
  website: bump the eslint group in /website with 3 updates (#14987)
  web: bump the eslint group across 2 directories with 3 updates (#14991)
  website/integrations: fix typos, update language and styling (#14978)
  website/integrations: add 1password (#14815)
  ...
2025-06-11 08:14:02 -07:00
Ken Sternberg
f4393b7b3c Merge branch 'main' into dev
* main: (38 commits)
  website/docs: added a link in our Upgrade docs to the Outpost upgrade docs, slight reformatting  (#14931)
  website: fix search across multiple subdomains (#14976)
  core: bump goauthentik.io/api/v3 from 3.2025060.1 to 3.2025061.1 (#14972)
  web: bump API Client version (#14971)
  root: backport 2025.6.1 bump (#14970)
  stages/email: Only attach logo to email if used (#14835)
  web: bump @codemirror/lang-python from 6.1.6 to 6.2.1 in /web (#14713)
  website: bump prettier-plugin-packagejson from 2.5.14 to 2.5.15 in /website (#14829)
  core: bump selenium/standalone-chrome from 136.0 to 137.0 in /tests/e2e (#14963)
  core: bump axllent/mailpit from v1.25.1 to v1.26.0 in /tests/e2e (#14964)
  core: bump astral-sh/uv from 0.7.11 to 0.7.12 (#14965)
  core: bump github.com/redis/go-redis/v9 from 9.9.0 to 9.10.0 (#14966)
  web: bump @types/mocha from 10.0.8 to 10.0.10 in /web (#14684)
  web: bump ts-pattern from 5.4.0 to 5.7.1 in /web (#14686)
  website: bump @types/node from 22.15.29 to 22.15.30 in /website (#14968)
  web: bump mermaid from 11.4.1 to 11.6.0 in /web (#14688)
  web: bump @fortawesome/fontawesome-free from 6.6.0 to 6.7.2 in /web (#14716)
  web: bump the eslint group across 2 directories with 3 updates (#14833)
  website: bump the eslint group in /website with 4 updates (#14967)
  website: bump @typescript-eslint/parser from 8.32.1 to 8.33.1 in /website (#14828)
  ...
2025-06-09 08:51:20 -07:00
Ken Sternberg
0457b59792 Merge branch 'main' into dev
* main: (54 commits)
  web/user: fix user settings flow not loading (#14911)
  website/docs: fix outdated and incorrect example kubernetes deployment (#14928)
  docusaurus-config: Update deps, colors. (#14796)
  admin: only run update checks in the default tenant (#14874)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in fr (#14923)
  core: bump astral-sh/uv from 0.7.10 to 0.7.11 (#14918)
  providers/proxy: set_oauth_defaults in reconcile instead of task (#14875)
  *: use ManagedAppConfig everywhere (#14839)
  tenants: fix tenant aware celery scheduler (#14921)
  core, web: update translations (#14910)
  core: bump goauthentik.io/api/v3 from 3.2025041.4 to 3.2025060.1 (#14919)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in zh_CN (#14915)
  translate: Updates for file web/xliff/en.xlf in zh_CN (#14916)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in zh-Hans (#14914)
  website/integrations: improve komodo config verification (#14849)
  website/integrations: fix komodo provider url (#14912)
  website/docs: fix note at end of rac credentials prompt (#14909)
  website/docs: add credentials prompt for rac doc (#14840)
  core: bump redis from 6.0.0 to v6.2.0 (#14895)
  core: bump protobuf from 6.30.2 to v6.31.1 (#14894)
  ...
2025-06-05 15:20:24 -07:00
Ken Sternberg
79d5bc02e9 Merge branch 'main' into dev
* main: (97 commits)
  website/docs: update style guide (#14373)
  website/docs: finalize release notes for `2025.6` (#14854)
  providers/rac: apply ConnectionToken scoped-settings last (#14838)
  lib/sync: fix static incorrect label of pages (#14851)
  website/docs: Add FIDO2 references to the documentation (#14826)
  website/docs: add LDAP docs for forward deletion and `memberUid` (#14814)
  stages/authenticator_webauthn: Update FIDO MDS3 & Passkey aaguid blobs (#14801)
  core: bump structlog from 25.3.0 to 25.4.0 (#14834)
  web: bump tar-fs from 3.0.8 to 3.0.9 in /web (#14836)
  website/integrations: Update Zammad SAML Instructions (#14774)
  website/integrations: remove trailing slash from budibase redirect (#14823)
  remove fluff from release notes 2025.6 (#14819)
  web: bump @sentry/browser from 9.22.0 to 9.23.0 in /web in the sentry group across 1 directory (#14776)
  website: bump postcss from 8.5.3 to 8.5.4 in /website (#14787)
  web: bump the esbuild group across 2 directories with 4 updates (#14711)
  core: bump github.com/redis/go-redis/v9 from 9.8.0 to 9.9.0 (#14733)
  core: bump twilio from 9.6.1 to 9.6.2 (#14789)
  website: bump @types/node from 22.15.21 to 22.15.29 in /website (#14808)
  core: bump astral-sh/uv from 0.7.8 to 0.7.9 (#14806)
  core: bump uvicorn[standard] from 0.34.2 to 0.34.3 (#14811)
  ...
2025-06-03 13:46:57 -07:00
Ken Sternberg
f0e742c3ae Merge branch 'main' into dev
* main: (27 commits)
  lib/sync/outgoing: sync in parallel (#14697)
  core, web: update translations (#14707)
  tests/e2e: fix flaky SAML Source test (#14708)
  web: fix lock (#14705)
  Update packages-npm-publish.yml (#14702)
  website/integrations: coder: fix typo (#14514)
  ci: Update packages-npm-publish.yml (#14701)
  web: bump the swc group across 2 directories with 12 updates (#14623)
  web: Use engine available on Github Actions. (#14699)
  web: bump the rollup group across 1 directory with 4 updates (#14682)
  ci: test with postgres 17 (#13967)
  web: bump knip from 5.33.0 to 5.58.0 in /web (#14685)
  web: bump fuse.js from 7.0.0 to 7.1.0 in /web (#14687)
  web: bump @formatjs/intl-listformat from 7.5.7 to 7.7.11 in /web (#14689)
  root: do not use /bin/bash directly (#14698)
  website/integrations: minio: notice about sso deprecation on CE (#14679)
  core: bump cryptography from 44.0.3 to 45.0.3 (#14690)
  core: bump django-tenants from 3.7.0 to 3.8.0 (#14691)
  core: bump astral-sh/uv from 0.7.7 to 0.7.8 (#14681)
  core: bump axllent/mailpit from v1.25.0 to v1.25.1 in /tests/e2e (#14693)
  ...
2025-05-27 08:33:10 -07:00
Ken Sternberg
8a389b4b46 Merge branch 'main' into dev
* main: (24 commits)
  web: Type Tidy (#14647)
  core: bump pydantic from 2.11.4 to 2.11.5 (#14652)
  core: bump google-api-python-client from 2.169.0 to 2.170.0 (#14653)
  sources/scim: fix all users being added to group when no members are given (#14645)
  web: bump @codemirror/lang-javascript from 6.2.2 to 6.2.4 in /web (#14657)
  web: bump @types/node from 22.15.19 to 22.15.21 in /web (#14660)
  core: bump astral-sh/uv from 0.7.6 to 0.7.7 (#14651)
  web: bump wireit from 0.14.9 to 0.14.12 in /web (#14656)
  web: bump country-flag-icons from 1.5.13 to 1.5.19 in /web (#14659)
  web: bump @trivago/prettier-plugin-sort-imports from 4.3.0 to 5.2.2 in /web (#14661)
  web: bump chart.js from 4.4.4 to 4.4.9 in /web (#14655)
  website: bump the goauthentik group in /website with 3 updates (#14654)
  web: bump dompurify from 3.2.4 to 3.2.6 in /web (#14658)
  web: fix lint (#14665)
  website/docs: improve-rac-documents (#14414)
  web: bump the rollup group across 2 directories with 3 updates (#14622)
  web: bump the sentry group across 1 directory with 2 updates (#14587)
  lifecycle/aws: bump aws-cdk from 2.1016.0 to 2.1016.1 in /lifecycle/aws (#14631)
  web: bump @patternfly/elements from 4.0.2 to 4.1.0 in /web (#14634)
  web: bump @lit/task from 1.0.1 to 1.0.2 in /web (#14635)
  ...
2025-05-23 14:46:54 -07:00
Ken Sternberg
e3402682e6 Merge branch 'main' into dev
* main: (55 commits)
  web: Fix missing Enterprise sidebar entries. (#14615)
  core, web: update translations (#14626)
  esbuild-plugin-live-reload: Publish. (#14624)
  web/NPM Workspaces: Prep ESBuild plugin for publish. (#14552)
  lifecycle: fix arguments not being passed to worker command (#14574)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in fr (#14611)
  providers/proxy: kubernetes outpost: fix reconcile when ingress class name changed (#14612)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in zh-Hans (#14608)
  translate: Updates for file web/xliff/en.xlf in zh_CN (#14607)
  translate: Updates for file web/xliff/en.xlf in zh-Hans (#14609)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in zh_CN (#14606)
  root: move forked dependencies to goauthentik org (#14590)
  core: bump library/node from 22 to 24 (#14410)
  core: bump django-guardian from 2.4.0 to v3.0.0 (#14453)
  enterprise/stages/mtls: improve certificate validation (#14582)
  translate: Updates for file web/xliff/en.xlf in it (#14575)
  core, web: update translations (#14578)
  core: bump sentry-sdk from 2.28.0 to 2.29.1 (#14579)
  core: bump astral-sh/uv from 0.7.5 to 0.7.6 (#14580)
  web/NPM Workspaces: ESbuild version cleanup (#14541)
  ...
2025-05-22 08:15:00 -07:00
Ken Sternberg
38c4701e63 Merge branch 'main' into dev
* main: (60 commits)
  website: bump the build group in /website with 6 updates (#14502)
  core: remove `OldAuthenticatedSession` content type (#14507)
  core: bump msgraph-sdk from 1.29.0 to 1.30.0 (#14503)
  core: bump twilio from 9.6.0 to 9.6.1 (#14505)
  core: bump psycopg[c,pool] from 3.2.8 to 3.2.9 (#14504)
  enterprise: fix expired license's users being counted (#14451)
  website/integrations: fix missing closing brace for semaphore (#14467)
  tests/e2e: Add E2E tests for Flow SFE (#14484)
  website: bump semver from 7.7.1 to 7.7.2 in /website (#14491)
  core: bump django from 5.1.8 to 5.1.9 (#14483)
  core: bump psycopg[c,pool] from 3.2.7 to 3.2.8 (#14481)
  core: bump sentry-sdk from 2.27.0 to 2.28.0 (#14482)
  root: pin package version in pyproject for dependabot (#14469)
  core: fix session migration when old session can't be loaded (#14466)
  root: temporarily deactivate database pool option (#14443)
  website: bump the build group in /website with 3 updates (#14475)
  website/docs: stages: fix-typo (#14477)
  website/docs: Update Kubernetes Bootstrap Instructions (#14471)
  root: improve sentry distributed tracing (#14468)
  Revert "web/admin: fix enterprise menu display" (#14458)
  ...
2025-05-14 08:17:09 -07:00
Ken Sternberg
cddc13fcf7 Merge branch 'main' into dev
* main: (45 commits)
  web, website: update browserslist (#14386)
  core, web: update translations (#14383)
  website/integrations: add atlassian (#14209)
  core: bump github.com/pires/go-proxyproto from 0.8.0 to 0.8.1 (#14388)
  ci: bump golangci/golangci-lint-action from 7 to 8 (#14389)
  core: bump axllent/mailpit from v1.24.1 to v1.24.2 in /tests/e2e (#14390)
  translate: Updates for file web/xliff/en.xlf in it (#14372)
  translate: Updates for file web/xliff/en.xlf in zh_CN (#14374)
  translate: Updates for file web/xliff/en.xlf in zh-Hans (#14375)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in pt (#14379)
  website/integrations: Fix outpost link for Home Assistant configuration (#14382)
  website/docs: fix leftover placeholder in release notes (#14377)
  website/integrations: minio: fix typo (#14376)
  core: bump goauthentik/fips-python from 3.12.10-slim-bookworm-fips to 3.13.3-slim-bookworm-fips (#12763)
  core: bump axllent/mailpit from v1.6.5 to v1.24.1 in /tests/e2e (#14341)
  core: bump selenium/standalone-chrome from 122.0 to 135.0 in /tests/e2e (#14342)
  core: bump lxml from 5.3.2 to v5.4.0 (#14355)
  core: bump azure-core from 1.33.0 to v1.34.0 (#14345)
  core: bump boto3 from 1.37.35 to v1.38.7 (#14346)
  core: bump celery from 5.5.1 to v5.5.2 (#14347)
  ...
2025-05-05 08:42:37 -07:00
Ken Sternberg
136e59cfba Merge branch 'main' into dev
* main: (54 commits)
  ci: use dependabot for compose correctly? (#14340)
  website/docs: use Universal Device Trust for GDTC instead of Okta (#14335)
  ci: use dependabot for docker-compose files (#14336)
  website/docs: fix dry-run release highlight (#14337)
  rbac: fix RoleObjectPermissionTable not showing `add_user_to_group` (#14312)
  core, web: update translations (#14326)
  core: bump github.com/sethvargo/go-envconfig from 1.2.0 to 1.3.0 (#14327)
  web: bump vite from 5.4.16 to 5.4.19 in /web (#14324)
  core: bump setuptools from 78.1.0 to v79.0.0 (#14173)
  core: bump ruff from 0.11.5 to v0.11.6 (#14171)
  core: bump s3transfer from 0.11.4 to v0.11.5 (#14172)
  core: bump packaging from 24.2 to v25.0 (#14169)
  core: bump aiohttp from 3.11.16 to v3.11.18 (#14166)
  core: bump boto3 from 1.37.35 to v1.37.38 (#14167)
  core: bump frozenlist from 1.5.0 to v1.6.0 (#14168)
  core: bump pdoc from 15.0.1 to v15.0.3 (#14170)
  core: bump trio from 0.29.0 to v0.30.0 (#14174)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in it (#14271)
  website: bump the build group across 1 directory with 9 updates (#14293)
  core, web: update translations (#14309)
  ...
2025-05-02 10:42:07 -07:00
Ken Sternberg
5da349342f Merge branch 'main' into dev
* main: (33 commits)
  website/integrations: adds missing trailing slash in homarr doc (#14249)
  lifecycle: fix test-all in docker (#14244)
  core, web: update translations (#14243)
  web/admin: prevent default logo flashing in admin interface (#13960)
  website/docs: Update release notes for 2025.4 (#14158)
  core, web: update translations (#14241)
  Updates for file web/xliff/en.xlf in zh_TW [Manual Sync] (#14225)
  translate: Updates for file web/xliff/en.xlf in nl [Manual Sync] (#14217)
  translate: Updates for file web/xliff/en.xlf in fi [Manual Sync] (#14219)
  translate: Updates for file web/xliff/en.xlf in de [Manual Sync] (#14220)
  translate: Updates for file web/xliff/en.xlf in fr [Manual Sync] (#14221)
  translate: Updates for file web/xliff/en.xlf in pl [Manual Sync] (#14222)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in es [Manual Sync] (#14223)
  translate: Updates for file web/xliff/en.xlf in zh-Hans [Manual Sync] (#14224)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in de [Manual Sync] (#14226)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in fi [Manual Sync] (#14227)
  translate: Updates for file web/xliff/en.xlf in tr [Manual Sync] (#14228)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in pl [Manual Sync] (#14229)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in ko [Manual Sync] (#14230)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in it [Manual Sync] (#14231)
  ...
2025-04-25 10:46:52 -07:00
Ken Sternberg
518e9e1554 Merge branch 'main' into dev
* main:
  web: Safari fixes merge branch (#14181)
  website: bump the build group in /website with 9 updates (#14204)
  website: bump typescript from 5.8.2 to 5.8.3 in /website (#13786)
  lifecycle/migrate: fix migration failing if killed during first startup (#14207)
  core, web: update translations (#14203)
  lifecycle/aws: bump aws-cdk from 2.1010.0 to 2.1012.0 in /lifecycle/aws (#14205)
  website/integrations: improves netbird documentation (#14191)
  website/docs: updated user count info (#14186)
  website/docs: rearranged brands docs (#14116)
  website: integrations: apache guacamole: remove redirect URI comments (#14113)
2025-04-24 10:01:18 -07:00
Ken Sternberg
baa64bc1b0 Merge branch 'main' into dev
* main:
  packages/docusaurus-theme: Fix header alignment, overscroll, vertical padding. (#14120)
  outposts: add support for gateway API (#13272)
  translate: Updates for file web/xliff/en.xlf in fr (#14200)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in fr (#14199)
  website/docs: adds code examples for getting user objects from a group object (#14101)
  translate: Updates for file web/xliff/en.xlf in zh-Hans (#14198)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in zh_CN (#14195)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in zh-Hans (#14197)
  translate: Updates for file web/xliff/en.xlf in zh_CN (#14196)
  website/integrations: mealie add integration (#14188)
  core, web: update translations (#14187)
  core: bump goauthentik.io/api/v3 from 3.2025024.8 to 3.2025024.9 (#14189)
  website/docs: update user object doc (#14132)
  website/docs: dev-docs: style guide: no longer using italic for vars (#14185)
  website/docs: dev docs: style guide: update style conventions for urls (#14184)
  website/integrations: paperless: use <slug>. instead of hardcoded slug value (#14183)
  website/docs: updates style guide code block section (#14088)
  website: components: delete multilinecodeblock src (#14094)
  Revert "policies: buffered policy access view for concurrent authorization attempts when unauthenticated (#13629)" (#14180)
2025-04-23 10:59:38 -07:00
Ken Sternberg
8b958408c2 Merge branch 'main' into dev
* main: (49 commits)
  core: bump uvicorn from 0.34.1 to v0.34.2 (#14175)
  website/integrations: add xcreds (#14163)
  core, web: update translations (#14179)
  web: update default flow background (#14115)
  web: bump API Client version (#14176)
  enterprise/policies: Add Password Uniqueness History Policy (#13453)
  web/xliff: fix duplicated translations (#14164)
  website/docs: fix postgres pool recommended settings (#14149)
  core: bump astral-sh/uv from 0.6.14 to 0.6.16 (#14161)
  web: fix scrollbar styling (#12600)
  website: integrations: gravity: fix issuer URL (#14155)
  web: Packagify live reload plugin. (#14134)
  web: bump API Client version (#14062)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in zh-Hans (#14146)
  translate: Updates for file web/xliff/en.xlf in zh_CN (#14145)
  core: bump goauthentik.io/api/v3 from 3.2025024.7 to 3.2025024.8 (#14143)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in zh_CN (#14144)
  translate: Updates for file web/xliff/en.xlf in zh-Hans (#14139)
  core, web: update translations (#14142)
  core: bump yarl from 1.19.0 to v1.20.0 (#14128)
  ...
2025-04-22 09:25:10 -07:00
Ken Sternberg
9a6ca4fba2 Merge branch 'main' into dev
* main: (93 commits)
  core: bump google-auth from 2.38.0 to v2.39.0 (#14076)
  core: bump sentry-sdk from 2.25.1 to v2.26.1 (#14079)
  core: bump prompt-toolkit from 3.0.50 to v3.0.51 (#14078)
  core: bump boto3 from 1.37.33 to v1.37.34 (#14074)
  core: bump msgraph-sdk from 1.27.0 to v1.28.0 (#14077)
  website/docs: fix minor typo in working_with_policies.md (#14071)
  core, web: update translations (#14064)
  stages/authenticator_webauthn: Update FIDO MDS3 & Passkey aaguid blobs (#14065)
  core: bump goauthentik.io/api/v3 from 3.2025024.4 to 3.2025024.6 (#14069)
  Small fix for Actual-Budget wiki guide (#14066)
  root: support db pool (#13534)
  rbac: add `InitialPermissions` (#13795)
  web: bump API Client version (#14058)
  core: Bump django from 5.0.14 to 5.1.8 (#14059)
  core: bump django-rest-framework from 3.14.0 to 3.16.0 (#14057)
  policies/reputation: limit reputation score (#14008)
  ci: fix api-py-publish by disabling poetry cache (#14010)
  core: bump goauthentik/fips-python from 3.12.9-slim-bookworm-fips to 3.12.10-slim-bookworm-fips (#14044)
  ci: add NPM packages publish (#13974)
  root: add packages/ to codeowners (#13975)
  ...
2025-04-15 08:23:44 -07:00
Ken Sternberg
9cf551b1d6 Merge branch 'main' into dev
* main: (204 commits)
  core: bump protobuf from 5.29.4 to v6.30.2 (#13950)
  core: bump pyasn1-modules from 0.4.1 to v0.4.2 (#13951)
  core: bump microsoft-kiota-authentication-azure from 1.9.2 to v1.9.3 (#13948)
  core: bump microsoft-kiota-http from 1.9.2 to v1.9.3 (#13949)
  core: bump trio-websocket from 0.11.1 to v0.12.2 (#13934)
  core: bump msgraph-core from 1.3.1 to v1.3.3 (#13900)
  core: bump jsii from 1.109.0 to v1.111.0 (#13886)
  core: bump setuptools from 72.1.0 to v78.1.0 (#13928)
  core: bump kombu from 5.3.7 to v5.5.2 (#13888)
  core: bump msgpack from 1.0.8 to v1.1.0 (#13899)
  core: bump msgraph-sdk from 1.24.0 to v1.26.0 (#13901)
  core: bump proto-plus from 1.24.0 to v1.26.1 (#13910)
  core: bump protobuf from 5.27.2 to v5.29.4 (#13911)
  core: bump pydantic from 2.10.6 to v2.11.3 (#13914)
  core: bump rich from 13.7.1 to v14.0.0 (#13922)
  core: bump twisted from 24.7.0 to v24.11.0 (#13936)
  core: bump watchfiles from 0.22.0 to v1.0.5 (#13941)
  core: bump typing-extensions from 4.12.2 to v4.13.1 (#13937)
  core: bump multidict from 6.0.5 to v6.2.0 (#13902)
  core: bump sentry-sdk from 2.22.0 to v2.25.1 (#13927)
  ...
2025-04-09 11:13:44 -07:00
Ken Sternberg
f63b3c2bbb Merge branch 'main' into dev
* main:
  ci: stop publishing latest tag (#13245)
  root: fix dependency install due to description-file (#13655)
2025-03-24 13:30:39 -07:00
Ken Sternberg
61dbc932da Merge branch 'main' into dev
* main: (76 commits)
  admin: fix system API when using bearer token (#13651)
  website: bump the build group in /website with 6 updates (#13645)
  core: bump goauthentik.io/api/v3 from 3.2025022.5 to 3.2025022.6 (#13646)
  translate: Updates for file web/xliff/en.xlf in fr (#13653)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in fr (#13652)
  website/integrations: add tandoor (#13560)
  core, web: update translations (#13642)
  providers/scim: fix group membership check failing (#13644)
  ci: add semgrep (#13643)
  flows: fix API not returning configured background (#13641)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in zh_CN (#13631)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in zh-Hans (#13633)
  translate: Updates for file web/xliff/en.xlf in zh_CN (#13632)
  translate: Updates for file web/xliff/en.xlf in zh-Hans (#13634)
  brands: fix migration 0008 by removing incorrect context manager usage (#13635)
  web: Fix prop. (#13630)
  core, web: update translations (#13628)
  web/admin: reworked sync status card (#13625)
  core: bump github.com/golang-jwt/jwt/v5 from 5.2.1 to 5.2.2 (#13626)
  translate: Updates for file web/xliff/en.xlf in zh-Hans (#13622)
  ...
2025-03-24 09:15:11 -07:00
Ken Sternberg
fa994ae318 Merge branch 'main' into dev
* main:
  website/docs: dev docs: bump node/postgres requirements  (#13516)
  sources: prevent deletion of built-in source (#12914)
  core: bump django-tenants (#13536)
  website/docs: dev docs: full: remove note on installing shell plugin (#13515)
  sources/oauth: fix duplicate authentication (#13322)
  web/admin: fix comment being rendered (#13530)
  core: Bump aws-cdk-lib from 2.183.0 to 2.184.0 (#13522)
  core, web: update translations (#13520)
  lifecycle/aws: Bump aws-cdk from 2.1003.0 to 2.1004.0 in /lifecycle/aws (#13524)
  core: Bump github.com/coreos/go-oidc/v3 from 3.12.0 to 3.13.0 (#13525)
2025-03-17 07:21:18 -07:00
Ken Sternberg
cd78fc01f2 Merge branch 'main' into dev
* main: (44 commits)
  providers/rac: fix signals and Endpoint caching (#13529)
  web/flows: fix missing padding on authenticator_validate card (#13420)
  web/user: show admin interface button on mobile (#13421)
  website: Bump the build group in /website with 6 updates (#13501)
  core: Bump goauthentik.io/api/v3 from 3.2025021.3 to 3.2025021.4 (#13495)
  core: Bump importlib-metadata from 8.5.0 to 8.6.1 (#13499)
  core: Bump msgraph-sdk from 1.23.0 to 1.24.0 (#13500)
  core: Bump google-api-python-client from 2.163.0 to 2.164.0 (#13498)
  core: Bump aws-cdk-lib from 2.182.0 to 2.183.0 (#13496)
  core: Bump psycopg from 3.2.5 to 3.2.6 (#13497)
  translate: Updates for file web/xliff/en.xlf in fr (#13514)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in fr (#13513)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in zh-Hans (#13510)
  translate: Updates for file web/xliff/en.xlf in zh-Hans (#13511)
  translate: Updates for file web/xliff/en.xlf in zh_CN (#13509)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in zh_CN (#13508)
  core, web: update translations (#13494)
  website: remove the last updated option from footer (#13493)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in fr (#13487)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in zh_CN (#13488)
  ...
2025-03-14 07:11:45 -07:00
Ken Sternberg
9251e608fa Merge branch 'main' into dev
* main:
  stages/email: token_expiry format (#13394)
  core, web: update translations (#13438)
  website/docs: in developer docs replace deprecated poetry shell command (#13460)
2025-03-11 14:58:45 -07:00
Ken Sternberg
0055882212 Merge branch 'main' into dev
* main:
  web: Ignore Storybook when running codespell. (#13454)
  core: bump ruff from 0.9.9 to 0.9.10 (#13448)
  core: bump webauthn from 2.5.1 to 2.5.2 (#13449)
  website/docs: backup and restore: remove extra period (#13440)
  website: bump prismjs from 1.29.0 to 1.30.0 in /website (#13456)
  web: bump prismjs from 1.29.0 to 1.30.0 in /web (#13455)
2025-03-11 07:01:31 -07:00
Ken Sternberg
68387362c2 Merge branch 'main' into dev
* main:
  web: admin interface: faster card load (#13331)
  web/admin: fix display bug for assigned users in application bindings in the wizard (#13435)
  website: bump the build group across 1 directory with 9 updates (#13442)
  core: bump django from 5.0.12 to 5.0.13 (#13425)
  providers/SCIM: fix object exists error for users, attempt to look up user ID in remote system (#13437)
  website/docs: sys mgmt: document authentik backups/restoration (#12943)
  website: fix build in docker (#13430)
  website/integrations: zipline: add (#13257)
2025-03-11 07:01:23 -07:00
Ken Sternberg
5fe344fb12 Merge branch 'main' into dev
* main:
  translate: Updates for file web/xliff/en.xlf in fr (#13431)
  lifecycle/aws: bump aws-cdk from 2.1002.0 to 2.1003.0 in /lifecycle/aws (#13426)
  translate: Updates for file web/xliff/en.xlf in zh_CN (#13428)
  translate: Updates for file web/xliff/en.xlf in zh-Hans (#13429)
  core, web: update translations (#13423)
2025-03-07 08:14:04 -08:00
Ken Sternberg
aa20df5cca Merge branch 'main' into dev
* main:
  website: add a better edit this page element (#13391)
  web/admin: allow user lists to show active only (#13403)
  core: Tidy contributor onboarding, fix typos. (#12700)
  translate: Updates for file web/xliff/en.xlf in zh-Hans (#13418)
  translate: Updates for file web/xliff/en.xlf in zh_CN (#13417)
  lib/config: fix conn_max_age parsing (#13370)
  core: bump golang.org/x/sync from 0.11.0 to 0.12.0 (#13407)
  stages/authenticator_email: Fix Enroll dropdown in the MFA Devices page (#13404)
  core: bump golang.org/x/oauth2 from 0.27.0 to 0.28.0 (#13408)
  core: bump aws-cdk-lib from 2.181.1 to 2.182.0 (#13409)
  core: bump google-api-python-client from 2.162.0 to 2.163.0 (#13410)
  core: bump msgraph-sdk from 1.22.0 to 1.23.0 (#13411)
  core: bump jinja2 from 3.1.5 to 3.1.6 (#13412)
  web/user: ensure modal container on user-settings page is min-height: 100% (#13402)
  core, web: update translations (#13405)
  web/admin: add button to clear application cache (#13399)
  blueprints: Adjust title for MFA set up (#13400)
2025-03-06 14:56:10 -08:00
Ken Sternberg
75c61afd18 Merge branch 'main' into dev
* main:
  web/admin: fix markdown being completely whited out in dark mode on proxy provider pages (#13387)
  web/admin: decorative display in user’s page breaks in other locales (#13393)
  website: bump the build group in /website with 6 updates (#13396)
  core: bump github.com/prometheus/client_golang from 1.21.0 to 1.21.1 (#13397)
  core: bump debugpy from 1.8.12 to 1.8.13 (#13395)
  website/docs: Update Open Web UI integration  (#13392)
  website/integrations: gravity: add (#13258)
  website/integrations: Pocketbase (#12906)
  ci: cache helper docker images (#13390)
2025-03-05 10:18:55 -08:00
Ken Sternberg
811e7946f7 Merge branch 'main' into dev
* main: (135 commits)
  providers/proxy: kubernetes outpost: fix reconcile when only annotations changed (#13372)
  website: bump the build group in /website with 3 updates (#13381)
  core, web: update translations (#13378)
  web/admin: prefer using datefns over moment.js (#13143)
  website/docs: fix typo (#13377)
  stages/authenticator_email: remove flaky assertions (#13371)
  translate: Updates for file web/xliff/en.xlf in fr (#13374)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in fr (#13373)
  website: bump typescript from 5.7.3 to 5.8.2 in /website (#13368)
  lifecycle/aws: bump aws-cdk from 2.1001.0 to 2.1002.0 in /lifecycle/aws (#13365)
  website: bump the build group in /website with 11 updates (#13367)
  ci: bump getsentry/action-release from 1 to 3 (#13366)
  website: bump @rspack/binding-darwin-arm64 from 1.1.6 to 1.2.6 in /website (#13354)
  core, web: update translations (#13346)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in zh-Hans (#13348)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in zh_CN (#13347)
  translate: Updates for file web/xliff/en.xlf in zh-Hans (#13349)
  translate: Updates for file web/xliff/en.xlf in zh_CN (#13350)
  ci: update versions for daily full testing (#13303)
  website: bump prettier from 3.5.2 to 3.5.3 in /website (#13355)
  ...
2025-03-04 08:29:59 -08:00
Ken Sternberg
19f25d5aa7 Merge branch 'main' into dev
* main:
  web/user: fix opening application with Enter not respecting new tab setting (#13115)
  web: bump API Client version (#13113)
  providers/rac: move to open source (#13015)
  website/docs: add 2025.2 release notes (#13002)
  core: clear expired database sessions (#13105)
  core: bump sentry-sdk from 2.21.0 to 2.22.0 (#13098)
  core: bump bandit from 1.8.2 to 1.8.3 (#13097)
  core: bump aws-cdk-lib from 2.178.2 to 2.179.0 (#13099)
  core: bump goauthentik.io/api/v3 from 3.2024123.4 to 3.2024123.6 (#13100)
  lifecycle/aws: bump aws-cdk from 2.178.2 to 2.179.0 in /lifecycle/aws (#13101)
  website/docs: Add AdventureLog Community Integration Documentation (#12928)
  website/docs: minor fixes (#13095)
  website/integrations: Update to Wizard and Styling Guide (#12919)
  web: bump API Client version (#13093)
  policies/geoip: distance + impossible travel (#12541)
  root: fix generated API docs not being excluded from codespell (#13091)
2025-02-19 08:32:55 -08:00
Ken Sternberg
ffbe8fb598 Merge branch 'main' into dev
* main: (24 commits)
  core: add additional RBAC permission to restrict setting the superuser status on groups (#12900)
  web: bump API Client version (#13089)
  core: bump github.com/spf13/cobra from 1.8.1 to 1.9.1 (#13085)
  stages/authenticator_email: Email OTP (#12630)
  website: bump dompurify and mermaid in /website (#13077)
  web: bump dompurify and mermaid in /web (#13078)
  core: bump django-filter from 24.3 to 25.1 (#13086)
  enterprise/audit: fix diff being created when not enabled (#13084)
  core, web: update translations (#13088)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in zh_CN (#13080)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in zh-Hans (#13081)
  translate: Updates for file web/xliff/en.xlf in zh-Hans (#13082)
  translate: Updates for file web/xliff/en.xlf in zh_CN (#13083)
  core: bump django-storages from 1.14.4 to 1.14.5 (#13087)
  web/user: fix redirects back to user settings (#13076)
  ci: parallelize unit tests (#13036)
  core, web: update translations (#13072)
  stages/authenticator_webauthn: Update FIDO MDS3 & Passkey aaguid blobs (#13073)
  root: Improve debugging experience (#12961)
  core, web: update translations (#13071)
  ...
2025-02-17 08:48:25 -08:00
Ken Sternberg
2279768e6f Merge branch 'main' into dev
* main: (35 commits)
  translate: Updates for file web/xliff/en.xlf in ko [Manual Sync] (#13045)
  translate: Updates for file web/xliff/en.xlf in pl [Manual Sync] (#13043)
  translate: Updates for file web/xliff/en.xlf in ru [Manual Sync] (#13055)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in pl [Manual Sync] (#13062)
  translate: Updates for file web/xliff/en.xlf in zh_TW [Manual Sync] (#13056)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in nl [Manual Sync] (#13058)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in ru [Manual Sync] (#13063)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in zh_TW [Manual Sync] (#13064)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in ko [Manual Sync] (#13060)
  translate: Updates for file web/xliff/en.xlf in nl [Manual Sync] (#13044)
  web: Silence ESBuild warning. (#13025)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in zh-Hans [Manual Sync] (#13066)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in tr [Manual Sync] (#13061)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in zh_CN [Manual Sync] (#13065)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in it [Manual Sync] (#13057)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in pt_BR [Manual Sync] (#13059)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in de [Manual Sync] (#13051)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in fi [Manual Sync] (#13052)
  translate: Updates for file web/xliff/en.xlf in zh-Hans [Manual Sync] (#13050)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in es [Manual Sync] (#13054)
  ...
2025-02-14 09:36:35 -08:00
Ken Sternberg
675e60b816 Merge branch 'main' into dev
* main:
  core: revert bump oss/go/microsoft/golang from 1.23-fips-bookworm to 1.24-fips-bookworm (#13012) (#13022)
2025-02-13 10:00:23 -08:00
Ken Sternberg
e338bef104 Merge branch 'main' into dev
* main: (111 commits)
  root: correctly use correct schema for install_id (#13018)
  website: bump docusaurus-plugin-openapi-docs from 4.3.3 to 4.3.4 in /website (#13011)
  web: bump API Client version (#13017)
  core: bump aws-cdk-lib from 2.178.1 to 2.178.2 (#13013)
  core: bump oss/go/microsoft/golang from 1.23-fips-bookworm to 1.24-fips-bookworm (#13012)
  website: bump docusaurus-theme-openapi-docs from 4.3.3 to 4.3.4 in /website (#13010)
  lifecycle/aws: bump aws-cdk from 2.178.1 to 2.178.2 in /lifecycle/aws (#13009)
  core: bump github.com/sethvargo/go-envconfig from 1.1.0 to 1.1.1 (#13008)
  web/admin: fix source selection for identification stage (#13007)
  core: bump sentry-sdk from 2.20.0 to 2.21.0 (#13014)
  website/integrations: Open WebUI (#12939)
  root: use correct default schema for install_id (#13006)
  website/docs: fix a minor typo (#13004)
  enterprise/providers/ssf: fixes v2 (#13003)
  root: make default postgres schema configurable (#12949)
  providers/oauth2: cleanup tokens when user is deactivated (#12859)
  website/docs: fix Nginx redirection example (#12920)
  core: bump twilio from 9.4.4 to 9.4.5 (#12993)
  core: bump coverage from 7.6.11 to 7.6.12 (#12994)
  core: bump cryptography from 44.0.0 to 44.0.1 (#12992)
  ...
2025-02-13 08:06:21 -08:00
Ken Sternberg
457b61c5b4 Merge branch 'main' into dev
* main:
  web: update gen-client-ts to OpenAPI 7.11.0 (#12756)
  website/integrations: rustdesk-server-pro (#12706)
  core: bump codespell from 2.3.0 to 2.4.0 (#12762)
  root: docker: ensure apt packages are up-to-date (#12683)
  ci: fix missing build args for dev and release (#12760)
  web: bump vite from 5.4.11 to 5.4.14 in /web (#12757)
  web: bump undici from 6.21.0 to 6.21.1 in /web (#12755)
  lifecycle: fix cryptography's OpenSSL path (#12753)
2025-01-22 10:09:24 -08:00
Ken Sternberg
25eefb7d55 Merge branch 'main' into dev
* main: (65 commits)
  stages/redirect: fix query parameter when redirecting to flow (#12750)
  website/integrations: cloudflare-access: refactor (#12663)
  sources/kerberos: handle principal expire time (#12748)
  lifecycle: build binary dependencies which link against SSL directly (#12724)
  website/docs: style guide: document styling preferences for URLs (#12715)
  website/integrations: nextcloud: fix broken link (#12744)
  core: bump selenium from 4.27.1 to 4.28.0 (#12745)
  lifecycle: move AWS CFN generation to lifecycle and fix CI (#12743)
  core: search users' attributes (#12740)
  web/components: ak-number-input: add support for min (#12703)
  website/integrations: nextcloud: fix url for "disable username changes" (#12725)
  core: bump pytest-github-actions-annotate-failures from 0.2.0 to 0.3.0 (#12735)
  website: bump katex from 0.16.11 to 0.16.21 in /website (#12731)
  web: bump katex from 0.16.11 to 0.16.21 in /web (#12730)
  website/integrations: Fix URL for authentik installation instead of mobilizon installation (#12729)
  core: bump debugpy from 1.8.11 to 1.8.12 (#12718)
  core: bump ruff from 0.9.1 to 0.9.2 (#12717)
  core: bump webauthn from 2.4.0 to 2.5.0 (#12719)
  core: bump structlog from 24.4.0 to 25.1.0 (#12720)
  website/integrations: all: install -> installation (#12676)
  ...
2025-01-21 09:56:23 -08:00
Ken Sternberg
50d2f69332 Merge branch 'main' into dev
* main:
  website: revise full development environment instructions (#12638)
  website: bump typescript from 5.7.2 to 5.7.3 in /website (#12620)
  website: bump aws-cdk from 2.174.1 to 2.175.0 in /website (#12621)
  ci: bump docker/setup-qemu-action from 3.2.0 to 3.3.0 (#12622)
  core: bump twilio from 9.4.1 to 9.4.2 (#12623)
  core: bump python-kadmin-rs from 0.5.2 to 0.5.3 (#12624)
  core: bump ruff from 0.8.6 to 0.9.0 (#12625)
  core: bump pydantic from 2.10.4 to 2.10.5 (#12626)
  core: bump google-api-python-client from 2.157.0 to 2.158.0 (#12628)
  core: bump goauthentik.io/api/v3 from 3.2024121.3 to 3.2024122.1 (#12629)
  web: bump API Client version (#12617)
  release: 2024.12.2 (#12615)
  website/docs: prepare 2024.12.2 release notes (#12614)
  providers/saml: fix invalid SAML Response when assertion and response are signed (#12611)
  core: fix error when creating new user with default path (#12609)
  rbac: permissions endpoint: allow authenticated users (#12608)
  website/docs: update customer portal (#12603)
  website/docs: policy for email whitelist: modernize (#12558)
2025-01-10 16:26:36 -08:00
Ken Sternberg
7d972ec711 Merge branch 'main' into dev
* main:
  lib: add expression helper ak_create_jwt to create JWTs (#12599)
  api: cleanup owner permissions (#12598)
  website: bump aws-cdk from 2.174.0 to 2.174.1 in /website (#12593)
  core: bump aws-cdk-lib from 2.174.0 to 2.174.1 (#12594)
  website/integrations: portainer: group config steps (#12548)
  translate: Updates for file web/xliff/en.xlf in fi (#12586)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in fi (#12584)
  website/docs: fix Nginx redirection example (#12561)
2025-01-08 10:13:59 -08:00
Ken Sternberg
854427e463 Merge branch 'main' into dev
* main:
  core: bump golang.org/x/oauth2 from 0.24.0 to 0.25.0 (#12571)
  website: bump the docusaurus group in /website with 9 updates (#12569)
  core: bump github.com/coreos/go-oidc/v3 from 3.11.0 to 3.12.0 (#12572)
  core: bump ruff from 0.8.5 to 0.8.6 (#12573)
  ci: release: fix AWS cfn template permissions (#12576)
  translate: Updates for file web/xliff/en.xlf in fr (#12578)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in fr (#12577)
  sources/kerberos: authenticate with the user's username instead of the first username in authentik (#12497)
  website/integrations: Fix deprecated terraform ressource authentik_scope_mapping in docs (#12554)
  website/user-sources Fix Free IPA docs page (#12549)
  core: bump aws-cdk-lib from 2.173.4 to 2.174.0 (#12574)
  website/integrations: semaphore: fix formatting (#12567)
  website: bump aws-cdk from 2.173.4 to 2.174.0 in /website (#12570)
  website/integrations: Update Frappe Application index.md (#12527)
  website: add api reference docs to redirect file (#12551)
2025-01-06 08:32:50 -08:00
Ken Sternberg
be349e2e14 Merge branch 'main' into dev
* main:
  core: bump github.com/getsentry/sentry-go from 0.30.0 to 0.31.1 (#12543)
  core: bump google-api-python-client from 2.156.0 to 2.157.0 (#12544)
  core: bump ruff from 0.8.4 to 0.8.5 (#12545)
  core: bump msgraph-sdk from 1.15.0 to 1.16.0 (#12546)
  Update index.mdx (#12542)
  web: fix source selection and outpost integration health (#12530)
  Ading a step to paperless guide (#12539)
  website/integrations: Semaphore (#12515)
  website/integrations: komga: document (#12476)
  website/integrations: fix missing quote in paperless-ngx (#12537)
  website/integrations: cloudflare access: upd placeholder for saas (#12536)
  website/integrations: veeam-enterprise-manager: don't hardcode helpcenter doc version (#12538)
2025-01-03 08:09:25 -08:00
Ken Sternberg
bd0e81b8ad Merge branch 'main' into dev
* main:
  website/integrations: meshcentral: document (#12509)
  stages/authenticator_webauthn: Update FIDO MDS3 & Passkey aaguid blobs (#12524)
  core: bump goauthentik.io/api/v3 from 3.2024121.2 to 3.2024121.3 (#12522)
  web: bump API Client version (#12520)
  website/integrations: chronograf: document (#12474)
  website/integrations: update preparation placeholder (#12507)
  providers/saml: fix handle Accept: application/xml for SAML Metadata endpoint (#12483) (#12518)
  core: bump aws-cdk-lib from 2.173.3 to 2.173.4 (#12513)
  website: bump aws-cdk from 2.173.3 to 2.173.4 in /website (#12514)
  core: bump coverage from 7.6.9 to 7.6.10 (#12499)
  core: bump aws-cdk-lib from 2.173.2 to 2.173.3 (#12500)
  website: bump aws-cdk from 2.173.2 to 2.173.3 in /website (#12501)
  core: bump github.com/go-ldap/ldap/v3 from 3.4.9 to 3.4.10 (#12502)
  website/docs: New "Whats Up Docker" URL (#12488)
2025-01-02 10:01:26 -08:00
Ken Sternberg
f6afb59515 Revert "This (temporary) change is needed to prevent the unit tests from failing."
This reverts commit dddde09be5.
2024-12-26 13:59:53 -08:00
Ken Sternberg
dddde09be5 This (temporary) change is needed to prevent the unit tests from failing.
\# What

\# Why

\# How

\# Designs

\# Test Steps

\# Other Notes
2024-12-26 13:59:00 -08:00
Ken Sternberg
6d7fc94698 Merge branch 'main' into dev
* main: (118 commits)
  outposts: fix version label (#12486)
  web: only load version context when authenticated (#12482)
  core: bump goauthentik.io/api/v3 from 3.2024120.2 to 3.2024121.2 (#12478)
  ci: bump helm/kind-action from 1.11.0 to 1.12.0 (#12479)
  web: fix build dev build (#12473)
  root: fix dev build version being invalid semver (#12472)
  internal: fix missing trailing slash in outpost websocket (#12470)
  web: bump API Client version (#12469)
  admin: monitor worker version (#12463)
  core: bump jinja2 from 3.1.4 to 3.1.5 (#12467)
  web: bump API Client version (#12468)
  release: 2024.12.1 (#12466)
  web: misc fixes for admin and flow inspector (#12461)
  website/docs: 2024.12.1 release notes (#12462)
  core: bump goauthentik.io/api/v3 from 3.2024120.1 to 3.2024120.2 (#12456)
  core: bump urllib3 from 2.2.3 to 2.3.0 (#12457)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in zh-Hans (#12454)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in zh_CN (#12453)
  translate: Updates for file web/xliff/en.xlf in zh-Hans (#12455)
  translate: Updates for file web/xliff/en.xlf in zh_CN (#12458)
  ...
2024-12-26 08:49:23 -08:00
Ken Sternberg
1dcf9108ad Merge branch 'main' into dev
* main:
  flows: better test stage's challenge responses (#12316)
  enterprise/stages/authenticator_endpoint_gdtc: don't set frame options globally (#12311)
  stages/identification: fix invalid challenge warning when no captcha stage is set (#12312)
  website/docs: prepare 2024.10.5 release notes (#12309)
  website: bump nanoid from 3.3.7 to 3.3.8 in /website (#12307)
  flows: silent authz flow (#12213)
  root:  use healthcheck in depends_on for postgres and redis (#12301)
  ci: ensure mark jobs always run and reflect correct status (#12288)
  enterprise: allow deletion/modification of users when in read-only mode (#12289)
  web/flows: resize captcha iframes (#12260)
2024-12-10 09:07:45 -08:00
Ken Sternberg
7bb6a3dfe6 Merge branch 'main' into dev
* main:
  website/docs: add page about the Cobalt pentest (#12249)
  core: bump aws-cdk-lib from 2.171.1 to 2.172.0 (#12296)
  website: bump aws-cdk from 2.171.1 to 2.172.0 in /website (#12295)
  core: bump sentry-sdk from 2.19.1 to 2.19.2 (#12297)
  core: bump coverage from 7.6.8 to 7.6.9 (#12299)
  core, web: update translations (#12290)
  root: fix override locale only if it is not empty (#12283)
  translate: Updates for file web/xliff/en.xlf in fr (#12276)
  core: bump twilio from 9.3.7 to 9.3.8 (#12282)
  website: bump path-to-regexp and express in /website (#12279)
  core: bump sentry-sdk from 2.19.0 to 2.19.1 (#12280)
  core: bump ruff from 0.8.1 to 0.8.2 (#12281)
  website/docs: fix lint (#12287)
  website/integrations: netbird: fix redirect URI regex (#12284)
2024-12-09 08:28:52 -08:00
Ken Sternberg
9cc440eee1 Merge branch 'main' into dev
* main:
  web: simplify `?inline` handler for Storybook (#12246)
  website/docs: Update Traefik middleware example to reflect latest version of Traefik (#12267)
  website/docs: add . in https://netbird.company* (#12166)
  core: bump goauthentik.io/api/v3 from 3.2024104.1 to 3.2024104.2 (#12263)
  core: bump pydantic from 2.10.2 to 2.10.3 (#12262)
  core: bump github.com/getsentry/sentry-go from 0.29.1 to 0.30.0 (#12264)
  core, web: update translations (#12268)
  website: bump @types/react from 18.3.12 to 18.3.13 in /website (#12269)
  website: bump prettier from 3.4.1 to 3.4.2 in /website (#12270)
  ci: bump actions/attest-build-provenance from 1 to 2 (#12271)
  core: bump golang.org/x/sync from 0.9.0 to 0.10.0 (#12272)
  core: bump django from 5.0.9 to 5.0.10 (#12273)
  core: bump webauthn from 2.3.0 to 2.4.0 (#12274)
  website/integrations: add The Lounge (#11971)
  core: bump python-kadmin-rs from 0.3.0 to 0.4.0 (#12257)
  root: fix health status code (#12255)
  ci: fix should_push always being false (#12252)
  web: bump API Client version (#12251)
  providers/oauth2: Add provider federation between OAuth2 Providers (#12083)
  website/integrations: mastodon: set correct uid field (#11945)
2024-12-05 10:47:00 -08:00
Ken Sternberg
fe9e4526ac Merge branch 'main' into dev
* main: (31 commits)
  web/admin: bugfix: dual select initialization revision (#12051)
  web: update tests for Chromedriver 131 (#12199)
  website/integrations: add Aruba Orchestrator (#12220)
  core: bump aws-cdk-lib from 2.167.1 to 2.171.1 (#12237)
  website: bump aws-cdk from 2.167.1 to 2.171.1 in /website (#12241)
  core, web: update translations (#12236)
  core: bump python-kadmin-rs from 0.2.0 to 0.3.0 (#12238)
  core: bump pytest from 8.3.3 to 8.3.4 (#12239)
  core: bump drf-spectacular from 0.27.2 to 0.28.0 (#12240)
  core, web: update translations (#12222)
  core: Bump ruff from 0.8.0 to 0.8.1 (#12224)
  core: Bump ua-parser from 0.18.0 to 1.0.0 (#12225)
  core: Bump msgraph-sdk from 1.13.0 to 1.14.0 (#12226)
  stages/authenticator_webauthn: Update FIDO MDS3 & Passkey aaguid blobs (#12234)
  website/docs: install: add aws (#12082)
  core: Bump pyjwt from 2.10.0 to 2.10.1 (#12217)
  core: Bump fido2 from 1.1.3 to 1.2.0 (#12218)
  core: Bump cryptography from 43.0.3 to 44.0.0 (#12219)
  providers/oauth2: allow m2m for JWKS without alg in keys (#12196)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in fr (#12210)
  ...
2024-12-02 09:10:36 -08:00
Ken Sternberg
20b66f850c Merge branch 'main' into dev
* main:
  website/docs: Add note about single group per role (#12169)
  website/docs: Fix documentation about attribute merging for indirect membership (#12168)
  root: support running authentik in subpath (#8675)
  docs: fix contribution link (#12189)
  core, web: update translations (#12190)
  core: Bump msgraph-sdk from 1.12.0 to 1.13.0 (#12191)
  core: Bump selenium from 4.26.1 to 4.27.0 (#12192)
2024-11-26 09:27:51 -08:00
Ken Sternberg
67b327414b Merge branch 'main' into dev
* main:
  website/docs: Fix CSP syntax (#12124)
2024-11-25 13:11:45 -08:00
Ken Sternberg
5b8d86b5a9 Merge branch 'main' into dev
* main:
  ci: only mirror if secret is available (#12181)
  root: fix database ssl options not set correctly (#12180)
  core, web: update translations (#12145)
  core: bump tornado from 6.4.1 to 6.4.2 (#12165)
  website: bump the docusaurus group in /website with 9 updates (#12172)
  website: bump typescript from 5.6.3 to 5.7.2 in /website (#12173)
  ci: bump actions/checkout from 3 to 4 (#12174)
  core: bump github.com/stretchr/testify from 1.9.0 to 1.10.0 (#12175)
  core: bump coverage from 7.6.7 to 7.6.8 (#12176)
  core: bump ruff from 0.7.4 to 0.8.0 (#12177)
2024-11-25 09:21:19 -08:00
Ken Sternberg
67aed3e318 Merge branch 'main' into dev
* main: (33 commits)
  ci: mirror repo to internal repo (#12160)
  core: bump goauthentik.io/api/v3 from 3.2024102.2 to 3.2024104.1 (#12149)
  core: bump debugpy from 1.8.8 to 1.8.9 (#12150)
  core: bump webauthn from 2.2.0 to 2.3.0 (#12151)
  core: bump pydantic from 2.10.0 to 2.10.1 (#12152)
  translate: Updates for file web/xliff/en.xlf in zh_CN (#12156)
  translate: Updates for file web/xliff/en.xlf in zh-Hans (#12157)
  core: bump sentry-sdk from 2.18.0 to 2.19.0 (#12153)
  web: bump API Client version (#12147)
  root: Backport version change (#12146)
  website/docs: update info about footer links to match new UI (#12120)
  website/docs: prepare release notes (#12142)
  providers/oauth2: fix migration (#12138)
  providers/oauth2: fix migration dependencies (#12123)
  web: bump API Client version (#12129)
  providers/oauth2: fix redirect uri input (#12122)
  providers/proxy: fix redirect_uri (#12121)
  website/docs: prepare release notes (#12119)
  web: bump API Client version (#12118)
  security: fix CVE 2024 52289 (#12113)
  ...
2024-11-22 13:56:13 -08:00
Ken Sternberg
9809b94030 Merge branch 'main' into dev
* main: (28 commits)
  providers/scim: accept string and int for SCIM IDs (#12093)
  website: bump the docusaurus group in /website with 9 updates (#12086)
  core: fix source_flow_manager throwing error when authenticated user attempts to re-authenticate with existing link (#12080)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in de (#12079)
  scripts: remove read_replicas from generated dev config (#12078)
  core: bump geoip2 from 4.8.0 to 4.8.1 (#12071)
  core: bump goauthentik.io/api/v3 from 3.2024100.2 to 3.2024102.2 (#12072)
  core: bump maxmind/geoipupdate from v7.0.1 to v7.1.0 (#12073)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in zh_CN (#12074)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in zh-Hans (#12075)
  translate: Updates for file web/xliff/en.xlf in zh-Hans (#12076)
  translate: Updates for file web/xliff/en.xlf in zh_CN (#12077)
  web/admin: auto-prefill user path for new users based on selected path (#12070)
  core: bump aiohttp from 3.10.2 to 3.10.11 (#12069)
  web/admin: fix brand title not respected in application list (#12068)
  core: bump pyjwt from 2.9.0 to 2.10.0 (#12063)
  web: add italian locale (#11958)
  web/admin: better footer links (#12004)
  core, web: update translations (#12052)
  core: bump twilio from 9.3.6 to 9.3.7 (#12061)
  ...
2024-11-20 10:23:54 -08:00
Ken Sternberg
e7527c551b Merge branch 'main' into dev
* main:
  translate: Updates for file locale/en/LC_MESSAGES/django.po in zh-Hans (#12045)
  translate: Updates for file web/xliff/en.xlf in zh_CN (#12047)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in zh_CN (#12044)
  translate: Updates for file web/xliff/en.xlf in zh-Hans (#12046)
  web/flows: fix invisible captcha call (#12048)
  rbac: fix incorrect object_description for object-level permissions (#12029)
  stages/authenticator_webauthn: Update FIDO MDS3 & Passkey aaguid blobs (#12036)
  core: bump coverage from 7.6.4 to 7.6.5 (#12037)
  ci: bump codecov/codecov-action from 4 to 5 (#12038)
  release: 2024.10.2 (#12031)
2024-11-15 12:47:17 -08:00
Ken Sternberg
36b10b434a :wqge branch 'main' into dev
* main:
  providers/ldap: fix global search_full_directory permission not being sufficient (#12028)
  website/docs: 2024.10.2 release notes (#12025)
  lifecycle: fix ak exit status not being passed (#12024)
  core: use versioned_script for path only (#12003)
  core, web: update translations (#12020)
  core: bump google-api-python-client from 2.152.0 to 2.153.0 (#12021)
  providers/oauth2: fix manual device code entry (#12017)
  crypto: validate that generated certificate's name is unique (#12015)
  core, web: update translations (#12006)
  core: bump google-api-python-client from 2.151.0 to 2.152.0 (#12007)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in zh-Hans (#12011)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in zh_CN (#12010)
  translate: Updates for file web/xliff/en.xlf in zh-Hans (#12012)
  translate: Updates for file web/xliff/en.xlf in zh_CN (#12013)
  providers/proxy: fix Issuer when AUTHENTIK_HOST_BROWSER is set (#11968)
  website/docs: move S3 ad GeoIP to System Management/Operations (#11998)
  website/integrations: nextcloud: add SSE warning (#11976)
2024-11-14 11:29:13 -08:00
Ken Sternberg
831797b871 Merge branch 'main' into dev
* main: (21 commits)
  web: bump API Client version (#11997)
  sources/kerberos: use new python-kadmin implementation (#11932)
  core: add ability to provide reason for impersonation (#11951)
  website/integrations:  update vcenter integration docs (#11768)
  core, web: update translations (#11995)
  website: bump postcss from 8.4.48 to 8.4.49 in /website (#11996)
  web: bump API Client version (#11992)
  blueprints: add default Password policy (#11793)
  stages/captcha: Run interactive captcha in Frame (#11857)
  core, web: update translations (#11979)
  core: bump packaging from 24.1 to 24.2 (#11985)
  core: bump ruff from 0.7.2 to 0.7.3 (#11986)
  core: bump msgraph-sdk from 1.11.0 to 1.12.0 (#11987)
  website: bump the docusaurus group in /website with 9 updates (#11988)
  website: bump postcss from 8.4.47 to 8.4.48 in /website (#11989)
  stages/password: use recovery flow from brand (#11953)
  core: bump golang.org/x/sync from 0.8.0 to 0.9.0 (#11962)
  web: bump cookie, swagger-client and express in /web (#11966)
  core, web: update translations (#11959)
  core: bump debugpy from 1.8.7 to 1.8.8 (#11961)
  ...
2024-11-12 10:08:48 -08:00
Ken Sternberg
5cc2c0f45f Merge branch 'main' into dev
* main:
  ci: fix dockerfile warning (#11956)
2024-11-07 12:58:15 -08:00
Ken Sternberg
32442766f4 Merge branch 'main' into dev
* main:
  website/docs: fix slug matching redirect URI causing broken refresh (#11950)
  website/integrations: jellyfin: update plugin catalog location (#11948)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in de (#11942)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in zh_CN (#11946)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in zh-Hans (#11947)
  website/docs: clarify traefik ingress setup (#11938)
  core: bump importlib-metadata from 8.4.0 to 8.5.0 (#11934)
  web: bump API Client version (#11930)
  root: backport version bump `2024.10.1` (#11929)
  website/docs: `2024.10.1` Release Notes (#11926)
  website: bump path-to-regexp from 1.8.0 to 1.9.0 in /website (#11924)
  core: bump sentry-sdk from 2.17.0 to 2.18.0 (#11918)
  website: bump the docusaurus group in /website with 9 updates (#11917)
  core: bump goauthentik.io/api/v3 from 3.2024100.1 to 3.2024100.2 (#11915)
  core, web: update translations (#11914)
2024-11-07 08:08:46 -08:00
Ken Sternberg
75790909a8 Merge branch 'main' into dev
* main:
  web: bump API Client version (#11909)
  enterprise/rac: fix API Schema for invalidation_flow (#11907)
2024-11-04 13:20:15 -08:00
Ken Sternberg
e0d5df89ca Merge branch 'main' into dev
* main:
  core: add `None` check to a device's `extra_description` (#11904)
  providers/oauth2: fix size limited index for tokens (#11879)
  web: fix missing status code on failed build (#11903)
  website: bump docusaurus-theme-openapi-docs from 4.1.0 to 4.2.0 in /website (#11897)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in de (#11891)
  stages/authenticator_webauthn: Update FIDO MDS3 & Passkey aaguid blobs (#11884)
  translate: Updates for file web/xliff/en.xlf in tr (#11878)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in tr (#11866)
  core: bump google-api-python-client from 2.149.0 to 2.151.0 (#11885)
  core: bump selenium from 4.26.0 to 4.26.1 (#11886)
  core, web: update translations (#11896)
  website: bump docusaurus-plugin-openapi-docs from 4.1.0 to 4.2.0 in /website (#11898)
  core: bump watchdog from 5.0.3 to 6.0.0 (#11899)
  core: bump ruff from 0.7.1 to 0.7.2 (#11900)
  core: bump django-pglock from 1.6.2 to 1.7.0 (#11901)
  website/docs: fix release notes to say Federation (#11889)
2024-11-04 10:25:52 -08:00
Ken Sternberg
f25a9c624e Merge branch 'main' into dev
* main:
  website: bump elliptic from 6.5.7 to 6.6.0 in /website (#11869)
  core: bump selenium from 4.25.0 to 4.26.0 (#11875)
  core: bump goauthentik.io/api/v3 from 3.2024083.14 to 3.2024100.1 (#11876)
  website/docs: add info about invalidation flow, default flows in general (#11800)
  website: fix docs redirect (#11873)
  website: remove RC disclaimer for version 2024.10 (#11871)
  website: update supported versions (#11841)
  web: bump API Client version (#11870)
  root: backport version bump 2024.10.0 (#11868)
  website/docs: 2024.8.4 release notes (#11862)
  web/admin: provide default invalidation flows for LDAP and Radius (#11861)
2024-11-04 10:25:45 -08:00
Ken Sternberg
914993a788 Merge branch 'main' into dev
* main: (43 commits)
  core, web: update translations (#11858)
  web/admin: fix code-based MFA toggle not working in wizard (#11854)
  sources/kerberos: add kiprop to ignored system principals (#11852)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in zh_CN (#11846)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in it (#11845)
  translate: Updates for file web/xliff/en.xlf in zh_CN (#11847)
  translate: Updates for file web/xliff/en.xlf in zh-Hans (#11848)
  translate: Updates for file locale/en/LC_MESSAGES/django.po in zh-Hans (#11849)
  translate: Updates for file web/xliff/en.xlf in it (#11850)
  website: 2024.10 Release Notes (#11839)
  translate: Updates for file web/xliff/en.xlf in zh-Hans (#11814)
  core, web: update translations (#11821)
  core: bump goauthentik.io/api/v3 from 3.2024083.13 to 3.2024083.14 (#11830)
  core: bump service-identity from 24.1.0 to 24.2.0 (#11831)
  core: bump twilio from 9.3.5 to 9.3.6 (#11832)
  core: bump pytest-randomly from 3.15.0 to 3.16.0 (#11833)
  website/docs: Update social-logins github (#11822)
  website/docs: remove � (#11823)
  lifecycle: fix kdc5-config missing (#11826)
  website/docs: update preview status of different features (#11817)
  ...
2024-10-30 10:04:59 -07:00
Ken Sternberg
89dad07a66 web: Add InvalidationFlow to Radius Provider dialogues
## What

- Bugfix: adds the InvalidationFlow to the Radius Provider dialogues
  - Repairs: `{"invalidation_flow":["This field is required."]}` message, which was *not* propagated
    to the Notification.
- Nitpick: Pretties `?foo=${true}` expressions: `s/\?([^=]+)=\$\{true\}/\1/`

## Note

Yes, I know I'm going to have to do more magic when we harmonize the forms, and no, I didn't add the
Property Mappings to the wizard, and yes, I know I'm going to have pain with the *new* version of
the wizard. But this is a serious bug; you can't make Radius servers with *either* of the current
dialogues at the moment.
2024-10-23 14:17:30 -07:00
466 changed files with 4324 additions and 142057 deletions

View File

@@ -22,7 +22,7 @@ runs:
sudo rm -rf /usr/local/lib/android
- name: Install uv
if: ${{ contains(inputs.dependencies, 'python') }}
uses: astral-sh/setup-uv@eac588ad8def6316056a12d4907a9d4d84ff7a3b # v5
uses: astral-sh/setup-uv@803947b9bd8e9f986429fa0c5a41c367cd732b41 # v5
with:
enable-cache: true
- name: Setup python
@@ -58,7 +58,7 @@ runs:
run: |
export PSQL_TAG=${{ inputs.postgresql_version }}
docker compose -f .github/actions/setup/compose.yml up -d
cd web && npm ci
cd web && npm i
- name: Generate config
if: ${{ contains(inputs.dependencies, 'python') }}
shell: uv run python {0}

View File

@@ -90,7 +90,7 @@ jobs:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: int128/docker-manifest-create-action@1a059c021f1d5e9f2bd39de745d5dd3a0ef6df90 # v2
- uses: int128/docker-manifest-create-action@a39573caa37b6a8a03302d43b57c3f48635096e2 # v2
id: build
with:
tags: ${{ matrix.tag }}

View File

@@ -38,7 +38,7 @@ jobs:
token: ${{ steps.generate_token.outputs.token }}
- name: Compress images
id: compress
uses: calibreapp/image-actions@d9c8ee5c3dc52ae4622c82ead88d658f4b16b65f # main
uses: calibreapp/image-actions@420075c115b26f8785e293c5bd5bef0911c506e5 # main
with:
GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
compressOnly: ${{ github.event_name != 'pull_request' }}

View File

@@ -40,7 +40,7 @@ jobs:
registry-url: "https://registry.npmjs.org"
- name: Get changed files
id: changed-files
uses: tj-actions/changed-files@8cba46e29c11878d930bca7870bb54394d3e8b21 # 24d32ffd492484c1d75e0c0b894501ddb9d30d62
uses: tj-actions/changed-files@e0021407031f5be11a464abee9a0776171c79891 # 24d32ffd492484c1d75e0c0b894501ddb9d30d62
with:
files: |
${{ matrix.package }}/package.json

View File

@@ -160,17 +160,10 @@ jobs:
node-version-file: web/package.json
cache: "npm"
cache-dependency-path: web/package-lock.json
- name: Install web dependencies
working-directory: web/
run: |
npm ci
- name: Generate API Clients
run: |
make gen-client-ts
make gen-client-go
- name: Build web
working-directory: web/
run: |
npm ci
npm run build-proxy
- name: Build outpost
run: |
@@ -199,7 +192,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
- uses: aws-actions/configure-aws-credentials@8df5847569e6427dd6c4fb1cf565c83acfa8afa7 # v6.0.0
- uses: aws-actions/configure-aws-credentials@61815dcd50bd041e203e49132bacad1fd04d2708 # v5
with:
role-to-assume: "arn:aws:iam::016170277896:role/github_goauthentik_authentik"
aws-region: ${{ env.AWS_REGION }}
@@ -217,12 +210,12 @@ jobs:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v5
- name: Run test suite in final docker images
run: |
echo "PG_PASS=$(openssl rand 32 | base64 -w 0)" >> lifecycle/container/.env
echo "AUTHENTIK_SECRET_KEY=$(openssl rand 32 | base64 -w 0)" >> lifecycle/container/.env
docker compose -f lifecycle/container/compose.yml pull -q
docker compose -f lifecycle/container/compose.yml up --no-start
docker compose -f lifecycle/container/compose.yml start postgresql
docker compose -f lifecycle/container/compose.yml run -u root server test-all
echo "PG_PASS=$(openssl rand 32 | base64 -w 0)" >> .env
echo "AUTHENTIK_SECRET_KEY=$(openssl rand 32 | base64 -w 0)" >> .env
docker compose pull -q
docker compose up --no-start
docker compose start postgresql
docker compose run -u root server test-all
sentry-release:
needs:
- build-server

View File

@@ -91,7 +91,6 @@ jobs:
# ID from https://api.github.com/users/authentik-automation[bot]
git config --global user.name '${{ steps.app-token.outputs.app-slug }}[bot]'
git config --global user.email '${{ steps.get-user-id.outputs.user-id }}+${{ steps.app-token.outputs.app-slug }}[bot]@users.noreply.github.com'
git pull
git commit -a -m "release: ${{ inputs.version }}" --allow-empty
git tag "version/${{ inputs.version }}" HEAD -m "version/${{ inputs.version }}"
git push --follow-tags

View File

@@ -148,11 +148,11 @@ bump: ## Bump authentik version. Usage: make bump version=20xx.xx.xx
ifndef version
$(error Usage: make bump version=20xx.xx.xx )
endif
$(eval current_version := $(shell cat ${PWD}/internal/constants/VERSION))
$(SED_INPLACE) 's/^version = ".*"/version = "$(version)"/' ${PWD}/pyproject.toml
$(SED_INPLACE) 's/^VERSION = ".*"/VERSION = "$(version)"/' ${PWD}/authentik/__init__.py
$(SED_INPLACE) 's/^version = ".*"/version = "$(version)"/' pyproject.toml
$(SED_INPLACE) 's/^VERSION = ".*"/VERSION = "$(version)"/' authentik/__init__.py
$(MAKE) gen-build gen-compose aws-cfn
$(SED_INPLACE) "s/\"${current_version}\"/\"$(version)\"/" ${PWD}/package.json ${PWD}/package-lock.json ${PWD}/web/package.json ${PWD}/web/package-lock.json
npm version --no-git-tag-version --allow-same-version $(version)
cd ${PWD}/web && npm version --no-git-tag-version --allow-same-version $(version)
echo -n $(version) > ${PWD}/internal/constants/VERSION
#########################

View File

@@ -3,7 +3,7 @@
from functools import lru_cache
from os import environ
VERSION = "2026.2.0"
VERSION = "2026.2.0-rc1"
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"

View File

@@ -1,8 +1,6 @@
"""Schema generation tests"""
from pathlib import Path
from tempfile import gettempdir
from uuid import uuid4
from django.core.management import call_command
from django.urls import reverse
@@ -31,14 +29,15 @@ class TestSchemaGeneration(APITestCase):
def test_build_schema(self):
"""Test schema build command"""
tmp = Path(gettempdir())
blueprint_file = tmp / f"{str(uuid4())}.json"
api_file = tmp / f"{str(uuid4())}.yml"
blueprint_file = Path("blueprints/schema.json")
api_file = Path("schema.yml")
blueprint_file.unlink()
api_file.unlink()
with (
CONFIG.patch("debug", True),
CONFIG.patch("tenants.enabled", True),
CONFIG.patch("outposts.disable_embedded_outpost", True),
):
call_command("build_schema", blueprint_file=blueprint_file, api_file=api_file)
call_command("build_schema")
self.assertTrue(blueprint_file.exists())
self.assertTrue(api_file.exists())

View File

@@ -43,6 +43,8 @@ def get_attrs(obj: SerializerModel) -> dict[str, Any]:
continue
if _field.read_only:
data.pop(field_name, None)
if _field.get_initial() == data.get(field_name, None):
data.pop(field_name, None)
if field_name.endswith("_set"):
data.pop(field_name, None)
return data

View File

@@ -3,7 +3,7 @@
from typing import Any
from django.db.models import Case, F, IntegerField, Q, Value, When
from django.db.models.functions import Concat, Length
from django.db.models.functions import Length
from django.http.request import HttpRequest
from django.utils.html import _json_script_escapes
from django.utils.safestring import mark_safe
@@ -26,8 +26,7 @@ def get_brand_for_request(request: HttpRequest) -> Brand:
domain_length=Length("domain"),
match_priority=Case(
When(
condition=Q(host_domain__iexact=F("domain"))
| Q(host_domain__iendswith=Concat(Value("."), F("domain"))),
condition=Q(host_domain__iendswith=F("domain")),
then=F("domain_length"),
),
default=Value(-1),

View File

@@ -28,8 +28,6 @@ SAML_ATTRIBUTES_GROUP = "http://schemas.xmlsoap.org/claims/Group"
SAML_BINDING_POST = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
SAML_BINDING_REDIRECT = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
SAML_STATUS_SUCCESS = "urn:oasis:names:tc:SAML:2.0:status:Success"
DSA_SHA1 = "http://www.w3.org/2000/09/xmldsig#dsa-sha1"
RSA_SHA1 = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"
# https://datatracker.ietf.org/doc/html/rfc4051#section-2.3.2

View File

@@ -72,7 +72,6 @@ from authentik.core.middleware import (
from authentik.core.models import (
USER_ATTRIBUTE_TOKEN_EXPIRING,
USER_PATH_SERVICE_ACCOUNT,
USERNAME_MAX_LENGTH,
Group,
Session,
Token,
@@ -145,7 +144,7 @@ class UserSerializer(ModelSerializer):
roles_obj = SerializerMethodField(allow_null=True)
uid = CharField(read_only=True)
username = CharField(
max_length=USERNAME_MAX_LENGTH,
max_length=150,
validators=[UniqueValidator(queryset=User.objects.all().order_by("username"))],
)

View File

@@ -1,7 +1,5 @@
"""authentik core models"""
import re
import traceback
from datetime import datetime, timedelta
from enum import StrEnum
from hashlib import sha256
@@ -52,7 +50,6 @@ from authentik.tenants.models import DEFAULT_TOKEN_DURATION, DEFAULT_TOKEN_LENGT
from authentik.tenants.utils import get_current_tenant, get_unique_identifier
LOGGER = get_logger()
USERNAME_MAX_LENGTH = 150
USER_PATH_SYSTEM_PREFIX = "goauthentik.io"
_USER_ATTR_PREFIX = f"{USER_PATH_SYSTEM_PREFIX}/user"
USER_ATTRIBUTE_DEBUG = f"{_USER_ATTR_PREFIX}/debug"
@@ -530,35 +527,23 @@ class User(SerializerModel, AttributesMixin, AbstractUser):
"default: in 30 days). See authentik logs for every will invocation of this "
"deprecation."
)
stacktrace = traceback.format_stack()
# The last line is this function, the next-to-last line is its caller
cause = stacktrace[-2] if len(stacktrace) > 1 else "Unknown, see stacktrace in logs"
if search := re.search(r'"(.*?)"', cause):
cause = f"Property mapping or Expression policy named {search.group(1)}"
LOGGER.warning(
"deprecation used",
message=message_logger,
deprecation=deprecation,
replacement=replacement,
cause=cause,
stacktrace=stacktrace,
)
if not Event.filter_not_expired(
action=EventAction.CONFIGURATION_WARNING,
context__deprecation=deprecation,
context__cause=cause,
action=EventAction.CONFIGURATION_WARNING, context__deprecation=deprecation
).exists():
event = Event.new(
EventAction.CONFIGURATION_WARNING,
deprecation=deprecation,
replacement=replacement,
message=message_event,
cause=cause,
)
event.expires = datetime.now() + timedelta(days=30)
event.save()
return self.groups
def set_password(self, raw_password, signal=True, sender=None, request=None):

View File

@@ -44,24 +44,19 @@
{% endblock %}
</div>
</main>
<footer
name="site-footer"
aria-label="{% trans 'Site footer' %}"
class="pf-c-login__footer pf-m-dark">
<div name="flow-links" aria-label="{% trans 'Flow links' %}">
<ul class="pf-c-list pf-m-inline" part="list">
{% for link in footer_links %}
<li part="list-item">
<a part="list-item-link" href="{{ link.href }}">{{ link.name }}</a>
</li>
{% endfor %}
<li part="list-item">
<span>
{% trans 'Powered by authentik' %}
</span>
</li>
</ul>
</div>
<footer aria-label="Site footer" class="pf-c-login__footer pf-m-dark">
<ul class="pf-c-list pf-m-inline">
{% for link in footer_links %}
<li>
<a href="{{ link.href }}">{{ link.name }}</a>
</li>
{% endfor %}
<li>
<span>
{% trans 'Powered by authentik' %}
</span>
</li>
</ul>
</footer>
</div>
</div>

View File

@@ -1,6 +1,5 @@
from hashlib import sha256
from json import loads
from unittest.mock import PropertyMock, patch
from django.urls import reverse
from jwt import encode
@@ -233,43 +232,3 @@ class TestEndpointStage(FlowTestCase):
plan = plan()
self.assertNotIn(PLAN_CONTEXT_AGENT_ENDPOINT_CHALLENGE, plan.context)
self.assertEqual(plan.context[PLAN_CONTEXT_DEVICE], self.device)
def test_endpoint_stage_connector_no_stage_optional(self):
flow = create_test_flow()
stage = EndpointStage.objects.create(connector=self.connector, mode=StageMode.OPTIONAL)
FlowStageBinding.objects.create(stage=stage, target=flow, order=0)
with patch(
"authentik.endpoints.connectors.agent.models.AgentConnector.stage",
PropertyMock(return_value=None),
):
with self.assertFlowFinishes() as plan:
res = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
)
self.assertStageRedirects(res, reverse("authentik_core:root-redirect"))
plan = plan()
self.assertNotIn(PLAN_CONTEXT_AGENT_ENDPOINT_CHALLENGE, plan.context)
self.assertNotIn(PLAN_CONTEXT_DEVICE, plan.context)
def test_endpoint_stage_connector_no_stage_required(self):
flow = create_test_flow()
stage = EndpointStage.objects.create(connector=self.connector, mode=StageMode.REQUIRED)
FlowStageBinding.objects.create(stage=stage, target=flow, order=0)
with patch(
"authentik.endpoints.connectors.agent.models.AgentConnector.stage",
PropertyMock(return_value=None),
):
with self.assertFlowFinishes() as plan:
res = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
)
self.assertStageResponse(
res,
component="ak-stage-access-denied",
error_message="Invalid stage configuration",
)
plan = plan()
self.assertNotIn(PLAN_CONTEXT_AGENT_ENDPOINT_CHALLENGE, plan.context)
self.assertNotIn(PLAN_CONTEXT_DEVICE, plan.context)

View File

@@ -1,4 +1,4 @@
from authentik.endpoints.models import EndpointStage, StageMode
from authentik.endpoints.models import EndpointStage
from authentik.flows.stage import StageView
PLAN_CONTEXT_ENDPOINT_CONNECTOR = "endpoint_connector"
@@ -6,24 +6,15 @@ PLAN_CONTEXT_ENDPOINT_CONNECTOR = "endpoint_connector"
class EndpointStageView(StageView):
def _get_inner(self) -> StageView | None:
def _get_inner(self):
stage: EndpointStage = self.executor.current_stage
inner_stage: type[StageView] | None = stage.connector.stage
if not inner_stage:
return None
return self.executor.stage_ok()
return inner_stage(self.executor, request=self.request)
def dispatch(self, request, *args, **kwargs):
inner = self._get_inner()
if inner is None:
stage: EndpointStage = self.executor.current_stage
if stage.mode == StageMode.OPTIONAL:
return self.executor.stage_ok()
else:
return self.executor.stage_invalid("Invalid stage configuration")
return inner.dispatch(request, *args, **kwargs)
return self._get_inner().dispatch(request, *args, **kwargs)
def cleanup(self):
inner = self._get_inner()
if inner is not None:
return inner.cleanup()
return self._get_inner().cleanup()

View File

@@ -15,7 +15,6 @@ from django.core.cache import cache
from django.db.models.query import QuerySet
from django.utils.timezone import now
from jwt import PyJWTError, decode, get_unverified_header
from jwt.algorithms import ECAlgorithm
from rest_framework.exceptions import ValidationError
from rest_framework.fields import (
ChoiceField,
@@ -110,20 +109,13 @@ class LicenseKey:
intermediate.verify_directly_issued_by(get_licensing_key())
except InvalidSignature, TypeError, ValueError, Error:
raise ValidationError("Unable to verify license") from None
_validate_curve_original = ECAlgorithm._validate_curve
try:
# authentik's license are generated with `algorithm="ES512"` and signed with
# a key of curve `secp384r1`. Starting with version 2.11.0, pyjwt enforces the spec, see
# https://github.com/jpadilla/pyjwt/commit/5b8622773358e56d3d3c0a9acf404809ff34433a
# authentik will change its license generation to `algorithm="ES384"` in 2026.
# TODO: remove this when the last incompatible license runs out.
ECAlgorithm._validate_curve = lambda *_: True
body = from_dict(
LicenseKey,
decode(
jwt,
our_cert.public_key(),
algorithms=["ES384", "ES512"],
algorithms=["ES512"],
audience=get_license_aud(),
options={"verify_exp": check_expiry, "verify_signature": check_expiry},
),
@@ -133,8 +125,6 @@ class LicenseKey:
if unverified["aud"] != get_license_aud():
raise ValidationError("Invalid Install ID in license") from None
raise ValidationError("Unable to verify license") from None
finally:
ECAlgorithm._validate_curve = _validate_curve_original
return body
@staticmethod

View File

@@ -1,150 +0,0 @@
from datetime import datetime
from django.db.models import BooleanField as ModelBooleanField
from django.db.models import Case, Q, Value, When
from django_filters.rest_framework import BooleanFilter, FilterSet
from drf_spectacular.utils import extend_schema
from rest_framework.decorators import action
from rest_framework.fields import IntegerField, SerializerMethodField
from rest_framework.mixins import CreateModelMixin
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.viewsets import GenericViewSet
from authentik.core.api.utils import ModelSerializer
from authentik.enterprise.api import EnterpriseRequiredMixin
from authentik.enterprise.lifecycle.api.reviews import ReviewSerializer
from authentik.enterprise.lifecycle.models import LifecycleIteration, ReviewState
from authentik.enterprise.lifecycle.utils import (
ContentTypeField,
ReviewerGroupSerializer,
ReviewerUserSerializer,
admin_link_for_model,
parse_content_type,
start_of_day,
)
from authentik.lib.utils.time import timedelta_from_string
class LifecycleIterationSerializer(EnterpriseRequiredMixin, ModelSerializer):
content_type = ContentTypeField()
object_verbose = SerializerMethodField()
object_admin_url = SerializerMethodField(read_only=True)
grace_period_end = SerializerMethodField(read_only=True)
reviews = ReviewSerializer(many=True, read_only=True, source="review_set.all")
user_can_review = SerializerMethodField(read_only=True)
reviewer_groups = ReviewerGroupSerializer(
many=True, read_only=True, source="rule.reviewer_groups"
)
min_reviewers = IntegerField(read_only=True, source="rule.min_reviewers")
reviewers = ReviewerUserSerializer(many=True, read_only=True, source="rule.reviewers")
next_review_date = SerializerMethodField(read_only=True)
class Meta:
model = LifecycleIteration
fields = [
"id",
"content_type",
"object_id",
"object_verbose",
"object_admin_url",
"state",
"opened_on",
"grace_period_end",
"next_review_date",
"reviews",
"user_can_review",
"reviewer_groups",
"min_reviewers",
"reviewers",
]
read_only_fields = fields
def get_object_verbose(self, iteration: LifecycleIteration) -> str:
return str(iteration.object)
def get_object_admin_url(self, iteration: LifecycleIteration) -> str:
return admin_link_for_model(iteration.object)
def get_grace_period_end(self, iteration: LifecycleIteration) -> datetime:
return start_of_day(
iteration.opened_on + timedelta_from_string(iteration.rule.grace_period)
)
def get_next_review_date(self, iteration: LifecycleIteration) -> datetime:
return start_of_day(iteration.opened_on + timedelta_from_string(iteration.rule.interval))
def get_user_can_review(self, iteration: LifecycleIteration) -> bool:
return iteration.user_can_review(self.context["request"].user)
class LifecycleIterationFilterSet(FilterSet):
user_is_reviewer = BooleanFilter(field_name="user_is_reviewer", lookup_expr="exact")
class IterationViewSet(EnterpriseRequiredMixin, CreateModelMixin, GenericViewSet):
queryset = LifecycleIteration.objects.all()
serializer_class = LifecycleIterationSerializer
ordering = ["-opened_on"]
ordering_fields = ["state", "content_type__model", "opened_on", "grace_period_end"]
filterset_class = LifecycleIterationFilterSet
def get_queryset(self):
user = self.request.user
return self.queryset.annotate(
user_is_reviewer=Case(
When(
Q(rule__reviewers=user)
| Q(rule__reviewer_groups__in=user.groups.all().with_ancestors()),
then=Value(True),
),
default=Value(False),
output_field=ModelBooleanField(),
)
).distinct()
@action(
detail=False,
methods=["get"],
url_path=r"latest/(?P<content_type>[^/]+)/(?P<object_id>[^/]+)",
)
def latest_iteration(self, request: Request, content_type: str, object_id: str) -> Response:
ct = parse_content_type(content_type)
try:
obj = (
self.get_queryset()
.filter(
content_type__app_label=ct["app_label"],
content_type__model=ct["model"],
object_id=object_id,
)
.latest("opened_on")
)
except LifecycleIteration.DoesNotExist:
return Response(status=404)
serializer = self.get_serializer(obj)
return Response(serializer.data)
@extend_schema(
operation_id="lifecycle_iterations_list_open",
responses={200: LifecycleIterationSerializer(many=True)},
)
@action(
detail=False,
methods=["get"],
url_path=r"open",
)
def open_iterations(self, request: Request):
iterations = self.get_queryset().filter(
Q(state=ReviewState.PENDING) | Q(state=ReviewState.OVERDUE)
)
iterations = self.filter_queryset(iterations)
page = self.paginate_queryset(iterations)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(iterations, many=True)
return Response(serializer.data)

View File

@@ -1,33 +0,0 @@
from django.utils.translation import gettext_lazy as _
from rest_framework.exceptions import ValidationError
from rest_framework.mixins import CreateModelMixin
from rest_framework.viewsets import GenericViewSet
from authentik.core.api.utils import ModelSerializer
from authentik.enterprise.api import EnterpriseRequiredMixin
from authentik.enterprise.lifecycle.models import LifecycleIteration, Review
from authentik.enterprise.lifecycle.utils import ReviewerUserSerializer
class ReviewSerializer(EnterpriseRequiredMixin, ModelSerializer):
reviewer = ReviewerUserSerializer(read_only=True)
class Meta:
model = Review
fields = ["id", "iteration", "reviewer", "timestamp", "note"]
read_only_fields = ["id", "timestamp", "reviewer"]
def validate_iteration(self, iteration: LifecycleIteration) -> LifecycleIteration:
user = self.context["request"].user
if not iteration.user_can_review(user):
raise ValidationError(_("You are not allowed to submit a review for this object."))
return iteration
class ReviewViewSet(EnterpriseRequiredMixin, CreateModelMixin, GenericViewSet):
queryset = Review.objects.all()
serializer_class = ReviewSerializer
def perform_create(self, serializer: ReviewSerializer) -> None:
review = serializer.save(reviewer=self.request.user)
review.iteration.on_review(self.request)

View File

@@ -1,113 +0,0 @@
from django.utils.translation import gettext as _
from rest_framework.exceptions import ValidationError
from rest_framework.fields import SerializerMethodField
from rest_framework.relations import SlugRelatedField
from rest_framework.viewsets import ModelViewSet
from authentik.core.api.utils import ModelSerializer
from authentik.core.models import User
from authentik.enterprise.api import EnterpriseRequiredMixin
from authentik.enterprise.lifecycle.models import LifecycleRule
from authentik.enterprise.lifecycle.utils import (
ContentTypeField,
ReviewerGroupSerializer,
ReviewerUserSerializer,
)
from authentik.lib.utils.time import timedelta_from_string
class LifecycleRuleSerializer(EnterpriseRequiredMixin, ModelSerializer):
content_type = ContentTypeField()
target_verbose = SerializerMethodField()
reviewer_groups_obj = ReviewerGroupSerializer(
many=True, read_only=True, source="reviewer_groups"
)
reviewers = SlugRelatedField(slug_field="uuid", many=True, queryset=User.objects.all())
reviewers_obj = ReviewerUserSerializer(many=True, read_only=True, source="reviewers")
class Meta:
model = LifecycleRule
fields = [
"id",
"name",
"content_type",
"object_id",
"interval",
"grace_period",
"reviewer_groups",
"reviewer_groups_obj",
"min_reviewers",
"min_reviewers_is_per_group",
"reviewers",
"reviewers_obj",
"notification_transports",
"target_verbose",
]
read_only_fields = ["id", "reviewers_obj", "reviewer_groups_obj", "target_verbose"]
def get_target_verbose(self, rule: LifecycleRule) -> str:
if rule.object_id is None:
return rule.content_type.model_class()._meta.verbose_name_plural
else:
return f"{rule.content_type.model_class()._meta.verbose_name}: {rule.object}"
def validate_object_id(self, value: str) -> str | None:
if value == "":
return None
return value
def validate(self, attrs: dict) -> dict:
if (
attrs.get("object_id") is not None
and not attrs["content_type"]
.get_all_objects_for_this_type(pk=attrs["object_id"])
.exists()
):
raise ValidationError({"object_id": _("Object does not exist")})
if "reviewer_groups" in attrs or "reviewers" in attrs:
reviewer_groups = attrs.get(
"reviewer_groups", self.instance.reviewer_groups.all() if self.instance else []
)
reviewers = attrs.get(
"reviewers", self.instance.reviewers.all() if self.instance else []
)
if len(reviewer_groups) == 0 and len(reviewers) == 0:
raise ValidationError(_("Either a reviewer group or a reviewer must be set."))
if "grace_period" in attrs or "interval" in attrs:
grace_period = attrs.get("grace_period", getattr(self.instance, "grace_period", None))
interval = attrs.get("interval", getattr(self.instance, "interval", None))
if (
grace_period is not None
and interval is not None
and (timedelta_from_string(grace_period) > timedelta_from_string(interval))
):
raise ValidationError(
{"grace_period": _("Grace period must be shorter than the interval.")}
)
if "content_type" in attrs or "object_id" in attrs:
content_type = attrs.get("content_type", getattr(self.instance, "content_type", None))
object_id = attrs.get("object_id", getattr(self.instance, "object_id", None))
if content_type is not None and object_id is None:
existing = LifecycleRule.objects.filter(
content_type=content_type, object_id__isnull=True
)
if self.instance:
existing = existing.exclude(pk=self.instance.pk)
if existing.exists():
raise ValidationError(
{
"content_type": _(
"Only one type-wide rule for each object type is allowed."
)
}
)
return attrs
class LifecycleRuleViewSet(ModelViewSet):
queryset = LifecycleRule.objects.all()
serializer_class = LifecycleRuleSerializer
search_fields = ["content_type__model", "reviewer_groups__name", "reviewers__username"]
ordering = ["name"]
ordering_fields = ["name", "content_type__model"]
filterset_fields = ["content_type__model"]

View File

@@ -1,22 +0,0 @@
from authentik.enterprise.apps import EnterpriseConfig
from authentik.lib.utils.time import fqdn_rand
from authentik.tasks.schedules.common import ScheduleSpec
class ReportsConfig(EnterpriseConfig):
name = "authentik.enterprise.lifecycle"
label = "authentik_lifecycle"
verbose_name = "authentik Enterprise.Lifecycle"
default = True
@property
def tenant_schedule_specs(self) -> list[ScheduleSpec]:
from authentik.enterprise.lifecycle.tasks import apply_lifecycle_rules
return [
ScheduleSpec(
actor=apply_lifecycle_rules,
crontab=f"{fqdn_rand('lifecycle_apply_lifecycle_rules')} "
f"{fqdn_rand('lifecycle_apply_lifecycle_rules', 24)} * * *",
)
]

View File

@@ -1,154 +0,0 @@
# Generated by Django 5.2.11 on 2026-02-09 15:57
import authentik.lib.utils.time
import django.db.models.deletion
import uuid
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
("authentik_core", "0057_remove_user_groups_remove_user_user_permissions_and_more"),
("authentik_events", "0016_alter_event_action"),
("contenttypes", "0002_remove_content_type_name"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name="LifecycleRule",
fields=[
("id", models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
("name", models.TextField(unique=True)),
("object_id", models.TextField(default=None, null=True)),
(
"interval",
models.TextField(
default="days=60",
validators=[authentik.lib.utils.time.timedelta_string_validator],
),
),
(
"grace_period",
models.TextField(
default="days=30",
validators=[authentik.lib.utils.time.timedelta_string_validator],
),
),
("min_reviewers", models.PositiveSmallIntegerField(default=1)),
("min_reviewers_is_per_group", models.BooleanField(default=False)),
(
"content_type",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, to="contenttypes.contenttype"
),
),
(
"notification_transports",
models.ManyToManyField(
blank=True,
help_text="Select which transports should be used to notify the reviewers. If none are selected, the notification will only be shown in the authentik UI.",
to="authentik_events.notificationtransport",
),
),
("reviewer_groups", models.ManyToManyField(blank=True, to="authentik_core.group")),
("reviewers", models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name="LifecycleIteration",
fields=[
(
"managed",
models.TextField(
default=None,
help_text="Objects that are managed by authentik. These objects are created and updated automatically. This flag only indicates that an object can be overwritten by migrations. You can still modify the objects via the API, but expect changes to be overwritten in a later update.",
null=True,
unique=True,
verbose_name="Managed by authentik",
),
),
("id", models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
("object_id", models.TextField()),
(
"state",
models.CharField(
choices=[
("REVIEWED", "Reviewed"),
("PENDING", "Pending"),
("OVERDUE", "Overdue"),
("CANCELED", "Canceled"),
],
default="PENDING",
max_length=10,
),
),
("opened_on", models.DateField(auto_now_add=True)),
(
"content_type",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, to="contenttypes.contenttype"
),
),
(
"rule",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="authentik_lifecycle.lifecyclerule",
),
),
],
),
migrations.CreateModel(
name="Review",
fields=[
("id", models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
("timestamp", models.DateTimeField(auto_now_add=True)),
("note", models.TextField(null=True)),
(
"iteration",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="authentik_lifecycle.lifecycleiteration",
),
),
(
"reviewer",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL
),
),
],
),
migrations.AddIndex(
model_name="lifecyclerule",
index=models.Index(fields=["content_type"], name="authentik_l_content_4e3a6a_idx"),
),
migrations.AddConstraint(
model_name="lifecyclerule",
constraint=models.UniqueConstraint(
condition=models.Q(("object_id__isnull", True)),
fields=("content_type",),
name="uniq_lifecycle_rule_ct_null_object",
),
),
migrations.AlterUniqueTogether(
name="lifecyclerule",
unique_together={("content_type", "object_id")},
),
migrations.AddIndex(
model_name="lifecycleiteration",
index=models.Index(
fields=["content_type", "opened_on"], name="authentik_l_content_09c32a_idx"
),
),
migrations.AlterUniqueTogether(
name="review",
unique_together={("iteration", "reviewer")},
),
]

View File

@@ -1,18 +0,0 @@
# Generated by Django 5.2.11 on 2026-02-13 09:33
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_lifecycle", "0001_initial"),
]
operations = [
migrations.AlterField(
model_name="lifecycleiteration",
name="opened_on",
field=models.DateTimeField(auto_now_add=True),
),
]

View File

@@ -1,292 +0,0 @@
from datetime import timedelta
from uuid import uuid4
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.db import models
from django.db.models import Q, QuerySet
from django.db.models.fields import Field
from django.db.models.functions import Cast
from django.http import HttpRequest
from django.utils import timezone
from django.utils.translation import gettext as _
from rest_framework.serializers import BaseSerializer
from authentik.blueprints.models import ManagedModel
from authentik.core.models import Group, User
from authentik.enterprise.lifecycle.utils import link_for_model, start_of_day
from authentik.events.models import Event, EventAction, NotificationSeverity, NotificationTransport
from authentik.lib.models import SerializerModel
from authentik.lib.utils.time import timedelta_from_string, timedelta_string_validator
class LifecycleRule(SerializerModel):
id = models.UUIDField(primary_key=True, default=uuid4)
name = models.TextField(unique=True)
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.TextField(null=True, default=None)
object = GenericForeignKey("content_type", "object_id")
interval = models.TextField(
default="days=60",
validators=[timedelta_string_validator],
)
# Grace period starts after a review is due
grace_period = models.TextField(
default="days=30",
validators=[timedelta_string_validator],
)
# The review has to be conducted by `min_reviewers` members of `reviewer_groups`
# (total or per group depending on `min_reviewers_is_per_group` flag) as well
# as all of `reviewers`
reviewer_groups = models.ManyToManyField("authentik_core.Group", blank=True)
min_reviewers = models.PositiveSmallIntegerField(default=1)
min_reviewers_is_per_group = models.BooleanField(default=False)
reviewers = models.ManyToManyField("authentik_core.User", blank=True)
notification_transports = models.ManyToManyField(
NotificationTransport,
help_text=_(
"Select which transports should be used to notify the reviewers. If none are "
"selected, the notification will only be shown in the authentik UI."
),
blank=True,
)
class Meta:
indexes = [models.Index(fields=["content_type"])]
unique_together = [["content_type", "object_id"]]
constraints = [
models.UniqueConstraint(
fields=["content_type"],
condition=Q(object_id__isnull=True),
name="uniq_lifecycle_rule_ct_null_object",
)
]
@property
def serializer(self) -> type[BaseSerializer]:
from authentik.enterprise.lifecycle.api.rules import LifecycleRuleSerializer
return LifecycleRuleSerializer
def _get_pk_field(self) -> Field:
model = self.content_type.model_class()
pk = model._meta.pk
while hasattr(pk, "target_field"):
pk = pk.target_field
return pk.__class__()
def get_objects(self) -> QuerySet:
qs = self.content_type.get_all_objects_for_this_type()
if self.object_id:
qs = qs.filter(pk=self.object_id)
else:
qs = qs.exclude(
pk__in=LifecycleRule.objects.filter(
content_type=self.content_type, object_id__isnull=False
).values_list(Cast("object_id", output_field=self._get_pk_field()), flat=True)
)
return qs
def _get_stale_iterations(self) -> QuerySet[LifecycleIteration]:
filter = ~Q(content_type=self.content_type)
if self.object_id:
filter = filter | ~Q(object_id=self.object_id)
filter = Q(state__in=(ReviewState.PENDING, ReviewState.OVERDUE)) & filter
return self.lifecycleiteration_set.filter(filter)
def _get_newly_overdue_iterations(self) -> QuerySet[LifecycleIteration]:
return self.lifecycleiteration_set.filter(
opened_on__lt=start_of_day(
timezone.now() + timedelta(days=1) - timedelta_from_string(self.grace_period)
),
state=ReviewState.PENDING,
)
def _get_newly_due_objects(self) -> QuerySet:
recent_iteration_ids = LifecycleIteration.objects.filter(
content_type=self.content_type,
object_id__isnull=False,
opened_on__gte=start_of_day(
timezone.now() + timedelta(days=1) - timedelta_from_string(self.interval)
),
).values_list(Cast("object_id", output_field=self._get_pk_field()), flat=True)
return self.get_objects().exclude(pk__in=recent_iteration_ids)
def apply(self):
self._get_stale_iterations().update(state=ReviewState.CANCELED)
for iteration in self._get_newly_overdue_iterations():
iteration.make_overdue()
for obj in self._get_newly_due_objects():
LifecycleIteration.start(content_type=self.content_type, object_id=obj.pk, rule=self)
def is_satisfied_for_iteration(self, iteration: LifecycleIteration) -> bool:
reviewers = self.reviewers.all()
if (
iteration.review_set.filter(reviewer__in=reviewers).distinct("reviewer").count()
< reviewers.count()
):
return False
if self.reviewer_groups.count() == 0:
return True
if self.min_reviewers_is_per_group:
for g in self.reviewer_groups.all():
if (
iteration.review_set.filter(
reviewer__groups__in=Group.objects.filter(pk=g.pk).with_descendants()
)
.distinct()
.count()
< self.min_reviewers
):
return False
return True
else:
return (
iteration.review_set.filter(
reviewer__groups__in=self.reviewer_groups.all().with_descendants()
)
.distinct()
.count()
>= self.min_reviewers
)
def get_reviewers(self) -> QuerySet[User]:
return User.objects.filter(
Q(id__in=self.reviewers.all().values_list("pk", flat=True))
| Q(groups__in=self.reviewer_groups.all().with_descendants())
).distinct()
def notify_reviewers(self, event: Event, severity: str):
from authentik.enterprise.lifecycle.tasks import send_notification
for transport in self.notification_transports.all():
for user in self.get_reviewers():
send_notification.send_with_options(
args=(transport.pk, event.pk, user.pk, severity),
rel_obj=transport,
)
if transport.send_once:
break
class ReviewState(models.TextChoices):
REVIEWED = "REVIEWED", _("Reviewed")
PENDING = "PENDING", _("Pending")
OVERDUE = "OVERDUE", _("Overdue")
CANCELED = "CANCELED", _("Canceled")
class LifecycleIteration(SerializerModel, ManagedModel):
id = models.UUIDField(primary_key=True, default=uuid4)
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.TextField(null=False)
object = GenericForeignKey("content_type", "object_id")
rule = models.ForeignKey(LifecycleRule, null=True, on_delete=models.SET_NULL)
state = models.CharField(max_length=10, choices=ReviewState, default=ReviewState.PENDING)
opened_on = models.DateTimeField(auto_now_add=True)
class Meta:
indexes = [models.Index(fields=["content_type", "opened_on"])]
@property
def serializer(self) -> type[BaseSerializer]:
from authentik.enterprise.lifecycle.api.iterations import LifecycleIterationSerializer
return LifecycleIterationSerializer
def _get_model_name(self) -> str:
return self.content_type.name.lower()
def _get_event_args(self) -> dict:
return {
"target": self.object,
"hyperlink": link_for_model(self.object),
"hyperlink_label": _(f"Go to {self._get_model_name()}"),
"lifecycle_iteration": self.id,
}
def initialize(self):
event = Event.new(
EventAction.REVIEW_INITIATED,
message=_(f"Access review is due for {self.content_type.name} {str(self.object)}"),
**self._get_event_args(),
)
event.save()
self.rule.notify_reviewers(event, NotificationSeverity.NOTICE)
def make_overdue(self):
self.state = ReviewState.OVERDUE
event = Event.new(
EventAction.REVIEW_OVERDUE,
message=_(f"Access review is overdue for {self.content_type.name} {str(self.object)}"),
**self._get_event_args(),
)
event.save()
self.rule.notify_reviewers(event, NotificationSeverity.ALERT)
self.save()
@staticmethod
def start(content_type: ContentType, object_id: str, rule: LifecycleRule) -> LifecycleIteration:
iteration = LifecycleIteration.objects.create(
content_type=content_type, object_id=object_id, rule=rule
)
iteration.initialize()
return iteration
def make_reviewed(self, request: HttpRequest):
self.state = ReviewState.REVIEWED
event = Event.new(
EventAction.REVIEW_COMPLETED,
message=_(f"Access review completed for {self.content_type.name} {str(self.object)}"),
**self._get_event_args(),
).from_http(request)
event.save()
self.rule.notify_reviewers(event, NotificationSeverity.NOTICE)
self.save()
def on_review(self, request: HttpRequest):
if self.state not in (ReviewState.PENDING, ReviewState.OVERDUE):
raise AssertionError("Review is not pending or overdue")
if self.rule.is_satisfied_for_iteration(self):
self.make_reviewed(request)
def user_can_review(self, user: User) -> bool:
if self.state not in (ReviewState.PENDING, ReviewState.OVERDUE):
return False
if self.review_set.filter(reviewer=user).exists():
return False
groups = self.rule.reviewer_groups.all()
if groups:
for group in groups:
if group.is_member(user):
return True
return False
else:
return user in self.rule.get_reviewers()
class Review(SerializerModel):
id = models.UUIDField(primary_key=True, default=uuid4)
iteration = models.ForeignKey(LifecycleIteration, on_delete=models.CASCADE)
reviewer = models.ForeignKey("authentik_core.User", on_delete=models.CASCADE)
timestamp = models.DateTimeField(auto_now_add=True)
note = models.TextField(null=True)
class Meta:
unique_together = [["iteration", "reviewer"]]
@property
def serializer(self) -> type[BaseSerializer]:
from authentik.enterprise.lifecycle.api.reviews import ReviewSerializer
return ReviewSerializer

View File

@@ -1,22 +0,0 @@
from django.db.models import Q
from django.db.models.signals import post_save, pre_delete
from django.dispatch import receiver
from authentik.enterprise.lifecycle.models import LifecycleRule, ReviewState
@receiver(post_save, sender=LifecycleRule)
def post_rule_save(sender, instance: LifecycleRule, created: bool, **_):
from authentik.enterprise.lifecycle.tasks import apply_lifecycle_rule
apply_lifecycle_rule.send_with_options(
args=(instance.id,),
rel_obj=instance,
)
@receiver(pre_delete, sender=LifecycleRule)
def pre_rule_delete(sender, instance: LifecycleRule, **_):
instance.lifecycleiteration_set.filter(
Q(state=ReviewState.PENDING) | Q(state=ReviewState.OVERDUE)
).update(state=ReviewState.CANCELED)

View File

@@ -1,45 +0,0 @@
from django.utils.translation import gettext_lazy as _
from dramatiq import actor
from authentik.core.models import User
from authentik.enterprise.lifecycle.models import LifecycleRule
from authentik.events.models import Event, Notification, NotificationTransport
@actor(description=_("Dispatch tasks to validate lifecycle rules."))
def apply_lifecycle_rules():
for rule in LifecycleRule.objects.all():
apply_lifecycle_rule.send_with_options(
args=(rule.id,),
rel_obj=rule,
)
@actor(description=_("Apply lifecycle rule."))
def apply_lifecycle_rule(rule_id: str):
rule = LifecycleRule.objects.filter(pk=rule_id).first()
if rule:
rule.apply()
@actor(description=_("Send lifecycle rule notification."))
def send_notification(transport_pk: int, event_pk: str, user_pk: int, severity: str):
event = Event.objects.filter(pk=event_pk).first()
if not event:
return
user = User.objects.filter(pk=user_pk).first()
if not user:
return
notification = Notification(
severity=severity,
body=event.summary,
event=event,
user=user,
hyperlink=event.hyperlink,
hyperlink_label=event.hyperlink_label,
)
transport = NotificationTransport.objects.filter(pk=transport_pk).first()
if not transport:
return
transport.send(notification)

View File

@@ -1,425 +0,0 @@
from django.contrib.contenttypes.models import ContentType
from django.urls import reverse
from rest_framework.test import APITestCase
from authentik.core.models import Application, Group
from authentik.core.tests.utils import create_test_admin_user, create_test_user
from authentik.enterprise.lifecycle.models import LifecycleIteration, LifecycleRule, ReviewState
from authentik.enterprise.reports.tests.utils import patch_license
from authentik.lib.generators import generate_id
@patch_license
class TestLifecycleRuleAPI(APITestCase):
def setUp(self):
self.user = create_test_admin_user()
self.client.force_login(self.user)
self.app = Application.objects.create(name=generate_id(), slug=generate_id())
self.content_type = ContentType.objects.get_for_model(Application)
self.reviewer_group = Group.objects.create(name=generate_id())
def test_list_rules(self):
rule = LifecycleRule.objects.create(
name=generate_id(),
content_type=self.content_type,
object_id=str(self.app.pk),
)
rule.reviewer_groups.add(self.reviewer_group)
response = self.client.get(reverse("authentik_api:lifecyclerule-list"))
self.assertEqual(response.status_code, 200)
self.assertGreaterEqual(len(response.data["results"]), 1)
def test_create_rule_with_reviewer_group(self):
response = self.client.post(
reverse("authentik_api:lifecyclerule-list"),
{
"name": generate_id(),
"content_type": f"{self.content_type.app_label}.{self.content_type.model}",
"object_id": str(self.app.pk),
"interval": "days=30",
"grace_period": "days=10",
"reviewer_groups": [str(self.reviewer_group.pk)],
"reviewers": [],
"min_reviewers": 1,
},
)
self.assertEqual(response.status_code, 201)
self.assertEqual(response.data["object_id"], str(self.app.pk))
self.assertEqual(response.data["interval"], "days=30")
def test_create_rule_with_explicit_reviewer(self):
reviewer = create_test_user()
response = self.client.post(
reverse("authentik_api:lifecyclerule-list"),
{
"name": generate_id(),
"content_type": f"{self.content_type.app_label}.{self.content_type.model}",
"object_id": str(self.app.pk),
"interval": "days=60",
"grace_period": "days=15",
"reviewer_groups": [],
"reviewers": [str(reviewer.uuid)],
"min_reviewers": 1,
},
)
self.assertEqual(response.status_code, 201)
self.assertIn(reviewer.uuid, response.data["reviewers"])
def test_create_rule_type_level(self):
response = self.client.post(
reverse("authentik_api:lifecyclerule-list"),
{
"name": generate_id(),
"content_type": f"{self.content_type.app_label}.{self.content_type.model}",
"object_id": None,
"interval": "days=90",
"grace_period": "days=30",
"reviewer_groups": [str(self.reviewer_group.pk)],
"reviewers": [],
"min_reviewers": 1,
},
)
self.assertEqual(response.status_code, 201)
self.assertIsNone(response.data["object_id"])
def test_create_rule_fails_without_reviewers(self):
response = self.client.post(
reverse("authentik_api:lifecyclerule-list"),
{
"name": generate_id(),
"content_type": f"{self.content_type.app_label}.{self.content_type.model}",
"object_id": str(self.app.pk),
"interval": "days=30",
"grace_period": "days=10",
"reviewer_groups": [],
"reviewers": [],
"min_reviewers": 1,
},
)
self.assertEqual(response.status_code, 400)
def test_create_rule_fails_grace_period_longer_than_interval(self):
response = self.client.post(
reverse("authentik_api:lifecyclerule-list"),
{
"name": generate_id(),
"content_type": f"{self.content_type.app_label}.{self.content_type.model}",
"object_id": str(self.app.pk),
"interval": "days=10",
"grace_period": "days=30",
"reviewer_groups": [str(self.reviewer_group.pk)],
"reviewers": [],
"min_reviewers": 1,
},
)
self.assertEqual(response.status_code, 400)
self.assertIn("grace_period", response.data)
def test_create_rule_fails_invalid_object_id(self):
response = self.client.post(
reverse("authentik_api:lifecyclerule-list"),
{
"name": generate_id(),
"content_type": f"{self.content_type.app_label}.{self.content_type.model}",
"object_id": "00000000-0000-0000-0000-000000000000",
"interval": "days=30",
"grace_period": "days=10",
"reviewer_groups": [str(self.reviewer_group.pk)],
"reviewers": [],
"min_reviewers": 1,
},
)
self.assertEqual(response.status_code, 400)
self.assertIn("object_id", response.data)
def test_retrieve_rule(self):
rule = LifecycleRule.objects.create(
name=generate_id(),
content_type=self.content_type,
object_id=str(self.app.pk),
)
rule.reviewer_groups.add(self.reviewer_group)
response = self.client.get(
reverse("authentik_api:lifecyclerule-detail", kwargs={"pk": rule.pk})
)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.data["id"], str(rule.pk))
def test_update_rule(self):
rule = LifecycleRule.objects.create(
name=generate_id(),
content_type=self.content_type,
object_id=str(self.app.pk),
interval="days=30",
)
rule.reviewer_groups.add(self.reviewer_group)
response = self.client.patch(
reverse("authentik_api:lifecyclerule-detail", kwargs={"pk": rule.pk}),
{"interval": "days=60"},
)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.data["interval"], "days=60")
def test_delete_rule(self):
rule = LifecycleRule.objects.create(
name=generate_id(),
content_type=self.content_type,
object_id=str(self.app.pk),
)
rule.reviewer_groups.add(self.reviewer_group)
response = self.client.delete(
reverse("authentik_api:lifecyclerule-detail", kwargs={"pk": rule.pk})
)
self.assertEqual(response.status_code, 204)
self.assertFalse(LifecycleRule.objects.filter(pk=rule.pk).exists())
@patch_license
class TestIterationAPI(APITestCase):
def setUp(self):
self.user = create_test_admin_user()
self.client.force_login(self.user)
self.app = Application.objects.create(name=generate_id(), slug=generate_id())
self.content_type = ContentType.objects.get_for_model(Application)
self.reviewer_group = Group.objects.create(name=generate_id())
self.reviewer_group.users.add(self.user)
def test_open_iterations(self):
rule = LifecycleRule.objects.create(
name=generate_id(),
content_type=self.content_type,
object_id=str(self.app.pk),
)
rule.reviewer_groups.add(self.reviewer_group)
response = self.client.get(reverse("authentik_api:lifecycleiteration-open-iterations"))
self.assertEqual(response.status_code, 200)
self.assertGreaterEqual(len(response.data["results"]), 1)
for iteration in response.data["results"]:
self.assertEqual(iteration["state"], ReviewState.PENDING)
def test_open_iterations_filter_user_is_reviewer(self):
rule = LifecycleRule.objects.create(
name=generate_id(),
content_type=self.content_type,
object_id=str(self.app.pk),
)
rule.reviewer_groups.add(self.reviewer_group)
response = self.client.get(
reverse("authentik_api:lifecycleiteration-open-iterations"),
{"user_is_reviewer": "true"},
)
self.assertEqual(response.status_code, 200)
# User is in reviewer_group, so should see the iteration
self.assertGreaterEqual(len(response.data["results"]), 1)
def test_latest_iteration(self):
rule = LifecycleRule.objects.create(
name=generate_id(),
content_type=self.content_type,
object_id=str(self.app.pk),
)
rule.reviewer_groups.add(self.reviewer_group)
response = self.client.get(
reverse(
"authentik_api:lifecycleiteration-latest-iteration",
kwargs={
"content_type": f"{self.content_type.app_label}.{self.content_type.model}",
"object_id": str(self.app.pk),
},
)
)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.data["object_id"], str(self.app.pk))
def test_latest_iteration_not_found(self):
response = self.client.get(
reverse(
"authentik_api:lifecycleiteration-latest-iteration",
kwargs={
"content_type": f"{self.content_type.app_label}.{self.content_type.model}",
"object_id": "00000000-0000-0000-0000-000000000000",
},
)
)
self.assertEqual(response.status_code, 404)
def test_iteration_includes_user_can_review(self):
rule = LifecycleRule.objects.create(
name=generate_id(),
content_type=self.content_type,
object_id=str(self.app.pk),
)
rule.reviewer_groups.add(self.reviewer_group)
response = self.client.get(reverse("authentik_api:lifecycleiteration-open-iterations"))
self.assertEqual(response.status_code, 200)
self.assertGreaterEqual(len(response.data["results"]), 1)
# user_can_review should be present
self.assertIn("user_can_review", response.data["results"][0])
@patch_license
class TestReviewAPI(APITestCase):
def setUp(self):
self.user = create_test_admin_user()
self.client.force_login(self.user)
self.app = Application.objects.create(name=generate_id(), slug=generate_id())
self.content_type = ContentType.objects.get_for_model(Application)
self.reviewer_group = Group.objects.create(name=generate_id())
self.reviewer_group.users.add(self.user)
def test_create_review(self):
rule = LifecycleRule.objects.create(
name=generate_id(),
content_type=self.content_type,
object_id=str(self.app.pk),
min_reviewers=1,
)
rule.reviewer_groups.add(self.reviewer_group)
# Get the auto-created iteration
iteration = LifecycleIteration.objects.get(
content_type=self.content_type, object_id=str(self.app.pk), rule=rule
)
response = self.client.post(
reverse("authentik_api:review-list"),
{
"iteration": str(iteration.pk),
"note": "Reviewed and approved",
},
)
self.assertEqual(response.status_code, 201)
self.assertEqual(response.data["iteration"], iteration.pk)
self.assertEqual(response.data["note"], "Reviewed and approved")
self.assertEqual(response.data["reviewer"]["pk"], self.user.pk)
def test_create_review_completes_iteration(self):
rule = LifecycleRule.objects.create(
name=generate_id(),
content_type=self.content_type,
object_id=str(self.app.pk),
min_reviewers=1,
)
rule.reviewer_groups.add(self.reviewer_group)
iteration = LifecycleIteration.objects.get(
content_type=self.content_type, object_id=str(self.app.pk), rule=rule
)
self.assertEqual(iteration.state, ReviewState.PENDING)
response = self.client.post(
reverse("authentik_api:review-list"),
{
"iteration": str(iteration.pk),
},
)
self.assertEqual(response.status_code, 201)
iteration.refresh_from_db()
self.assertEqual(iteration.state, ReviewState.REVIEWED)
def test_create_review_sets_reviewer_from_request(self):
rule = LifecycleRule.objects.create(
name=generate_id(),
content_type=self.content_type,
object_id=str(self.app.pk),
min_reviewers=1,
)
rule.reviewer_groups.add(self.reviewer_group)
iteration = LifecycleIteration.objects.get(
content_type=self.content_type, object_id=str(self.app.pk), rule=rule
)
response = self.client.post(
reverse("authentik_api:review-list"),
{
"iteration": str(iteration.pk),
},
)
self.assertEqual(response.status_code, 201)
# Reviewer should be the logged-in user
self.assertEqual(response.data["reviewer"]["pk"], self.user.pk)
def test_non_reviewer_cannot_review(self):
other_group = Group.objects.create(name=generate_id())
other_user = create_test_user()
other_group.users.add(other_user)
rule = LifecycleRule.objects.create(
name=generate_id(),
content_type=self.content_type,
object_id=str(self.app.pk),
min_reviewers=1,
)
rule.reviewer_groups.add(other_group)
iteration = LifecycleIteration.objects.get(
content_type=self.content_type, object_id=str(self.app.pk), rule=rule
)
# Current user is not in the reviewer group
self.assertFalse(iteration.user_can_review(self.user))
def test_non_reviewer_review_via_api_rejected(self):
other_group = Group.objects.create(name=generate_id())
other_user = create_test_user()
other_group.users.add(other_user)
rule = LifecycleRule.objects.create(
name=generate_id(),
content_type=self.content_type,
object_id=str(self.app.pk),
min_reviewers=1,
)
rule.reviewer_groups.add(other_group)
iteration = LifecycleIteration.objects.get(
content_type=self.content_type, object_id=str(self.app.pk), rule=rule
)
# Current user (self.user) is NOT in the reviewer group
response = self.client.post(
reverse("authentik_api:review-list"),
{"iteration": str(iteration.pk)},
)
self.assertEqual(response.status_code, 400)
def test_duplicate_review_via_api_rejected(self):
rule = LifecycleRule.objects.create(
name=generate_id(),
content_type=self.content_type,
object_id=str(self.app.pk),
min_reviewers=2,
)
rule.reviewer_groups.add(self.reviewer_group)
iteration = LifecycleIteration.objects.get(
content_type=self.content_type, object_id=str(self.app.pk), rule=rule
)
# First review should succeed
response = self.client.post(
reverse("authentik_api:review-list"),
{"iteration": str(iteration.pk)},
)
self.assertEqual(response.status_code, 201)
# Second review by same user should be rejected
response = self.client.post(
reverse("authentik_api:review-list"),
{"iteration": str(iteration.pk)},
)
self.assertEqual(response.status_code, 400)

View File

@@ -1,845 +0,0 @@
import datetime as dt
from datetime import timedelta
from unittest.mock import patch
from django.contrib.contenttypes.models import ContentType
from django.test import RequestFactory, TestCase
from django.utils import timezone
from authentik.core.models import Application, Group
from authentik.core.tests.utils import create_test_user
from authentik.enterprise.lifecycle.models import (
LifecycleIteration,
LifecycleRule,
Review,
ReviewState,
)
from authentik.events.models import (
Event,
EventAction,
NotificationSeverity,
NotificationTransport,
)
from authentik.lib.generators import generate_id
from authentik.rbac.models import Role
class TestLifecycleModels(TestCase):
def setUp(self):
self.factory = RequestFactory()
def _get_request(self):
return self.factory.get("/")
def _create_object(self, model):
if model is Application:
return Application.objects.create(name=generate_id(), slug=generate_id())
if model is Role:
return Role.objects.create(name=generate_id())
if model is Group:
return Group.objects.create(name=generate_id())
raise AssertionError(f"Unsupported model {model}")
def _create_rule_for_object(self, obj, **kwargs) -> LifecycleRule:
content_type = ContentType.objects.get_for_model(obj)
return LifecycleRule.objects.create(
name=generate_id(),
content_type=content_type,
object_id=str(obj.pk),
**kwargs,
)
def _create_rule_for_type(self, model, **kwargs) -> LifecycleRule:
content_type = ContentType.objects.get_for_model(model)
return LifecycleRule.objects.create(
name=generate_id(),
content_type=content_type,
object_id=None,
**kwargs,
)
def test_iteration_start_supported_objects(self):
"""Ensure iterations are automatically started for applications, roles, and groups."""
for model in (Application, Role, Group):
with self.subTest(model=model.__name__):
obj = self._create_object(model)
content_type = ContentType.objects.get_for_model(obj)
before_events = Event.objects.filter(action=EventAction.REVIEW_INITIATED).count()
rule = self._create_rule_for_object(obj)
# Verify iteration was created automatically
iteration = LifecycleIteration.objects.get(
content_type=content_type, object_id=str(obj.pk), rule=rule
)
self.assertEqual(iteration.state, ReviewState.PENDING)
self.assertEqual(iteration.object, obj)
self.assertEqual(iteration.rule, rule)
self.assertEqual(
Event.objects.filter(action=EventAction.REVIEW_INITIATED).count(),
before_events + 1,
)
def test_review_requires_all_explicit_reviewers(self):
obj = Group.objects.create(name=generate_id())
rule = self._create_rule_for_object(obj)
reviewer_one = create_test_user()
reviewer_two = create_test_user()
rule.reviewers.add(reviewer_one, reviewer_two)
content_type = ContentType.objects.get_for_model(obj)
iteration = LifecycleIteration.objects.get(
content_type=content_type, object_id=str(obj.pk), rule=rule
)
request = self._get_request()
Review.objects.create(iteration=iteration, reviewer=reviewer_one)
iteration.on_review(request)
iteration.refresh_from_db()
self.assertEqual(iteration.state, ReviewState.PENDING)
Review.objects.create(iteration=iteration, reviewer=reviewer_two)
iteration.on_review(request)
iteration.refresh_from_db()
self.assertEqual(iteration.state, ReviewState.REVIEWED)
self.assertTrue(Event.objects.filter(action=EventAction.REVIEW_COMPLETED).exists())
def test_review_min_reviewers_from_groups(self):
"""Group-based reviews complete once the minimum number of reviewers review."""
obj = Application.objects.create(name=generate_id(), slug=generate_id())
rule = self._create_rule_for_object(obj, min_reviewers=2)
reviewer_group = Group.objects.create(name=generate_id())
reviewer_one = create_test_user()
reviewer_two = create_test_user()
reviewer_group.users.add(reviewer_one, reviewer_two)
rule.reviewer_groups.add(reviewer_group)
content_type = ContentType.objects.get_for_model(obj)
iteration = LifecycleIteration.objects.get(
content_type=content_type, object_id=str(obj.pk), rule=rule
)
request = self._get_request()
Review.objects.create(iteration=iteration, reviewer=reviewer_one)
iteration.on_review(request)
iteration.refresh_from_db()
self.assertEqual(iteration.state, ReviewState.PENDING)
Review.objects.create(iteration=iteration, reviewer=reviewer_two)
iteration.on_review(request)
iteration.refresh_from_db()
self.assertEqual(iteration.state, ReviewState.REVIEWED)
def test_review_explicit_and_group_reviewers(self):
"""Reviews require both explicit reviewers AND min_reviewers from groups."""
obj = Application.objects.create(name=generate_id(), slug=generate_id())
rule = self._create_rule_for_object(obj, min_reviewers=1)
reviewer_group = Group.objects.create(name=generate_id())
group_member = create_test_user()
reviewer_group.users.add(group_member)
rule.reviewer_groups.add(reviewer_group)
explicit_reviewer = create_test_user()
rule.reviewers.add(explicit_reviewer)
content_type = ContentType.objects.get_for_model(obj)
iteration = LifecycleIteration.objects.get(
content_type=content_type, object_id=str(obj.pk), rule=rule
)
request = self._get_request()
# Only group member reviews - not satisfied (explicit reviewer missing)
Review.objects.create(iteration=iteration, reviewer=group_member)
iteration.on_review(request)
iteration.refresh_from_db()
self.assertEqual(iteration.state, ReviewState.PENDING)
# Explicit reviewer reviews - now satisfied
Review.objects.create(iteration=iteration, reviewer=explicit_reviewer)
iteration.on_review(request)
iteration.refresh_from_db()
self.assertEqual(iteration.state, ReviewState.REVIEWED)
def test_review_min_reviewers_per_group(self):
obj = Application.objects.create(name=generate_id(), slug=generate_id())
rule = self._create_rule_for_object(obj, min_reviewers=1, min_reviewers_is_per_group=True)
group_one = Group.objects.create(name=generate_id())
group_two = Group.objects.create(name=generate_id())
member_group_one = create_test_user()
member_group_two = create_test_user()
group_one.users.add(member_group_one)
group_two.users.add(member_group_two)
rule.reviewer_groups.add(group_one, group_two)
content_type = ContentType.objects.get_for_model(obj)
iteration = LifecycleIteration.objects.get(
content_type=content_type, object_id=str(obj.pk), rule=rule
)
request = self._get_request()
# Only member from group_one reviews - not satisfied (need member from each group)
Review.objects.create(iteration=iteration, reviewer=member_group_one)
iteration.on_review(request)
iteration.refresh_from_db()
self.assertEqual(iteration.state, ReviewState.PENDING)
# Member from group_two reviews - now satisfied
Review.objects.create(iteration=iteration, reviewer=member_group_two)
iteration.on_review(request)
iteration.refresh_from_db()
self.assertEqual(iteration.state, ReviewState.REVIEWED)
def test_review_reviewers_from_child_groups(self):
obj = Application.objects.create(name=generate_id(), slug=generate_id())
rule = self._create_rule_for_object(obj, min_reviewers=1)
parent_group = Group.objects.create(name=generate_id())
child_group = Group.objects.create(name=generate_id())
child_group.parents.add(parent_group)
child_member = create_test_user()
child_group.users.add(child_member)
rule.reviewer_groups.add(parent_group)
content_type = ContentType.objects.get_for_model(obj)
iteration = LifecycleIteration.objects.get(
content_type=content_type, object_id=str(obj.pk), rule=rule
)
request = self._get_request()
# Child group member should be able to review
self.assertTrue(iteration.user_can_review(child_member))
Review.objects.create(iteration=iteration, reviewer=child_member)
iteration.on_review(request)
iteration.refresh_from_db()
self.assertEqual(iteration.state, ReviewState.REVIEWED)
def test_review_reviewers_from_nested_child_groups(self):
obj = Application.objects.create(name=generate_id(), slug=generate_id())
rule = self._create_rule_for_object(obj, min_reviewers=2)
grandparent = Group.objects.create(name=generate_id())
parent = Group.objects.create(name=generate_id())
child = Group.objects.create(name=generate_id())
parent.parents.add(grandparent)
child.parents.add(parent)
parent_member = create_test_user()
child_member = create_test_user()
parent.users.add(parent_member)
child.users.add(child_member)
rule.reviewer_groups.add(grandparent)
content_type = ContentType.objects.get_for_model(obj)
iteration = LifecycleIteration.objects.get(
content_type=content_type, object_id=str(obj.pk), rule=rule
)
request = self._get_request()
# Both nested members should be able to review
self.assertTrue(iteration.user_can_review(parent_member))
self.assertTrue(iteration.user_can_review(child_member))
Review.objects.create(iteration=iteration, reviewer=parent_member)
iteration.on_review(request)
iteration.refresh_from_db()
self.assertEqual(iteration.state, ReviewState.PENDING)
Review.objects.create(iteration=iteration, reviewer=child_member)
iteration.on_review(request)
iteration.refresh_from_db()
self.assertEqual(iteration.state, ReviewState.REVIEWED)
def test_notify_reviewers_send_once(self):
obj = Group.objects.create(name=generate_id())
rule = self._create_rule_for_object(obj)
reviewer_one = create_test_user()
reviewer_two = create_test_user()
rule.reviewers.add(reviewer_one, reviewer_two)
transport_once = NotificationTransport.objects.create(
name=generate_id(),
send_once=True,
)
transport_all = NotificationTransport.objects.create(
name=generate_id(),
send_once=False,
)
rule.notification_transports.add(transport_once, transport_all)
event = Event.new(EventAction.REVIEW_INITIATED, target=obj)
event.save()
with patch(
"authentik.enterprise.lifecycle.tasks.send_notification.send_with_options"
) as send_with_options:
rule.notify_reviewers(event, NotificationSeverity.NOTICE)
reviewer_pks = {reviewer_one.pk, reviewer_two.pk}
self.assertEqual(send_with_options.call_count, len(reviewer_pks) + 1)
calls = [call.kwargs["args"] for call in send_with_options.call_args_list]
once_calls = [args for args in calls if args[0] == transport_once.pk]
all_calls = [args for args in calls if args[0] == transport_all.pk]
self.assertEqual(len(once_calls), 1)
self.assertEqual(len(all_calls), len(reviewer_pks))
self.assertIn(once_calls[0][2], reviewer_pks)
self.assertEqual({args[2] for args in all_calls}, reviewer_pks)
def test_apply_marks_overdue_and_opens_due_reviews(self):
app_one = Application.objects.create(name=generate_id(), slug=generate_id())
app_two = Application.objects.create(name=generate_id(), slug=generate_id())
content_type = ContentType.objects.get_for_model(Application)
rule_overdue = LifecycleRule.objects.create(
name=generate_id(),
content_type=content_type,
object_id=str(app_one.pk),
interval="days=365",
grace_period="days=10",
)
# Get the automatically created iteration and backdate it past the grace period
iteration = LifecycleIteration.objects.get(
content_type=content_type, object_id=str(app_one.pk), rule=rule_overdue
)
LifecycleIteration.objects.filter(pk=iteration.pk).update(
opened_on=(timezone.now() - timedelta(days=20))
)
# Apply again to trigger overdue logic
rule_overdue.apply()
iteration.refresh_from_db()
self.assertEqual(iteration.state, ReviewState.OVERDUE)
self.assertEqual(
LifecycleIteration.objects.filter(
content_type=content_type, object_id=str(app_one.pk)
).count(),
1,
)
LifecycleRule.objects.create(
name=generate_id(),
content_type=content_type,
object_id=str(app_two.pk),
interval="days=30",
grace_period="days=10",
)
self.assertEqual(
LifecycleIteration.objects.filter(
content_type=content_type, object_id=str(app_two.pk)
).count(),
1,
)
new_iteration = LifecycleIteration.objects.get(
content_type=content_type, object_id=str(app_two.pk)
)
self.assertEqual(new_iteration.state, ReviewState.PENDING)
def test_apply_idempotent(self):
app_due = Application.objects.create(name=generate_id(), slug=generate_id())
app_overdue = Application.objects.create(name=generate_id(), slug=generate_id())
content_type = ContentType.objects.get_for_model(Application)
initiated_before = Event.objects.filter(action=EventAction.REVIEW_INITIATED).count()
overdue_before = Event.objects.filter(action=EventAction.REVIEW_OVERDUE).count()
rule_due = LifecycleRule.objects.create(
name=generate_id(),
content_type=content_type,
object_id=str(app_due.pk),
interval="days=30",
grace_period="days=30",
)
reviewer = create_test_user()
rule_due.reviewers.add(reviewer)
transport = NotificationTransport.objects.create(name=generate_id())
rule_due.notification_transports.add(transport)
rule_overdue = LifecycleRule.objects.create(
name=generate_id(),
content_type=content_type,
object_id=str(app_overdue.pk),
interval="days=365",
grace_period="days=10",
)
overdue_iteration = LifecycleIteration.objects.get(
content_type=content_type, object_id=str(app_overdue.pk), rule=rule_overdue
)
LifecycleIteration.objects.filter(pk=overdue_iteration.pk).update(
opened_on=(timezone.now() - timedelta(days=20))
)
# Apply overdue rule to mark iteration as overdue
rule_overdue.apply()
due_iteration = LifecycleIteration.objects.get(
content_type=content_type, object_id=str(app_due.pk)
)
overdue_iteration.refresh_from_db()
self.assertEqual(due_iteration.state, ReviewState.PENDING)
self.assertEqual(overdue_iteration.state, ReviewState.OVERDUE)
initiated_after_first = Event.objects.filter(action=EventAction.REVIEW_INITIATED).count()
overdue_after_first = Event.objects.filter(action=EventAction.REVIEW_OVERDUE).count()
# Both rules created iterations on save
self.assertEqual(initiated_after_first, initiated_before + 2)
self.assertEqual(overdue_after_first, overdue_before + 1)
# Apply again - should be idempotent
rule_due.apply()
rule_overdue.apply()
due_iteration.refresh_from_db()
overdue_iteration.refresh_from_db()
self.assertEqual(due_iteration.state, ReviewState.PENDING)
self.assertEqual(overdue_iteration.state, ReviewState.OVERDUE)
self.assertEqual(
Event.objects.filter(action=EventAction.REVIEW_INITIATED).count(),
initiated_after_first,
)
self.assertEqual(
Event.objects.filter(action=EventAction.REVIEW_OVERDUE).count(),
overdue_after_first,
)
def test_rule_matches_entire_type(self):
"""A rule with object_id=None matches all objects of that type."""
app_one = Application.objects.create(name=generate_id(), slug=generate_id())
app_two = Application.objects.create(name=generate_id(), slug=generate_id())
content_type = ContentType.objects.get_for_model(Application)
rule = LifecycleRule.objects.create(
name=generate_id(),
content_type=content_type,
object_id=None,
interval="days=30",
grace_period="days=10",
)
objects = list(rule.get_objects())
self.assertIn(app_one, objects)
self.assertIn(app_two, objects)
def test_rule_type_excludes_objects_with_specific_rules(self):
app_with_rule = Application.objects.create(name=generate_id(), slug=generate_id())
app_without_rule = Application.objects.create(name=generate_id(), slug=generate_id())
content_type = ContentType.objects.get_for_model(Application)
# Create a specific rule for app_with_rule
LifecycleRule.objects.create(
name=generate_id(),
content_type=content_type,
object_id=str(app_with_rule.pk),
interval="days=30",
)
# Create a type-level rule
type_rule = LifecycleRule.objects.create(
name=generate_id(),
content_type=content_type,
object_id=None,
interval="days=60",
)
objects = list(type_rule.get_objects())
self.assertNotIn(app_with_rule, objects)
self.assertIn(app_without_rule, objects)
def test_rule_type_apply_creates_iterations_for_all_objects(self):
app_one = Application.objects.create(name=generate_id(), slug=generate_id())
app_two = Application.objects.create(name=generate_id(), slug=generate_id())
content_type = ContentType.objects.get_for_model(Application)
LifecycleRule.objects.create(
name=generate_id(),
content_type=content_type,
object_id=None,
interval="days=30",
grace_period="days=10",
)
self.assertTrue(
LifecycleIteration.objects.filter(
content_type=content_type, object_id=str(app_one.pk)
).exists()
)
self.assertTrue(
LifecycleIteration.objects.filter(
content_type=content_type, object_id=str(app_two.pk)
).exists()
)
def test_delete_rule_cancels_open_iterations(self):
obj = Application.objects.create(name=generate_id(), slug=generate_id())
rule = self._create_rule_for_object(obj)
content_type = ContentType.objects.get_for_model(obj)
pending_iteration = LifecycleIteration.objects.get(
content_type=content_type, object_id=str(obj.pk), rule=rule
)
self.assertEqual(pending_iteration.state, ReviewState.PENDING)
overdue_iteration = LifecycleIteration.objects.create(
content_type=content_type,
object_id=str(obj.pk),
rule=rule,
state=ReviewState.OVERDUE,
)
reviewed_iteration = LifecycleIteration.objects.create(
content_type=content_type,
object_id=str(obj.pk),
rule=rule,
state=ReviewState.REVIEWED,
)
rule.delete()
pending_iteration.refresh_from_db()
overdue_iteration.refresh_from_db()
reviewed_iteration.refresh_from_db()
self.assertEqual(pending_iteration.state, ReviewState.CANCELED)
self.assertEqual(overdue_iteration.state, ReviewState.CANCELED)
self.assertEqual(reviewed_iteration.state, ReviewState.REVIEWED) # Not affected
def test_update_rule_target_cancels_stale_iterations(self):
app_one = Application.objects.create(name=generate_id(), slug=generate_id())
app_two = Application.objects.create(name=generate_id(), slug=generate_id())
content_type = ContentType.objects.get_for_model(Application)
rule = LifecycleRule.objects.create(
name=generate_id(),
content_type=content_type,
object_id=str(app_one.pk),
interval="days=30",
)
iteration_for_app_one = LifecycleIteration.objects.get(
content_type=content_type, object_id=str(app_one.pk), rule=rule
)
self.assertEqual(iteration_for_app_one.state, ReviewState.PENDING)
# Change rule target to app_two - save() triggers apply() which cancels stale iterations
rule.object_id = str(app_two.pk)
rule.save()
iteration_for_app_one.refresh_from_db()
self.assertEqual(iteration_for_app_one.state, ReviewState.CANCELED)
def test_update_rule_content_type_cancels_stale_iterations(self):
app = Application.objects.create(name=generate_id(), slug=generate_id())
group = Group.objects.create(name=generate_id())
app_content_type = ContentType.objects.get_for_model(Application)
group_content_type = ContentType.objects.get_for_model(Group)
# Creating rule triggers automatic apply() which creates a iteration for app
rule = LifecycleRule.objects.create(
name=generate_id(),
content_type=app_content_type,
object_id=str(app.pk),
interval="days=30",
)
iteration = LifecycleIteration.objects.get(
content_type=app_content_type, object_id=str(app.pk), rule=rule
)
self.assertEqual(iteration.state, ReviewState.PENDING)
# Change content type to Group - save() triggers apply() which cancels stale iterations
rule.content_type = group_content_type
rule.object_id = str(group.pk)
rule.save()
iteration.refresh_from_db()
self.assertEqual(iteration.state, ReviewState.CANCELED)
def test_user_can_review_checks_group_hierarchy(self):
obj = Application.objects.create(name=generate_id(), slug=generate_id())
rule = self._create_rule_for_object(obj)
parent_group = Group.objects.create(name=generate_id())
child_group = Group.objects.create(name=generate_id())
child_group.parents.add(parent_group)
parent_member = create_test_user()
child_member = create_test_user()
non_member = create_test_user()
parent_group.users.add(parent_member)
child_group.users.add(child_member)
rule.reviewer_groups.add(parent_group)
content_type = ContentType.objects.get_for_model(obj)
# iteration is created automatically when rule is saved
iteration = LifecycleIteration.objects.get(
content_type=content_type, object_id=str(obj.pk), rule=rule
)
self.assertTrue(iteration.user_can_review(parent_member))
self.assertTrue(iteration.user_can_review(child_member))
self.assertFalse(iteration.user_can_review(non_member))
def test_user_cannot_review_twice(self):
obj = Application.objects.create(name=generate_id(), slug=generate_id())
rule = self._create_rule_for_object(obj)
reviewer = create_test_user()
rule.reviewers.add(reviewer)
content_type = ContentType.objects.get_for_model(obj)
# iteration is created automatically when rule is saved
iteration = LifecycleIteration.objects.get(
content_type=content_type, object_id=str(obj.pk), rule=rule
)
self.assertTrue(iteration.user_can_review(reviewer))
Review.objects.create(iteration=iteration, reviewer=reviewer)
self.assertFalse(iteration.user_can_review(reviewer))
def test_user_cannot_review_completed_iteration(self):
obj = Application.objects.create(name=generate_id(), slug=generate_id())
rule = self._create_rule_for_object(obj)
reviewer = create_test_user()
rule.reviewers.add(reviewer)
content_type = ContentType.objects.get_for_model(obj)
# Get the automatically created pending iteration and test with different states
iteration = LifecycleIteration.objects.get(
content_type=content_type, object_id=str(obj.pk), rule=rule
)
for state in (ReviewState.REVIEWED, ReviewState.CANCELED):
iteration.state = state
iteration.save()
self.assertFalse(iteration.user_can_review(reviewer))
def test_get_reviewers_includes_child_group_members(self):
obj = Application.objects.create(name=generate_id(), slug=generate_id())
rule = self._create_rule_for_object(obj)
parent_group = Group.objects.create(name=generate_id())
child_group = Group.objects.create(name=generate_id())
child_group.parents.add(parent_group)
parent_member = create_test_user()
child_member = create_test_user()
parent_group.users.add(parent_member)
child_group.users.add(child_member)
rule.reviewer_groups.add(parent_group)
reviewers = list(rule.get_reviewers())
self.assertIn(parent_member, reviewers)
self.assertIn(child_member, reviewers)
def test_get_reviewers_includes_explicit_reviewers(self):
obj = Application.objects.create(name=generate_id(), slug=generate_id())
rule = self._create_rule_for_object(obj)
explicit_reviewer = create_test_user()
rule.reviewers.add(explicit_reviewer)
group = Group.objects.create(name=generate_id())
group_member = create_test_user()
group.users.add(group_member)
rule.reviewer_groups.add(group)
reviewers = list(rule.get_reviewers())
self.assertIn(explicit_reviewer, reviewers)
self.assertIn(group_member, reviewers)
class TestLifecycleDateBoundaries(TestCase):
"""Verify that start_of_day normalization ensures correct overdue/due
detection regardless of exact task execution time within a day.
The daily task may run at any point during the day. The start_of_day
normalization in _get_newly_overdue_iterations and _get_newly_due_objects
ensures that the boundary is always at midnight, so millisecond variations
in task execution time do not affect results."""
def _create_rule_and_iteration(self, grace_period="days=1", interval="days=365"):
app = Application.objects.create(name=generate_id(), slug=generate_id())
content_type = ContentType.objects.get_for_model(Application)
rule = LifecycleRule.objects.create(
name=generate_id(),
content_type=content_type,
object_id=str(app.pk),
interval=interval,
grace_period=grace_period,
)
iteration = LifecycleIteration.objects.get(
content_type=content_type, object_id=str(app.pk), rule=rule
)
return app, rule, iteration
def test_overdue_iteration_opened_yesterday(self):
"""grace_period=1 day: iteration opened yesterday at any time is overdue today."""
_, rule, iteration = self._create_rule_and_iteration(grace_period="days=1")
fixed_now = dt.datetime(2025, 6, 15, 14, 30, 0, tzinfo=dt.UTC)
for opened_on in [
dt.datetime(2025, 6, 14, 0, 0, 0, tzinfo=dt.UTC),
dt.datetime(2025, 6, 14, 12, 0, 0, tzinfo=dt.UTC),
dt.datetime(2025, 6, 14, 23, 59, 59, 999999, tzinfo=dt.UTC),
]:
with self.subTest(opened_on=opened_on):
LifecycleIteration.objects.filter(pk=iteration.pk).update(
opened_on=opened_on, state=ReviewState.PENDING
)
with patch("django.utils.timezone.now", return_value=fixed_now):
self.assertIn(iteration, list(rule._get_newly_overdue_iterations()))
def test_not_overdue_iteration_opened_today(self):
"""grace_period=1 day: iteration opened today at any time is NOT overdue."""
_, rule, iteration = self._create_rule_and_iteration(grace_period="days=1")
fixed_now = dt.datetime(2025, 6, 15, 14, 30, 0, tzinfo=dt.UTC)
for opened_on in [
dt.datetime(2025, 6, 15, 0, 0, 0, tzinfo=dt.UTC),
dt.datetime(2025, 6, 15, 14, 30, 0, tzinfo=dt.UTC),
dt.datetime(2025, 6, 15, 23, 59, 59, 999999, tzinfo=dt.UTC),
]:
with self.subTest(opened_on=opened_on):
LifecycleIteration.objects.filter(pk=iteration.pk).update(
opened_on=opened_on, state=ReviewState.PENDING
)
with patch("django.utils.timezone.now", return_value=fixed_now):
self.assertNotIn(iteration, list(rule._get_newly_overdue_iterations()))
def test_overdue_independent_of_task_execution_time(self):
"""Overdue detection gives the same result whether the task runs at 00:00:01 or 23:59:59."""
_, rule, iteration = self._create_rule_and_iteration(grace_period="days=1")
opened_on = dt.datetime(2025, 6, 14, 18, 0, 0, tzinfo=dt.UTC)
LifecycleIteration.objects.filter(pk=iteration.pk).update(
opened_on=opened_on, state=ReviewState.PENDING
)
for task_time in [
dt.datetime(2025, 6, 15, 0, 0, 1, tzinfo=dt.UTC),
dt.datetime(2025, 6, 15, 12, 0, 0, tzinfo=dt.UTC),
dt.datetime(2025, 6, 15, 23, 59, 59, tzinfo=dt.UTC),
]:
with self.subTest(task_time=task_time):
with patch("django.utils.timezone.now", return_value=task_time):
self.assertIn(iteration, list(rule._get_newly_overdue_iterations()))
def test_overdue_boundary_multi_day_grace_period(self):
"""grace_period=30 days: overdue after 30 full days, not after 29."""
_, rule, iteration = self._create_rule_and_iteration(grace_period="days=30")
fixed_now = dt.datetime(2025, 6, 15, 14, 30, 0, tzinfo=dt.UTC)
# Opened 30 days ago (May 16), should go overdue
LifecycleIteration.objects.filter(pk=iteration.pk).update(
opened_on=dt.datetime(2025, 5, 16, 12, 0, 0, tzinfo=dt.UTC),
state=ReviewState.PENDING,
)
with patch("django.utils.timezone.now", return_value=fixed_now):
self.assertIn(iteration, list(rule._get_newly_overdue_iterations()))
# Opened 29 days ago (May 17), should NOT go overdue
LifecycleIteration.objects.filter(pk=iteration.pk).update(
opened_on=dt.datetime(2025, 5, 17, 12, 0, 0, tzinfo=dt.UTC),
state=ReviewState.PENDING,
)
with patch("django.utils.timezone.now", return_value=fixed_now):
self.assertNotIn(iteration, list(rule._get_newly_overdue_iterations()))
def test_due_object_iteration_opened_yesterday(self):
"""interval=1 day: object with iteration opened yesterday is due for a new review."""
app, rule, iteration = self._create_rule_and_iteration(interval="days=1")
fixed_now = dt.datetime(2025, 6, 15, 14, 30, 0, tzinfo=dt.UTC)
for opened_on in [
dt.datetime(2025, 6, 14, 0, 0, 0, tzinfo=dt.UTC),
dt.datetime(2025, 6, 14, 12, 0, 0, tzinfo=dt.UTC),
dt.datetime(2025, 6, 14, 23, 59, 59, 999999, tzinfo=dt.UTC),
]:
with self.subTest(opened_on=opened_on):
LifecycleIteration.objects.filter(pk=iteration.pk).update(opened_on=opened_on)
with patch("django.utils.timezone.now", return_value=fixed_now):
self.assertIn(app, list(rule._get_newly_due_objects()))
def test_not_due_object_iteration_opened_today(self):
"""interval=1 day: object with iteration opened today is NOT due."""
app, rule, iteration = self._create_rule_and_iteration(interval="days=1")
fixed_now = dt.datetime(2025, 6, 15, 14, 30, 0, tzinfo=dt.UTC)
for opened_on in [
dt.datetime(2025, 6, 15, 0, 0, 0, tzinfo=dt.UTC),
dt.datetime(2025, 6, 15, 14, 30, 0, tzinfo=dt.UTC),
dt.datetime(2025, 6, 15, 23, 59, 59, 999999, tzinfo=dt.UTC),
]:
with self.subTest(opened_on=opened_on):
LifecycleIteration.objects.filter(pk=iteration.pk).update(opened_on=opened_on)
with patch("django.utils.timezone.now", return_value=fixed_now):
self.assertNotIn(app, list(rule._get_newly_due_objects()))
def test_due_independent_of_task_execution_time(self):
"""Due detection gives the same result whether the task runs at 00:00:01 or 23:59:59."""
app, rule, iteration = self._create_rule_and_iteration(interval="days=1")
opened_on = dt.datetime(2025, 6, 14, 18, 0, 0, tzinfo=dt.UTC)
LifecycleIteration.objects.filter(pk=iteration.pk).update(opened_on=opened_on)
for task_time in [
dt.datetime(2025, 6, 15, 0, 0, 1, tzinfo=dt.UTC),
dt.datetime(2025, 6, 15, 12, 0, 0, tzinfo=dt.UTC),
dt.datetime(2025, 6, 15, 23, 59, 59, tzinfo=dt.UTC),
]:
with self.subTest(task_time=task_time):
with patch("django.utils.timezone.now", return_value=task_time):
self.assertIn(app, list(rule._get_newly_due_objects()))
def test_due_boundary_multi_day_interval(self):
"""interval=30 days: due after 30 full days, not after 29."""
app, rule, iteration = self._create_rule_and_iteration(interval="days=30")
fixed_now = dt.datetime(2025, 6, 15, 14, 30, 0, tzinfo=dt.UTC)
# Previous review opened 30 days ago (May 16), review is due for the object
LifecycleIteration.objects.filter(pk=iteration.pk).update(
opened_on=dt.datetime(2025, 5, 16, 12, 0, 0, tzinfo=dt.UTC)
)
with patch("django.utils.timezone.now", return_value=fixed_now):
self.assertIn(app, list(rule._get_newly_due_objects()))
# Previous review opened 29 days ago (May 17), new review is NOT due
LifecycleIteration.objects.filter(pk=iteration.pk).update(
opened_on=dt.datetime(2025, 5, 17, 12, 0, 0, tzinfo=dt.UTC)
)
with patch("django.utils.timezone.now", return_value=fixed_now):
self.assertNotIn(app, list(rule._get_newly_due_objects()))
def test_apply_overdue_at_boundary(self):
"""apply() marks iteration overdue when grace period just expired,
regardless of what time the daily task runs."""
_, rule, iteration = self._create_rule_and_iteration(
grace_period="days=1", interval="days=365"
)
opened_on = dt.datetime(2025, 6, 14, 20, 0, 0, tzinfo=dt.UTC)
for task_time in [
dt.datetime(2025, 6, 15, 0, 0, 1, tzinfo=dt.UTC),
dt.datetime(2025, 6, 15, 23, 59, 59, tzinfo=dt.UTC),
]:
with self.subTest(task_time=task_time):
LifecycleIteration.objects.filter(pk=iteration.pk).update(
opened_on=opened_on, state=ReviewState.PENDING
)
with patch("django.utils.timezone.now", return_value=task_time):
rule.apply()
iteration.refresh_from_db()
self.assertEqual(iteration.state, ReviewState.OVERDUE)

View File

@@ -1,11 +0,0 @@
"""API URLs"""
from authentik.enterprise.lifecycle.api.iterations import IterationViewSet
from authentik.enterprise.lifecycle.api.reviews import ReviewViewSet
from authentik.enterprise.lifecycle.api.rules import LifecycleRuleViewSet
api_urlpatterns = [
("lifecycle/iterations", IterationViewSet),
("lifecycle/reviews", ReviewViewSet),
("lifecycle/rules", LifecycleRuleViewSet),
]

View File

@@ -1,75 +0,0 @@
from datetime import datetime
from urllib import parse
from django.contrib.contenttypes.models import ContentType
from django.db.models import Model
from django.urls import reverse
from rest_framework.serializers import ChoiceField, Serializer, UUIDField
from authentik.core.api.utils import ModelSerializer
from authentik.core.models import Application, Group, User
from authentik.rbac.models import Role
def parse_content_type(value: str) -> dict:
app_label, model = value.split(".")
return {"app_label": app_label, "model": model}
def model_choices() -> list[tuple[str, str]]:
return [
("authentik_core.application", "Application"),
("authentik_core.group", "Group"),
("authentik_rbac.role", "Role"),
]
def admin_link_for_model(model: Model) -> str:
if isinstance(model, Application):
url = f"/core/applications/{model.slug}"
elif isinstance(model, Group):
url = f"/identity/groups/{model.pk}"
elif isinstance(model, Role):
url = f"/identity/roles/{model.pk}"
else:
raise TypeError("Unsupported model")
return url + ";" + parse.quote('{"page":"page-lifecycle"}')
def link_for_model(model: Model) -> str:
return f"{reverse("authentik_core:if-admin")}#{admin_link_for_model(model)}"
def start_of_day(dt: datetime) -> datetime:
return dt.replace(hour=0, minute=0, second=0, microsecond=0)
class ContentTypeField(ChoiceField):
def __init__(self, **kwargs):
super().__init__(choices=model_choices(), **kwargs)
def to_representation(self, content_type: ContentType) -> str:
return f"{content_type.app_label}.{content_type.model}"
def to_internal_value(self, data: str) -> ContentType:
return ContentType.objects.get(**parse_content_type(data))
class GenericForeignKeySerializer(Serializer):
content_type = ContentTypeField()
object_id = UUIDField()
class ReviewerGroupSerializer(ModelSerializer):
class Meta:
model = Group
fields = [
"pk",
"name",
]
class ReviewerUserSerializer(ModelSerializer):
class Meta:
model = User
fields = ["pk", "uuid", "username", "name"]

View File

@@ -78,8 +78,7 @@ class MicrosoftEntraUserClient(MicrosoftEntraSyncClient[User, MicrosoftEntraProv
def create(self, user: User):
"""Create user from scratch and create a connection object"""
microsoft_user = self.to_schema(user, None)
if microsoft_user.user_principal_name:
self.check_email_valid(microsoft_user.user_principal_name)
self.check_email_valid(microsoft_user.user_principal_name)
with transaction.atomic():
try:
response = self._request(self.client.users.post(microsoft_user))
@@ -119,8 +118,7 @@ class MicrosoftEntraUserClient(MicrosoftEntraSyncClient[User, MicrosoftEntraProv
def update(self, user: User, connection: MicrosoftEntraProviderUser):
"""Update existing user"""
microsoft_user = self.to_schema(user, connection)
if microsoft_user.user_principal_name:
self.check_email_valid(microsoft_user.user_principal_name)
self.check_email_valid(microsoft_user.user_principal_name)
response = self._request(
self.client.users.by_user_id(connection.microsoft_id).patch(microsoft_user)
)

View File

@@ -2,10 +2,10 @@
from django.http import HttpRequest
from django.urls import reverse
from rest_framework.fields import CharField, SerializerMethodField, URLField
from rest_framework.fields import SerializerMethodField, URLField
from authentik.core.api.providers import ProviderSerializer
from authentik.core.models import Provider
from authentik.core.models import Application
from authentik.enterprise.api import EnterpriseRequiredMixin
from authentik.enterprise.providers.ws_federation.models import WSFederationProvider
from authentik.enterprise.providers.ws_federation.processors.metadata import MetadataProcessor
@@ -16,31 +16,8 @@ class WSFederationProviderSerializer(EnterpriseRequiredMixin, SAMLProviderSerial
"""WSFederationProvider Serializer"""
reply_url = URLField(source="acs_url")
wtrealm = CharField(source="audience")
url_wsfed = SerializerMethodField()
def get_url_download_metadata(self, instance: WSFederationProvider) -> str:
"""Get metadata download URL"""
if "request" not in self._context:
return ""
request: HttpRequest = self._context["request"]._request
try:
return request.build_absolute_uri(
reverse(
"authentik_providers_ws_federation:metadata-download",
kwargs={"application_slug": instance.application.slug},
)
)
except Provider.application.RelatedObjectDoesNotExist:
return request.build_absolute_uri(
reverse(
"authentik_api:wsfederationprovider-metadata",
kwargs={
"pk": instance.pk,
},
)
+ "?download"
)
wtrealm = SerializerMethodField()
def get_url_wsfed(self, instance: WSFederationProvider) -> str:
"""Get WS-Fed url"""
@@ -49,11 +26,16 @@ class WSFederationProviderSerializer(EnterpriseRequiredMixin, SAMLProviderSerial
request: HttpRequest = self._context["request"]._request
return request.build_absolute_uri(reverse("authentik_providers_ws_federation:wsfed"))
def get_wtrealm(self, instance: WSFederationProvider) -> str:
try:
return f"goauthentik.io://app/{instance.application.slug}"
except Application.DoesNotExist:
return None
class Meta(SAMLProviderSerializer.Meta):
model = WSFederationProvider
fields = ProviderSerializer.Meta.fields + [
"reply_url",
"wtrealm",
"assertion_valid_not_before",
"assertion_valid_not_on_or_after",
"session_valid_not_on_or_after",
@@ -69,6 +51,7 @@ class WSFederationProviderSerializer(EnterpriseRequiredMixin, SAMLProviderSerial
"default_name_id_policy",
"url_download_metadata",
"url_wsfed",
"wtrealm",
]
extra_kwargs = ProviderSerializer.Meta.extra_kwargs

View File

@@ -8,10 +8,6 @@ from authentik.providers.saml.models import SAMLProvider
class WSFederationProvider(SAMLProvider):
"""WS-Federation for applications which support WS-Fed."""
# Alias'd fields:
# - acs_url -> reply_url
# - audience -> realm / wtrealm
@property
def serializer(self) -> type[Serializer]:
from authentik.enterprise.providers.ws_federation.api.providers import (

View File

@@ -1,4 +1,5 @@
from dataclasses import dataclass
from urllib.parse import urlparse
from django.http import HttpRequest
from django.shortcuts import get_object_or_404
@@ -36,6 +37,8 @@ class SignInRequest:
wreply: str
wctx: str | None
app_slug: str
@staticmethod
def parse(request: HttpRequest) -> SignInRequest:
action = request.GET.get("wa")
@@ -44,26 +47,26 @@ class SignInRequest:
realm = request.GET.get("wtrealm")
if not realm:
raise ValueError("Missing Realm")
parsed = urlparse(realm)
req = SignInRequest(
wa=action,
wtrealm=realm,
wreply=request.GET.get("wreply"),
wctx=request.GET.get("wctx", ""),
app_slug=parsed.path[1:],
)
_, provider = req.get_app_provider()
if not req.wreply:
req.wreply = provider.acs_url
if not req.wreply.startswith(provider.acs_url):
raise ValueError("Invalid wreply")
return req
def get_app_provider(self):
application = get_object_or_404(Application, slug=self.app_slug)
provider: WSFederationProvider = get_object_or_404(
WSFederationProvider, audience=self.wtrealm
WSFederationProvider, pk=application.provider_id
)
application = get_object_or_404(Application, provider=provider)
return application, provider

View File

@@ -1,4 +1,5 @@
from dataclasses import dataclass
from urllib.parse import urlparse
from django.http import HttpRequest
from django.shortcuts import get_object_or_404
@@ -14,6 +15,8 @@ class SignOutRequest:
wtrealm: str
wreply: str
app_slug: str
@staticmethod
def parse(request: HttpRequest) -> SignOutRequest:
action = request.GET.get("wa")
@@ -22,23 +25,23 @@ class SignOutRequest:
realm = request.GET.get("wtrealm")
if not realm:
raise ValueError("Missing Realm")
parsed = urlparse(realm)
req = SignOutRequest(
wa=action,
wtrealm=realm,
wreply=request.GET.get("wreply"),
app_slug=parsed.path[1:],
)
_, provider = req.get_app_provider()
if not req.wreply:
req.wreply = provider.acs_url
if not req.wreply.startswith(provider.acs_url):
raise ValueError("Invalid wreply")
return req
def get_app_provider(self):
application = get_object_or_404(Application, slug=self.app_slug)
provider: WSFederationProvider = get_object_or_404(
WSFederationProvider, audience=self.wtrealm
WSFederationProvider, pk=application.provider_id
)
application = get_object_or_404(Application, provider=provider)
return application, provider

View File

@@ -43,6 +43,7 @@ class TestWSFedSignIn(TestCase):
wtrealm="",
wreply="",
wctx=None,
app_slug="",
),
)
token = proc.response()[WS_FED_POST_KEY_RESULT]
@@ -64,6 +65,7 @@ class TestWSFedSignIn(TestCase):
wtrealm="",
wreply="",
wctx=None,
app_slug="",
),
)
token = proc.response()[WS_FED_POST_KEY_RESULT]

View File

@@ -4,7 +4,6 @@ from django.urls import path
from authentik.enterprise.providers.ws_federation.api.providers import WSFederationProviderViewSet
from authentik.enterprise.providers.ws_federation.views import WSFedEntryView
from authentik.providers.saml.views.metadata import MetadataDownload
urlpatterns = [
path(
@@ -12,12 +11,6 @@ urlpatterns = [
WSFedEntryView.as_view(),
name="wsfed",
),
# Metadata
path(
"<slug:application_slug>/metadata/",
MetadataDownload.as_view(),
name="metadata-download",
),
]
api_urlpatterns = [

View File

@@ -1,8 +1,6 @@
from django.http import Http404, HttpRequest, HttpResponse
from django.shortcuts import get_object_or_404, redirect
from django.urls import reverse
from django.shortcuts import get_object_or_404
from django.utils.translation import gettext as _
from django.views import View
from structlog.stdlib import get_logger
from authentik.core.models import Application, AuthenticatedSession
@@ -162,24 +160,3 @@ class WSFedFlowFinalView(ChallengeStageView):
"attrs": response,
},
)
class MetadataDownload(View):
"""Redirect to metadata download"""
def dispatch(self, request: HttpRequest, application_slug: str) -> HttpResponse:
app = Application.objects.filter(slug=application_slug).with_provider().first()
if not app:
raise Http404
provider = app.get_provider()
if not provider:
raise Http404
return redirect(
reverse(
"authentik_api:wsfederationprovider-metadata",
kwargs={
"pk": provider.pk,
},
)
+ "?download"
)

View File

@@ -4,7 +4,6 @@ TENANT_APPS = [
"authentik.enterprise.audit",
"authentik.enterprise.endpoints.connectors.agent",
"authentik.enterprise.endpoints.connectors.fleet",
"authentik.enterprise.lifecycle",
"authentik.enterprise.policies.unique_password",
"authentik.enterprise.providers.google_workspace",
"authentik.enterprise.providers.microsoft_entra",

View File

@@ -1,54 +0,0 @@
# Generated by Django 5.2.10 on 2026-02-03 09:52
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_events", "0015_alter_event_action_choices"),
]
operations = [
migrations.AlterField(
model_name="event",
name="action",
field=models.TextField(
choices=[
("login", "Login"),
("login_failed", "Login Failed"),
("logout", "Logout"),
("user_write", "User Write"),
("suspicious_request", "Suspicious Request"),
("password_set", "Password Set"),
("secret_view", "Secret View"),
("secret_rotate", "Secret Rotate"),
("invitation_used", "Invite Used"),
("authorize_application", "Authorize Application"),
("source_linked", "Source Linked"),
("impersonation_started", "Impersonation Started"),
("impersonation_ended", "Impersonation Ended"),
("flow_execution", "Flow Execution"),
("policy_execution", "Policy Execution"),
("policy_exception", "Policy Exception"),
("property_mapping_exception", "Property Mapping Exception"),
("system_task_execution", "System Task Execution"),
("system_task_exception", "System Task Exception"),
("system_exception", "System Exception"),
("configuration_error", "Configuration Error"),
("configuration_warning", "Configuration Warning"),
("model_created", "Model Created"),
("model_updated", "Model Updated"),
("model_deleted", "Model Deleted"),
("email_sent", "Email Sent"),
("update_available", "Update Available"),
("export_ready", "Export Ready"),
("review_initiated", "Review Initiated"),
("review_overdue", "Review Overdue"),
("review_attested", "Review Attested"),
("review_completed", "Review Completed"),
("custom_", "Custom Prefix"),
]
),
),
]

View File

@@ -123,11 +123,6 @@ class EventAction(models.TextChoices):
EXPORT_READY = "export_ready"
REVIEW_INITIATED = "review_initiated"
REVIEW_OVERDUE = "review_overdue"
REVIEW_ATTESTED = "review_attested"
REVIEW_COMPLETED = "review_completed"
CUSTOM_PREFIX = "custom_"

View File

@@ -31,9 +31,6 @@ class FlowLayout(models.TextChoices):
SIDEBAR_LEFT = "sidebar_left"
SIDEBAR_RIGHT = "sidebar_right"
SIDEBAR_LEFT_FRAME_BACKGROUND = "sidebar_left_frame_background"
SIDEBAR_RIGHT_FRAME_BACKGROUND = "sidebar_right_frame_background"
class ErrorDetailSerializer(PassiveSerializer):
"""Serializer for rest_framework's error messages"""

View File

@@ -1,29 +0,0 @@
# Generated by Django 5.2.10 on 2026-01-16 17:50
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_flows", "0030_alter_flow_background"),
]
operations = [
migrations.AlterField(
model_name="flow",
name="layout",
field=models.TextField(
choices=[
("stacked", "Stacked"),
("content_left", "Content Left"),
("content_right", "Content Right"),
("sidebar_left", "Sidebar Left"),
("sidebar_right", "Sidebar Right"),
("sidebar_left_frame_background", "Sidebar Left Frame Background"),
("sidebar_right_frame_background", "Sidebar Right Frame Background"),
],
default="stacked",
),
),
]

View File

@@ -9,15 +9,7 @@
{{ block.super }}
<link rel="prefetch" href="{{ flow_background_url }}" />
{% if flow.compatibility_mode and not inspector %}
{% comment %}
@see {@link web/types/webcomponents.d.ts} for type definitions.
{% endcomment %}
<script data-id="shady-dom">
"use strict";
window.ShadyDOM = window.ShadyDOM || {}
window.ShadyDOM.force = true
</script>
<script data-id="shady-dom">ShadyDOM = { force: true };</script>
{% endif %}
{% include "base/header_js.html" %}
<script data-id="flow-config">
@@ -53,11 +45,16 @@
slug="{{ flow.slug }}"
class="pf-c-login"
data-layout="{{ flow.layout|default:'stacked' }}"
loading
>
{% include "base/placeholder.html" %}
<ak-brand-links name="flow-links" slot="footer"></ak-brand-links>
<ak-brand-links
slot="footer"
exportparts="list:brand-links-list, list-item:brand-links-list-item"
role="contentinfo"
aria-label="{% trans 'Site footer' %}"
class="pf-c-login__footer {% if flow.layout == 'stacked' %}pf-m-dark{% endif %}"
></ak-brand-links>
</ak-flow-executor>
</div>
</div>

View File

@@ -42,7 +42,7 @@ ARG_SANITIZE = re.compile(r"[:.-]")
def sanitize_arg(arg_name: str) -> str:
return re.sub(ARG_SANITIZE, "_", slugify(arg_name))
return re.sub(ARG_SANITIZE, "_", arg_name)
class BaseEvaluator:
@@ -311,9 +311,7 @@ class BaseEvaluator:
def wrap_expression(self, expression: str) -> str:
"""Wrap expression in a function, call it, and save the result as `result`"""
handler_signature = ",".join(
[x for x in [sanitize_arg(x) for x in self._context.keys()] if x]
)
handler_signature = ",".join(sanitize_arg(x) for x in self._context.keys())
full_expression = ""
full_expression += f"def handler({handler_signature}):\n"
full_expression += indent(expression, " ")

View File

@@ -1,16 +1,10 @@
"""Migration helpers"""
from collections.abc import Iterable
from typing import TYPE_CHECKING
from django.apps.registry import Apps
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
from authentik.events.utils import cleanse_dict, sanitize_dict
if TYPE_CHECKING:
from authentik.events.models import EventAction
def fallback_names(app: str, model: str, field: str):
"""Factory function that checks all instances of `app`.`model` instance's `field`
@@ -71,12 +65,3 @@ def progress_bar(iterable: Iterable):
print_progress_bar(i + 1)
# Print New Line on Complete
print()
def migration_event(
apps: Apps, schema_editor: BaseDatabaseSchemaEditor, action: EventAction, **kwargs
):
db_alias = schema_editor.connection.alias
Event = apps.get_model("authentik_events", "Event")
event = Event(action=action, app="authentik", context=cleanse_dict(sanitize_dict(kwargs)))
event.save(using=db_alias)

View File

@@ -88,7 +88,7 @@ class DomainlessURLValidator(URLValidator):
def __call__(self, value: str):
# Check if the scheme is valid.
scheme = value.split("://", maxsplit=1)[0].lower()
scheme = value.split("://")[0].lower()
if scheme not in self.schemes:
value = "default" + value
super().__call__(value)

View File

@@ -1,6 +1,5 @@
"""Test Evaluator base functions"""
from pathlib import Path
from unittest.mock import patch
from django.test import RequestFactory, TestCase
@@ -354,18 +353,3 @@ class TestEvaluator(TestCase):
self.assertEqual(message.to, ["to@example.com"])
self.assertEqual(message.cc, ["cc1@example.com", "cc2@example.com"])
self.assertEqual(message.bcc, ["bcc1@example.com", "bcc2@example.com"])
def test_expr_arg_escape(self):
"""Test escaping of arguments"""
eval = BaseEvaluator()
eval._context = {
'z=getattr(getattr(__import__("os"), "popen")("id > /tmp/test"), "read")()': "bar",
"@@": "baz",
"{{": "baz",
"aa@@": "baz",
}
res = eval.evaluate("return locals()")
self.assertEqual(
res, {"zgetattrgetattr__import__os_popenid_tmptest_read": "bar", "aa": "baz"}
)
self.assertFalse(Path("/tmp/test").exists())

View File

@@ -12,9 +12,9 @@ HEADER = "### Managed by authentik"
FOOTER = "### End Managed by authentik"
def opener(path: Path | str, flags: int):
"""File opener to create files as 600 perms"""
return os.open(path, flags, 0o600)
def opener(path, flags):
"""File opener to create files as 700 perms"""
return os.open(path, flags, 0o700)
class SSHManagedExternallyException(DockerException):

View File

@@ -7,7 +7,6 @@ from tempfile import gettempdir
from docker.tls import TLSConfig
from authentik.crypto.models import CertificateKeyPair
from authentik.outposts.docker_ssh import opener
class DockerInlineTLS:
@@ -30,7 +29,7 @@ class DockerInlineTLS:
def write_file(self, name: str, contents: str) -> str:
"""Wrapper for mkstemp that uses fdopen"""
path = Path(gettempdir(), name)
with open(path, "w", encoding="utf8", opener=opener) as _file:
with open(path, "w", encoding="utf8") as _file:
_file.write(contents)
self._paths.append(str(path))
return str(path)

View File

@@ -132,14 +132,9 @@ class PolicyEngine:
# If we didn't find any static bindings, do nothing
return
self.logger.debug("P_ENG: Found static bindings", **matched_bindings)
if self.mode == PolicyEngineMode.MODE_ANY:
if matched_bindings.get("passing", 0) > 0:
# Any passing static binding -> passing
passing = True
elif self.mode == PolicyEngineMode.MODE_ALL:
if matched_bindings.get("passing", 0) == matched_bindings["total"]:
# All static bindings are passing -> passing
passing = True
if matched_bindings.get("passing", 0) > 0:
# Any passing static binding -> passing
passing = True
elif matched_bindings["total"] > 0 and matched_bindings.get("passing", 0) < 1:
# No matching static bindings but at least one is configured -> not passing
passing = False
@@ -190,16 +185,6 @@ class PolicyEngine:
# Only call .recv() if no result is saved, otherwise we just deadlock here
if not proc_info.result:
proc_info.result = proc_info.connection.recv()
if proc_info.result and proc_info.result._exec_time:
HIST_POLICIES_EXECUTION_TIME.labels(
binding_order=proc_info.binding.order,
binding_target_type=proc_info.binding.target_type,
binding_target_name=proc_info.binding.target_name,
object_type=(
class_to_path(self.request.obj.__class__) if self.request.obj else ""
),
mode="execute_process",
).observe(proc_info.result._exec_time)
return self
@property

View File

@@ -1,57 +0,0 @@
# Generated by Django 5.2.11 on 2026-02-04 18:28
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_policies_event_matcher", "0025_alter_eventmatcherpolicy_action"),
]
operations = [
migrations.AlterField(
model_name="eventmatcherpolicy",
name="action",
field=models.TextField(
choices=[
("login", "Login"),
("login_failed", "Login Failed"),
("logout", "Logout"),
("user_write", "User Write"),
("suspicious_request", "Suspicious Request"),
("password_set", "Password Set"),
("secret_view", "Secret View"),
("secret_rotate", "Secret Rotate"),
("invitation_used", "Invite Used"),
("authorize_application", "Authorize Application"),
("source_linked", "Source Linked"),
("impersonation_started", "Impersonation Started"),
("impersonation_ended", "Impersonation Ended"),
("flow_execution", "Flow Execution"),
("policy_execution", "Policy Execution"),
("policy_exception", "Policy Exception"),
("property_mapping_exception", "Property Mapping Exception"),
("system_task_execution", "System Task Execution"),
("system_task_exception", "System Task Exception"),
("system_exception", "System Exception"),
("configuration_error", "Configuration Error"),
("configuration_warning", "Configuration Warning"),
("model_created", "Model Created"),
("model_updated", "Model Updated"),
("model_deleted", "Model Deleted"),
("email_sent", "Email Sent"),
("update_available", "Update Available"),
("export_ready", "Export Ready"),
("review_initiated", "Review Initiated"),
("review_overdue", "Review Overdue"),
("review_attested", "Review Attested"),
("review_completed", "Review Completed"),
("custom_", "Custom Prefix"),
],
default=None,
help_text="Match created events with this action type. When left empty, all action types will be matched.",
null=True,
),
),
]

View File

@@ -2,7 +2,6 @@
from multiprocessing import get_context
from multiprocessing.connection import Connection
from time import perf_counter
from django.core.cache import cache
from sentry_sdk import start_span
@@ -12,6 +11,8 @@ from structlog.stdlib import get_logger
from authentik.events.models import Event, EventAction
from authentik.lib.config import CONFIG
from authentik.lib.utils.errors import exception_to_dict
from authentik.lib.utils.reflection import class_to_path
from authentik.policies.apps import HIST_POLICIES_EXECUTION_TIME
from authentik.policies.exceptions import PolicyException
from authentik.policies.models import PolicyBinding
from authentik.policies.types import CACHE_PREFIX, PolicyRequest, PolicyResult
@@ -122,9 +123,18 @@ class PolicyProcess(PROCESS_CLASS):
def profiling_wrapper(self):
"""Run with profiling enabled"""
with start_span(
op="authentik.policy.process.execute",
) as span:
with (
start_span(
op="authentik.policy.process.execute",
) as span,
HIST_POLICIES_EXECUTION_TIME.labels(
binding_order=self.binding.order,
binding_target_type=self.binding.target_type,
binding_target_name=self.binding.target_name,
object_type=class_to_path(self.request.obj.__class__) if self.request.obj else "",
mode="execute_process",
).time(),
):
span: Span
span.set_data("policy", self.binding.policy)
span.set_data("request", self.request)
@@ -132,14 +142,8 @@ class PolicyProcess(PROCESS_CLASS):
def run(self): # pragma: no cover
"""Task wrapper to run policy checking"""
result = None
try:
start = perf_counter()
result = self.profiling_wrapper()
end = perf_counter()
result._exec_time = max((end - start), 0)
self.connection.send(self.profiling_wrapper())
except Exception as exc: # noqa
LOGGER.warning("Policy failed to run", exc=exc)
result = PolicyResult(False, str(exc))
finally:
self.connection.send(result)
self.connection.send(PolicyResult(False, str(exc)))

View File

@@ -33,9 +33,6 @@ class TestPolicyEngine(TestCase):
self.policy_raises = ExpressionPolicy.objects.create(
name=generate_id(), expression="{{ 0/0 }}"
)
self.group_member = Group.objects.create(name=generate_id())
self.user.groups.add(self.group_member)
self.group_non_member = Group.objects.create(name=generate_id())
def test_engine_empty(self):
"""Ensure empty policy list passes"""
@@ -54,7 +51,7 @@ class TestPolicyEngine(TestCase):
self.assertEqual(result.passing, True)
self.assertEqual(result.messages, ("dummy",))
def test_engine_mode_all_dyn(self):
def test_engine_mode_all(self):
"""Ensure all policies passes with AND mode (false and true -> false)"""
pbm = PolicyBindingModel.objects.create(policy_engine_mode=PolicyEngineMode.MODE_ALL)
PolicyBinding.objects.create(target=pbm, policy=self.policy_false, order=0)
@@ -70,7 +67,7 @@ class TestPolicyEngine(TestCase):
),
)
def test_engine_mode_any_dyn(self):
def test_engine_mode_any(self):
"""Ensure all policies passes with OR mode (false and true -> true)"""
pbm = PolicyBindingModel.objects.create(policy_engine_mode=PolicyEngineMode.MODE_ANY)
PolicyBinding.objects.create(target=pbm, policy=self.policy_false, order=0)
@@ -86,26 +83,6 @@ class TestPolicyEngine(TestCase):
),
)
def test_engine_mode_all_static(self):
"""Ensure all policies passes with OR mode (false and true -> true)"""
pbm = PolicyBindingModel.objects.create(policy_engine_mode=PolicyEngineMode.MODE_ALL)
PolicyBinding.objects.create(target=pbm, group=self.group_member, order=0)
PolicyBinding.objects.create(target=pbm, group=self.group_non_member, order=1)
engine = PolicyEngine(pbm, self.user)
result = engine.build().result
self.assertEqual(result.passing, False)
self.assertEqual(result.messages, ())
def test_engine_mode_any_static(self):
"""Ensure all policies passes with OR mode (false and true -> true)"""
pbm = PolicyBindingModel.objects.create(policy_engine_mode=PolicyEngineMode.MODE_ANY)
PolicyBinding.objects.create(target=pbm, group=self.group_member, order=0)
PolicyBinding.objects.create(target=pbm, group=self.group_non_member, order=1)
engine = PolicyEngine(pbm, self.user)
result = engine.build().result
self.assertEqual(result.passing, True)
self.assertEqual(result.messages, ())
def test_engine_negate(self):
"""Test negate flag"""
pbm = PolicyBindingModel.objects.create()

View File

@@ -77,8 +77,6 @@ class PolicyResult:
log_messages: list[LogEvent] | None
_exec_time: int | None
def __init__(self, passing: bool, *messages: str):
self.passing = passing
self.messages = messages
@@ -86,7 +84,6 @@ class PolicyResult:
self.source_binding = None
self.source_results = []
self.log_messages = []
self._exec_time = None
def __repr__(self):
return self.__str__()

View File

@@ -68,8 +68,6 @@ class IDToken:
at_hash: str | None = None
# Session ID, https://openid.net/specs/openid-connect-frontchannel-1_0.html#ClaimsContents
sid: str | None = None
# JWT ID, https://www.rfc-editor.org/rfc/rfc7519.html#section-4.1.7
jti: str | None = None
claims: dict[str, Any] = field(default_factory=dict)
@@ -83,7 +81,6 @@ class IDToken:
(token.expires if token.expires is not None else default_token_duration()).timestamp()
)
id_token.iss = provider.get_issuer(request)
id_token.jti = generate_id()
id_token.aud = provider.client_id
id_token.claims = {}

View File

@@ -5,7 +5,6 @@ from urllib.parse import parse_qs, urlparse
from django.test import RequestFactory
from django.urls import reverse
from django.utils import translation
from django.utils.timezone import now
from authentik.blueprints.tests import apply_blueprint
@@ -691,21 +690,18 @@ class TestAuthorize(OAuthTestCase):
Application.objects.create(name="app", slug="app", provider=provider)
state = generate_id()
self.client.logout()
try:
response = self.client.get(
reverse("authentik_providers_oauth2:authorize"),
data={
"response_type": "code",
"client_id": "test",
"state": state,
"redirect_uri": "foo://localhost",
"ui_locales": "invalid fr",
},
)
parsed = parse_qs(urlparse(response.url).query)
self.assertEqual(parsed["locale"], ["fr"])
finally:
translation.deactivate()
response = self.client.get(
reverse("authentik_providers_oauth2:authorize"),
data={
"response_type": "code",
"client_id": "test",
"state": state,
"redirect_uri": "foo://localhost",
"ui_locales": "invalid fr",
},
)
parsed = parse_qs(urlparse(response.url).query)
self.assertEqual(parsed["locale"], ["fr"])
@apply_blueprint("default/flow-default-authentication-flow.yaml")
def test_ui_locales_invalid(self):

View File

@@ -1,6 +1,5 @@
"""Device backchannel tests"""
from base64 import b64encode
from json import loads
from django.urls import reverse
@@ -27,7 +26,7 @@ class TesOAuth2DeviceBackchannel(OAuthTestCase):
provider=self.provider,
)
def test_backchannel_invalid_client_id_via_post_body(self):
def test_backchannel_invalid(self):
"""Test backchannel"""
res = self.client.post(
reverse("authentik_providers_oauth2:device"),
@@ -51,7 +50,7 @@ class TesOAuth2DeviceBackchannel(OAuthTestCase):
)
self.assertEqual(res.status_code, 400)
def test_backchannel_client_id_via_post_body(self):
def test_backchannel(self):
"""Test backchannel"""
res = self.client.post(
reverse("authentik_providers_oauth2:device"),
@@ -62,37 +61,3 @@ class TesOAuth2DeviceBackchannel(OAuthTestCase):
self.assertEqual(res.status_code, 200)
body = loads(res.content.decode())
self.assertEqual(body["expires_in"], 60)
def test_backchannel_invalid_client_id_via_auth_header(self):
"""Test backchannel"""
creds = b64encode(b"foo:").decode()
res = self.client.post(
reverse("authentik_providers_oauth2:device"),
HTTP_AUTHORIZATION=f"Basic {creds}",
)
self.assertEqual(res.status_code, 400)
res = self.client.post(
reverse("authentik_providers_oauth2:device"),
)
self.assertEqual(res.status_code, 400)
# test without application
self.application.provider = None
self.application.save()
res = self.client.post(
reverse("authentik_providers_oauth2:device"),
data={
"client_id": "test",
},
)
self.assertEqual(res.status_code, 400)
def test_backchannel_client_id_via_auth_header(self):
"""Test backchannel"""
creds = b64encode(f"{self.provider.client_id}:".encode()).decode()
res = self.client.post(
reverse("authentik_providers_oauth2:device"),
HTTP_AUTHORIZATION=f"Basic {creds}",
)
self.assertEqual(res.status_code, 200)
body = loads(res.content.decode())
self.assertEqual(body["expires_in"], 60)

View File

@@ -16,7 +16,7 @@ from authentik.lib.config import CONFIG
from authentik.lib.utils.time import timedelta_from_string
from authentik.providers.oauth2.errors import DeviceCodeError
from authentik.providers.oauth2.models import DeviceToken, OAuth2Provider
from authentik.providers.oauth2.utils import TokenResponse, extract_client_auth
from authentik.providers.oauth2.utils import TokenResponse
from authentik.providers.oauth2.views.device_init import QS_KEY_CODE
LOGGER = get_logger()
@@ -32,7 +32,7 @@ class DeviceView(View):
def parse_request(self):
"""Parse incoming request"""
client_id, _ = extract_client_auth(self.request)
client_id = self.request.POST.get("client_id", None)
if not client_id:
raise DeviceCodeError("invalid_client")
provider = OAuth2Provider.objects.filter(client_id=client_id).first()

View File

@@ -38,7 +38,6 @@ from authentik.core.models import (
USER_ATTRIBUTE_EXPIRES,
USER_ATTRIBUTE_GENERATED,
USER_PATH_SYSTEM_PREFIX,
USERNAME_MAX_LENGTH,
Application,
Token,
TokenIntents,
@@ -503,7 +502,7 @@ class TokenParams:
self.user, _ = User.objects.update_or_create(
# trim username to ensure the entire username is max 150 chars
# (22 chars being the length of the "template")
username=f"ak-{self.provider.name[: USERNAME_MAX_LENGTH - 22]}-client_credentials",
username=f"ak-{self.provider.name[:150-22]}-client_credentials",
defaults={
"last_login": timezone.now(),
"name": f"Autogenerated user from application {app.name} (client credentials)",

View File

@@ -434,16 +434,14 @@ class AssertionProcessor:
def build_response(self) -> str:
"""Build string XML Response and sign if signing is enabled."""
root_response = self.get_response()
# Sign assertion first (before encryption)
if self.provider.signing_kp and self.provider.sign_assertion:
assertion = root_response.xpath("//saml:Assertion", namespaces=NS_MAP)[0]
self._sign(assertion)
# Encrypt assertion (this replaces Assertion with EncryptedAssertion)
if self.provider.signing_kp:
if self.provider.sign_assertion:
assertion = root_response.xpath("//saml:Assertion", namespaces=NS_MAP)[0]
self._sign(assertion)
if self.provider.sign_response:
response = root_response.xpath("//samlp:Response", namespaces=NS_MAP)[0]
self._sign(response)
if self.provider.encryption_kp:
assertion = root_response.xpath("//saml:Assertion", namespaces=NS_MAP)[0]
self._encrypt(assertion, root_response)
# Sign response AFTER encryption so signature covers the encrypted content
if self.provider.signing_kp and self.provider.sign_response:
response = root_response.xpath("//samlp:Response", namespaces=NS_MAP)[0]
self._sign(response)
return etree.tostring(root_response).decode("utf-8") # nosec

View File

@@ -37,11 +37,6 @@ class ServiceProviderMetadata:
name_id_policy: SAMLNameIDPolicy
signing_keypair: CertificateKeyPair | None = None
encryption_keypair: CertificateKeyPair | None = None
# Single Logout Service (optional)
sls_binding: str | None = None
sls_location: str | None = None
def to_provider(
self, name: str, authorization_flow: Flow, invalidation_flow: Flow
@@ -55,19 +50,10 @@ class ServiceProviderMetadata:
provider.sp_binding = self.acs_binding
provider.acs_url = self.acs_location
provider.default_name_id_policy = self.name_id_policy
# Single Logout Service
if self.sls_location:
provider.sls_url = self.sls_location
if self.sls_binding:
provider.sls_binding = self.sls_binding
if self.signing_keypair and self.auth_n_request_signed:
self.signing_keypair.name = f"Provider {name} - SAML Signing Certificate"
self.signing_keypair.save()
provider.verification_kp = self.signing_keypair
if self.encryption_keypair:
self.encryption_keypair.name = f"Provider {name} - SAML Encryption Certificate"
self.encryption_keypair.save()
provider.encryption_kp = self.encryption_keypair
if self.assertion_signed:
provider.signing_kp = CertificateKeyPair.objects.exclude(key_data__iexact="").first()
# Set all auto-generated Property-mappings as defaults
@@ -81,7 +67,7 @@ class ServiceProviderMetadataParser:
"""Service-Provider Metadata Parser"""
def get_signing_cert(self, root: etree.Element) -> CertificateKeyPair | None:
"""Extract signing X509Certificate from metadata, when given."""
"""Extract X509Certificate from metadata, when given."""
signing_certs = root.xpath(
'//md:SPSSODescriptor/md:KeyDescriptor[@use="signing"]//ds:X509Certificate/text()',
namespaces=NS_MAP,
@@ -95,21 +81,6 @@ class ServiceProviderMetadataParser:
certificate_data=raw_cert,
)
def get_encryption_cert(self, root: etree.Element) -> CertificateKeyPair | None:
"""Extract encryption X509Certificate from metadata, when given."""
encryption_certs = root.xpath(
'//md:SPSSODescriptor/md:KeyDescriptor[@use="encryption"]//ds:X509Certificate/text()',
namespaces=NS_MAP,
)
if len(encryption_certs) < 1:
return None
raw_cert = format_cert(encryption_certs[0])
# sanity check, make sure the certificate is valid.
load_pem_x509_certificate(raw_cert.encode("utf-8"), default_backend())
return CertificateKeyPair(
certificate_data=raw_cert,
)
def check_signature(self, root: etree.Element, keypair: CertificateKeyPair):
"""If Metadata is signed, check validity of signature"""
xmlsec.tree.add_ids(root, ["ID"])
@@ -166,25 +137,12 @@ class ServiceProviderMetadataParser:
signing_keypair = self.get_signing_cert(root)
if signing_keypair:
self.check_signature(root, signing_keypair)
encryption_keypair = self.get_encryption_cert(root)
name_id_format = descriptor.findall(f"{{{NS_SAML_METADATA}}}NameIDFormat")
name_id_policy = SAMLNameIDPolicy.UNSPECIFIED
if len(name_id_format) > 0:
name_id_policy = SAMLNameIDPolicy(name_id_format[0].text)
# Parse SingleLogoutService (optional)
sls_binding = None
sls_location = None
sls_services = descriptor.findall(f"{{{NS_SAML_METADATA}}}SingleLogoutService")
if len(sls_services) > 0:
sls_service = sls_services[0]
sls_binding = {
SAML_BINDING_REDIRECT: SAMLBindings.REDIRECT,
SAML_BINDING_POST: SAMLBindings.POST,
}.get(sls_service.attrib.get("Binding"))
sls_location = sls_service.attrib.get("Location")
return ServiceProviderMetadata(
entity_id=entity_id,
acs_binding=acs_binding,
@@ -192,8 +150,5 @@ class ServiceProviderMetadataParser:
auth_n_request_signed=auth_n_request_signed,
assertion_signed=assertion_signed,
signing_keypair=signing_keypair,
encryption_keypair=encryption_keypair,
name_id_policy=name_id_policy,
sls_binding=sls_binding,
sls_location=sls_location,
)

View File

@@ -29,7 +29,7 @@ from authentik.providers.saml.models import SAMLPropertyMapping, SAMLProvider
from authentik.providers.saml.processors.assertion import AssertionProcessor
from authentik.providers.saml.processors.authn_request_parser import AuthNRequestParser
from authentik.sources.saml.exceptions import MismatchedRequestID
from authentik.sources.saml.models import SAMLBindingTypes, SAMLSource
from authentik.sources.saml.models import SAMLSource
from authentik.sources.saml.processors.request import SESSION_KEY_REQUEST_ID, RequestProcessor
from authentik.sources.saml.processors.response import ResponseProcessor
@@ -104,7 +104,6 @@ class TestAuthNRequest(TestCase):
signing_kp=self.cert,
verification_kp=self.cert,
signed_assertion=True,
binding_type=SAMLBindingTypes.POST,
)
def test_signed_valid(self):
@@ -195,213 +194,6 @@ class TestAuthNRequest(TestCase):
response_parser = ResponseProcessor(self.source, http_request)
response_parser.parse()
def test_request_sign_response_and_encrypt(self):
"""Test SAML with sign_response enabled AND encryption.
This tests the fix for signature invalidation when encryption is enabled.
The response must be signed AFTER encryption, not before, because encryption
replaces the Assertion with EncryptedAssertion which changes the response content.
"""
self.provider.sign_response = True
self.provider.sign_assertion = False
self.provider.encryption_kp = self.cert
self.provider.save()
self.source.encryption_kp = self.cert
self.source.signed_response = True
self.source.signed_assertion = False # Only response is signed, not assertion
self.source.save()
http_request = self.request_factory.get("/", user=get_anonymous_user())
# First create an AuthNRequest
request_proc = RequestProcessor(self.source, http_request, "test_state")
request = request_proc.build_auth_n()
# To get an assertion we need a parsed request (parsed by provider)
parsed_request = AuthNRequestParser(self.provider).parse(
b64encode(request.encode()).decode(), "test_state"
)
# Now create a response and convert it to string (provider)
response_proc = AssertionProcessor(self.provider, http_request, parsed_request)
response = response_proc.build_response()
# Verify the response contains EncryptedAssertion and a signature
response_xml = fromstring(response)
self.assertEqual(len(response_xml.xpath("//saml:EncryptedAssertion", namespaces=NS_MAP)), 1)
self.assertEqual(
len(response_xml.xpath("//samlp:Response/ds:Signature", namespaces=NS_MAP)), 1
)
# Now parse the response (source) - this will verify the signature and decrypt
http_request.POST = QueryDict(mutable=True)
http_request.POST["SAMLResponse"] = b64encode(response.encode()).decode()
response_parser = ResponseProcessor(self.source, http_request)
response_parser.parse()
def test_request_sign_assertion_and_encrypt(self):
"""Test SAML with sign_assertion enabled AND encryption.
The assertion signature should be inside the encrypted content and
remain valid after decryption.
"""
self.provider.sign_response = False
self.provider.sign_assertion = True
self.provider.encryption_kp = self.cert
self.provider.save()
self.source.encryption_kp = self.cert
self.source.signed_assertion = True
self.source.save()
http_request = self.request_factory.get("/", user=get_anonymous_user())
# First create an AuthNRequest
request_proc = RequestProcessor(self.source, http_request, "test_state")
request = request_proc.build_auth_n()
# To get an assertion we need a parsed request (parsed by provider)
parsed_request = AuthNRequestParser(self.provider).parse(
b64encode(request.encode()).decode(), "test_state"
)
# Now create a response and convert it to string (provider)
response_proc = AssertionProcessor(self.provider, http_request, parsed_request)
response = response_proc.build_response()
# Verify the response contains EncryptedAssertion
response_xml = fromstring(response)
self.assertEqual(len(response_xml.xpath("//saml:EncryptedAssertion", namespaces=NS_MAP)), 1)
# Now parse the response (source) - this will decrypt and verify assertion signature
http_request.POST = QueryDict(mutable=True)
http_request.POST["SAMLResponse"] = b64encode(response.encode()).decode()
response_parser = ResponseProcessor(self.source, http_request)
response_parser.parse()
def test_request_sign_both_and_encrypt(self):
"""Test SAML with both sign_assertion and sign_response enabled AND encryption.
This is the most complex scenario: assertion is signed, then encrypted,
then the response is signed. Both signatures should be valid.
"""
self.provider.sign_response = True
self.provider.sign_assertion = True
self.provider.encryption_kp = self.cert
self.provider.save()
self.source.encryption_kp = self.cert
self.source.signed_assertion = True
self.source.signed_response = True
self.source.save()
http_request = self.request_factory.get("/", user=get_anonymous_user())
# First create an AuthNRequest
request_proc = RequestProcessor(self.source, http_request, "test_state")
request = request_proc.build_auth_n()
# To get an assertion we need a parsed request (parsed by provider)
parsed_request = AuthNRequestParser(self.provider).parse(
b64encode(request.encode()).decode(), "test_state"
)
# Now create a response and convert it to string (provider)
response_proc = AssertionProcessor(self.provider, http_request, parsed_request)
response = response_proc.build_response()
# Verify the response contains EncryptedAssertion and response signature
response_xml = fromstring(response)
self.assertEqual(len(response_xml.xpath("//saml:EncryptedAssertion", namespaces=NS_MAP)), 1)
self.assertEqual(
len(response_xml.xpath("//samlp:Response/ds:Signature", namespaces=NS_MAP)), 1
)
# Now parse the response (source) - this will verify response signature,
# decrypt, then verify assertion signature
http_request.POST = QueryDict(mutable=True)
http_request.POST["SAMLResponse"] = b64encode(response.encode()).decode()
response_parser = ResponseProcessor(self.source, http_request)
response_parser.parse()
def test_encrypted_assertion_namespace_preservation(self):
"""Test that encrypted assertions include namespace declarations.
When an assertion is encrypted, the resulting decrypted XML must include
the necessary namespace declarations (xmlns:saml) since it's now a standalone
document fragment, no longer inheriting namespaces from the parent Response.
"""
self.provider.encryption_kp = self.cert
self.provider.save()
self.source.encryption_kp = self.cert
self.source.save()
http_request = self.request_factory.get("/", user=get_anonymous_user())
# First create an AuthNRequest
request_proc = RequestProcessor(self.source, http_request, "test_state")
request = request_proc.build_auth_n()
# To get an assertion we need a parsed request (parsed by provider)
parsed_request = AuthNRequestParser(self.provider).parse(
b64encode(request.encode()).decode(), "test_state"
)
# Now create a response and convert it to string (provider)
response_proc = AssertionProcessor(self.provider, http_request, parsed_request)
response = response_proc.build_response()
# Parse the encrypted response
response_xml = fromstring(response)
encrypted_assertion = response_xml.xpath("//saml:EncryptedAssertion", namespaces=NS_MAP)[0]
encrypted_data = encrypted_assertion.xpath("//xenc:EncryptedData", namespaces=NS_MAP)[0]
# Decrypt the assertion manually to verify namespace is present
import xmlsec
manager = xmlsec.KeysManager()
key = xmlsec.Key.from_memory(self.cert.key_data, xmlsec.constants.KeyDataFormatPem, None)
manager.add_key(key)
enc_ctx = xmlsec.EncryptionContext(manager)
decrypted = enc_ctx.decrypt(encrypted_data)
# The decrypted assertion should have xmlns:saml namespace declaration
decrypted_str = etree.tostring(decrypted).decode()
self.assertIn("xmlns:saml", decrypted_str)
# Also verify full round-trip works (source can parse it)
http_request.POST = QueryDict(mutable=True)
http_request.POST["SAMLResponse"] = b64encode(response.encode()).decode()
response_parser = ResponseProcessor(self.source, http_request)
response_parser.parse()
def test_encrypted_response_schema_validation(self):
"""Test that encrypted SAML responses validate against the SAML schema.
The response with EncryptedAssertion must be valid per saml-schema-protocol-2.0.xsd.
This ensures we don't have invalid elements like EncryptedData inside Assertion.
"""
self.provider.encryption_kp = self.cert
self.provider.save()
http_request = self.request_factory.get("/", user=get_anonymous_user())
# First create an AuthNRequest
request_proc = RequestProcessor(self.source, http_request, "test_state")
request = request_proc.build_auth_n()
# To get an assertion we need a parsed request (parsed by provider)
parsed_request = AuthNRequestParser(self.provider).parse(
b64encode(request.encode()).decode(), "test_state"
)
# Now create a response and convert it to string (provider)
response_proc = AssertionProcessor(self.provider, http_request, parsed_request)
response = response_proc.build_response()
# Validate against SAML schema
schema = etree.XMLSchema(
etree.parse("schemas/saml-schema-protocol-2.0.xsd", parser=etree.XMLParser()) # nosec
)
self.assertTrue(schema.validate(lxml_from_string(response)))
# Verify structure: should have EncryptedAssertion, not Assertion with EncryptedData inside
response_xml = fromstring(response)
self.assertEqual(len(response_xml.xpath("//saml:EncryptedAssertion", namespaces=NS_MAP)), 1)
self.assertEqual(len(response_xml.xpath("//saml:Assertion", namespaces=NS_MAP)), 0)
def test_request_signed(self):
"""Test full SAML Request/Response flow, fully signed"""
http_request = self.request_factory.get("/", user=get_anonymous_user())

View File

@@ -12,7 +12,7 @@ from authentik.lib.xml import lxml_from_string
from authentik.providers.saml.models import SAMLPropertyMapping, SAMLProvider
from authentik.providers.saml.processors.assertion import AssertionProcessor
from authentik.providers.saml.processors.authn_request_parser import AuthNRequestParser
from authentik.sources.saml.models import SAMLBindingTypes, SAMLSource
from authentik.sources.saml.models import SAMLSource
from authentik.sources.saml.processors.request import RequestProcessor
@@ -35,7 +35,6 @@ class TestSchema(TestCase):
issuer="authentik",
signing_kp=cert,
pre_authentication_flow=create_test_flow(),
binding_type=SAMLBindingTypes.POST,
)
self.request_factory = RequestFactory()

View File

@@ -1,7 +1,5 @@
"""authentik SAML IDP Views"""
from datetime import datetime, timedelta
from django.core.validators import URLValidator
from django.http import HttpRequest, HttpResponse
from django.http.response import HttpResponseBadRequest
@@ -37,8 +35,6 @@ REQUEST_KEY_SAML_SIG_ALG = "SigAlg"
REQUEST_KEY_SAML_RESPONSE = "SAMLResponse"
REQUEST_KEY_RELAY_STATE = "RelayState"
DEPRECATION_SP_BINDING_REDIRECT = "authentik.providers.saml.sp_binding_redirect"
PLAN_CONTEXT_SAML_AUTH_N_REQUEST = "authentik/providers/saml/authn_request"
PLAN_CONTEXT_SAML_LOGOUT_REQUEST = "authentik/providers/saml/logout_request"
PLAN_CONTEXT_SAML_LOGOUT_NATIVE_SESSIONS = "goauthentik.io/providers/saml/native_sessions"
@@ -122,20 +118,6 @@ class SAMLFlowFinalView(ChallengeStageView):
},
)
if provider.sp_binding == SAMLBindings.REDIRECT:
if not Event.filter_not_expired(
action=EventAction.CONFIGURATION_WARNING,
context__deprecation=DEPRECATION_SP_BINDING_REDIRECT,
).exists():
event = Event.new(
EventAction.CONFIGURATION_WARNING,
deprecation=DEPRECATION_SP_BINDING_REDIRECT,
message=(
"Redirect binding for Service Provider binding is deprecated "
"and will be removed in a future version. Use Post binding instead."
),
)
event.expires = datetime.now() + timedelta(days=30)
event.save()
url_args = {
REQUEST_KEY_SAML_RESPONSE: deflate_and_base64_encode(response),
}

View File

@@ -1,6 +1,6 @@
"""Custom SCIM schemas"""
from enum import StrEnum
from enum import Enum
from typing import Self
from django.core.exceptions import ValidationError
@@ -192,7 +192,7 @@ class ServiceProviderConfiguration(BaseServiceProviderConfiguration):
)
class PatchOp(StrEnum):
class PatchOp(str, Enum):
replace = "replace"
remove = "remove"
add = "add"

View File

@@ -16,7 +16,7 @@ def make_many_groups(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
many-to-many relationship in SCIMProvider.group_filters"""
db_alias = schema_editor.connection.alias
from authentik.lib.migrations import migration_event
Event = apps.get_model("authentik_events", "Event")
SCIMProvider = apps.get_model("authentik_providers_scim", "scimprovider")
@@ -26,10 +26,8 @@ def make_many_groups(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
provider.group_filters.add(provider.filter_group)
provider.dry_run = True
provider.save(update_fields=["dry_run"])
migration_event(
apps,
schema_editor,
EventAction.CONFIGURATION_WARNING,
event = Event.new(
action=EventAction.CONFIGURATION_WARNING,
message=(
"SCIM Providers' `filter_group` has been removed in favor of `group_filters`. Your configuration has been migrated."
"To prevent users/groups from being removed, the provider's dry-run mode has been enabled. Please review "
@@ -37,6 +35,7 @@ def make_many_groups(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
),
provider=provider,
)
event.save(using=db_alias)
class Migration(migrations.Migration):

View File

@@ -1,7 +1,5 @@
"""OpenID Type tests"""
import time
from django.test import RequestFactory, TestCase
from jwt import encode
from requests_mock import Mocker
@@ -64,34 +62,23 @@ class TestTypeOpenID(TestCase):
@Mocker()
def test_userinfo_jwt(self, mock: Mocker):
"""Test id_token fallback when profile_url is empty"""
"""Test userinfo API call"""
jwks_cert = create_test_cert()
client_id = generate_id()
self.source.profile_url = ""
self.source.consumer_key = client_id
self.source.oidc_jwks = {"keys": [JWKSView.get_jwk_for_key(jwks_cert, "sig")]}
self.source.save()
token = generate_id()
now = int(time.time())
id_token_payload = {
"iss": "https://example.com",
"sub": OPENID_USER["sub"],
"aud": client_id,
"exp": now + 3600,
"iat": now,
"name": OPENID_USER["name"],
"email": OPENID_USER["email"],
"nickname": OPENID_USER["nickname"],
}
profile = (
OpenIDConnectOAuth2Callback(request=self.factory.get("/"))
.get_client(self.source)
.get_profile_info(
{
"token_type": "Bearer",
"token_type": "foo",
"access_token": token,
"id_token": encode(
id_token_payload,
{
"foo": "bar",
},
key=jwks_cert.private_key,
algorithm="RS256",
headers={"kid": self.source.oidc_jwks["keys"][0]["kid"]},
@@ -99,8 +86,9 @@ class TestTypeOpenID(TestCase):
}
)
)
self.assertEqual(profile["sub"], OPENID_USER["sub"])
self.assertEqual(profile["name"], OPENID_USER["name"])
self.assertEqual(profile["email"], OPENID_USER["email"])
self.assertEqual(profile["aud"], client_id)
self.assertEqual(profile["iss"], "https://example.com")
self.assertEqual(
profile,
{
"foo": "bar",
},
)

View File

@@ -49,15 +49,8 @@ class OpenIDConnectClient(UserprofileHeaderAuthClient):
raw = get_unverified_header(id_token)
jwk = PyJWKSet.from_dict(self.source.oidc_jwks)
key = [key for key in jwk.keys if key.key_id == raw["kid"]][0]
return decode(
id_token,
key=key,
algorithms=[raw["alg"]],
audience=self.get_client_id(),
options={"verify_iss": False},
)
except (PyJWTError, IndexError, ValueError) as exc:
self.logger.warning("Failed to decode id_token", exc=exc)
return decode(id_token, key=key, algorithms=raw["alg"])
except PyJWTError, IndexError, ValueError:
return None

View File

@@ -7,7 +7,6 @@ from django.http import HttpRequest
from django.templatetags.static import static
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from lxml.etree import _Element # nosec
from rest_framework.serializers import Serializer
from authentik.common.saml.constants import (
@@ -218,8 +217,9 @@ class SAMLSource(Source):
def property_mapping_type(self) -> type[PropertyMapping]:
return SAMLSourcePropertyMapping
def get_base_user_properties(self, root: _Element, assertion: _Element, name_id: Any, **kwargs):
def get_base_user_properties(self, root: Any, name_id: Any, **kwargs):
attributes = {}
assertion = root.find(f"{{{NS_SAML_ASSERTION}}}Assertion")
if assertion is None:
raise ValueError("Assertion element not found")
attribute_statement = assertion.find(f"{{{NS_SAML_ASSERTION}}}AttributeStatement")

View File

@@ -20,7 +20,7 @@ from authentik.lib.xml import remove_xml_newlines
from authentik.providers.saml.utils import get_random_id
from authentik.providers.saml.utils.encoding import deflate_and_base64_encode
from authentik.providers.saml.utils.time import get_time_string
from authentik.sources.saml.models import SAMLBindingTypes, SAMLSource
from authentik.sources.saml.models import SAMLSource
SESSION_KEY_REQUEST_ID = "authentik/sources/saml/request_id"
@@ -70,7 +70,7 @@ class RequestProcessor:
# Create issuer object
auth_n_request.append(self.get_issuer())
if self.source.signing_kp and self.source.binding_type != SAMLBindingTypes.REDIRECT:
if self.source.signing_kp:
sign_algorithm_transform = SIGN_ALGORITHM_TRANSFORM_MAP.get(
self.source.signature_algorithm, xmlsec.constants.TransformRsaSha1
)
@@ -91,7 +91,7 @@ class RequestProcessor:
(used for POST Bindings)"""
auth_n_request = self.get_auth_n()
if self.source.signing_kp and self.source.binding_type != SAMLBindingTypes.REDIRECT:
if self.source.signing_kp:
xmlsec.tree.add_ids(auth_n_request, ["ID"])
ctx = xmlsec.SignatureContext()

View File

@@ -23,14 +23,12 @@ from authentik.common.saml.constants import (
SAML_NAME_ID_FORMAT_TRANSIENT,
SAML_NAME_ID_FORMAT_WINDOWS,
SAML_NAME_ID_FORMAT_X509,
SAML_STATUS_SUCCESS,
)
from authentik.core.models import (
USER_ATTRIBUTE_DELETE_ON_LOGOUT,
USER_ATTRIBUTE_EXPIRES,
USER_ATTRIBUTE_GENERATED,
USER_ATTRIBUTE_SOURCES,
USERNAME_MAX_LENGTH,
User,
)
from authentik.core.sources.flow_manager import SourceFlowManager
@@ -66,8 +64,6 @@ class ResponseProcessor:
_http_request: HttpRequest
_assertion: _Element | None = None
def __init__(self, source: SAMLSource, request: HttpRequest):
self._source = source
self._http_request = request
@@ -124,7 +120,6 @@ class ResponseProcessor:
index_of,
decrypted_assertion,
)
self._assertion = decrypted_assertion
def _verify_signature(self, signature_node: _Element):
"""Verify a single signature node"""
@@ -165,10 +160,6 @@ class ResponseProcessor:
raise InvalidSignature("No Signature exists in the Assertion element.")
self._verify_signature(signature_nodes[0])
parent = signature_nodes[0].getparent()
if parent is None or parent.tag != f"{{{NS_SAML_ASSERTION}}}Assertion":
raise InvalidSignature("No Signature exists in the Assertion element.")
self._assertion = parent
def _verify_request_id(self):
if self._source.allow_idp_initiated:
@@ -195,19 +186,9 @@ class ResponseProcessor:
status = self._root.find(f"{{{NS_SAML_PROTOCOL}}}Status")
if status is None:
return
status_code = status.find(f"{{{NS_SAML_PROTOCOL}}}StatusCode")
message = status.find(f"{{{NS_SAML_PROTOCOL}}}StatusMessage")
message_text = message.text if message is not None else None
detail = status.find(f"{{{NS_SAML_PROTOCOL}}}StatusDetail")
detail_text = etree.tostring(detail, encoding="unicode") if detail is not None else None
if status_code.attrib.get("Value") != SAML_STATUS_SUCCESS:
if detail_text and message_text:
raise ValueError(f"{message_text}: {detail_text}")
raise ValueError(
detail_text or message_text or f"SAML Status: {status_code.attrib.get('Value')}"
)
if message_text or detail_text:
LOGGER.debug("SAML Status message", message=message_text, detail=detail_text)
if message is not None:
raise ValueError(message.text)
def _handle_name_id_transient(self) -> SourceFlowManager:
"""Handle a NameID with the Format of Transient. This is a bit more complex than other
@@ -216,14 +197,11 @@ class ResponseProcessor:
on logout and periodically."""
# Create a temporary User
name_id = self._get_name_id()
username = name_id.text
# trim username to ensure it is max 150 chars
username = f"ak-{username[: USERNAME_MAX_LENGTH - 14]}-transient"
expiry = mktime(
(now() + timedelta_from_string(self._source.temporary_user_delete_after)).timetuple()
)
user: User = User.objects.create(
username=username,
username=name_id.text,
attributes={
USER_ATTRIBUTE_GENERATED: True,
USER_ATTRIBUTE_SOURCES: [
@@ -246,21 +224,14 @@ class ResponseProcessor:
identifier=str(name_id.text),
user_info={
"root": self._root,
"assertion": self.get_assertion(),
"name_id": name_id,
},
policy_context={},
)
def get_assertion(self) -> Element | None:
"""Get assertion element, if we have a signed assertion"""
if self._assertion is not None:
return self._assertion
return self._root.find(f"{{{NS_SAML_ASSERTION}}}Assertion")
def _get_name_id(self) -> Element:
"""Get NameID Element"""
assertion = self.get_assertion()
assertion = self._root.find(f"{{{NS_SAML_ASSERTION}}}Assertion")
if assertion is None:
raise ValueError("Assertion element not found")
subject = assertion.find(f"{{{NS_SAML_ASSERTION}}}Subject")
@@ -313,7 +284,6 @@ class ResponseProcessor:
identifier=str(name_id.text),
user_info={
"root": self._root,
"assertion": self.get_assertion(),
"name_id": name_id,
},
policy_context={

View File

@@ -1,13 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<saml2p:Response xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol" Destination="https://127.0.0.1:9443/source/saml/google/acs/" ID="_ee7a8865ac457e7b22cb4f16b39ceca9" IssueInstant="2022-10-14T13:52:04.479Z" Version="2.0">
<saml2:Issuer xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">https://accounts.google.com/o/saml2?idpid=</saml2:Issuer>
<saml2p:Status>
<saml2p:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Requester">
<saml2p:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:RequestDenied"></saml2p:StatusCode>
</saml2p:StatusCode>
<saml2p:StatusMessage>Authentication failed</saml2p:StatusMessage>
<saml2p:StatusDetail>
<Cause>User account is disabled</Cause>
</saml2p:StatusDetail>
</saml2p:Status>
</saml2p:Response>

View File

@@ -1,68 +0,0 @@
<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="_8e8dc5f69a98cc4c1ff3427e5ce34606fd672f91e6" Version="2.0" IssueInstant="2014-07-17T01:01:48Z" Destination="http://sp.example.com/demo1/index.php?acs" InResponseTo="ONELOGIN_4fee3b046395c4e751011e97f8900b5273d56685">
<saml:Issuer>http://idp.example.com/metadata.php</saml:Issuer>
<samlp:Status>
<samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
</samlp:Status>
<saml:Assertion xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" ID="_other_id_pfxa06693ef-cec7-f4a6-cb7f-ad074445a1a3" Version="2.0" IssueInstant="2014-07-17T01:01:48Z">
<saml:Issuer>http://idp.example.com/metadata.php</saml:Issuer>
<saml:Subject>
<saml:NameID SPNameQualifier="http://sp.example.com/demo1/metadata.php" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient">bad</saml:NameID>
<saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
<saml:SubjectConfirmationData NotOnOrAfter="2024-01-18T06:21:48Z" Recipient="http://sp.example.com/demo1/index.php?acs" InResponseTo="ONELOGIN_4fee3b046395c4e751011e97f8900b5273d56685"/>
</saml:SubjectConfirmation>
</saml:Subject>
<saml:Conditions NotBefore="2014-07-17T01:01:18Z" NotOnOrAfter="2024-01-18T06:21:48Z">
<saml:AudienceRestriction>
<saml:Audience>http://sp.example.com/demo1/metadata.php</saml:Audience>
</saml:AudienceRestriction>
</saml:Conditions>
<saml:AuthnStatement AuthnInstant="2014-07-17T01:01:48Z" SessionNotOnOrAfter="2024-07-17T09:01:48Z" SessionIndex="_be9967abd904ddcae3c0eb4189adbe3f71e327cf93">
<saml:AuthnContext>
<saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</saml:AuthnContextClassRef>
</saml:AuthnContext>
</saml:AuthnStatement>
<saml:AttributeStatement>
<saml:Attribute Name="uid" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
<saml:AttributeValue xsi:type="xs:string">bad</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute Name="mail" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
<saml:AttributeValue xsi:type="xs:string">bad</saml:AttributeValue>
</saml:Attribute>
</saml:AttributeStatement>
</saml:Assertion>
<saml:Assertion xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" ID="pfxa06693ef-cec7-f4a6-cb7f-ad074445a1a3" Version="2.0" IssueInstant="2014-07-17T01:01:48Z">
<saml:Issuer>http://idp.example.com/metadata.php</saml:Issuer><ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:SignedInfo><ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
<ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
<ds:Reference URI="#pfxa06693ef-cec7-f4a6-cb7f-ad074445a1a3"><ds:Transforms><ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/><ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/></ds:Transforms><ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/><ds:DigestValue>zNDuGxwP4gVkv/Dzt7kiKo/4gzk=</ds:DigestValue></ds:Reference></ds:SignedInfo><ds:SignatureValue>GLP/vE8uxerB0uDpPslUgLPBL6ePQB619MoQ0I2Y5lAtFE6CB1zh8BnzChRx/bFjNy4byfOe8mFfM0r7WUi1PJOFWyUPoatdLl7wHHBIRTnPpYmu3Tb2Gz0sOP0F8wW7JkBft5gJfVw49nk5si9/3Q3o52jnJZ7dPtqfIOh8uNeopikK0HLF6sU05qCCtjcXfniEnLQFNBFMo9uY5GQqmR5n3nqPz1wYyyfFOAbVmGgBIoO2PfGX2GVLQhltc9qf2JMhks4jgZsZ8iLUIiH1lcLGWZEEs94k8k0P6gSv1uZ7Vbhksd/N9Jq9pCVuEJ/jRPcAdVjzbxqKQAj6ELwr8O6fepTzA+CAdwEolBnx/C6TmSbVZ+IWk6QUGe4x4+IAukC+0hkKENlO0ELOScksvyhpgHbxNA4rp+DhGupCaO/I2RrsQkmvavbqm+wSEspK7scK112SDunjDvqPHsPYgukD33T/97PxTLorg2kKP9HHJwPJKoXXeyOGcA6vwK+RqrAlZ2dLGAgcXo+sJcdCLuvxDNz9VXofBjBZIKVKdmYhm0QJaPYHtuQsAyFavQhdOBOmGHb7QX3YE3Xy4dX4LymtT+Jlb1I4FJSht/9HUIHW1FdhfDak4f7gUgjuMamMddLD0jVgeESupSREzFv/gj2IrctkbgjAO0iuuiBgKMg=</ds:SignatureValue>
<ds:KeyInfo><ds:X509Data><ds:X509Certificate>MIIFUzCCAzugAwIBAgIRAL6tbNcE9Ej9gNlbGKswfFMwDQYJKoZIhvcNAQELBQAwHTEbMBkGA1UEAwwSYXV0aGVudGlrIDIwMjUuNi4zMB4XDTI1MDcxNTE4MDQzNloXDTI2MDcxNjE4MDQzNlowVjEqMCgGA1UEAwwhYXV0aGVudGlrIFNlbGYtc2lnbmVkIENlcnRpZmljYXRlMRIwEAYDVQQKDAlhdXRoZW50aWsxFDASBgNVBAsMC1NlbGYtc2lnbmVkMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAjmut/+bBRLlyrbf+WIfg8ZTw9t6VnsiU1n04nPTulpRAz4nBOoOHNRIruSpZyFeFa6x9jwn4Ma5EFUH7HqnRvhoujm8U17OglXWZt0DLCZ6S5xPmdMogFXjJDmg9okIcI/cb9VbR6I8uvm1oiaOWCr36RTiqZ6rmdjQcuUPLr1+V/LxWQI463S+5QA2HZxAGalp45MJAz2sa9iczktKMgyYlfjj1cruFARxxeheu5qIK7aQWfyPj1QlMb9mi4VQaxUwGrAui4Tq614ivRJY2SkZb0Aq/LLSQoQWYHtYyQIasrOXJm0JuPDqhINPBDowyhu8DihC3uzOpmTXLKc5UoIQk+Q1h5iH74A3/kxOJUw13FXzRiDxC/yGthPYLyFHsDiJolscMKSCqlDvEMcpM4mxFeud9sKUb71SZr8sqmJl3qtvZmKpkR4y8pN2c00p10t0htqONmr5kyPxmhz0HCrosiPYB4olNjaydKviNTtPJ7TtnPyeA3iXGzCP1e80XzUoJrDqON5/GcpYgqsP/kGj8Qvqesa4Fez+1+5pAGHN2VzQbkHAgK3s4YRXrGLTs7wg27F9T0RE28Mm0RYBkYpdp4/5PuTTulthB9mkUBSJMgENmQAYkapvonFDsJkTi39qnsddbZusOLT4z3hsA38eFEwRqnbNZVUGPIp/O1SsCAwEAAaNVMFMwUQYDVR0RAQH/BEcwRYJDRUZ4QXVLRzV6SlVUTWpWNTJoMkRJMUQ5MXdLblZKaXFwNmpwRTRTTy5zZWxmLXNpZ25lZC5nb2F1dGhlbnRpay5pbzANBgkqhkiG9w0BAQsFAAOCAgEAYLThxDVpA1OIAVK/buueRJExIWr6y4s6NtpuR8UQEcfq5hfoc4zMFGHR5+u1WFIb5siK25xh/OnS7bLdLic6AkjZSrx91+0v2Jn9gfUqbs5AJ040XzAAdx/Mb4s0+537yhB+/JXPylR1QxhGbO7koXQ5JDhAXWKCw2O1C+80mN8dbhQvDkEtsXrHrtXclcqf2TT89XAzc5HAC8NmP4SF+FafAREQB1KdaG4QAbc/gnjsX2YJD89SDL+3jMp6F7R1Ym+bWt5oWqx2tkm6HGXd3fbpfQlnfrRN60tMjjLmw1cDMhOhpdragY5zokniEUL2pKVtrxFp7V1ZpoMI0Kt5MKkOXrezi542NWSgkGehlsDLD9wtuCNem2arR0mNnMLdYkMG7G0dpAq3Tl32dgfMfyKnNyE2O/6/EeEuzUH2NfTU1p7AUQfLrf4rtNcJEs9OAPuC9vy7w9YEpF997T+FhR2Ub1C423NQj4bwlS/9f7MIBkSi1EgnQuiSGB5epxAKI3oOVrmzOpTuvr6wZXV9pM3zdfbcoGuFWP6Ix7W8G5vg+0WvoSjc2fwGXYlidEK3xlQSMAaQ4CMClpPsKLScRq1nrQGzPYoiL1DYubsOWx9ohll6+jNjKI6f79WwbHYrW4EeRIOz38+m46EDjAWZBMgrE7J/3DhgeLEVJYBA5K0=</ds:X509Certificate></ds:X509Data></ds:KeyInfo></ds:Signature>
<saml:Subject>
<saml:NameID SPNameQualifier="http://sp.example.com/demo1/metadata.php" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient">_ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7</saml:NameID>
<saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
<saml:SubjectConfirmationData NotOnOrAfter="2024-01-18T06:21:48Z" Recipient="http://sp.example.com/demo1/index.php?acs" InResponseTo="ONELOGIN_4fee3b046395c4e751011e97f8900b5273d56685"/>
</saml:SubjectConfirmation>
</saml:Subject>
<saml:Conditions NotBefore="2014-07-17T01:01:18Z" NotOnOrAfter="2024-01-18T06:21:48Z">
<saml:AudienceRestriction>
<saml:Audience>http://sp.example.com/demo1/metadata.php</saml:Audience>
</saml:AudienceRestriction>
</saml:Conditions>
<saml:AuthnStatement AuthnInstant="2014-07-17T01:01:48Z" SessionNotOnOrAfter="2024-07-17T09:01:48Z" SessionIndex="_be9967abd904ddcae3c0eb4189adbe3f71e327cf93">
<saml:AuthnContext>
<saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</saml:AuthnContextClassRef>
</saml:AuthnContext>
</saml:AuthnStatement>
<saml:AttributeStatement>
<saml:Attribute Name="uid" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
<saml:AttributeValue xsi:type="xs:string">test</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute Name="mail" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
<saml:AttributeValue xsi:type="xs:string">test@example.com</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute Name="eduPersonAffiliation" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
<saml:AttributeValue xsi:type="xs:string">users</saml:AttributeValue>
<saml:AttributeValue xsi:type="xs:string">examplerole1</saml:AttributeValue>
</saml:Attribute>
</saml:AttributeStatement>
</saml:Assertion>
</samlp:Response>

View File

@@ -1,44 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<saml2p:Response xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol" Destination="https://127.0.0.1:9443/source/saml/google/acs/" ID="_1e17063957f10819a5a8e147971fec22" IssueInstant="2022-10-14T14:11:49.590Z" Version="2.0">
<saml2:Issuer xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">https://accounts.google.com/o/saml2?idpid=</saml2:Issuer>
<saml2p:Status>
<saml2p:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"></saml2p:StatusCode>
<saml2p:StatusMessage>Login successful</saml2p:StatusMessage>
<saml2p:StatusDetail>
<Detail>Additional info from IdP</Detail>
</saml2p:StatusDetail>
</saml2p:Status>
<saml2:Assertion xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion" ID="_346001c5708ffd118c40edbc0c72fc60" IssueInstant="2022-10-14T14:11:49.590Z" Version="2.0">
<saml2:Issuer>https://accounts.google.com/o/saml2?idpid=</saml2:Issuer>
<saml2:Subject>
<saml2:NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent">jens@goauthentik.io</saml2:NameID>
<saml2:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
<saml2:SubjectConfirmationData NotOnOrAfter="2022-10-14T14:16:49.590Z" Recipient="https://127.0.0.1:9443/source/saml/google/acs/"></saml2:SubjectConfirmationData>
</saml2:SubjectConfirmation>
</saml2:Subject>
<saml2:Conditions NotBefore="2022-10-14T14:06:49.590Z" NotOnOrAfter="2022-10-14T14:16:49.590Z">
<saml2:AudienceRestriction>
<saml2:Audience>https://accounts.google.com/o/saml2?idpid=</saml2:Audience>
</saml2:AudienceRestriction>
</saml2:Conditions>
<saml2:AttributeStatement>
<saml2:Attribute Name="name">
<saml2:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:anyType">foo</saml2:AttributeValue>
</saml2:Attribute>
<saml2:Attribute Name="sn">
<saml2:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:anyType">bar</saml2:AttributeValue>
</saml2:Attribute>
<saml2:Attribute Name="email">
<saml2:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:anyType">foo@bar.baz</saml2:AttributeValue>
</saml2:Attribute>
</saml2:AttributeStatement>
<saml2:AuthnStatement AuthnInstant="2022-10-14T12:16:21.000Z" SessionIndex="_346001c5708ffd118c40edbc0c72fc60">
<saml2:AuthnContext>
<saml2:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified</saml2:AuthnContextClassRef>
</saml2:AuthnContext>
</saml2:AuthnStatement>
</saml2:Assertion>
</saml2p:Response>

View File

@@ -36,9 +36,7 @@ class TestPropertyMappings(TestCase):
def test_user_base_properties(self):
"""Test user base properties"""
properties = self.source.get_base_user_properties(
root=ROOT, assertion=ROOT.find(f"{{{NS_SAML_ASSERTION}}}Assertion"), name_id=NAME_ID
)
properties = self.source.get_base_user_properties(root=ROOT, name_id=NAME_ID)
self.assertEqual(
properties,
{
@@ -51,11 +49,7 @@ class TestPropertyMappings(TestCase):
def test_group_base_properties(self):
"""Test group base properties"""
properties = self.source.get_base_user_properties(
root=ROOT_GROUPS,
assertion=ROOT_GROUPS.find(f"{{{NS_SAML_ASSERTION}}}Assertion"),
name_id=NAME_ID,
)
properties = self.source.get_base_user_properties(root=ROOT_GROUPS, name_id=NAME_ID)
self.assertEqual(properties["groups"], ["group 1", "group 2"])
for group_id in ["group 1", "group 2"]:
properties = self.source.get_base_group_properties(root=ROOT, group_id=group_id)

View File

@@ -72,39 +72,6 @@ class TestResponseProcessor(TestCase):
},
)
def test_success_with_status_message_and_detail(self):
"""Test success with StatusMessage and StatusDetail present (should not raise error)"""
request = self.factory.post(
"/",
data={
"SAMLResponse": b64encode(
load_fixture("fixtures/response_success_with_message.xml").encode()
).decode()
},
)
parser = ResponseProcessor(self.source, request)
parser.parse()
sfm = parser.prepare_flow_manager()
self.assertEqual(sfm.user_properties["username"], "jens@goauthentik.io")
def test_error_with_message_and_detail(self):
"""Test error status with StatusMessage and StatusDetail includes both in error"""
request = self.factory.post(
"/",
data={
"SAMLResponse": b64encode(
load_fixture("fixtures/response_error_with_detail.xml").encode()
).decode()
},
)
with self.assertRaises(ValueError) as ctx:
ResponseProcessor(self.source, request).parse()
# Should contain both detail and message
self.assertIn("User account is disabled", str(ctx.exception))
self.assertIn("Authentication failed", str(ctx.exception))
def test_encrypted_correct(self):
"""Test encrypted"""
key = load_fixture("fixtures/encrypted-key.pem")
@@ -164,31 +131,6 @@ class TestResponseProcessor(TestCase):
parser = ResponseProcessor(self.source, request)
parser.parse()
def test_verification_assertion_duplicate(self):
"""Test verifying signature inside assertion, where the response has another assertion
before our signed assertion"""
key = load_fixture("fixtures/signature_cert.pem")
kp = CertificateKeyPair.objects.create(
name=generate_id(),
certificate_data=key,
)
self.source.verification_kp = kp
self.source.signed_assertion = True
self.source.signed_response = False
request = self.factory.post(
"/",
data={
"SAMLResponse": b64encode(
load_fixture("fixtures/response_signed_assertion_dup.xml").encode()
).decode()
},
)
parser = ResponseProcessor(self.source, request)
parser.parse()
self.assertNotEqual(parser._get_name_id().text, "bad")
self.assertEqual(parser._get_name_id().text, "_ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7")
def test_verification_response(self):
"""Test verifying signature inside response"""
key = load_fixture("fixtures/signature_cert.pem")

View File

@@ -156,7 +156,7 @@ class ACSView(View):
processor = ResponseProcessor(source, request)
try:
processor.parse()
except (InvalidSignature, MissingSAMLResponse, VerificationError, ValueError) as exc:
except (InvalidSignature, MissingSAMLResponse, VerificationError) as exc:
return bad_request_message(request, str(exc))
try:

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -6,7 +6,6 @@ from django.contrib.auth.views import redirect_to_login
from django.http.request import HttpRequest
from structlog.stdlib import get_logger
from authentik.core.middleware import get_user
from authentik.core.models import Session
from authentik.events.context_processors.asn import ASN_CONTEXT_PROCESSOR
from authentik.events.context_processors.geoip import GEOIP_CONTEXT_PROCESSOR
@@ -55,13 +54,11 @@ class SessionBindingBroken(SentryIgnoredException):
def logout_extra(request: HttpRequest, exc: SessionBindingBroken):
"""Similar to django's logout method, but able to carry more info to the signal"""
# Since this middleware runs before the AuthenticationMiddleware, we can't use `request.user`
# as it hasn't been populated yet.
user = get_user(request)
if not getattr(user, "is_authenticated", True):
user = None
# Dispatch the signal before the user is logged out so the receivers have a
# chance to find out *who* logged out.
user = getattr(request, "user", None)
if not getattr(user, "is_authenticated", True):
user = None
user_logged_out.send(
sender=user.__class__, request=request, user=user, event_extra=exc.to_event()
)

View File

@@ -10,8 +10,6 @@ from django.utils.timezone import now
from authentik.blueprints.tests import apply_blueprint
from authentik.core.models import AuthenticatedSession, Session
from authentik.core.tests.utils import create_test_flow, create_test_user
from authentik.events.models import Event, EventAction
from authentik.events.utils import get_user
from authentik.flows.markers import StageMarker
from authentik.flows.models import FlowDesignation, FlowStageBinding
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlan
@@ -272,7 +270,6 @@ class TestUserLoginStage(FlowTestCase):
def test_session_binding_broken(self):
"""Test session binding"""
Event.objects.all().delete()
self.client.force_login(self.user)
session = self.client.session
session[Session.Keys.LAST_IP] = "192.0.2.1"
@@ -288,5 +285,3 @@ class TestUserLoginStage(FlowTestCase):
)
+ f"?{NEXT_ARG_NAME}={reverse("authentik_api:user-me")}",
)
event = Event.objects.filter(action=EventAction.LOGOUT).first()
self.assertEqual(event.user, get_user(self.user))

View File

@@ -13,9 +13,6 @@ from django_dramatiq_postgres.middleware import HTTPServer
from django_dramatiq_postgres.middleware import (
MetricsMiddleware as BaseMetricsMiddleware,
)
from django_dramatiq_postgres.middleware import (
_MetricsHandler as BaseMetricsHandler,
)
from dramatiq.broker import Broker
from dramatiq.message import Message
from dramatiq.middleware import Middleware
@@ -26,7 +23,6 @@ from authentik import authentik_full_version
from authentik.events.models import Event, EventAction
from authentik.lib.sentry import should_ignore_exception
from authentik.lib.utils.reflection import class_to_path
from authentik.root.monitoring import monitoring_set
from authentik.root.signals import post_startup, pre_startup, startup
from authentik.tasks.models import Task, TaskLog, TaskStatus, WorkerStatus
from authentik.tenants.models import Tenant
@@ -264,15 +260,7 @@ class WorkerStatusMiddleware(Middleware):
sleep(30)
class _MetricsHandler(BaseMetricsHandler):
def do_GET(self) -> None:
monitoring_set.send_robust(self)
return super().do_GET()
class MetricsMiddleware(BaseMetricsMiddleware):
handler_class = _MetricsHandler
@property
def forks(self):
from authentik.tasks.forks import worker_metrics

View File

@@ -1,18 +1,14 @@
"""admin signals"""
from datetime import timedelta
import pglock
from django.db.models import Count
from django.dispatch import receiver
from django.utils.timezone import now
from django_dramatiq_postgres.models import TaskState
from django.utils.timezone import now, timedelta
from packaging.version import parse
from prometheus_client import Gauge
from authentik import authentik_full_version
from authentik.root.monitoring import monitoring_set
from authentik.tasks.models import Task, WorkerStatus
from authentik.tasks.models import WorkerStatus
OLD_GAUGE_WORKERS = Gauge(
"authentik_admin_workers",
@@ -24,11 +20,6 @@ GAUGE_WORKERS = Gauge(
"Currently connected workers, their versions and if they are the same version as authentik",
["version", "version_matched"],
)
GAUGE_TASKS_QUEUED = Gauge(
"authentik_tasks_queued",
"The number of tasks in queue.",
["queue_name", "actor_name"],
)
_version = parse(authentik_full_version())
@@ -52,16 +43,3 @@ def monitoring_set_workers(sender, **kwargs):
for version, stats in worker_version_count.items():
OLD_GAUGE_WORKERS.labels(version, stats["matching"]).set(stats["count"])
GAUGE_WORKERS.labels(version, stats["matching"]).set(stats["count"])
@receiver(monitoring_set)
def monitoring_set_queued_tasks(sender, **kwargs):
"""Set number of queued tasks"""
for stats in Task.objects.values("queue_name", "actor_name").distinct():
GAUGE_TASKS_QUEUED.labels(stats["queue_name"], stats["actor_name"]).set(0)
for stats in (
Task.objects.filter(state=TaskState.QUEUED)
.values("queue_name", "actor_name")
.annotate(count=Count("pk"))
):
GAUGE_TASKS_QUEUED.labels(stats["queue_name"], stats["actor_name"]).set(stats["count"])

View File

@@ -2,7 +2,7 @@
"$schema": "http://json-schema.org/draft-07/schema",
"$id": "https://goauthentik.io/blueprints/schema.json",
"type": "object",
"title": "authentik 2026.2.0 Blueprint schema",
"title": "authentik 2026.2.0-rc1 Blueprint schema",
"required": [
"version",
"entries"
@@ -696,126 +696,6 @@
}
}
},
{
"type": "object",
"required": [
"model",
"identifiers"
],
"properties": {
"model": {
"const": "authentik_lifecycle.lifecycleiteration"
},
"id": {
"type": "string"
},
"state": {
"type": "string",
"enum": [
"absent",
"created",
"must_created",
"present"
],
"default": "present"
},
"conditions": {
"type": "array",
"items": {
"type": "boolean"
}
},
"permissions": {
"$ref": "#/$defs/model_authentik_lifecycle.lifecycleiteration_permissions"
},
"attrs": {
"$ref": "#/$defs/model_authentik_lifecycle.lifecycleiteration"
},
"identifiers": {
"$ref": "#/$defs/model_authentik_lifecycle.lifecycleiteration"
}
}
},
{
"type": "object",
"required": [
"model",
"identifiers"
],
"properties": {
"model": {
"const": "authentik_lifecycle.lifecyclerule"
},
"id": {
"type": "string"
},
"state": {
"type": "string",
"enum": [
"absent",
"created",
"must_created",
"present"
],
"default": "present"
},
"conditions": {
"type": "array",
"items": {
"type": "boolean"
}
},
"permissions": {
"$ref": "#/$defs/model_authentik_lifecycle.lifecyclerule_permissions"
},
"attrs": {
"$ref": "#/$defs/model_authentik_lifecycle.lifecyclerule"
},
"identifiers": {
"$ref": "#/$defs/model_authentik_lifecycle.lifecyclerule"
}
}
},
{
"type": "object",
"required": [
"model",
"identifiers"
],
"properties": {
"model": {
"const": "authentik_lifecycle.review"
},
"id": {
"type": "string"
},
"state": {
"type": "string",
"enum": [
"absent",
"created",
"must_created",
"present"
],
"default": "present"
},
"conditions": {
"type": "array",
"items": {
"type": "boolean"
}
},
"permissions": {
"$ref": "#/$defs/model_authentik_lifecycle.review_permissions"
},
"attrs": {
"$ref": "#/$defs/model_authentik_lifecycle.review"
},
"identifiers": {
"$ref": "#/$defs/model_authentik_lifecycle.review"
}
}
},
{
"type": "object",
"required": [
@@ -5682,18 +5562,6 @@
"authentik_flows.view_flowstagebinding",
"authentik_flows.view_flowtoken",
"authentik_flows.view_stage",
"authentik_lifecycle.add_lifecycleiteration",
"authentik_lifecycle.add_lifecyclerule",
"authentik_lifecycle.add_review",
"authentik_lifecycle.change_lifecycleiteration",
"authentik_lifecycle.change_lifecyclerule",
"authentik_lifecycle.change_review",
"authentik_lifecycle.delete_lifecycleiteration",
"authentik_lifecycle.delete_lifecyclerule",
"authentik_lifecycle.delete_review",
"authentik_lifecycle.view_lifecycleiteration",
"authentik_lifecycle.view_lifecyclerule",
"authentik_lifecycle.view_review",
"authentik_outposts.add_dockerserviceconnection",
"authentik_outposts.add_kubernetesserviceconnection",
"authentik_outposts.add_outpost",
@@ -6770,192 +6638,6 @@
}
}
},
"model_authentik_lifecycle.lifecycleiteration": {
"type": "object",
"properties": {
"content_type": {
"type": "string",
"enum": [
"authentik_core.application",
"authentik_core.group",
"authentik_rbac.role"
],
"title": "Content type"
}
},
"required": []
},
"model_authentik_lifecycle.lifecycleiteration_permissions": {
"type": "array",
"items": {
"type": "object",
"required": [
"permission"
],
"properties": {
"permission": {
"type": "string",
"enum": [
"add_lifecycleiteration",
"change_lifecycleiteration",
"delete_lifecycleiteration",
"view_lifecycleiteration"
]
},
"user": {
"type": "integer"
},
"role": {
"type": "string"
}
}
}
},
"model_authentik_lifecycle.lifecyclerule": {
"type": "object",
"properties": {
"name": {
"type": "string",
"minLength": 1,
"title": "Name"
},
"content_type": {
"type": "string",
"enum": [
"authentik_core.application",
"authentik_core.group",
"authentik_rbac.role"
],
"title": "Content type"
},
"object_id": {
"type": [
"string",
"null"
],
"minLength": 1,
"title": "Object id"
},
"interval": {
"type": "string",
"minLength": 1,
"title": "Interval"
},
"grace_period": {
"type": "string",
"minLength": 1,
"title": "Grace period"
},
"reviewer_groups": {
"type": "array",
"items": {
"type": "string",
"format": "uuid"
},
"title": "Reviewer groups"
},
"min_reviewers": {
"type": "integer",
"minimum": 0,
"maximum": 32767,
"title": "Min reviewers"
},
"min_reviewers_is_per_group": {
"type": "boolean",
"title": "Min reviewers is per group"
},
"reviewers": {
"type": "array",
"items": {
"type": "string",
"pattern": "^[-a-zA-Z0-9_]+$"
},
"title": "Reviewers"
},
"notification_transports": {
"type": "array",
"items": {
"type": "string",
"format": "uuid",
"description": "Select which transports should be used to notify the reviewers. If none are selected, the notification will only be shown in the authentik UI."
},
"title": "Notification transports",
"description": "Select which transports should be used to notify the reviewers. If none are selected, the notification will only be shown in the authentik UI."
}
},
"required": []
},
"model_authentik_lifecycle.lifecyclerule_permissions": {
"type": "array",
"items": {
"type": "object",
"required": [
"permission"
],
"properties": {
"permission": {
"type": "string",
"enum": [
"add_lifecyclerule",
"change_lifecyclerule",
"delete_lifecyclerule",
"view_lifecyclerule"
]
},
"user": {
"type": "integer"
},
"role": {
"type": "string"
}
}
}
},
"model_authentik_lifecycle.review": {
"type": "object",
"properties": {
"iteration": {
"type": "string",
"format": "uuid",
"title": "Iteration"
},
"note": {
"type": [
"string",
"null"
],
"minLength": 1,
"title": "Note"
}
},
"required": []
},
"model_authentik_lifecycle.review_permissions": {
"type": "array",
"items": {
"type": "object",
"required": [
"permission"
],
"properties": {
"permission": {
"type": "string",
"enum": [
"add_review",
"change_review",
"delete_review",
"view_review"
]
},
"user": {
"type": "integer"
},
"role": {
"type": "string"
}
}
}
},
"model_authentik_enterprise.license": {
"type": "object",
"properties": {
@@ -7483,11 +7165,6 @@
"minLength": 1,
"title": "Reply url"
},
"wtrealm": {
"type": "string",
"minLength": 1,
"title": "Wtrealm"
},
"assertion_valid_not_before": {
"type": "string",
"minLength": 1,
@@ -7853,10 +7530,6 @@
"email_sent",
"update_available",
"export_ready",
"review_initiated",
"review_overdue",
"review_attested",
"review_completed",
"custom_"
],
"title": "Action"
@@ -7975,10 +7648,6 @@
"email_sent",
"update_available",
"export_ready",
"review_initiated",
"review_overdue",
"review_attested",
"review_completed",
"custom_"
],
"title": "Action"
@@ -8297,9 +7966,7 @@
"content_left",
"content_right",
"sidebar_left",
"sidebar_right",
"sidebar_left_frame_background",
"sidebar_right_frame_background"
"sidebar_right"
],
"title": "Layout"
},
@@ -8728,10 +8395,6 @@
"email_sent",
"update_available",
"export_ready",
"review_initiated",
"review_overdue",
"review_attested",
"review_completed",
"custom_"
],
"title": "Action",
@@ -8819,7 +8482,6 @@
"authentik.enterprise.audit",
"authentik.enterprise.endpoints.connectors.agent",
"authentik.enterprise.endpoints.connectors.fleet",
"authentik.enterprise.lifecycle",
"authentik.enterprise.policies.unique_password",
"authentik.enterprise.providers.google_workspace",
"authentik.enterprise.providers.microsoft_entra",
@@ -8949,9 +8611,6 @@
"authentik_brands.brand",
"authentik_blueprints.blueprintinstance",
"authentik_endpoints_connectors_fleet.fleetconnector",
"authentik_lifecycle.lifecyclerule",
"authentik_lifecycle.lifecycleiteration",
"authentik_lifecycle.review",
"authentik_policies_unique_password.uniquepasswordpolicy",
"authentik_providers_google_workspace.googleworkspaceprovider",
"authentik_providers_google_workspace.googleworkspaceprovidermapping",
@@ -11270,18 +10929,6 @@
"authentik_flows.view_flowstagebinding",
"authentik_flows.view_flowtoken",
"authentik_flows.view_stage",
"authentik_lifecycle.add_lifecycleiteration",
"authentik_lifecycle.add_lifecyclerule",
"authentik_lifecycle.add_review",
"authentik_lifecycle.change_lifecycleiteration",
"authentik_lifecycle.change_lifecyclerule",
"authentik_lifecycle.change_review",
"authentik_lifecycle.delete_lifecycleiteration",
"authentik_lifecycle.delete_lifecyclerule",
"authentik_lifecycle.delete_review",
"authentik_lifecycle.view_lifecycleiteration",
"authentik_lifecycle.view_lifecyclerule",
"authentik_lifecycle.view_review",
"authentik_outposts.add_dockerserviceconnection",
"authentik_outposts.add_kubernetesserviceconnection",
"authentik_outposts.add_outpost",

View File

@@ -29,7 +29,7 @@ entries:
password=request.user.password
)
# ...otherwise we set an immutable ID based on the user's UID
user["on_premises_immutable_id"] = request.user.uid
user["on_premises_immutable_id"] = request.user.uid,
return user
- identifiers:
managed: goauthentik.io/providers/microsoft_entra/group

6
go.mod
View File

@@ -23,16 +23,16 @@ require (
github.com/jellydator/ttlcache/v3 v3.4.0
github.com/mitchellh/mapstructure v1.5.0
github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484
github.com/pires/go-proxyproto v0.10.0
github.com/pires/go-proxyproto v0.9.2
github.com/prometheus/client_golang v1.23.2
github.com/sethvargo/go-envconfig v1.3.0
github.com/sirupsen/logrus v1.9.4
github.com/spf13/cobra v1.10.2
github.com/stretchr/testify v1.11.1
github.com/wwt/guac v1.3.2
goauthentik.io/api/v3 v3.2026020.17-0.20260210174940-ae049de99535
goauthentik.io/api/v3 v3.2026020.17-0.20260203144237-cf0a7b7393e7
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab
golang.org/x/oauth2 v0.35.0
golang.org/x/oauth2 v0.34.0
golang.org/x/sync v0.19.0
gopkg.in/yaml.v2 v2.4.0
gorm.io/driver/postgres v1.6.0

16
go.sum
View File

@@ -158,8 +158,8 @@ github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
github.com/pires/go-proxyproto v0.10.0 h1:08wrdt9NQYTjLWeag3EBIS7ZNi6Vwl3rGsEjVLaAhvU=
github.com/pires/go-proxyproto v0.10.0/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
github.com/pires/go-proxyproto v0.9.2 h1:H1UdHn695zUVVmB0lQ354lOWHOy6TZSpzBl3tgN0s1U=
github.com/pires/go-proxyproto v0.9.2/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -214,10 +214,10 @@ go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
goauthentik.io/api/v3 v3.2026020.17-0.20260205232234-280022b0a8de h1:X1ELA34R1N+S+EWR8mcZRTwyZTze3bVKJh4cmeppxIY=
goauthentik.io/api/v3 v3.2026020.17-0.20260205232234-280022b0a8de/go.mod h1:uYa+yGMglhJy8ymyUQ8KQiJjOb3UZTuPQ24Ot2s9BCo=
goauthentik.io/api/v3 v3.2026020.17-0.20260210174940-ae049de99535 h1:DPk8z6SGesp0gbmaD2zTAKVSd/NQ++Nu+lu3UrCkNvE=
goauthentik.io/api/v3 v3.2026020.17-0.20260210174940-ae049de99535/go.mod h1:uYa+yGMglhJy8ymyUQ8KQiJjOb3UZTuPQ24Ot2s9BCo=
goauthentik.io/api/v3 v3.2026020.17-0.20260126165226-52b0b9497497 h1:uebnevXt0MnVIdmBPh39hCggT5Mz/DW8diDvv1n9W50=
goauthentik.io/api/v3 v3.2026020.17-0.20260126165226-52b0b9497497/go.mod h1:uYa+yGMglhJy8ymyUQ8KQiJjOb3UZTuPQ24Ot2s9BCo=
goauthentik.io/api/v3 v3.2026020.17-0.20260203144237-cf0a7b7393e7 h1:0dDYUvv3LXNgYgY0uSpws78J0EPBWGyk6hw45OZwFmY=
goauthentik.io/api/v3 v3.2026020.17-0.20260203144237-cf0a7b7393e7/go.mod h1:uYa+yGMglhJy8ymyUQ8KQiJjOb3UZTuPQ24Ot2s9BCo=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
@@ -234,8 +234,8 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ=
golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=

View File

@@ -1 +1 @@
2026.2.0
2026.2.0-rc1

View File

@@ -1,7 +1,6 @@
package application
import (
"context"
"net/http"
"net/url"
"strings"
@@ -20,41 +19,8 @@ func (a *Application) handleAuthStart(rw http.ResponseWriter, r *http.Request, f
state, err := a.createState(r, rw, fwd)
if err != nil {
a.log.WithError(err).Warning("failed to create state")
if !strings.HasPrefix(err.Error(), "failed to get session") {
rw.WriteHeader(400)
return
}
// Client has a cookie but we're unable to load the session from
// storage (TMPDIR=/dev/shm). This can happen if the session file
// was deleted due to container restart or session invalidation
// (e.g., logout on auth server).
//
// Re-save an empty session and try again.
session, err := a.sessions.Get(r, a.SessionName())
if err != nil && !strings.HasSuffix(err.Error(), "no such file or directory") {
a.log.WithError(err).Warning("failed to get session")
rw.WriteHeader(400)
return
}
err = a.sessions.Save(r, rw, session)
if err != nil {
a.log.WithError(err).Warning("failed to save session")
rw.WriteHeader(400)
return
}
// The registry caches the previous attempt to open the session so it
// needs to be cleared in order to get the session in createState().
*r = *r.WithContext(context.Background())
state, err = a.createState(r, rw, fwd)
if err != nil {
a.log.WithError(err).Warning("failed to create state on retry")
rw.WriteHeader(400)
return
}
rw.WriteHeader(400)
return
}
http.Redirect(rw, r, a.oauthConfig.AuthCodeURL(state), http.StatusFound)
}
@@ -76,7 +42,7 @@ func (a *Application) redirectToStart(rw http.ResponseWriter, r *http.Request) {
}
}
redirectUrl := urlJoin(a.proxyConfig.ExternalHost, r.URL.EscapedPath())
redirectUrl := urlJoin(a.proxyConfig.ExternalHost, r.URL.Path)
if a.Mode() == api.PROXYMODE_FORWARD_DOMAIN {
dom := strings.TrimPrefix(*a.proxyConfig.CookieDomain, ".")

View File

@@ -27,24 +27,6 @@ func TestRedirectToStart_Proxy(t *testing.T) {
assert.Equal(t, "https://test.goauthentik.io/foo/bar/baz", s.Values[constants.SessionRedirect])
}
func TestRedirectToStart_Proxy_EncodedSlash(t *testing.T) {
a := newTestApplication()
a.proxyConfig.Mode = api.PROXYMODE_PROXY.Ptr()
a.proxyConfig.ExternalHost = "https://test.goauthentik.io"
// %2F is a URL-encoded forward slash, used by apps like RabbitMQ in queue paths
req, _ := http.NewRequest("GET", "/api/queues/%2F/MYChannelCreated", nil)
rr := httptest.NewRecorder()
a.redirectToStart(rr, req)
assert.Equal(t, http.StatusFound, rr.Code)
loc, _ := rr.Result().Location()
assert.Contains(t, loc.String(), "%252F", "encoded slash %2F must be preserved in redirect URL")
s, _ := a.sessions.Get(req, a.SessionName())
assert.Contains(t, s.Values[constants.SessionRedirect].(string), "%2F", "encoded slash %2F must be preserved in session redirect")
}
func TestRedirectToStart_Forward(t *testing.T) {
a := newTestApplication()
a.proxyConfig.Mode = api.PROXYMODE_FORWARD_SINGLE.Ptr()

View File

@@ -9,7 +9,7 @@
"version": "0.0.0",
"license": "MIT",
"devDependencies": {
"aws-cdk": "^2.1105.0",
"aws-cdk": "^2.1104.0",
"cross-env": "^10.1.0"
},
"engines": {
@@ -25,9 +25,9 @@
"license": "MIT"
},
"node_modules/aws-cdk": {
"version": "2.1105.0",
"resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1105.0.tgz",
"integrity": "sha512-1RY2UZJv31XYobEGFHQEb7c2HXNzDbHuHqdnfdYyygvZW4Nrm8MJCW42lqItQCn+wF52Ixc7r2VR5eR4YGtVhA==",
"version": "2.1104.0",
"resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1104.0.tgz",
"integrity": "sha512-TGIK2Mpfqi0BA6Np9aJz0d5HAvTxWd17FrwtXlJuwqdQbR9R/IRqsabF6xRAuhFTz7/YrrHHU9H4VK/Xfnh7Vg==",
"dev": true,
"license": "Apache-2.0",
"bin": {

View File

@@ -7,7 +7,7 @@
"aws-cfn": "cross-env CI=false cdk synth --version-reporting=false > template.yaml"
},
"devDependencies": {
"aws-cdk": "^2.1105.0",
"aws-cdk": "^2.1104.0",
"cross-env": "^10.1.0"
},
"engines": {

View File

@@ -18,7 +18,7 @@ Parameters:
Description: authentik Docker image
AuthentikVersion:
Type: String
Default: 2026.2.0
Default: 2026.2.0-rc1
Description: authentik Docker image tag
AuthentikServerCPU:
Type: Number

View File

@@ -31,7 +31,7 @@ services:
AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik}
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY:?secret key required}
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2026.2.0}
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2026.2.0-rc1}
ports:
- ${COMPOSE_PORT_HTTP:-9000}:9000
- ${COMPOSE_PORT_HTTPS:-9443}:9443
@@ -53,7 +53,7 @@ services:
AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik}
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY:?secret key required}
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2026.2.0}
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2026.2.0-rc1}
restart: unless-stopped
shm_size: 512mb
user: root

Some files were not shown because too many files have changed in this diff Show More