Compare commits

..

56 Commits

Author SHA1 Message Date
Teffen Ellis
4bd7464bd8 core, web: Fix Han language codes. 2025-12-02 20:26:31 +01:00
Marcin Koziuk
f914af70f1 web/admin: fix brands default switch label (#18518) 2025-12-02 15:21:10 +00:00
dependabot[bot]
a46c5e5d89 web: bump express from 4.21.2 to 4.22.1 in /packages/docusaurus-config (#18520)
Bumps [express](https://github.com/expressjs/express) from 4.21.2 to 4.22.1.
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/v4.22.1/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.21.2...v4.22.1)

---
updated-dependencies:
- dependency-name: express
  dependency-version: 4.22.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-02 15:43:19 +01:00
dependabot[bot]
1ebd7cf0f4 website: bump the goauthentik group in /website with 2 updates (#18513)
Bumps the goauthentik group in /website with 2 updates: [@goauthentik/eslint-config](https://github.com/goauthentik/authentik/tree/HEAD/packages/eslint-config) and [@goauthentik/docusaurus-config](https://github.com/goauthentik/authentik/tree/HEAD/packages/docusaurus-config).


Updates `@goauthentik/eslint-config` from 1.1.0 to 1.1.1
- [Release notes](https://github.com/goauthentik/authentik/releases)
- [Commits](https://github.com/goauthentik/authentik/commits/HEAD/packages/eslint-config)

Updates `@goauthentik/docusaurus-config` from 2.2.1 to 2.2.2
- [Release notes](https://github.com/goauthentik/authentik/releases)
- [Commits](https://github.com/goauthentik/authentik/commits/HEAD/packages/docusaurus-config)

---
updated-dependencies:
- dependency-name: "@goauthentik/eslint-config"
  dependency-version: 1.1.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: goauthentik
- dependency-name: "@goauthentik/docusaurus-config"
  dependency-version: 2.2.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: goauthentik
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-02 15:28:58 +01:00
authentik-automation[bot]
5a7a76f9b3 core, web: update translations (#18510)
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2025-12-02 14:34:49 +01:00
dependabot[bot]
f607378487 ci: bump softprops/action-gh-release from 2.4.2 to 2.5.0 (#18512)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-02 14:32:47 +01:00
dependabot[bot]
e114a68505 core: bump astral-sh/uv from 0.9.13 to 0.9.14 (#18514)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-02 14:32:13 +01:00
dependabot[bot]
ebe028f3c9 core: bump goauthentik/fips-debian from de70579 to c718f60 (#18515)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-02 14:31:59 +01:00
Connor Peshek
eb60276846 integrations/slack: Add SCIM tutorial (#18508)
* integrations/slack: Add SCIM tutorial

* Update wording

* Update index.md

Co-authored-by: Dominic R <dominic@sdko.org>
Signed-off-by: Connor Peshek <connor@connorpeshek.me>

* Small changes

---------

Signed-off-by: Connor Peshek <connor@connorpeshek.me>
Co-authored-by: connor peshek <connorpeshek@connors-MacBook-Pro.local>
Co-authored-by: Dominic R <dominic@sdko.org>
Co-authored-by: dewi-tik <dewi@goauthentik.io>
2025-12-02 04:28:26 -06:00
Teffen Ellis
952a0f796d translate: fix source locale not matching transifex (#18503)
Co-authored-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-12-01 19:08:58 +00:00
Teffen Ellis
b7205ff167 web: Fixes for Docusaurus & ESlint Upgrade (#18452)
* Fix style regressions in Docusaurus 3.9

* Fix ignore file regression.

* Bump package versions.

* Update packages/docusaurus-config/css/navbar.css

Co-authored-by: Ken Sternberg <133134217+kensternberg-authentik@users.noreply.github.com>
Signed-off-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com>

---------

Signed-off-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com>
Co-authored-by: Ken Sternberg <133134217+kensternberg-authentik@users.noreply.github.com>
2025-12-01 13:57:32 -05:00
Marc 'risson' Schmitt
5379c509d6 translate: fix config (#18504)
Co-authored-by: Teffen Ellis <teffen@goauthentik.io>
2025-12-01 18:20:14 +00:00
Marc 'risson' Schmitt
63119df516 core, web: unified locales (#18502)
Co-authored-by: Teffen Ellis <teffen@goauthentik.io>
2025-12-01 18:47:44 +01:00
transifex-integration[bot]
cd53ab5eba translate: Updates for project authentik and language ko (#18488)
Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2025-12-01 16:56:30 +00:00
transifex-integration[bot]
5aa7a1c62d translate: Updates for project authentik and language es (#18485)
Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2025-12-01 16:54:55 +00:00
transifex-integration[bot]
092c8a3db4 translate: Updates for project authentik and language zh_TW (#18499)
Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2025-12-01 16:50:40 +00:00
transifex-integration[bot]
9d8427b1b4 translate: Updates for project authentik and language ru (#18500)
Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2025-12-01 16:49:31 +00:00
transifex-integration[bot]
92556ca783 translate: Updates for project authentik and language pt (#18498)
Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2025-12-01 16:46:20 +00:00
transifex-integration[bot]
d5c2aedc8e translate: Updates for project authentik and language fr (#18496)
Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2025-12-01 16:46:08 +00:00
transifex-integration[bot]
02304081ed translate: Updates for project authentik and language de (#18487)
Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2025-12-01 16:45:48 +00:00
transifex-integration[bot]
d60b6faa61 translate: Updates for project authentik and language nl (#18497)
Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2025-12-01 16:45:29 +00:00
transifex-integration[bot]
b12d1ed410 translate: Updates for project authentik and language fi (#18490)
Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2025-12-01 16:44:45 +00:00
authentik-automation[bot]
1276d87d69 core, web: update translations (#18380)
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2025-12-01 17:01:05 +01:00
Marc 'risson' Schmitt
0ba097ad95 web: re-add en.xlf locale (#18469) 2025-12-01 15:54:11 +01:00
Sebastian Knackstedt
3ff7332742 stages/user_write: Fix user attributes are not sanitized under certains conditions (#17890)
* stages/user_write: Fix user attributes are not sanitized under certain conditions

* add test and forbid attributes replacement

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
2025-12-01 15:12:13 +01:00
Jens L.
72f0f98706 providers/scim: compare users/groups before sending update request (#18456)
* implement user

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add compare for groups

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-12-01 13:50:41 +01:00
Jens L.
873a47f9a2 enterprise/endpoints/connectors/agent: fix Apple JWE encryption when FIPS is enabled (#18464)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-12-01 13:31:03 +01:00
dependabot[bot]
382bd324c2 website: bump @types/react from 19.2.6 to 19.2.7 in /website (#18357)
Bumps [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react) from 19.2.6 to 19.2.7.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react)

---
updated-dependencies:
- dependency-name: "@types/react"
  dependency-version: 19.2.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-01 13:30:23 +01:00
dependabot[bot]
71e57611d0 core: bump goauthentik/fips-debian from ac4c80b to de70579 (#18419)
Bumps goauthentik/fips-debian from `ac4c80b` to `de70579`.

---
updated-dependencies:
- dependency-name: goauthentik/fips-debian
  dependency-version: trixie-slim-fips
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-01 13:30:02 +01:00
dependabot[bot]
04a8f02b2a core: bump github.com/getsentry/sentry-go from 0.39.0 to 0.40.0 (#18416)
Bumps [github.com/getsentry/sentry-go](https://github.com/getsentry/sentry-go) from 0.39.0 to 0.40.0.
- [Release notes](https://github.com/getsentry/sentry-go/releases)
- [Changelog](https://github.com/getsentry/sentry-go/blob/master/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-go/compare/v0.39.0...v0.40.0)

---
updated-dependencies:
- dependency-name: github.com/getsentry/sentry-go
  dependency-version: 0.40.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-01 13:29:28 +01:00
dependabot[bot]
2250cea934 website: bump prettier-plugin-packagejson from 2.5.19 to 2.5.20 in /website (#18460)
website: bump prettier-plugin-packagejson in /website

Bumps [prettier-plugin-packagejson](https://github.com/matzkoh/prettier-plugin-packagejson) from 2.5.19 to 2.5.20.
- [Release notes](https://github.com/matzkoh/prettier-plugin-packagejson/releases)
- [Commits](https://github.com/matzkoh/prettier-plugin-packagejson/compare/v2.5.19...v2.5.20)

---
updated-dependencies:
- dependency-name: prettier-plugin-packagejson
  dependency-version: 2.5.20
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-01 13:28:52 +01:00
dependabot[bot]
b6d21bad71 core: bump goauthentik.io/api/v3 from 3.2025120.7 to 3.2025120.11 (#18461)
Bumps [goauthentik.io/api/v3](https://github.com/goauthentik/client-go) from 3.2025120.7 to 3.2025120.11.
- [Release notes](https://github.com/goauthentik/client-go/releases)
- [Commits](https://github.com/goauthentik/client-go/compare/v3.2025120.7...v3.2025120.11)

---
updated-dependencies:
- dependency-name: goauthentik.io/api/v3
  dependency-version: 3.2025120.11
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-01 13:28:43 +01:00
lame
2bb86f6f12 website/integrations: add GLPI (#17937)
Co-authored-by: dewi-tik <dewi@goauthentik.io>
Co-authored-by: Dominic R <dominic@sdko.org>
2025-12-01 05:24:31 +00:00
Dewi Roberts
c75ff13770 website/integrations: small fixes (#18423)
Co-authored-by: Dominic R <dominic@sdko.org>
2025-11-30 23:59:21 -05:00
Jens L.
874a20b908 enterprise: Apple Platform SSO (#15318)
* init

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* snap

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* format

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* it works

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* give session

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix session

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* better endpoint

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add api

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* attempt endpoint

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* refactor into endpoints system

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* start reworking

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* make it work more

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* re-add user data

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add jwks

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add rest of the endpoints

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix test

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix lookup

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix device group selection

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix incorrect device id

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix register

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* implement the thing

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix a bunch of things

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix issuer

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix fully

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* format

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add test for apple JWE

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add tests

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add more tests

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add token tests

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* make auth session duration configurable, merge migrations

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* update api & ui

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix enterprise

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* include platform sso in generated mdm config

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-12-01 00:28:09 +01:00
Jens L.
ea7cbafefb crypto: only generate managed keypair if non-existent (#18457)
* crypto: only generate managed keypair if non-existent

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix translation

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-11-30 20:49:55 +01:00
Marc 'risson' Schmitt
46b889eab1 ci: remove translation-rename (#18444) 2025-11-28 20:38:01 +00:00
transifex-integration[bot]
c0744d6cf3 translate: Updates for project authentik and language tr (#18438)
translate: Translate django.po in tr

73% of minimum 60% translated source file: 'django.po'
on 'tr'.

Sync of partially translated files: 
untranslated content is included with an empty translation 
or source language content depending on file format

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2025-11-28 18:20:25 +00:00
transifex-integration[bot]
9bae5caee4 translate: Updates for project authentik and language fr (#18431)
translate: Translate django.po in fr

98% of minimum 60% translated source file: 'django.po'
on 'fr'.

Sync of partially translated files: 
untranslated content is included with an empty translation 
or source language content depending on file format

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2025-11-28 18:20:06 +00:00
transifex-integration[bot]
5756b189bb translate: Updates for project authentik and language ru (#18442)
translate: Translate django.po in ru

72% of minimum 60% translated source file: 'django.po'
on 'ru'.

Sync of partially translated files: 
untranslated content is included with an empty translation 
or source language content depending on file format

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2025-11-28 18:19:48 +00:00
transifex-integration[bot]
af2f86e030 translate: Updates for project authentik and language cs_CZ (#18443)
translate: Translate django.po in cs_CZ

88% of minimum 60% translated source file: 'django.po'
on 'cs_CZ'.

Sync of partially translated files: 
untranslated content is included with an empty translation 
or source language content depending on file format

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2025-11-28 18:19:27 +00:00
transifex-integration[bot]
be90f63f43 translate: Updates for project authentik and language pt (#18437)
translate: Translate django.po in pt

91% of minimum 60% translated source file: 'django.po'
on 'pt'.

Sync of partially translated files: 
untranslated content is included with an empty translation 
or source language content depending on file format

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2025-11-28 18:18:58 +00:00
transifex-integration[bot]
7357b9cc58 translate: Updates for project authentik and language zh-Hans (#18439)
translate: Translate django.po in zh-Hans

83% of minimum 60% translated source file: 'django.po'
on 'zh-Hans'.

Sync of partially translated files: 
untranslated content is included with an empty translation 
or source language content depending on file format

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2025-11-28 18:18:32 +00:00
transifex-integration[bot]
ec467bc2d1 translate: Updates for project authentik and language zh_CN (#18441)
translate: Translate django.po in zh_CN

97% of minimum 60% translated source file: 'django.po'
on 'zh_CN'.

Sync of partially translated files: 
untranslated content is included with an empty translation 
or source language content depending on file format

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2025-11-28 18:17:00 +00:00
Dewi Roberts
d140e4fdd3 website/integrations: add local browser setting to seafile (#18428) 2025-11-28 18:16:33 +00:00
transifex-integration[bot]
75ead31448 translate: Updates for project authentik and language pl (#18430)
translate: Translate django.po in pl

69% of minimum 60% translated source file: 'django.po'
on 'pl'.

Sync of partially translated files: 
untranslated content is included with an empty translation 
or source language content depending on file format

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2025-11-28 18:16:07 +00:00
transifex-integration[bot]
713bb04c21 translate: Updates for project authentik and language pt_BR (#18436)
translate: Translate django.po in pt_BR

98% of minimum 60% translated source file: 'django.po'
on 'pt_BR'.

Sync of partially translated files: 
untranslated content is included with an empty translation 
or source language content depending on file format

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2025-11-28 18:15:47 +00:00
transifex-integration[bot]
c14a029b23 translate: Updates for project authentik and language nl (#18434)
translate: Translate django.po in nl

64% of minimum 60% translated source file: 'django.po'
on 'nl'.

Sync of partially translated files: 
untranslated content is included with an empty translation 
or source language content depending on file format

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2025-11-28 18:15:17 +00:00
transifex-integration[bot]
6092a26450 translate: Updates for project authentik and language es (#18433)
translate: Translate django.po in es

90% of minimum 60% translated source file: 'django.po'
on 'es'.

Sync of partially translated files: 
untranslated content is included with an empty translation 
or source language content depending on file format

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2025-11-28 18:14:41 +00:00
transifex-integration[bot]
6fba028404 translate: Updates for project authentik and language de (#18435)
translate: Translate django.po in de

93% of minimum 60% translated source file: 'django.po'
on 'de'.

Sync of partially translated files: 
untranslated content is included with an empty translation 
or source language content depending on file format

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2025-11-28 18:13:49 +00:00
transifex-integration[bot]
678e1d87ba translate: Updates for project authentik and language fi (#18429)
translate: Translate django.po in fi

99% of minimum 60% translated source file: 'django.po'
on 'fi'.

Sync of partially translated files: 
untranslated content is included with an empty translation 
or source language content depending on file format

Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
2025-11-28 18:13:15 +00:00
Jens L.
f1a1f327cd endpoints: rework perms (#18422)
* fix api being incorrect

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* more lenient facts

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix authz flow not returning slug

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* different auth header for multi-auth

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix tests

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-11-28 17:26:11 +01:00
NiceDevil
d94117a8e3 website/integrations: Amazon Business (#17894)
* New Integration Guide Amazon Business

* WIP - Remove non metadata steps and standardize formatting

* Finished tidying up different sections, changed verification section, added resource.

* SSO Login Buttons not available

This Button isn't available, at least to me in germany.
I just open amazon and click on the normal login button where amazon is asking the user to enter the email address.

If you enter your businessemail here you get redirected to authentik. No separate button, I will post a screenshot :)

Signed-off-by: NiceDevil <17103076+nicedevil007@users.noreply.github.com>

* Spelling

* Update website/integrations/miscellaneous/amazon-business/index.mdx

Co-authored-by: Dominic R <dominic@sdko.org>
Signed-off-by: NiceDevil <17103076+nicedevil007@users.noreply.github.com>

* as suggested

Signed-off-by: NiceDevil <17103076+nicedevil007@users.noreply.github.com>

* Apply suggestion from @dominic-r

Signed-off-by: Dominic R <dominic@sdko.org>

* Apply suggestions from code review

Signed-off-by: Dominic R <dominic@sdko.org>

* Apply suggestion from @dominic-r

Signed-off-by: Dominic R <dominic@sdko.org>

* Apply suggestion from @dominic-r

Signed-off-by: Dominic R <dominic@sdko.org>

* Apply suggestion from @dominic-r

Signed-off-by: Dominic R <dominic@sdko.org>

* Apply suggestion from @dominic-r

Signed-off-by: Dominic R <dominic@sdko.org>

* Apply suggestion from @dominic-r

Signed-off-by: Dominic R <dominic@sdko.org>

* Apply suggestions from code review

Signed-off-by: Dominic R <dominic@sdko.org>

* Apply suggestions from code review

Co-authored-by: Dominic R <dominic@sdko.org>
Signed-off-by: Dewi Roberts <dewi@goauthentik.io>

---------

Signed-off-by: NiceDevil <17103076+nicedevil007@users.noreply.github.com>
Signed-off-by: Dominic R <dominic@sdko.org>
Signed-off-by: Dewi Roberts <dewi@goauthentik.io>
Co-authored-by: nicedevil007 <nicedevil007@users.noreply.github.com>
Co-authored-by: dewi-tik <dewi@goauthentik.io>
Co-authored-by: Dominic R <dominic@sdko.org>
2025-11-28 07:44:52 -05:00
Timo Reusch
fa32567230 website/integrations: add Placetel (#18399)
* website/integrations: Add configuration guide for Placetel

* Small updates

* Suggestions

* Apply suggestions from code review

Co-authored-by: Dominic R <dominic@sdko.org>
Signed-off-by: Dewi Roberts <dewi@goauthentik.io>

* Apply suggestions from code review

Co-authored-by: Dominic R <dominic@sdko.org>
Signed-off-by: Dewi Roberts <dewi@goauthentik.io>

* Minor changes

* Improve clarity

* Apply suggestion from @dominic-r

Signed-off-by: Dominic R <dominic@sdko.org>

* Apply suggestion from @dominic-r

Signed-off-by: Dominic R <dominic@sdko.org>

---------

Signed-off-by: Dewi Roberts <dewi@goauthentik.io>
Signed-off-by: Dominic R <dominic@sdko.org>
Co-authored-by: dewi-tik <dewi@goauthentik.io>
Co-authored-by: Dominic R <dominic@sdko.org>
2025-11-27 20:33:56 +00:00
Jens L.
59da20e81c endpoints: include device ID in agent config (#18414)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-11-27 19:23:56 +01:00
Jens L.
1fb71371cb endpoints: AuthN and AuthZ (#18350)
* start agent auth

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* also check windows system disk (hardcode C: for now)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add process table

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* include jwks

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* nonce

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* snap

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* missing exp and username (temp values)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix missing meta

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* rework auth and migrations

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* include system config in agent config

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix a bunch of broken stuff

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* update ui

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* format

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add device to login event

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add ssh

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix tests

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* start adding tests

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add tests

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* policies

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* remove domain name

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix tests

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix leftover

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add device to flow context

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* dont allow access without policies

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* some ui changes

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* re-invent the wheel again

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* start updating tests

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* t

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* Revert "t"

This reverts commit b74db5f5d4.

* Revert "start updating tests"

This reverts commit b2524c00b2.

* Revert "re-invent the wheel again"

This reverts commit c7cdf4c018.

* fixup

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix ui

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* re-migrate

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* update tests

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix web build

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* f

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix tests

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add device users and device groups

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* expand users

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-11-27 19:05:57 +01:00
190 changed files with 160643 additions and 210706 deletions

View File

@@ -1,3 +1,4 @@
---
git:
filters:
- filter_type: file

View File

@@ -89,7 +89,7 @@ jobs:
git tag "version/${{ inputs.version }}" HEAD -m "version/${{ inputs.version }}"
git push --follow-tags
- name: Create Release
uses: softprops/action-gh-release@5be0e66d93ac7ed76da52eca8bb058f665c3a5fe # v2.4.2
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
with:
token: "${{ steps.app-token.outputs.token }}"
tag_name: "version/${{ inputs.version }}"

View File

@@ -1,41 +0,0 @@
---
# Rename transifex pull requests to have a correct naming
# Also enables auto squash-merge
name: Translation - Auto-rename Transifex PRs
on:
pull_request:
types: [opened, reopened]
permissions:
# Permission to rename PR
pull-requests: write
jobs:
rename_pr:
runs-on: ubuntu-latest
if: ${{ github.event.pull_request.user.login == 'transifex-integration[bot]'}}
steps:
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v5
- id: generate_token
uses: actions/create-github-app-token@7e473efe3cb98aa54f8d4bac15400b15fad77d94 # v2
with:
app-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
- name: Get current title
id: title
env:
GH_TOKEN: ${{ steps.generate_token.outputs.token }}
run: |
title=$(gh pr view ${{ github.event.pull_request.number }} --json "title" -q ".title")
echo "title=${title}" >> "$GITHUB_OUTPUT"
- name: Rename
env:
GH_TOKEN: ${{ steps.generate_token.outputs.token }}
run: |
gh pr edit ${{ github.event.pull_request.number }} -t "translate: ${{ steps.title.outputs.title }}" --add-label dependencies
- uses: peter-evans/enable-pull-request-automerge@a660677d5469627102a1c1e11409dd063606628d # v3
with:
token: ${{ steps.generate_token.outputs.token }}
pull-request-number: ${{ github.event.pull_request.number }}
merge-method: squash

View File

@@ -76,7 +76,7 @@ RUN --mount=type=secret,id=GEOIPUPDATE_ACCOUNT_ID \
/bin/sh -c "GEOIPUPDATE_LICENSE_KEY_FILE=/run/secrets/GEOIPUPDATE_LICENSE_KEY /usr/bin/entry.sh || echo 'Failed to get GeoIP database, disabling'; exit 0"
# Stage 4: Download uv
FROM ghcr.io/astral-sh/uv:0.9.13@sha256:f07d1bf7b1fb4b983eed2b31320e25a2a76625bdf83d5ff0208fe105d4d8d2f5 AS uv
FROM ghcr.io/astral-sh/uv:0.9.14@sha256:fef8e5fb8809f4b57069e919ffcd1529c92b432a2c8d8ad1768087b0b018d840 AS uv
# Stage 5: Base python image
FROM ghcr.io/goauthentik/fips-python:3.13.9-slim-trixie-fips@sha256:700fc8c1e290bd14e5eaca50b1d8e8c748c820010559cbfb4c4f8dfbe2c4c9ff AS python-base

View File

@@ -27,16 +27,16 @@ except OSError:
ipc_key = None
def validate_auth(header: bytes) -> str | None:
def validate_auth(header: bytes, format="bearer") -> str | None:
"""Validate that the header is in a correct format,
returns type and credentials"""
auth_credentials = header.decode().strip()
if auth_credentials == "" or " " not in auth_credentials:
return None
auth_type, _, auth_credentials = auth_credentials.partition(" ")
if auth_type.lower() != "bearer":
if not compare_digest(auth_type.lower(), format):
LOGGER.debug("Unsupported authentication type, denying", type=auth_type.lower())
raise AuthenticationFailed("Unsupported authentication type")
return None
if auth_credentials == "": # nosec # noqa
raise AuthenticationFailed("Malformed header")
return auth_credentials

View File

@@ -24,8 +24,7 @@ class TestAPIAuth(TestCase):
def test_invalid_type(self):
"""Test invalid type"""
with self.assertRaises(AuthenticationFailed):
bearer_auth(b"foo bar")
self.assertIsNone(bearer_auth(b"foo bar"))
def test_invalid_empty(self):
"""Test invalid type"""
@@ -34,9 +33,8 @@ class TestAPIAuth(TestCase):
def test_invalid_no_token(self):
"""Test invalid with no token"""
with self.assertRaises(AuthenticationFailed):
auth = b64encode(b":abc").decode()
self.assertIsNone(bearer_auth(f"Basic :{auth}".encode()))
auth = b64encode(b":abc").decode()
self.assertIsNone(bearer_auth(f"Basic :{auth}".encode()))
def test_bearer_valid(self):
"""Test valid token"""

View File

@@ -5,6 +5,7 @@ from pathlib import Path
from django.conf import settings
from django.db import models
from django.dispatch import Signal
from django.http import HttpRequest
from drf_spectacular.utils import extend_schema
from rest_framework.fields import (
BooleanField,
@@ -63,7 +64,8 @@ class ConfigView(APIView):
permission_classes = [AllowAny]
def get_capabilities(self) -> list[Capabilities]:
@staticmethod
def get_capabilities(request: HttpRequest) -> list[Capabilities]:
"""Get all capabilities this server instance supports"""
caps = []
deb_test = settings.DEBUG or settings.TEST
@@ -76,18 +78,19 @@ class ConfigView(APIView):
for processor in get_context_processors():
if cap := processor.capability():
caps.append(cap)
if self.request.tenant.impersonation:
if request.tenant.impersonation:
caps.append(Capabilities.CAN_IMPERSONATE)
if settings.DEBUG: # pragma: no cover
caps.append(Capabilities.CAN_DEBUG)
if "authentik.enterprise" in settings.INSTALLED_APPS:
caps.append(Capabilities.IS_ENTERPRISE)
for _, result in capabilities.send(sender=self):
for _, result in capabilities.send(sender=ConfigView):
if result:
caps.append(result)
return caps
def get_config(self) -> ConfigSerializer:
@staticmethod
def get_config(request: HttpRequest) -> ConfigSerializer:
"""Get Config"""
return ConfigSerializer(
{
@@ -98,7 +101,7 @@ class ConfigView(APIView):
"send_pii": CONFIG.get("error_reporting.send_pii"),
"traces_sample_rate": float(CONFIG.get("error_reporting.sample_rate", 0.4)),
},
"capabilities": self.get_capabilities(),
"capabilities": ConfigView.get_capabilities(request),
"cache_timeout": CONFIG.get_int("cache.timeout"),
"cache_timeout_flows": CONFIG.get_int("cache.timeout_flows"),
"cache_timeout_policies": CONFIG.get_int("cache.timeout_policies"),
@@ -108,4 +111,4 @@ class ConfigView(APIView):
@extend_schema(responses={200: ConfigSerializer(many=False)})
def get(self, request: Request) -> Response:
"""Retrieve public configuration options"""
return Response(self.get_config().data)
return Response(ConfigView.get_config(request).data)

View File

@@ -44,6 +44,8 @@ from authentik.core.models import (
)
from authentik.endpoints.connectors.agent.models import (
AgentDeviceConnection,
AppleNonce,
DeviceAuthenticationToken,
)
from authentik.endpoints.connectors.agent.models import (
DeviceToken as EndpointDeviceToken,
@@ -150,6 +152,8 @@ def excluded_models() -> list[type[Model]]:
EndpointDeviceToken,
Device,
DeviceConnection,
DeviceAuthenticationToken,
AppleNonce,
AgentDeviceConnection,
DeviceFactSnapshot,
DeviceToken,

View File

@@ -14,6 +14,7 @@ from drf_spectacular.utils import (
extend_schema_field,
)
from guardian.shortcuts import get_objects_for_user
from rest_framework.authentication import SessionAuthentication
from rest_framework.decorators import action
from rest_framework.fields import CharField, IntegerField, SerializerMethodField
from rest_framework.request import Request
@@ -22,10 +23,12 @@ from rest_framework.serializers import ListSerializer, ValidationError
from rest_framework.validators import UniqueValidator
from rest_framework.viewsets import ModelViewSet
from authentik.api.authentication import TokenAuthentication
from authentik.api.validation import validate
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import JSONDictField, ModelSerializer, PassiveSerializer
from authentik.core.models import Group, User
from authentik.endpoints.connectors.agent.auth import AgentAuth
from authentik.rbac.api.roles import RoleSerializer
from authentik.rbac.decorators import permission_required
@@ -228,6 +231,11 @@ class GroupViewSet(UsedByMixin, ModelViewSet):
search_fields = ["name", "is_superuser"]
filterset_class = GroupFilter
ordering = ["name"]
authentication_classes = [
TokenAuthentication,
SessionAuthentication,
AgentAuth,
]
def get_ql_fields(self):
from djangoql.schema import BoolField, StrField

View File

@@ -109,6 +109,8 @@ class TokenSerializer(ManagedSerializer, ModelSerializer):
class TokenSetKeySerializer(PassiveSerializer):
"""Set token's key"""
key = CharField()

View File

@@ -31,6 +31,7 @@ from drf_spectacular.utils import (
inline_serializer,
)
from guardian.shortcuts import get_objects_for_user
from rest_framework.authentication import SessionAuthentication
from rest_framework.decorators import action
from rest_framework.exceptions import ValidationError
from rest_framework.fields import (
@@ -52,6 +53,7 @@ from rest_framework.validators import UniqueValidator
from rest_framework.viewsets import ModelViewSet
from structlog.stdlib import get_logger
from authentik.api.authentication import TokenAuthentication
from authentik.api.validation import validate
from authentik.blueprints.v1.importer import SERIALIZER_CONTEXT_BLUEPRINT
from authentik.brands.models import Brand
@@ -76,6 +78,7 @@ from authentik.core.models import (
User,
UserTypes,
)
from authentik.endpoints.connectors.agent.auth import AgentAuth
from authentik.events.models import Event, EventAction
from authentik.flows.exceptions import FlowNonApplicableException
from authentik.flows.models import FlowToken
@@ -433,6 +436,11 @@ class UserViewSet(UsedByMixin, ModelViewSet):
serializer_class = UserSerializer
filterset_class = UsersFilter
search_fields = ["email", "name", "uuid", "username"]
authentication_classes = [
TokenAuthentication,
SessionAuthentication,
AgentAuth,
]
def get_ql_fields(self):
from djangoql.schema import BoolField, StrField

View File

@@ -8,7 +8,6 @@ from django.http.response import HttpResponse
from django.shortcuts import redirect
from django.utils.translation import gettext as _
from django.views.generic.base import RedirectView, TemplateView
from rest_framework.request import Request
from authentik import authentik_build_hash
from authentik.admin.tasks import LOCAL_VERSION
@@ -47,7 +46,7 @@ class InterfaceView(TemplateView):
def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
brand = CurrentBrandSerializer(self.request.brand)
kwargs["config_json"] = dumps(ConfigView(request=Request(self.request)).get_config().data)
kwargs["config_json"] = dumps(ConfigView.get_config(self.request).data)
kwargs["ui_theme"] = brand.data["ui_theme"]
kwargs["brand_json"] = dumps(brand.data)
kwargs["version_family"] = f"{LOCAL_VERSION.major}.{LOCAL_VERSION.minor}"

View File

@@ -1,7 +1,5 @@
"""authentik crypto app config"""
from datetime import UTC, datetime
from dramatiq.broker import get_broker
from authentik.blueprints.apps import ManagedAppConfig
@@ -47,10 +45,7 @@ class AuthentikCryptoConfig(ManagedAppConfig):
cert: CertificateKeyPair | None = CertificateKeyPair.objects.filter(
managed=MANAGED_KEY
).first()
now = datetime.now(tz=UTC)
if not cert or (
now < cert.certificate.not_valid_after_utc or now > cert.certificate.not_valid_after_utc
):
if not cert:
self._create_update_cert()
@ManagedAppConfig.reconcile_tenant

View File

@@ -2,24 +2,24 @@ from rest_framework.viewsets import ModelViewSet
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import ModelSerializer
from authentik.endpoints.models import DeviceGroup
from authentik.endpoints.models import DeviceAccessGroup
class DeviceGroupSerializer(ModelSerializer):
class DeviceAccessGroupSerializer(ModelSerializer):
class Meta:
model = DeviceGroup
model = DeviceAccessGroup
fields = [
"pbm_uuid",
"name",
]
class DeviceGroupViewSet(UsedByMixin, ModelViewSet):
"""DeviceGroup Viewset"""
class DeviceAccessGroupViewSet(UsedByMixin, ModelViewSet):
"""DeviceAccessGroup Viewset"""
queryset = DeviceGroup.objects.all()
serializer_class = DeviceGroupSerializer
queryset = DeviceAccessGroup.objects.all()
serializer_class = DeviceAccessGroupSerializer
search_fields = [
"pbm_uuid",
"name",

View File

@@ -4,15 +4,15 @@ from rest_framework.viewsets import GenericViewSet
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import ModelSerializer
from authentik.endpoints.api.device_access_group import DeviceAccessGroupSerializer
from authentik.endpoints.api.device_connections import DeviceConnectionSerializer
from authentik.endpoints.api.device_fact_snapshots import DeviceFactSnapshotSerializer
from authentik.endpoints.api.device_group import DeviceGroupSerializer
from authentik.endpoints.models import Device
class EndpointDeviceSerializer(ModelSerializer):
group_obj = DeviceGroupSerializer(source="group")
access_group_obj = DeviceAccessGroupSerializer(source="access_group", required=False)
facts = SerializerMethodField()
@@ -25,8 +25,8 @@ class EndpointDeviceSerializer(ModelSerializer):
"device_uuid",
"pbm_uuid",
"name",
"group",
"group_obj",
"access_group",
"access_group_obj",
"expiring",
"expires",
"facts",
@@ -58,7 +58,7 @@ class DeviceViewSet(
GenericViewSet,
):
queryset = Device.objects.all().select_related("group")
queryset = Device.objects.all().select_related("access_group")
serializer_class = EndpointDeviceSerializer
search_fields = [
"name",

View File

@@ -0,0 +1,66 @@
from rest_framework.fields import (
BooleanField,
CharField,
IntegerField,
SerializerMethodField,
)
from authentik.api.v3.config import ConfigSerializer, ConfigView
from authentik.core.api.utils import PassiveSerializer
from authentik.crypto.apps import MANAGED_KEY
from authentik.crypto.models import CertificateKeyPair
from authentik.endpoints.connectors.agent.models import AgentConnector
from authentik.endpoints.models import Device
from authentik.lib.utils.time import timedelta_from_string
from authentik.providers.oauth2.views.jwks import JWKSView
class AgentConfigSerializer(PassiveSerializer):
device_id = SerializerMethodField()
refresh_interval = SerializerMethodField()
authorization_flow = SerializerMethodField()
jwks = SerializerMethodField()
nss_uid_offset = IntegerField()
nss_gid_offset = IntegerField()
auth_terminate_session_on_expiry = BooleanField()
system_config = SerializerMethodField()
def get_device_id(self, instance: AgentConnector) -> str:
device: Device = self.context["device"]
return device.pk
def get_refresh_interval(self, instance: AgentConnector) -> int:
return int(timedelta_from_string(instance.refresh_interval).total_seconds())
def get_authorization_flow(self, instance: AgentConnector) -> str | None:
if not instance.authorization_flow:
return None
return instance.authorization_flow.slug
def get_jwks(self, instance: AgentConnector) -> dict:
kp = CertificateKeyPair.objects.filter(managed=MANAGED_KEY).first()
return {"keys": [JWKSView.get_jwk_for_key(kp, "sig")]}
def get_system_config(self, instance: AgentConnector) -> ConfigSerializer:
return ConfigView.get_config(self.context["request"]).data
class EnrollSerializer(PassiveSerializer):
device_serial = CharField(required=True)
device_name = CharField(required=True)
class AgentTokenResponseSerializer(PassiveSerializer):
token = CharField(required=True)
expires_in = IntegerField(required=0)
class AgentAuthenticationResponse(PassiveSerializer):
url = CharField()

View File

@@ -6,11 +6,8 @@ from drf_spectacular.utils import OpenApiResponse, extend_schema
from rest_framework.decorators import action
from rest_framework.exceptions import PermissionDenied, ValidationError
from rest_framework.fields import (
BooleanField,
CharField,
ChoiceField,
IntegerField,
SerializerMethodField,
)
from rest_framework.relations import PrimaryKeyRelatedField
from rest_framework.request import Request
@@ -20,6 +17,11 @@ from rest_framework.viewsets import ModelViewSet
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import PassiveSerializer
from authentik.endpoints.api.connectors import ConnectorSerializer
from authentik.endpoints.connectors.agent.api.agent import (
AgentConfigSerializer,
AgentTokenResponseSerializer,
EnrollSerializer,
)
from authentik.endpoints.connectors.agent.auth import (
AgentAuth,
AgentEnrollmentAuth,
@@ -32,37 +34,24 @@ from authentik.endpoints.connectors.agent.models import (
)
from authentik.endpoints.facts import DeviceFacts, OSFamily
from authentik.endpoints.models import Device
from authentik.lib.utils.time import timedelta_from_string
from authentik.lib.utils.reflection import ConditionalInheritance
class AgentConnectorSerializer(ConnectorSerializer):
class Meta(ConnectorSerializer.Meta):
model = AgentConnector
fields = "__all__"
class AgentConfigSerializer(PassiveSerializer):
nss_uid_offset = IntegerField()
nss_gid_offset = IntegerField()
authentication_flow = CharField()
auth_terminate_session_on_expiry = BooleanField()
refresh_interval = SerializerMethodField()
def get_refresh_interval(self, instance: AgentConnector) -> int:
return int(timedelta_from_string(instance.refresh_interval).total_seconds())
class EnrollSerializer(PassiveSerializer):
device_serial = CharField()
device_name = CharField()
class EnrollResponseSerializer(PassiveSerializer):
token = CharField()
fields = ConnectorSerializer.Meta.fields + [
"snapshot_expiry",
"auth_session_duration",
"auth_terminate_session_on_expiry",
"refresh_interval",
"authorization_flow",
"nss_uid_offset",
"nss_gid_offset",
"challenge_key",
"jwt_federation_providers",
]
class MDMConfigSerializer(PassiveSerializer):
@@ -88,7 +77,13 @@ class MDMConfigResponseSerializer(PassiveSerializer):
config = CharField(required=True)
class AgentConnectorViewSet(UsedByMixin, ModelViewSet):
class AgentConnectorViewSet(
ConditionalInheritance(
"authentik.enterprise.endpoints.connectors.agent.api.connectors.AgentConnectorViewSetMixin"
),
UsedByMixin,
ModelViewSet,
):
queryset = AgentConnector.objects.all()
serializer_class = AgentConnectorSerializer
@@ -96,61 +91,6 @@ class AgentConnectorViewSet(UsedByMixin, ModelViewSet):
ordering = ["name"]
filterset_fields = ["name", "enabled"]
@extend_schema(
request=EnrollSerializer(),
responses={200: EnrollResponseSerializer},
)
@action(
methods=["POST"],
detail=False,
authentication_classes=[AgentEnrollmentAuth],
)
def enroll(self, request: Request):
token: EnrollmentToken = request.auth
data = EnrollSerializer(data=request.data)
data.is_valid(raise_exception=True)
device, _ = Device.objects.get_or_create(
identifier=data.validated_data["device_serial"],
defaults={
"name": data.validated_data["device_name"],
"expiring": False,
"group": token.device_group,
},
)
connection, _ = AgentDeviceConnection.objects.update_or_create(
device=device,
connector=token.connector,
)
token = DeviceToken.objects.create(device=connection, expiring=False)
return Response(
{
"token": token.key,
}
)
@extend_schema(
responses=AgentConfigSerializer(),
request=OpenApiTypes.NONE,
)
@action(methods=["GET"], detail=False, authentication_classes=[AgentAuth])
def agent_config(self, request: Request):
token: DeviceToken = request.auth
connector: AgentConnector = token.device.connector.agentconnector
return Response(AgentConfigSerializer(connector).data)
@extend_schema(
request=DeviceFacts(),
responses={204: OpenApiResponse(description="Successfully checked in")},
)
@action(methods=["POST"], detail=False, authentication_classes=[AgentAuth])
def check_in(self, request: Request):
token: DeviceToken = request.auth
data = DeviceFacts(data=request.data)
data.is_valid(raise_exception=True)
connection: AgentDeviceConnection = token.device
connection.create_snapshot(data.validated_data)
return Response(status=204)
@extend_schema(
request=MDMConfigSerializer(),
responses=MDMConfigResponseSerializer(),
@@ -167,3 +107,63 @@ class AgentConnectorViewSet(UsedByMixin, ModelViewSet):
ctrl = connector.controller(connector)
payload = ctrl.generate_mdm_config(data.validated_data["platform"], request, token)
return Response({"config": payload})
@extend_schema(
request=EnrollSerializer(),
responses={200: AgentTokenResponseSerializer},
)
@action(
methods=["POST"],
detail=False,
authentication_classes=[AgentEnrollmentAuth],
)
def enroll(self, request: Request):
token: EnrollmentToken = request.auth
data = EnrollSerializer(data=request.data)
data.is_valid(raise_exception=True)
device, _ = Device.objects.get_or_create(
identifier=data.validated_data["device_serial"],
defaults={
"name": data.validated_data["device_name"],
"expiring": False,
"access_group": token.device_group,
},
)
connection, _ = AgentDeviceConnection.objects.update_or_create(
device=device,
connector=token.connector,
)
token = DeviceToken.objects.create(device=connection, expiring=False)
return Response(
{
"token": token.key,
"expires_in": 0,
}
)
@extend_schema(
request=OpenApiTypes.NONE,
responses=AgentConfigSerializer(),
)
@action(methods=["GET"], detail=False, authentication_classes=[AgentAuth])
def agent_config(self, request: Request):
token: DeviceToken = request.auth
connector: AgentConnector = token.device.connector.agentconnector
return Response(
AgentConfigSerializer(
connector, context={"request": request, "device": token.device.device}
).data
)
@extend_schema(
request=DeviceFacts(),
responses={204: OpenApiResponse(description="Successfully checked in")},
)
@action(methods=["POST"], detail=False, authentication_classes=[AgentAuth])
def check_in(self, request: Request):
token: DeviceToken = request.auth
data = DeviceFacts(data=request.data)
data.is_valid(raise_exception=True)
connection: AgentDeviceConnection = token.device
connection.create_snapshot(data.validated_data)
return Response(status=204)

View File

@@ -7,7 +7,7 @@ from rest_framework.viewsets import ModelViewSet
from authentik.core.api.tokens import TokenViewSerializer
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import ModelSerializer
from authentik.endpoints.api.device_group import DeviceGroupSerializer
from authentik.endpoints.api.device_access_group import DeviceAccessGroupSerializer
from authentik.endpoints.connectors.agent.models import EnrollmentToken
from authentik.events.models import Event, EventAction
from authentik.rbac.decorators import permission_required
@@ -15,7 +15,9 @@ from authentik.rbac.decorators import permission_required
class EnrollmentTokenSerializer(ModelSerializer):
device_group_obj = DeviceGroupSerializer(source="device_group", read_only=True, required=False)
device_group_obj = DeviceAccessGroupSerializer(
source="device_group", read_only=True, required=False
)
class Meta:
model = EnrollmentToken

View File

@@ -30,7 +30,9 @@ class AgentAuth(BaseAuthentication):
def authenticate(self, request: Request) -> tuple[User, Any] | None:
auth = get_authorization_header(request)
key = validate_auth(auth)
key = validate_auth(auth, format="bearer+agent")
if not key:
return None
device_token = DeviceToken.filter_not_expired(key=key).first()
if not device_token:
raise PermissionDenied()

View File

@@ -1,4 +1,5 @@
from plistlib import PlistFormat, dumps
from uuid import uuid4
from xml.etree.ElementTree import Element, SubElement, tostring # nosec
from django.http import HttpRequest
@@ -63,18 +64,60 @@ class AgentConnectorController(BaseController[AgentConnector]):
return payload
def _generate_mdm_config_macos(self, request: HttpRequest, token: EnrollmentToken) -> str:
token_uuid = str(token.pk).upper()
payload = dumps(
{
"PayloadContent": [
# Config for authentik Platform Agent (sysd)
{
"PayloadDisplayName": "authentik Platform",
"PayloadIdentifier": f"io.goauthentik.platform.{str(token.pk).upper()}",
"PayloadIdentifier": f"io.goauthentik.platform.{token_uuid}",
"PayloadType": "io.goauthentik.platform",
"PayloadUUID": str(token.pk).upper(),
"PayloadUUID": str(uuid4()),
"PayloadVersion": 1,
"RegistrationToken": token.key,
"URL": request.build_absolute_uri(reverse("authentik_core:root-redirect")),
}
},
# Config for MDM-associated domains (required for PSSO)
{
"PayloadDisplayName": "Associated Domains",
"PayloadIdentifier": f"com.apple.associated-domains.{token_uuid}",
"PayloadType": "com.apple.associated-domains",
"PayloadUUID": str(uuid4()),
"PayloadVersion": 1,
"Configuration": [
{
"ApplicationIdentifier": "232G855Y8N.io.goauthentik.platform.agent",
"AssociatedDomains": [f"authsrv:{request.get_host()}"],
"EnableDirectDownloads": False,
}
],
},
# Config for Platform SSO
{
"PayloadDisplayName": "Platform Single Sign-On",
"PayloadIdentifier": f"com.apple.extensiblesso.{token_uuid}",
"PayloadType": "com.apple.extensiblesso",
"PayloadUUID": str(uuid4()),
"PayloadVersion": 1,
"ExtensionIdentifier": "io.goauthentik.platform.psso",
"TeamIdentifier": "232G855Y8N",
"Type": "Redirect",
"URLs": [request.build_absolute_uri("")],
"PlatformSSO": {
"AccountDisplayName": "authentik",
"AllowDeviceIdentifiersInAttestation": True,
"AuthenticationMethod": "UserSecureEnclaveKey",
"EnableAuthorization": True,
"EnableCreateUserAtLogin": True,
"FileVaultPolicy": ["RequireAuthentication"],
"LoginPolicy": ["RequireAuthentication"],
"NewUserAuthorizationMode": "Standard",
"UnlockPolicy": ["RequireAuthentication"],
"UseSharedDeviceKeys": True,
"UserAuthorizationMode": "Standard",
},
},
],
"PayloadDisplayName": "authentik Platform",
"PayloadIdentifier": str(self.connector.pk).upper(),

View File

@@ -0,0 +1,94 @@
# Generated by Django 5.2.8 on 2025-11-27 00:16
import django.db.models.deletion
import uuid
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_endpoints", "0002_rename_devicegroup_deviceaccessgroup_and_more"),
("authentik_endpoints_connectors_agent", "0001_initial"),
(
"authentik_providers_oauth2",
"0031_remove_oauth2provider_backchannel_logout_uri_and_more",
),
]
operations = [
migrations.CreateModel(
name="DeviceAuthenticationToken",
fields=[
("expires", models.DateTimeField(default=None, null=True)),
("expiring", models.BooleanField(default=True)),
(
"identifier",
models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False),
),
("token", models.TextField()),
],
options={
"verbose_name": "Device authentication token",
"verbose_name_plural": "Device authentication tokens",
"abstract": False,
},
),
migrations.AlterModelOptions(
name="devicetoken",
options={"verbose_name": "Device Token", "verbose_name_plural": "Device Tokens"},
),
migrations.RenameField(
model_name="agentconnector",
old_name="authentication_flow",
new_name="authorization_flow",
),
migrations.AddField(
model_name="agentconnector",
name="jwt_federation_providers",
field=models.ManyToManyField(
blank=True, default=None, to="authentik_providers_oauth2.oauth2provider"
),
),
migrations.AddIndex(
model_name="devicetoken",
index=models.Index(fields=["key"], name="authentik_e_key_504bbc_idx"),
),
migrations.AddField(
model_name="deviceauthenticationtoken",
name="connector",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="authentik_endpoints_connectors_agent.agentconnector",
),
),
migrations.AddField(
model_name="deviceauthenticationtoken",
name="device",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, to="authentik_endpoints.device"
),
),
migrations.AddField(
model_name="deviceauthenticationtoken",
name="device_token",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="authentik_endpoints_connectors_agent.devicetoken",
),
),
migrations.AddIndex(
model_name="deviceauthenticationtoken",
index=models.Index(fields=["expires"], name="authentik_e_expires_d52fb2_idx"),
),
migrations.AddIndex(
model_name="deviceauthenticationtoken",
index=models.Index(fields=["expiring"], name="authentik_e_expirin_e9b873_idx"),
),
migrations.AddIndex(
model_name="deviceauthenticationtoken",
index=models.Index(
fields=["expiring", "expires"], name="authentik_e_expirin_8c95fe_idx"
),
),
]

View File

@@ -0,0 +1,70 @@
# Generated by Django 5.2.8 on 2025-11-30 22:04
import authentik.lib.utils.time
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
(
"authentik_endpoints_connectors_agent",
"0002_deviceauthenticationtoken_alter_devicetoken_options_and_more",
),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.AddField(
model_name="agentconnector",
name="auth_session_duration",
field=models.TextField(
default="hours=8", validators=[authentik.lib.utils.time.timedelta_string_validator]
),
),
migrations.AddField(
model_name="deviceauthenticationtoken",
name="user",
field=models.ForeignKey(
default=None,
null=True,
on_delete=django.db.models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
),
),
migrations.CreateModel(
name="AppleNonce",
fields=[
(
"id",
models.AutoField(
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
),
),
("expires", models.DateTimeField(default=None, null=True)),
("expiring", models.BooleanField(default=True)),
("nonce", models.TextField()),
(
"device_token",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="authentik_endpoints_connectors_agent.devicetoken",
),
),
],
options={
"verbose_name": "Apple Nonce",
"verbose_name_plural": "Apple Nonces",
"abstract": False,
"indexes": [
models.Index(fields=["expires"], name="authentik_e_expires_e5d275_idx"),
models.Index(fields=["expiring"], name="authentik_e_expirin_0b4d8e_idx"),
models.Index(
fields=["expiring", "expires"], name="authentik_e_expirin_355561_idx"
),
],
},
),
]

View File

@@ -5,9 +5,15 @@ from django.db import models
from django.utils.translation import gettext_lazy as _
from rest_framework.serializers import Serializer
from authentik.core.models import ExpiringModel, default_token_key
from authentik.core.models import ExpiringModel, User, default_token_key
from authentik.crypto.models import CertificateKeyPair
from authentik.endpoints.models import Connector, DeviceConnection, DeviceGroup, DeviceUserBinding
from authentik.endpoints.models import (
Connector,
Device,
DeviceAccessGroup,
DeviceConnection,
DeviceUserBinding,
)
from authentik.flows.stage import StageView
from authentik.lib.generators import generate_key
from authentik.lib.models import SerializerModel
@@ -20,17 +26,25 @@ if TYPE_CHECKING:
class AgentConnector(Connector):
"""Configure authentication and add device compliance using the authentik Agent."""
nss_uid_offset = models.PositiveIntegerField(default=1000)
nss_gid_offset = models.PositiveIntegerField(default=1000)
authentication_flow = models.ForeignKey(
"authentik_flows.Flow", null=True, on_delete=models.SET_DEFAULT, default=None
)
auth_terminate_session_on_expiry = models.BooleanField(default=False)
refresh_interval = models.TextField(
default="minutes=30",
validators=[timedelta_string_validator],
)
auth_session_duration = models.TextField(
default="hours=8", validators=[timedelta_string_validator]
)
auth_terminate_session_on_expiry = models.BooleanField(default=False)
authorization_flow = models.ForeignKey(
"authentik_flows.Flow", null=True, on_delete=models.SET_DEFAULT, default=None
)
jwt_federation_providers = models.ManyToManyField(
"authentik_providers_oauth2.OAuth2Provider", blank=True, default=None
)
nss_uid_offset = models.PositiveIntegerField(default=1000)
nss_gid_offset = models.PositiveIntegerField(default=1000)
challenge_key = models.ForeignKey(CertificateKeyPair, on_delete=models.CASCADE, null=True)
@property
@@ -66,11 +80,11 @@ class AgentConnector(Connector):
class AgentDeviceConnection(DeviceConnection):
apple_signing_key = models.TextField()
apple_encryption_key = models.TextField()
apple_key_exchange_key = models.TextField()
apple_sign_key_id = models.TextField()
apple_encryption_key = models.TextField()
apple_enc_key_id = models.TextField()
apple_signing_key = models.TextField()
apple_sign_key_id = models.TextField()
class AgentDeviceUserBinding(DeviceUserBinding):
@@ -80,20 +94,30 @@ class AgentDeviceUserBinding(DeviceUserBinding):
class DeviceToken(ExpiringModel):
"""Per-device token used for authentication."""
token_uuid = models.UUIDField(primary_key=True, default=uuid4)
device = models.ForeignKey(AgentDeviceConnection, on_delete=models.CASCADE)
key = models.TextField(default=generate_key)
class Meta:
verbose_name = _("Device Token")
verbose_name_plural = _("Device Tokens")
indexes = ExpiringModel.Meta.indexes + [
models.Index(fields=["key"]),
]
class EnrollmentToken(ExpiringModel, SerializerModel):
"""Token used during enrollment, a device will receive
a device token for further authentication"""
token_uuid = models.UUIDField(primary_key=True, editable=False, default=uuid4)
name = models.TextField()
key = models.TextField(default=default_token_key)
connector = models.ForeignKey(AgentConnector, on_delete=models.CASCADE)
device_group = models.ForeignKey(
DeviceGroup, on_delete=models.SET_DEFAULT, default=None, null=True
DeviceAccessGroup, on_delete=models.SET_DEFAULT, default=None, null=True
)
@property
@@ -113,3 +137,29 @@ class EnrollmentToken(ExpiringModel, SerializerModel):
permissions = [
("view_enrollment_token_key", _("View token's key")),
]
class DeviceAuthenticationToken(ExpiringModel):
identifier = models.UUIDField(default=uuid4, primary_key=True)
device = models.ForeignKey(Device, on_delete=models.CASCADE)
device_token = models.ForeignKey(DeviceToken, on_delete=models.CASCADE)
connector = models.ForeignKey(AgentConnector, on_delete=models.CASCADE)
user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, default=None)
token = models.TextField()
def __str__(self):
return f"Device authentication token {self.identifier}"
class Meta(ExpiringModel.Meta):
verbose_name = _("Device authentication token")
verbose_name_plural = _("Device authentication tokens")
class AppleNonce(ExpiringModel):
nonce = models.TextField()
device_token = models.ForeignKey(DeviceToken, on_delete=models.CASCADE)
class Meta(ExpiringModel.Meta):
verbose_name = _("Apple Nonce")
verbose_name_plural = _("Apple Nonces")

View File

@@ -4,11 +4,12 @@ from django.urls import reverse
from django.utils.timezone import now
from rest_framework.test import APITestCase
from authentik.blueprints.tests import reconcile_app
from authentik.core.tests.utils import create_test_admin_user
from authentik.endpoints.connectors.agent.api.connectors import AgentDeviceConnection
from authentik.endpoints.connectors.agent.models import AgentConnector, DeviceToken, EnrollmentToken
from authentik.endpoints.facts import OSFamily
from authentik.endpoints.models import Device, DeviceGroup
from authentik.endpoints.models import Device, DeviceAccessGroup
from authentik.lib.generators import generate_id
CHECK_IN_DATA_VALID = {
@@ -35,9 +36,7 @@ CHECK_IN_DATA_VALID = {
class TestAgentAPI(APITestCase):
def setUp(self):
self.connector = AgentConnector.objects.create(
name=generate_id(),
)
self.connector = AgentConnector.objects.create(name=generate_id())
self.token = EnrollmentToken.objects.create(name=generate_id(), connector=self.connector)
self.device = Device.objects.create(
identifier=generate_id(),
@@ -60,7 +59,7 @@ class TestAgentAPI(APITestCase):
self.assertEqual(response.status_code, 200)
def test_enroll_group(self):
device_group = DeviceGroup.objects.create(name=generate_id())
device_group = DeviceAccessGroup.objects.create(name=generate_id())
self.token.device_group = device_group
self.token.save()
ident = generate_id()
@@ -72,7 +71,7 @@ class TestAgentAPI(APITestCase):
self.assertEqual(response.status_code, 200)
device = Device.objects.filter(identifier=ident).first()
self.assertIsNotNone(device)
self.assertEqual(device.group, device_group)
self.assertEqual(device.access_group, device_group)
def test_enroll_expired(self):
dev_id = generate_id()
@@ -87,10 +86,11 @@ class TestAgentAPI(APITestCase):
self.assertEqual(response.status_code, 403)
self.assertFalse(Device.objects.filter(identifier=dev_id).exists())
@reconcile_app("authentik_crypto")
def test_config(self):
response = self.client.get(
reverse("authentik_api:agentconnector-agent-config"),
HTTP_AUTHORIZATION=f"Bearer {self.device_token.key}",
HTTP_AUTHORIZATION=f"Bearer+agent {self.device_token.key}",
)
self.assertEqual(response.status_code, 200)
@@ -98,7 +98,7 @@ class TestAgentAPI(APITestCase):
response = self.client.post(
reverse("authentik_api:agentconnector-check-in"),
data=CHECK_IN_DATA_VALID,
HTTP_AUTHORIZATION=f"Bearer {self.device_token.key}",
HTTP_AUTHORIZATION=f"Bearer+agent {self.device_token.key}",
)
self.assertEqual(response.status_code, 204)
@@ -109,7 +109,7 @@ class TestAgentAPI(APITestCase):
response = self.client.post(
reverse("authentik_api:agentconnector-check-in"),
data=CHECK_IN_DATA_VALID,
HTTP_AUTHORIZATION=f"Bearer {self.device_token.key}",
HTTP_AUTHORIZATION=f"Bearer+agent {self.device_token.key}",
)
self.assertEqual(response.status_code, 403)
@@ -120,7 +120,7 @@ class TestAgentAPI(APITestCase):
response = self.client.post(
reverse("authentik_api:agentconnector-check-in"),
data=CHECK_IN_DATA_VALID,
HTTP_AUTHORIZATION=f"Bearer {self.device_token.key}",
HTTP_AUTHORIZATION=f"Bearer+agent {self.device_token.key}",
)
self.assertEqual(response.status_code, 403)

View File

@@ -67,9 +67,9 @@ class NetworkSerializer(Serializer):
class HardwareSerializer(Serializer):
model = CharField()
manufacturer = CharField()
serial = CharField(allow_blank=True)
model = CharField(required=False)
manufacturer = CharField(required=False)
serial = CharField()
cpu_name = CharField(required=False)
cpu_count = IntegerField(required=False)
@@ -91,6 +91,18 @@ class ProcessSerializer(Serializer):
user = CharField(required=False)
class DeviceUserSerializer(Serializer):
id = CharField(required=True)
username = CharField(required=False)
name = CharField(required=False)
home = CharField(required=False)
class DeviceGroupSerializer(Serializer):
id = CharField(required=True)
name = CharField(required=False)
class DeviceFacts(Serializer):
os = OperatingSystemSerializer(required=False, allow_null=True)
disks = ListField(child=DiskSerializer(), required=False, allow_null=True)
@@ -98,4 +110,6 @@ class DeviceFacts(Serializer):
hardware = HardwareSerializer(required=False, allow_null=True)
software = ListField(child=SoftwareSerializer(), required=False, allow_null=True)
processes = ListField(child=ProcessSerializer(), required=False, allow_null=True)
users = ListField(child=DeviceUserSerializer(), required=False, allow_null=True)
groups = ListField(child=DeviceGroupSerializer(), required=False, allow_null=True)
vendor = JSONDictField(required=False)

View File

@@ -0,0 +1,61 @@
# Generated by Django 5.2.8 on 2025-11-27 00:16
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_endpoints", "0001_initial"),
("authentik_endpoints_connectors_agent", "0001_initial"),
("authentik_policies", "0011_policybinding_failure_result_and_more"),
]
operations = [
migrations.RenameModel(
old_name="DeviceGroup",
new_name="DeviceAccessGroup",
),
migrations.AlterModelOptions(
name="device",
options={"verbose_name": "Device", "verbose_name_plural": "Devices"},
),
migrations.AlterModelOptions(
name="deviceaccessgroup",
options={
"verbose_name": "Device access group",
"verbose_name_plural": "Device access groups",
},
),
migrations.AlterModelOptions(
name="deviceconnection",
options={
"verbose_name": "Device connection",
"verbose_name_plural": "Device connections",
},
),
migrations.AlterModelOptions(
name="devicefactsnapshot",
options={
"verbose_name": "Device fact snapshot",
"verbose_name_plural": "Device fact snapshots",
},
),
migrations.AlterModelOptions(
name="deviceuserbinding",
options={
"verbose_name": "Device User binding",
"verbose_name_plural": "Device User bindings",
},
),
migrations.RenameField(
model_name="device",
old_name="group",
new_name="access_group",
),
migrations.AlterField(
model_name="device",
name="name",
field=models.TextField(unique=True),
),
]

View File

@@ -6,6 +6,7 @@ from django.core.cache import cache
from django.db import models
from django.db.models import OuterRef, Subquery
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
from model_utils.managers import InheritanceManager
from rest_framework.serializers import Serializer
from structlog.stdlib import get_logger
@@ -30,10 +31,12 @@ DEVICE_FACTS_CACHE_TIMEOUT = 3600
class Device(ExpiringModel, AttributesMixin, PolicyBindingModel):
device_uuid = models.UUIDField(default=uuid4, primary_key=True)
name = models.TextField()
name = models.TextField(unique=True)
identifier = models.TextField(unique=True)
connections = models.ManyToManyField("Connector", through="DeviceConnection")
group = models.ForeignKey("DeviceGroup", null=True, on_delete=models.SET_DEFAULT, default=None)
access_group = models.ForeignKey(
"DeviceAccessGroup", null=True, on_delete=models.SET_DEFAULT, default=None
)
@property
def cache_key_facts(self):
@@ -64,6 +67,13 @@ class Device(ExpiringModel, AttributesMixin, PolicyBindingModel):
last_updated = max(last_updated, snapshort_created)
return DeviceFactSnapshot(data=data, created=last_updated)
def __str__(self):
return f"Device {self.name} {self.identifier} ({self.pk})"
class Meta(ExpiringModel.Meta):
verbose_name = _("Device")
verbose_name_plural = _("Devices")
class DeviceUserBinding(PolicyBinding):
is_primary = models.BooleanField(default=False)
@@ -71,6 +81,10 @@ class DeviceUserBinding(PolicyBinding):
# by a connector and not manually
connector = models.ForeignKey("Connector", on_delete=models.CASCADE, null=True)
class Meta(PolicyBinding.Meta):
verbose_name = _("Device User binding")
verbose_name_plural = _("Device User bindings")
class DeviceConnection(SerializerModel):
device_connection_uuid = models.UUIDField(default=uuid4, primary_key=True)
@@ -96,6 +110,10 @@ class DeviceConnection(SerializerModel):
return DeviceConnectionSerializer
class Meta:
verbose_name = _("Device connection")
verbose_name_plural = _("Device connections")
class DeviceFactSnapshot(ExpiringModel, SerializerModel):
snapshot_id = models.UUIDField(primary_key=True, default=uuid4)
@@ -112,19 +130,24 @@ class DeviceFactSnapshot(ExpiringModel, SerializerModel):
return DeviceFactSnapshotSerializer
class Meta(ExpiringModel.Meta):
verbose_name = _("Device fact snapshot")
verbose_name_plural = _("Device fact snapshots")
class Connector(ScheduledModel, SerializerModel):
connector_uuid = models.UUIDField(default=uuid4, primary_key=True)
name = models.TextField()
enabled = models.BooleanField(default=True)
objects = InheritanceManager()
snapshot_expiry = models.TextField(
default="hours=24",
validators=[timedelta_string_validator],
)
objects = InheritanceManager()
@property
def stage(self) -> type[StageView] | None:
return None
@@ -152,10 +175,20 @@ class Connector(ScheduledModel, SerializerModel):
]
class DeviceGroup(PolicyBindingModel):
class DeviceAccessGroup(PolicyBindingModel):
name = models.TextField(unique=True)
@property
def serializer(self) -> type[Serializer]:
from authentik.endpoints.api.device_access_group import DeviceAccessGroupSerializer
return DeviceAccessGroupSerializer
class Meta:
verbose_name = _("Device access group")
verbose_name_plural = _("Device access groups")
class EndpointStage(Stage):

View File

@@ -1,5 +1,5 @@
from authentik.endpoints.api.connectors import ConnectorViewSet
from authentik.endpoints.api.device_group import DeviceGroupViewSet
from authentik.endpoints.api.device_access_group import DeviceAccessGroupViewSet
from authentik.endpoints.api.device_user_bindings import DeviceUserBindingViewSet
from authentik.endpoints.api.devices import DeviceViewSet
@@ -7,5 +7,5 @@ api_urlpatterns = [
("endpoints/connectors", ConnectorViewSet, "endpoint_connectors"),
("endpoints/devices", DeviceViewSet, "endpoint_device"),
("endpoints/device_bindings", DeviceUserBindingViewSet, "endpoint_device_bindings"),
("endpoints/device_groups", DeviceGroupViewSet, "endpoint_device_groups"),
("endpoints/device_access_groups", DeviceAccessGroupViewSet, "endpoint_device_access_groups"),
]

View File

@@ -0,0 +1,111 @@
from django.http import Http404, HttpResponseBadRequest
from django.urls import reverse
from django.utils.timezone import now
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import OpenApiParameter, OpenApiResponse, extend_schema
from rest_framework.authentication import get_authorization_header
from rest_framework.decorators import action
from rest_framework.exceptions import ValidationError
from rest_framework.request import Request
from rest_framework.response import Response
from structlog.stdlib import get_logger
from authentik.api.authentication import validate_auth
from authentik.endpoints.connectors.agent.api.agent import (
AgentAuthenticationResponse,
AgentTokenResponseSerializer,
)
from authentik.endpoints.connectors.agent.auth import AgentAuth
from authentik.endpoints.connectors.agent.models import (
DeviceAuthenticationToken,
DeviceToken,
)
from authentik.endpoints.models import Device
from authentik.enterprise.endpoints.connectors.agent.auth import (
agent_auth_fed_validate,
agent_auth_issue_token,
check_device_policies,
)
from authentik.events.models import Event, EventAction
from authentik.flows.planner import PLAN_CONTEXT_DEVICE
from authentik.stages.password.stage import PLAN_CONTEXT_METHOD, PLAN_CONTEXT_METHOD_ARGS
LOGGER = get_logger()
class AgentConnectorViewSetMixin:
@extend_schema(
request=OpenApiTypes.NONE,
responses=AgentAuthenticationResponse(),
)
@action(methods=["POST"], detail=False, authentication_classes=[AgentAuth])
def auth_ia(self, request: Request) -> Response:
token: DeviceToken = request.auth
auth_token = DeviceAuthenticationToken.objects.create(
device=token.device.device,
device_token=token,
connector=token.device.connector.agentconnector,
)
return Response(
{
"url": request.build_absolute_uri(
reverse(
"authentik_enterprise_endpoints_connectors_agent:authenticate",
kwargs={"token_uuid": auth_token.identifier},
)
),
}
)
@extend_schema(
request=OpenApiTypes.NONE,
parameters=[OpenApiParameter("device", OpenApiTypes.STR, location="query", required=True)],
responses={
200: AgentTokenResponseSerializer(),
404: OpenApiResponse(description="Device not found"),
},
)
@action(
methods=["POST"],
detail=False,
pagination_class=None,
filter_backends=[],
permission_classes=[],
authentication_classes=[],
)
def auth_fed(self, request: Request) -> Response:
raw_token = validate_auth(get_authorization_header(request))
if not raw_token:
LOGGER.warning("Missing token")
return HttpResponseBadRequest()
device = Device.objects.filter(name=request.query_params.get("device")).first()
if not device:
LOGGER.warning("Couldn't find device")
raise Http404
federated_token, connector = agent_auth_fed_validate(raw_token, device)
LOGGER.info(
"successfully verified JWT with provider", provider=federated_token.provider.name
)
policy_result = check_device_policies(device, federated_token.user, request._request)
if not policy_result.passing:
raise ValidationError(
{"policy_result": "Policy denied access", "policy_messages": policy_result.messages}
)
token, exp = agent_auth_issue_token(device, connector, federated_token.user)
rel_exp = int((exp - now()).total_seconds())
Event.new(
EventAction.LOGIN,
**{
PLAN_CONTEXT_METHOD: "jwt",
PLAN_CONTEXT_METHOD_ARGS: {
"jwt": federated_token,
"provider": federated_token.provider,
},
PLAN_CONTEXT_DEVICE: device,
},
).from_http(request, user=federated_token.user)
return Response({"token": token, "expires_in": rel_exp})

View File

@@ -9,4 +9,5 @@ class AuthentikEnterpriseEndpointsConnectorAgentAppConfig(EnterpriseConfig):
default = True
mountpoints = {
"authentik.enterprise.endpoints.connectors.agent.urls_root": "",
"authentik.enterprise.endpoints.connectors.agent.urls": "endpoints/agent/",
}

View File

@@ -0,0 +1,87 @@
from django.http import Http404, HttpRequest
from django.utils.timezone import now
from jwt import PyJWTError, decode, encode
from rest_framework.exceptions import ValidationError
from structlog.stdlib import get_logger
from authentik.core.models import User
from authentik.crypto.apps import MANAGED_KEY
from authentik.crypto.models import CertificateKeyPair
from authentik.endpoints.connectors.agent.models import AgentConnector
from authentik.endpoints.models import Device
from authentik.lib.utils.time import timedelta_from_string
from authentik.policies.engine import PolicyEngine
from authentik.policies.models import PolicyBindingModel
from authentik.providers.oauth2.models import AccessToken, JWTAlgorithms, OAuth2Provider
LOGGER = get_logger()
PLATFORM_ISSUER = "goauthentik.io/platform"
def agent_auth_issue_token(device: Device, connector: AgentConnector, user: User, **kwargs):
kp = CertificateKeyPair.objects.filter(managed=MANAGED_KEY).first()
if not kp:
return None, None
exp = now() + timedelta_from_string(connector.auth_session_duration)
token = encode(
{
"iss": PLATFORM_ISSUER,
"aud": str(device.pk),
"iat": int(now().timestamp()),
"exp": int(exp.timestamp()),
"preferred_username": user.username,
**kwargs,
},
kp.private_key,
headers={
"kid": kp.kid,
},
algorithm=JWTAlgorithms.from_private_key(kp.private_key),
)
return token, exp
def agent_auth_fed_validate(
raw_token: str, device: Device
) -> tuple[AccessToken, AgentConnector | None]:
connectors_for_device = AgentConnector.objects.filter(device__in=[device])
connector = connectors_for_device.first()
providers = OAuth2Provider.objects.filter(agentconnector__in=connectors_for_device)
federated_token = AccessToken.objects.filter(token=raw_token, provider__in=providers).first()
if not federated_token:
LOGGER.warning("Couldn't lookup provider")
raise Http404
_key, _alg = federated_token.provider.jwt_key
try:
decode(
raw_token,
_key,
algorithms=[_alg],
options={
"verify_aud": False,
},
)
return federated_token, connector
except (PyJWTError, ValueError, TypeError, AttributeError) as exc:
LOGGER.warning("failed to verify JWT", exc=exc, provider=federated_token.provider.name)
raise ValidationError() from None
def check_device_policies(device: Device, user: User, request: HttpRequest):
"""Check policies bound to device group and device"""
if device.access_group:
result = check_pbm_policies(device.access_group, user, request)
if result.passing:
return result
return check_pbm_policies(device, user, request)
def check_pbm_policies(pbm: PolicyBindingModel, user: User, request: HttpRequest):
policy_engine = PolicyEngine(pbm, user, request)
policy_engine.use_cache = False
policy_engine.empty_result = False
policy_engine.mode = pbm.policy_engine_mode
policy_engine.build()
result = policy_engine.result
LOGGER.debug("PolicyAccessView user_has_access", user=user.username, result=result, pbm=pbm.pk)
return result

View File

@@ -0,0 +1,121 @@
from base64 import urlsafe_b64encode
from json import dumps
from secrets import token_bytes
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.ciphers import Cipher
from cryptography.hazmat.primitives.ciphers.algorithms import AES
from cryptography.hazmat.primitives.ciphers.modes import GCM
from cryptography.hazmat.primitives.kdf.concatkdf import ConcatKDFHash
from django.http import HttpResponse
from jwcrypto.common import base64url_decode, base64url_encode
from authentik.endpoints.connectors.agent.models import AgentDeviceConnection
def length_prefixed(data: bytes) -> bytes:
length = len(data)
return length.to_bytes(4, "big") + data
def build_apu(public_key: ec.EllipticCurvePublicKey):
# X9.63 representation: 0x04 || X || Y
public_numbers = public_key.public_numbers()
x_bytes = public_numbers.x.to_bytes(32, "big")
y_bytes = public_numbers.y.to_bytes(32, "big")
x963 = bytes([0x04]) + x_bytes + y_bytes
result = length_prefixed(b"APPLE") + length_prefixed(x963)
return result
def encrypt_token_with_a256_gcm(body: dict, device_encryption_key: str, apv: bytes) -> str:
ephemeral_key = ec.generate_private_key(curve=ec.SECP256R1())
device_public_key = serialization.load_pem_public_key(
device_encryption_key.encode(), backend=default_backend()
)
shared_secret_z = ephemeral_key.exchange(ec.ECDH(), device_public_key)
apu = build_apu(ephemeral_key.public_key())
jwe_header = {
"enc": "A256GCM",
"kid": "ephemeralKey",
"epk": {
"x": base64url_encode(
ephemeral_key.public_key().public_numbers().x.to_bytes(32, "big")
),
"y": base64url_encode(
ephemeral_key.public_key().public_numbers().y.to_bytes(32, "big")
),
"kty": "EC",
"crv": "P-256",
},
"typ": "platformsso-login-response+jwt",
"alg": "ECDH-ES",
"apu": base64url_encode(apu),
"apv": base64url_encode(apv),
}
party_u_info = length_prefixed(apu)
party_v_info = length_prefixed(apv)
supp_pub_info = (256).to_bytes(4, "big")
other_info = length_prefixed(b"A256GCM") + party_u_info + party_v_info + supp_pub_info
ckdf = ConcatKDFHash(
algorithm=hashes.SHA256(),
length=32,
otherinfo=other_info,
)
derived_key = ckdf.derive(shared_secret_z)
nonce = token_bytes(96)
header_json = dumps(jwe_header, separators=(",", ":")).encode()
aad = urlsafe_b64encode(header_json).rstrip(b"=")
cipher = Cipher(AES(derived_key), GCM(nonce))
encryptor = cipher.encryptor()
encryptor.authenticate_additional_data(aad)
ciphertext = encryptor.update(dumps(body).encode()) + encryptor.finalize()
# base64url encoding
protected_b64 = urlsafe_b64encode(header_json).rstrip(b"=")
iv_b64 = urlsafe_b64encode(nonce).rstrip(b"=")
ciphertext_b64 = urlsafe_b64encode(ciphertext).rstrip(b"=")
tag_b64 = urlsafe_b64encode(encryptor.tag).rstrip(b"=")
jwe_compact = b".".join(
[
protected_b64,
b"",
iv_b64,
ciphertext_b64,
tag_b64,
]
)
return jwe_compact.decode()
class JWEResponse(HttpResponse):
def __init__(
self,
data: dict,
device: AgentDeviceConnection,
apv: str,
):
super().__init__(
content=encrypt_token_with_a256_gcm(
data, device.apple_encryption_key, base64url_decode(apv)
),
content_type="application/platformsso-login-response+jwt",
)

View File

@@ -0,0 +1,55 @@
from json import loads
from urllib.parse import quote
from django.test import TestCase
from django.urls import reverse
from authentik.blueprints.tests import reconcile_app
from authentik.core.tests.utils import create_test_user
from authentik.endpoints.connectors.agent.models import (
AgentConnector,
AgentDeviceConnection,
AppleNonce,
DeviceToken,
EnrollmentToken,
)
from authentik.endpoints.models import Device
from authentik.lib.generators import generate_id
class TestAppleViews(TestCase):
def setUp(self):
self.connector = AgentConnector.objects.create(name=generate_id())
self.token = EnrollmentToken.objects.create(name=generate_id(), connector=self.connector)
self.device = Device.objects.create(
name=generate_id(),
identifier=generate_id(),
)
self.connection = AgentDeviceConnection.objects.create(
device=self.device,
connector=self.connector,
)
self.user = create_test_user()
def test_apple_site_association(self):
res = self.client.get(reverse("authentik_enterprise_endpoints_connectors_agent_root:asa"))
self.assertEqual(res.status_code, 200)
@reconcile_app("authentik_crypto")
def test_apple_jwks(self):
res = self.client.get(reverse("authentik_enterprise_endpoints_connectors_agent:psso-jwks"))
self.assertEqual(res.status_code, 200)
def test_apple_nonce(self):
device_token = DeviceToken.objects.create(device=self.connection)
res = self.client.post(
reverse("authentik_enterprise_endpoints_connectors_agent:psso-nonce"),
data={"x-ak-device-token": quote(device_token.key)},
)
self.assertEqual(res.status_code, 200)
nonce = loads(res.content.decode()).get("Nonce")
self.assertIsNotNone(nonce)
db_nonce = AppleNonce.objects.filter(nonce=nonce).first()
self.assertIsNotNone(db_nonce)
self.assertFalse(db_nonce.is_expired)

View File

@@ -0,0 +1,39 @@
from base64 import urlsafe_b64decode
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import ec
from django.test import TestCase
from jwcrypto.jwe import JWE
from jwcrypto.jwk import JWK
from authentik.enterprise.endpoints.connectors.agent.http import (
base64url_decode,
encrypt_token_with_a256_gcm,
)
class TestAppleJWE(TestCase):
def test_encrypt(self):
data = {"foo": "bar"}
apv = (
"AAAABUFwcGxlAAAAQQTFgZOospN6KbkhXhx1lfa-AKYxjEfJhTJrkpdEY_srMmkPzS7VN0Bzt2AtNBEXE"
"aphDONiP2Mq6Oxytv5JKOxHAAAAJDgyOThERkY5LTVFMUUtNEUwMS04OEUwLUI3QkQzOUM4QjA3Qw"
)
key = ec.generate_private_key(curve=ec.SECP256R1())
pub = (
key.public_key()
.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo,
)
.decode()
)
res = encrypt_token_with_a256_gcm(data, pub, base64url_decode(apv))
parsed = JWE()
parsed.deserialize(res, JWK.from_pyca(key))
payload = parsed.payload
self.assertEqual(payload, b'{"foo": "bar"}')
self.assertEqual(parsed.jose_header["apv"], apv)
self.assertEqual(parsed.jose_header["typ"], "platformsso-login-response+jwt")
self.assertIn(b"APPLE", urlsafe_b64decode(parsed.jose_header["apu"]))

View File

@@ -0,0 +1,110 @@
from json import loads
from unittest.mock import MagicMock, patch
from django.urls import reverse
from rest_framework.test import APITestCase
from authentik.blueprints.tests import reconcile_app
from authentik.core.tests.utils import create_test_user
from authentik.endpoints.connectors.agent.models import (
AgentConnector,
AgentDeviceConnection,
DeviceAuthenticationToken,
DeviceToken,
EnrollmentToken,
)
from authentik.endpoints.models import Device
from authentik.enterprise.license import LicenseKey
from authentik.enterprise.models import License
from authentik.enterprise.tests.test_license import expiry_valid
from authentik.lib.generators import generate_id
class TestAppleRegister(APITestCase):
def setUp(self):
self.connector = AgentConnector.objects.create(name=generate_id())
self.token = EnrollmentToken.objects.create(name=generate_id(), connector=self.connector)
self.device = Device.objects.create(
name=generate_id(),
identifier=generate_id(),
)
self.connection = AgentDeviceConnection.objects.create(
device=self.device,
connector=self.connector,
)
self.user = create_test_user()
self.device_token = DeviceToken.objects.create(device=self.connection)
@patch(
"authentik.enterprise.license.LicenseKey.validate",
MagicMock(
return_value=LicenseKey(
aud="",
exp=expiry_valid,
name=generate_id(),
internal_users=100,
external_users=100,
)
),
)
@reconcile_app("authentik_crypto")
def test_register_device(self):
License.objects.create(key=generate_id())
response = self.client.post(
reverse("authentik_api:psso-register-device"),
data={
"device_signing_key": generate_id(),
"device_encryption_key": generate_id(),
"sign_key_id": generate_id(),
"enc_key_id": generate_id(),
},
HTTP_AUTHORIZATION=f"Bearer+agent {self.device_token.key}",
)
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
response.content,
{
"client_id": str(self.connector.pk),
"audience": str(self.device.pk),
"issuer": "http://testserver/endpoints/agent/psso/token/",
"jwks_endpoint": "http://testserver/endpoints/agent/psso/jwks/",
"nonce_endpoint": "http://testserver/endpoints/agent/psso/nonce/",
"token_endpoint": "http://testserver/endpoints/agent/psso/token/",
},
)
@patch(
"authentik.enterprise.license.LicenseKey.validate",
MagicMock(
return_value=LicenseKey(
aud="",
exp=expiry_valid,
name=generate_id(),
internal_users=100,
external_users=100,
)
),
)
@reconcile_app("authentik_crypto")
def test_register_user(self):
License.objects.create(key=generate_id())
device_auth = DeviceAuthenticationToken.objects.create(
device=self.device,
device_token=self.device_token,
connector=self.connector,
user=self.user,
token=generate_id(),
)
response = self.client.post(
reverse("authentik_api:psso-register-user"),
data={
"user_auth": device_auth.token,
"user_secure_enclave_key": generate_id(),
"enclave_key_id": generate_id(),
},
HTTP_AUTHORIZATION=f"Bearer+agent {self.device_token.key}",
)
self.assertEqual(response.status_code, 200)
body = loads(response.content)
self.assertEqual(body["username"], self.user.username)

View File

@@ -0,0 +1,108 @@
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import ec
from django.test import TestCase
from django.urls import reverse
from jwt import encode
from authentik.blueprints.tests import reconcile_app
from authentik.core.tests.utils import create_test_cert, create_test_user
from authentik.crypto.builder import PrivateKeyAlg
from authentik.endpoints.connectors.agent.models import (
AgentConnector,
AgentDeviceConnection,
AgentDeviceUserBinding,
AppleNonce,
DeviceToken,
EnrollmentToken,
)
from authentik.endpoints.models import Device
from authentik.lib.generators import generate_id
from authentik.providers.oauth2.models import JWTAlgorithms
class TestAppleToken(TestCase):
def setUp(self):
self.apple_sign_key = create_test_cert(PrivateKeyAlg.ECDSA)
sign_key_pem = self.apple_sign_key.public_key.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo,
).decode()
self.enc_key = ec.generate_private_key(curve=ec.SECP256R1())
self.enc_pub = (
self.enc_key.public_key()
.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo,
)
.decode()
)
self.connector = AgentConnector.objects.create(name=generate_id())
self.token = EnrollmentToken.objects.create(name=generate_id(), connector=self.connector)
self.device = Device.objects.create(
name=generate_id(),
identifier=generate_id(),
)
self.connection = AgentDeviceConnection.objects.create(
device=self.device,
connector=self.connector,
apple_sign_key_id=self.apple_sign_key.kid,
apple_signing_key=sign_key_pem,
apple_encryption_key=self.enc_pub,
)
self.user = create_test_user()
AgentDeviceUserBinding.objects.create(
target=self.device,
user=self.user,
order=0,
apple_enclave_key_id=self.apple_sign_key.kid,
apple_secure_enclave_key=sign_key_pem,
)
self.device_token = DeviceToken.objects.create(device=self.connection)
@reconcile_app("authentik_crypto")
def test_token(self):
nonce = generate_id()
AppleNonce.objects.create(
device_token=self.device_token,
nonce=nonce,
)
embedded = encode(
{"iss": str(self.connector.pk), "aud": str(self.device.pk), "request_nonce": nonce},
self.apple_sign_key.private_key,
headers={
"kid": self.apple_sign_key.kid,
},
algorithm=JWTAlgorithms.from_private_key(self.apple_sign_key.private_key),
)
assertion = encode(
{
"iss": str(self.connector.pk),
"aud": "http://testserver/endpoints/agent/psso/token/",
"request_nonce": nonce,
"assertion": embedded,
"jwe_crypto": {
"apv": (
"AAAABUFwcGxlAAAAQQTFgZOospN6KbkhXhx1lfa-AKYxjEfJhTJrkpdEY_srMmkPzS7VN0Bzt2AtNBEXE"
"aphDONiP2Mq6Oxytv5JKOxHAAAAJDgyOThERkY5LTVFMUUtNEUwMS04OEUwLUI3QkQzOUM4QjA3Qw"
)
},
},
self.apple_sign_key.private_key,
headers={
"kid": self.apple_sign_key.kid,
},
algorithm=JWTAlgorithms.from_private_key(self.apple_sign_key.private_key),
)
res = self.client.post(
reverse("authentik_enterprise_endpoints_connectors_agent:psso-token"),
data={
"assertion": assertion,
"platform_sso_version": "1.0",
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
},
)
self.assertEqual(res.status_code, 200)

View File

@@ -0,0 +1,111 @@
from json import loads
from django.urls import reverse
from django.utils.timezone import now
from jwt import decode
from rest_framework.test import APITestCase
from authentik.blueprints.tests import reconcile_app
from authentik.core.models import Group
from authentik.core.tests.utils import create_test_user
from authentik.endpoints.connectors.agent.api.connectors import AgentDeviceConnection
from authentik.endpoints.connectors.agent.models import AgentConnector, EnrollmentToken
from authentik.endpoints.models import Device, DeviceAccessGroup
from authentik.lib.generators import generate_id
from authentik.policies.models import PolicyBinding
from authentik.providers.oauth2.models import AccessToken, OAuth2Provider
class TestConnectorAuthFed(APITestCase):
def setUp(self):
self.connector = AgentConnector.objects.create(name=generate_id())
self.token = EnrollmentToken.objects.create(name=generate_id(), connector=self.connector)
self.device = Device.objects.create(
name=generate_id(),
identifier=generate_id(),
)
self.connection = AgentDeviceConnection.objects.create(
device=self.device,
connector=self.connector,
)
self.user = create_test_user()
self.provider = OAuth2Provider.objects.create(name=generate_id())
self.raw_token = self.provider.encode({"foo": "bar"})
self.token = AccessToken.objects.create(
provider=self.provider, user=self.user, token=self.raw_token, auth_time=now()
)
self.connector.jwt_federation_providers.add(self.provider)
@reconcile_app("authentik_crypto")
def test_auth_fed(self):
response = self.client.post(
reverse("authentik_api:agentconnector-auth-fed") + f"?device={self.device.name}",
HTTP_AUTHORIZATION=f"Bearer {self.raw_token}",
)
self.assertEqual(response.status_code, 400)
@reconcile_app("authentik_crypto")
def test_auth_fed_policy_group(self):
device_group = DeviceAccessGroup.objects.create(name=generate_id())
self.device.access_group = device_group
self.device.save()
group = Group.objects.create(name=generate_id())
group.users.add(self.user)
PolicyBinding.objects.create(target=device_group, group=group, order=0)
response = self.client.post(
reverse("authentik_api:agentconnector-auth-fed") + f"?device={self.device.name}",
HTTP_AUTHORIZATION=f"Bearer {self.raw_token}",
)
self.assertEqual(response.status_code, 200)
res = loads(response.content)
token = decode(res["token"], options={"verify_signature": False})
self.assertEqual(token["iss"], "goauthentik.io/platform")
self.assertEqual(token["aud"], str(self.device.pk))
@reconcile_app("authentik_crypto")
def test_auth_fed_policy_group_deny(self):
device_group = DeviceAccessGroup.objects.create(name=generate_id())
self.device.access_group = device_group
self.device.save()
group = Group.objects.create(name=generate_id())
# group.users.add(self.user)
PolicyBinding.objects.create(target=device_group, group=group, order=0)
response = self.client.post(
reverse("authentik_api:agentconnector-auth-fed") + f"?device={self.device.name}",
HTTP_AUTHORIZATION=f"Bearer {self.raw_token}",
)
self.assertEqual(response.status_code, 400)
self.assertJSONEqual(
response.content,
{
"policy_result": "Policy denied access",
"policy_messages": [],
},
)
@reconcile_app("authentik_crypto")
def test_auth_fed_invalid(self):
# No token
response = self.client.post(
reverse("authentik_api:agentconnector-auth-fed") + f"?device={self.device.name}foo",
)
self.assertEqual(response.status_code, 400)
# No device
response = self.client.post(
reverse("authentik_api:agentconnector-auth-fed") + f"?device={self.device.name}foo",
HTTP_AUTHORIZATION=f"Bearer {self.raw_token}",
)
self.assertEqual(response.status_code, 404)
# invalid token
response = self.client.post(
reverse("authentik_api:agentconnector-auth-fed") + f"?device={self.device.name}",
HTTP_AUTHORIZATION=f"Bearer {self.raw_token}aa",
)
self.assertEqual(response.status_code, 404)

View File

@@ -0,0 +1,128 @@
from hashlib import sha256
from json import loads
from unittest.mock import MagicMock, patch
from urllib.parse import parse_qs, urlparse
from django.urls import reverse
from jwt import decode
from authentik.blueprints.tests import reconcile_app
from authentik.core.models import Group
from authentik.core.tests.utils import create_test_flow, create_test_user
from authentik.endpoints.connectors.agent.api.connectors import AgentDeviceConnection
from authentik.endpoints.connectors.agent.models import AgentConnector, DeviceToken, EnrollmentToken
from authentik.endpoints.models import Device, DeviceAccessGroup
from authentik.enterprise.endpoints.connectors.agent.views.auth_interactive import QS_AGENT_IA_TOKEN
from authentik.enterprise.license import LicenseKey
from authentik.enterprise.models import License
from authentik.enterprise.tests.test_license import expiry_valid
from authentik.flows.tests import FlowTestCase
from authentik.lib.generators import generate_id
from authentik.policies.models import PolicyBinding
class TestConnectorAuthIA(FlowTestCase):
def setUp(self):
self.connector = AgentConnector.objects.create(
name=generate_id(),
authorization_flow=create_test_flow(),
)
self.token = EnrollmentToken.objects.create(name=generate_id(), connector=self.connector)
self.device = Device.objects.create(
name=generate_id(),
identifier=generate_id(),
)
self.connection = AgentDeviceConnection.objects.create(
device=self.device,
connector=self.connector,
)
self.device_token = DeviceToken.objects.create(
device=self.connection,
key=generate_id(),
)
self.user = create_test_user()
@patch(
"authentik.enterprise.license.LicenseKey.validate",
MagicMock(
return_value=LicenseKey(
aud="",
exp=expiry_valid,
name=generate_id(),
internal_users=100,
external_users=100,
)
),
)
def test_auth_ia_initiate(self):
License.objects.create(key=generate_id())
response = self.client.post(
reverse("authentik_api:agentconnector-auth-ia"),
HTTP_AUTHORIZATION=f"Bearer+agent {self.device_token.key}",
)
self.assertEqual(response.status_code, 200)
@reconcile_app("authentik_crypto")
def test_auth_ia_fulfill(self):
self.client.force_login(self.user)
response = self.client.post(
reverse("authentik_api:agentconnector-auth-ia"),
HTTP_AUTHORIZATION=f"Bearer+agent {self.device_token.key}",
HTTP_X_AUTHENTIK_PLATFORM_AUTH_DTH=sha256(self.device_token.key.encode()).hexdigest(),
)
self.assertEqual(response.status_code, 200)
res = loads(response.content)
response = self.client.get(
res["url"],
HTTP_AUTHORIZATION=f"Bearer+agent {self.device_token.key}",
HTTP_X_AUTHENTIK_PLATFORM_AUTH_DTH=sha256(self.device_token.key.encode()).hexdigest(),
)
self.assertEqual(response.status_code, 200)
self.assertIn(b"Permission denied", response.content)
@patch(
"authentik.enterprise.license.LicenseKey.validate",
MagicMock(
return_value=LicenseKey(
aud="",
exp=expiry_valid,
name=generate_id(),
internal_users=100,
external_users=100,
)
),
)
@reconcile_app("authentik_crypto")
def test_auth_ia_fulfill_policy(self):
License.objects.create(key=generate_id())
device_group = DeviceAccessGroup.objects.create(name=generate_id())
self.device.access_group = device_group
self.device.save()
group = Group.objects.create(name=generate_id())
group.users.add(self.user)
PolicyBinding.objects.create(target=device_group, group=group, order=0)
self.client.force_login(self.user)
response = self.client.post(
reverse("authentik_api:agentconnector-auth-ia"),
HTTP_AUTHORIZATION=f"Bearer+agent {self.device_token.key}",
HTTP_X_AUTHENTIK_PLATFORM_AUTH_DTH=sha256(self.device_token.key.encode()).hexdigest(),
)
self.assertEqual(response.status_code, 200)
res = loads(response.content)
response = self.client.get(
res["url"],
HTTP_AUTHORIZATION=f"Bearer+agent {self.device_token.key}",
HTTP_X_AUTHENTIK_PLATFORM_AUTH_DTH=sha256(self.device_token.key.encode()).hexdigest(),
)
self.assertEqual(response.status_code, 302)
url = urlparse(response.url)
self.assertEqual(url.scheme, "goauthentik.io")
qs = parse_qs(url.query)
raw_token = qs[QS_AGENT_IA_TOKEN][0]
token = decode(raw_token.encode(), options={"verify_signature": False})
self.assertEqual(token["iss"], "goauthentik.io/platform")
self.assertEqual(token["aud"], str(self.device.pk))

View File

@@ -0,0 +1,36 @@
from django.urls import path
from authentik.enterprise.endpoints.connectors.agent.views.apple_jwks import AppleJWKSView
from authentik.enterprise.endpoints.connectors.agent.views.apple_nonce import NonceView
from authentik.enterprise.endpoints.connectors.agent.views.apple_register import (
RegisterDeviceView,
RegisterUserView,
)
from authentik.enterprise.endpoints.connectors.agent.views.apple_token import TokenView
from authentik.enterprise.endpoints.connectors.agent.views.auth_interactive import (
AgentInteractiveAuth,
)
urlpatterns = [
path(
"authenticate/<uuid:token_uuid>/",
AgentInteractiveAuth.as_view(),
name="authenticate",
),
path("psso/token/", TokenView.as_view(), name="psso-token"),
path("psso/jwks/", AppleJWKSView.as_view(), name="psso-jwks"),
path("psso/nonce/", NonceView.as_view(), name="psso-nonce"),
]
api_urlpatterns = [
path(
"endpoints/agents/psso/register/device/",
RegisterDeviceView.as_view(),
name="psso-register-device",
),
path(
"endpoints/agents/psso/register/user/",
RegisterUserView.as_view(),
name="psso-register-user",
),
]

View File

@@ -0,0 +1,14 @@
from django.http import Http404
from authentik.crypto.apps import MANAGED_KEY
from authentik.crypto.models import CertificateKeyPair
from authentik.providers.oauth2.views.jwks import JWKSView
class AppleJWKSView(JWKSView):
def get_keys(self):
kp = CertificateKeyPair.objects.filter(managed=MANAGED_KEY).first()
if not kp:
raise Http404
yield self.get_jwk_for_key(kp, "sig")

View File

@@ -0,0 +1,32 @@
from base64 import b64encode
from datetime import timedelta
from secrets import token_bytes
from urllib.parse import unquote
from django.http import HttpRequest, HttpResponseBadRequest, JsonResponse
from django.utils.decorators import method_decorator
from django.utils.timezone import now
from django.views import View
from django.views.decorators.csrf import csrf_exempt
from authentik.endpoints.connectors.agent.models import AppleNonce, DeviceToken
@method_decorator(csrf_exempt, name="dispatch")
class NonceView(View):
def post(self, request: HttpRequest, *args, **kwargs):
raw_token = unquote(self.request.POST.get("x-ak-device-token"))
device_token = DeviceToken.filter_not_expired(key=raw_token).first()
if not device_token:
return HttpResponseBadRequest()
nonce = AppleNonce.objects.create(
nonce=b64encode(token_bytes(32)).decode(),
expires=now() + timedelta(minutes=5),
device_token=device_token,
)
return JsonResponse(
{
"Nonce": nonce.nonce,
}
)

View File

@@ -0,0 +1,131 @@
from django.urls import reverse
from drf_spectacular.utils import extend_schema
from rest_framework.exceptions import ValidationError
from rest_framework.fields import CharField
from rest_framework.permissions import IsAuthenticated
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.views import APIView
from authentik.api.validation import validate
from authentik.core.api.users import UserSelfSerializer
from authentik.core.api.utils import PassiveSerializer
from authentik.endpoints.connectors.agent.auth import AgentAuth
from authentik.endpoints.connectors.agent.models import (
AgentDeviceConnection,
AgentDeviceUserBinding,
DeviceAuthenticationToken,
DeviceToken,
)
from authentik.enterprise.api import EnterpriseRequiredMixin
from authentik.lib.generators import generate_key
class RegisterDeviceView(APIView):
class AgentPSSODeviceRegistration(EnterpriseRequiredMixin, PassiveSerializer):
"""Register Apple device via Platform SSO"""
device_signing_key = CharField()
device_encryption_key = CharField()
sign_key_id = CharField()
enc_key_id = CharField()
class AgentPSSODeviceRegistrationResponse(PassiveSerializer):
"""authentik settings for Platform SSO tokens"""
client_id = CharField()
issuer = CharField()
token_endpoint = CharField()
jwks_endpoint = CharField()
audience = CharField()
nonce_endpoint = CharField()
permission_classes = [IsAuthenticated]
pagination_class = None
filter_backends = []
serializer_class = AgentPSSODeviceRegistration
authentication_classes = [AgentAuth]
@extend_schema(
responses={
200: AgentPSSODeviceRegistrationResponse(),
}
)
@validate(AgentPSSODeviceRegistration)
def post(self, request: Request, body: AgentPSSODeviceRegistration) -> Response:
device_token: DeviceToken = request.auth
conn: AgentDeviceConnection = device_token.device
conn.apple_signing_key = body.validated_data["device_signing_key"]
conn.apple_encryption_key = body.validated_data["device_encryption_key"]
conn.apple_sign_key_id = body.validated_data["sign_key_id"]
conn.apple_enc_key_id = body.validated_data["enc_key_id"]
conn.apple_key_exchange_key = generate_key()
conn.save()
return Response(
data={
"client_id": str(conn.connector.pk),
"issuer": self.request.build_absolute_uri(
reverse("authentik_enterprise_endpoints_connectors_agent:psso-token")
),
"audience": str(conn.device.pk),
"token_endpoint": request.build_absolute_uri(
reverse("authentik_enterprise_endpoints_connectors_agent:psso-token")
),
"jwks_endpoint": request.build_absolute_uri(
reverse("authentik_enterprise_endpoints_connectors_agent:psso-jwks")
),
"nonce_endpoint": request.build_absolute_uri(
reverse("authentik_enterprise_endpoints_connectors_agent:psso-nonce")
),
}
)
class RegisterUserView(APIView):
class AgentPSSOUserRegistration(EnterpriseRequiredMixin, PassiveSerializer):
"""Register Apple device user via Platform SSO"""
user_auth = CharField()
user_secure_enclave_key = CharField()
enclave_key_id = CharField()
permission_classes = [IsAuthenticated]
pagination_class = None
filter_backends = []
serializer_class = AgentPSSOUserRegistration
authentication_classes = [AgentAuth]
@extend_schema(
responses={
200: UserSelfSerializer(),
}
)
@validate(AgentPSSOUserRegistration)
def post(self, request: Request, body: AgentPSSOUserRegistration) -> Response:
device_token: DeviceToken = request.auth
conn: AgentDeviceConnection = device_token.device
user_token = DeviceAuthenticationToken.filter_not_expired(
device=conn.device,
token=body.validated_data["user_auth"],
device_token=device_token,
).first()
if not user_token:
raise ValidationError("Invalid user authentication")
AgentDeviceUserBinding.objects.update_or_create(
target=conn.device,
user=user_token.user,
connector=conn.connector,
create_defaults={
"is_primary": True,
"order": 0,
},
defaults={
"apple_secure_enclave_key": body.validated_data["user_secure_enclave_key"],
"apple_enclave_key_id": body.validated_data["enclave_key_id"],
},
)
return Response(
UserSelfSerializer(instance=user_token.user, context={"request": request}).data
)

View File

@@ -0,0 +1,185 @@
from typing import Any
from django.http import HttpRequest, HttpResponse
from django.urls import reverse
from django.utils.decorators import method_decorator
from django.utils.timezone import now
from django.views import View
from django.views.decorators.csrf import csrf_exempt
from jwt import PyJWTError, decode, encode, get_unverified_header
from rest_framework.exceptions import ValidationError
from structlog.stdlib import get_logger
from authentik.core.models import AuthenticatedSession, Session, User
from authentik.core.sessions import SessionStore
from authentik.crypto.apps import MANAGED_KEY
from authentik.crypto.models import CertificateKeyPair
from authentik.endpoints.connectors.agent.models import (
AgentConnector,
AgentDeviceConnection,
AgentDeviceUserBinding,
AppleNonce,
DeviceAuthenticationToken,
)
from authentik.enterprise.endpoints.connectors.agent.http import JWEResponse
from authentik.events.models import Event, EventAction
from authentik.events.signals import SESSION_LOGIN_EVENT
from authentik.lib.utils.time import timedelta_from_string
from authentik.providers.oauth2.constants import TOKEN_TYPE
from authentik.providers.oauth2.id_token import IDToken
from authentik.providers.oauth2.models import JWTAlgorithms
from authentik.root.middleware import SessionMiddleware
LOGGER = get_logger()
@method_decorator(csrf_exempt, name="dispatch")
class TokenView(View):
device_connection: AgentDeviceConnection
connector: AgentConnector
def post(self, request: HttpRequest) -> HttpResponse:
assertion = request.POST.get("assertion", request.POST.get("request"))
if not assertion:
return HttpResponse(status=400)
self.now = now()
try:
self.jwt_request = self.validate_request_token(assertion)
except PyJWTError as exc:
LOGGER.warning("failed to parse JWT", exc=exc)
raise ValidationError("Invalid request") from None
version = request.POST.get("platform_sso_version")
grant_type = request.POST.get("grant_type")
handler_func = (
f"handle_v{version}_{grant_type}".replace("-", "_")
.replace("+", "_")
.replace(":", "_")
.replace(".", "_")
)
handler = getattr(self, handler_func, None)
if not handler:
LOGGER.debug("Handler not found", handler=handler_func)
return HttpResponse(status=400)
LOGGER.debug("sending to handler", handler=handler_func)
return handler()
def validate_request_token(self, assertion: str) -> dict[str, Any]:
# Decode without validation to get header
header = get_unverified_header(assertion)
LOGGER.debug("token header", header=header)
expected_kid = header["kid"]
self.device_connection = (
AgentDeviceConnection.objects.filter(apple_sign_key_id=expected_kid)
.select_related("device")
.first()
)
self.connector = AgentConnector.objects.get(pk=self.device_connection.connector.pk)
LOGGER.debug("got device", device=self.device_connection.device)
expected_aud = self.request.build_absolute_uri(
reverse("authentik_enterprise_endpoints_connectors_agent:psso-token")
)
if not self.device_connection.apple_signing_key:
LOGGER.warning("Failed to issue token for device, no apple_signing_key")
raise ValidationError("Invalid request")
# Properly decode the JWT with the key from the device
decoded = decode(
assertion,
self.device_connection.apple_signing_key,
algorithms=["ES256"],
audience=expected_aud,
issuer=str(self.connector.pk),
)
self.remote_nonce = decoded.get("nonce")
# Check that the nonce hasn't been used before
nonce = AppleNonce.filter_not_expired(nonce=decoded["request_nonce"]).first()
if not nonce:
raise ValidationError("Invalid nonce")
self.nonce = nonce
nonce.delete()
return decoded
def validate_embedded_assertion(self, assertion: str) -> tuple[AgentDeviceUserBinding, dict]:
"""Decode an embedded assertion and validate it by looking up the matching device user"""
decode_unvalidated = get_unverified_header(assertion)
expected_kid = decode_unvalidated["kid"]
device_user = AgentDeviceUserBinding.objects.filter(
target=self.device_connection.device, apple_enclave_key_id=expected_kid
).first()
if not device_user:
LOGGER.warning("Could not find device user binding for user")
raise ValidationError("Invalid request")
decoded: dict[str, Any] = decode(
assertion,
device_user.apple_secure_enclave_key,
audience=str(self.device_connection.device.pk),
algorithms=["ES256"],
)
if decoded.get("nonce") != self.jwt_request.get("nonce"):
LOGGER.warning("Mis-matched nonce to outer assertion")
raise ValidationError("Invalid nonce")
return device_user, decoded
def create_auth_session(self, user: User):
event = Event.new(EventAction.LOGIN).from_http(self.request, user=user)
store = SessionStore()
store[SESSION_LOGIN_EVENT] = event
store.save()
session = Session.objects.filter(session_key=store.session_key).first()
session.expires = self.now + timedelta_from_string(self.connector.auth_session_duration)
AuthenticatedSession.objects.create(session=session, user=user)
session = SessionMiddleware.encode_session(store.session_key, user)
return session
def create_id_token(self, user: User, **kwargs):
issuer = self.request.build_absolute_uri(
reverse("authentik_enterprise_endpoints_connectors_agent:psso-token")
)
id_token = IDToken(
iss=issuer,
sub=user.username,
aud=str(self.connector.pk),
exp=int(
(self.now + timedelta_from_string(self.connector.auth_session_duration)).timestamp()
),
iat=int(now().timestamp()),
**kwargs,
)
kp = CertificateKeyPair.objects.filter(managed=MANAGED_KEY).first()
return encode(
id_token.to_dict(),
kp.private_key,
headers={
"kid": kp.kid,
},
algorithm=JWTAlgorithms.from_private_key(kp.private_key),
)
def handle_v1_0_urn_ietf_params_oauth_grant_type_jwt_bearer(self):
try:
user, inner = self.validate_embedded_assertion(self.jwt_request["assertion"])
except PyJWTError as exc:
LOGGER.warning("failed to validate inner assertion", exc=exc)
raise ValidationError("Invalid request") from None
id_token = self.create_id_token(user.user)
auth_token = DeviceAuthenticationToken.objects.create(
device=self.device_connection.device,
connector=self.connector,
user=user.user,
device_token=self.nonce.device_token,
)
return JWEResponse(
{
"refresh_token": auth_token.token,
"refresh_token_expires_in": int((auth_token.expires - now()).total_seconds()),
"id_token": id_token,
"token_type": TOKEN_TYPE,
"session_key": self.create_auth_session(user.user),
},
device=self.device_connection,
apv=self.jwt_request["jwe_crypto"]["apv"],
)

View File

@@ -0,0 +1,111 @@
from hashlib import sha256
from hmac import compare_digest
from django.http import Http404, HttpRequest, HttpResponse, HttpResponseBadRequest, QueryDict
from authentik.endpoints.connectors.agent.models import AgentConnector, DeviceAuthenticationToken
from authentik.endpoints.models import Device
from authentik.enterprise.endpoints.connectors.agent.auth import (
agent_auth_issue_token,
check_device_policies,
)
from authentik.enterprise.policy import EnterprisePolicyAccessView
from authentik.flows.exceptions import FlowNonApplicableException
from authentik.flows.models import in_memory_stage
from authentik.flows.planner import PLAN_CONTEXT_DEVICE, FlowPlanner
from authentik.flows.stage import StageView
from authentik.providers.oauth2.utils import HttpResponseRedirectScheme
PLAN_CONTEXT_DEVICE_AUTH_TOKEN = "goauthentik.io/endpoints/device_auth_token" # nosec
QS_AGENT_IA_TOKEN = "ak-auth-ia-token" # nosec
class AgentInteractiveAuth(EnterprisePolicyAccessView):
"""Agent device authentication"""
auth_token: DeviceAuthenticationToken
device: Device
connector: AgentConnector
def resolve_provider_application(self):
auth_token = (
DeviceAuthenticationToken.filter_not_expired(identifier=self.kwargs["token_uuid"])
.prefetch_related()
.first()
)
if not auth_token:
raise Http404
self.auth_token = auth_token
self.device = auth_token.device
self.connector = auth_token.connector.agentconnector
def user_has_access(self, user=None, pbm=None):
enterprise_result = self.check_license()
if not enterprise_result.passing:
return enterprise_result
return check_device_policies(self.device, user or self.request.user, self.request)
def modify_flow_context(self, flow, context):
return {
PLAN_CONTEXT_DEVICE: self.device,
**context,
}
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
device_token_hash = request.headers.get("X-Authentik-Platform-Auth-DTH")
if not device_token_hash:
return HttpResponseBadRequest("Invalid device token")
if not compare_digest(
device_token_hash, sha256(self.auth_token.device_token.key.encode()).hexdigest()
):
return HttpResponseBadRequest("Invalid device token")
planner = FlowPlanner(self.connector.authorization_flow)
planner.allow_empty_flows = True
try:
plan = planner.plan(
self.request,
{
PLAN_CONTEXT_DEVICE: self.device,
PLAN_CONTEXT_DEVICE_AUTH_TOKEN: self.auth_token,
},
)
except FlowNonApplicableException:
return self.handle_no_permission_authenticated()
plan.append_stage(in_memory_stage(AgentAuthFulfillmentStage))
return plan.to_redirect(
self.request,
self.connector.authorization_flow,
allowed_silent_types=[AgentAuthFulfillmentStage],
)
class AgentAuthFulfillmentStage(StageView):
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
device: Device = self.executor.plan.context.pop(PLAN_CONTEXT_DEVICE)
auth_token: DeviceAuthenticationToken = self.executor.plan.context.pop(
PLAN_CONTEXT_DEVICE_AUTH_TOKEN
)
token, exp = agent_auth_issue_token(
device,
auth_token.connector.agentconnector,
request.user,
jti=str(auth_token.identifier),
)
if not token or not exp:
return self.executor.stage_invalid("Failed to generate token")
auth_token.user = request.user
auth_token.token = token
auth_token.expires = exp
auth_token.expiring = True
auth_token.save()
qd = QueryDict(mutable=True)
qd[QS_AGENT_IA_TOKEN] = token
return HttpResponseRedirectScheme(
"goauthentik.io://platform/finished?" + qd.urlencode(),
allowed_schemes=["goauthentik.io"],
)

View File

@@ -4,7 +4,7 @@ from django.utils.translation import gettext_lazy as _
from authentik.core.models import User, UserTypes
from authentik.enterprise.license import LicenseKey
from authentik.policies.types import PolicyRequest, PolicyResult
from authentik.policies.types import PolicyResult
from authentik.policies.views import PolicyAccessView
@@ -21,8 +21,6 @@ class EnterprisePolicyAccessView(PolicyAccessView):
def user_has_access(self, user: User | None = None) -> PolicyResult:
user = user or self.request.user
request = PolicyRequest(user)
request.http_request = self.request
result = super().user_has_access(user)
enterprise_result = self.check_license()
if not enterprise_result.passing:

View File

@@ -14,7 +14,12 @@ from authentik.core.models import AuthenticatedSession, User
from authentik.core.signals import login_failed, password_changed
from authentik.events.models import Event, EventAction
from authentik.flows.models import Stage
from authentik.flows.planner import PLAN_CONTEXT_OUTPOST, PLAN_CONTEXT_SOURCE, FlowPlan
from authentik.flows.planner import (
PLAN_CONTEXT_DEVICE,
PLAN_CONTEXT_OUTPOST,
PLAN_CONTEXT_SOURCE,
FlowPlan,
)
from authentik.flows.views.executor import SESSION_KEY_PLAN
from authentik.stages.invitation.models import Invitation
from authentik.stages.invitation.signals import invitation_used
@@ -42,6 +47,9 @@ def on_user_logged_in(sender, request: HttpRequest, user: User, **_):
if PLAN_CONTEXT_OUTPOST in flow_plan.context:
# Save outpost context
kwargs[PLAN_CONTEXT_OUTPOST] = flow_plan.context[PLAN_CONTEXT_OUTPOST]
if PLAN_CONTEXT_DEVICE in flow_plan.context:
# Save device
kwargs[PLAN_CONTEXT_DEVICE] = flow_plan.context[PLAN_CONTEXT_DEVICE]
event = Event.new(EventAction.LOGIN, **kwargs).from_http(request, user=user)
request.session[SESSION_LOGIN_EVENT] = event
request.session.save()

View File

@@ -37,6 +37,7 @@ PLAN_CONTEXT_PENDING_USER = "pending_user"
PLAN_CONTEXT_SSO = "is_sso"
PLAN_CONTEXT_REDIRECT = "redirect"
PLAN_CONTEXT_APPLICATION = "application"
PLAN_CONTEXT_DEVICE = "device"
PLAN_CONTEXT_SOURCE = "source"
PLAN_CONTEXT_OUTPOST = "outpost"
PLAN_CONTEXT_POST = "goauthentik.io/http/post"

View File

@@ -1,6 +1,8 @@
"""authentik OAuth2 JWKS Views"""
from base64 import b64encode, urlsafe_b64encode
from collections.abc import Generator
from typing import Literal
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric.ec import (
@@ -64,7 +66,7 @@ class JWKSView(View):
"""Show RSA Key data for Provider"""
@staticmethod
def get_jwk_for_key(key: CertificateKeyPair, use: str) -> dict | None:
def get_jwk_for_key(key: CertificateKeyPair, use: Literal["sig", "enc"]) -> dict | None:
"""Convert a certificate-key pair into JWK"""
private_key = key.private_key
key_data = None
@@ -112,10 +114,9 @@ class JWKSView(View):
)
return key_data
def get(self, request: HttpRequest, application_slug: str) -> HttpResponse:
"""Show JWK Key data for Provider"""
def get_keys(self) -> Generator[dict | None]:
provider_ids = Application.objects.filter(
slug=application_slug,
slug=self.kwargs["application_slug"],
).values_list(
"provider_id",
flat=True,
@@ -129,15 +130,15 @@ class JWKSView(View):
if provider is None:
raise Http404()
response_data = {}
if signing_key := provider.signing_key:
jwk = JWKSView.get_jwk_for_key(signing_key, "sig")
if jwk:
response_data.setdefault("keys", [])
response_data["keys"].append(jwk)
yield JWKSView.get_jwk_for_key(signing_key, "sig")
if encryption_key := provider.encryption_key:
jwk = JWKSView.get_jwk_for_key(encryption_key, "enc")
yield JWKSView.get_jwk_for_key(encryption_key, "enc")
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
"""Show JWK Key data for Provider"""
response_data = {}
for jwk in self.get_keys():
if jwk:
response_data.setdefault("keys", [])
response_data["keys"].append(jwk)

View File

@@ -557,6 +557,8 @@ class TokenView(View):
provider: OAuth2Provider | None = None
params: TokenParams | None = None
params_class = TokenParams
provider_class = OAuth2Provider
def dispatch(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse:
response = super().dispatch(request, *args, **kwargs)
@@ -576,12 +578,14 @@ class TokenView(View):
op="authentik.providers.oauth2.post.parse",
):
client_id, client_secret = extract_client_auth(request)
self.provider = OAuth2Provider.objects.filter(client_id=client_id).first()
self.provider = self.provider_class.objects.filter(client_id=client_id).first()
if not self.provider:
LOGGER.warning("OAuth2Provider does not exist", client_id=client_id)
raise TokenError("invalid_client")
CTX_AUTH_VIA.set("oauth_client_secret")
self.params = TokenParams.parse(request, self.provider, client_id, client_secret)
self.params = self.params_class.parse(
request, self.provider, client_id, client_secret
)
with start_span(
op="authentik.providers.oauth2.post.response",

View File

@@ -1,12 +1,15 @@
"""Group client"""
from itertools import batched
from typing import Any
from django.db import transaction
from orjson import dumps
from pydantic import ValidationError
from pydanticscim.group import GroupMember
from authentik.core.models import Group
from authentik.lib.merge import MERGE_LIST_UNIQUE
from authentik.lib.sync.mapper import PropertyMappingManager
from authentik.lib.sync.outgoing.base import Direction
from authentik.lib.sync.outgoing.exceptions import (
@@ -114,10 +117,23 @@ class SCIMGroupClient(SCIMClient[Group, SCIMProviderGroup, SCIMGroupSchema]):
self._patch_add_users(connection, users)
return connection
def diff(self, local_created: dict[str, Any], connection: SCIMProviderUser):
"""Check if a group is different than what we last wrote to the remote system.
Returns true if there is a difference in data."""
local_known = connection.attributes
local_updated = {}
MERGE_LIST_UNIQUE.merge(local_updated, local_known)
MERGE_LIST_UNIQUE.merge(local_updated, local_created)
return dumps(local_updated) != dumps(local_known)
def update(self, group: Group, connection: SCIMProviderGroup):
"""Update existing group"""
scim_group = self.to_schema(group, connection)
scim_group.id = connection.scim_id
payload = scim_group.model_dump(mode="json", exclude_unset=True)
if not self.diff(payload, connection):
self.logger.debug("Skipping group write as data has not changed")
return self.patch_compare_users(group)
try:
if self._config.patch.supported:
return self._update_patch(group, scim_group, connection)

View File

@@ -1,10 +1,14 @@
"""User client"""
from typing import Any
from django.db import transaction
from django.utils.http import urlencode
from orjson import dumps
from pydantic import ValidationError
from authentik.core.models import User
from authentik.lib.merge import MERGE_LIST_UNIQUE
from authentik.lib.sync.mapper import PropertyMappingManager
from authentik.lib.sync.outgoing.exceptions import ObjectExistsSyncException, StopSync
from authentik.policies.utils import delete_none_values
@@ -92,17 +96,30 @@ class SCIMUserClient(SCIMClient[User, SCIMProviderUser, SCIMUserSchema]):
provider=self.provider, user=user, scim_id=scim_id, attributes=response
)
def diff(self, local_created: dict[str, Any], connection: SCIMProviderUser):
"""Check if a user is different than what we last wrote to the remote system.
Returns true if there is a difference in data."""
local_known = connection.attributes
local_updated = {}
MERGE_LIST_UNIQUE.merge(local_updated, local_known)
MERGE_LIST_UNIQUE.merge(local_updated, local_created)
return dumps(local_updated) != dumps(local_known)
def update(self, user: User, connection: SCIMProviderUser):
"""Update existing user"""
scim_user = self.to_schema(user, connection)
scim_user.id = connection.scim_id
payload = scim_user.model_dump(
mode="json",
exclude_unset=True,
)
if not self.diff(payload, connection):
self.logger.debug("Skipping user write as data has not changed")
return
response = self._request(
"PUT",
f"/Users/{connection.scim_id}",
json=scim_user.model_dump(
mode="json",
exclude_unset=True,
),
json=payload,
)
connection.attributes = response
connection.save()

View File

@@ -9,7 +9,7 @@ from requests_mock import Mocker
from authentik.blueprints.tests import apply_blueprint
from authentik.core.models import Application, Group, User
from authentik.lib.generators import generate_id
from authentik.providers.scim.models import SCIMMapping, SCIMProvider
from authentik.providers.scim.models import SCIMMapping, SCIMProvider, SCIMProviderGroup
class SCIMGroupTests(TestCase):
@@ -106,6 +106,7 @@ class SCIMGroupTests(TestCase):
"displayName": group.name,
},
)
group.name = generate_id()
group.save()
self.assertEqual(mock.call_count, 4)
self.assertEqual(mock.request_history[0].method, "GET")
@@ -148,3 +149,56 @@ class SCIMGroupTests(TestCase):
self.assertEqual(mock.request_history[0].method, "GET")
self.assertEqual(mock.request_history[3].method, "DELETE")
self.assertEqual(mock.request_history[3].url, f"https://localhost/Groups/{scim_id}")
@Mocker()
def test_group_create_update_noop(self, mock: Mocker):
"""Test group creation and update"""
scim_id = generate_id()
mock.get(
"https://localhost/ServiceProviderConfig",
json={},
)
mock.post(
"https://localhost/Groups",
json={
"id": scim_id,
},
)
mock.put(
"https://localhost/Groups",
json={
"id": scim_id,
},
)
uid = generate_id()
group = Group.objects.create(
name=uid,
)
self.assertEqual(mock.call_count, 2)
self.assertEqual(mock.request_history[0].method, "GET")
self.assertEqual(mock.request_history[1].method, "POST")
body = loads(mock.request_history[1].body)
with open("schemas/scim-group.schema.json", encoding="utf-8") as schema:
validate(body, loads(schema.read()))
self.assertEqual(
body,
{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"],
"externalId": str(group.pk),
"displayName": group.name,
},
)
conn = SCIMProviderGroup.objects.filter(group=group).first()
conn.attributes = {
"id": scim_id,
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"],
"externalId": str(group.pk),
"displayName": group.name,
}
conn.save()
group.save()
self.assertEqual(mock.call_count, 4)
self.assertEqual(mock.request_history[0].method, "GET")
self.assertEqual(mock.request_history[1].method, "POST")
self.assertEqual(mock.request_history[2].method, "GET")
self.assertEqual(mock.request_history[2].method, "GET")

View File

@@ -10,7 +10,7 @@ from authentik.blueprints.tests import apply_blueprint
from authentik.core.models import Application, Group, User
from authentik.lib.generators import generate_id
from authentik.lib.sync.outgoing.base import SAFE_METHODS
from authentik.providers.scim.models import SCIMMapping, SCIMProvider
from authentik.providers.scim.models import SCIMMapping, SCIMProvider, SCIMProviderUser
from authentik.providers.scim.tasks import scim_sync, scim_sync_objects
from authentik.tasks.models import Task
from authentik.tenants.models import Tenant
@@ -273,6 +273,8 @@ class SCIMUserTests(TestCase):
"userName": uid,
},
)
# Update user
user.name = "foo bar"
user.save()
self.assertEqual(mock.call_count, 4)
self.assertEqual(mock.request_history[0].method, "GET")
@@ -455,3 +457,85 @@ class SCIMUserTests(TestCase):
self.assertIsNotNone(log.attributes["url"])
self.assertIsNotNone(log.attributes["body"])
self.assertIsNotNone(log.attributes["method"])
@Mocker()
def test_user_create_update_noop(self, mock: Mocker):
"""Test user creation and update"""
scim_id = generate_id()
mock: Mocker
mock.get(
"https://localhost/ServiceProviderConfig",
json={},
)
mock.post(
"https://localhost/Users",
json={
"id": scim_id,
},
)
mock.put(
"https://localhost/Users",
json={
"id": scim_id,
},
)
uid = generate_id()
user = User.objects.create(
username=uid,
name=f"{uid} {uid}",
email=f"{uid}@goauthentik.io",
)
self.assertEqual(mock.call_count, 2)
self.assertEqual(mock.request_history[0].method, "GET")
self.assertEqual(mock.request_history[1].method, "POST")
body = loads(mock.request_history[1].body)
self.assertEqual(
body,
{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"active": True,
"emails": [
{
"primary": True,
"type": "other",
"value": f"{uid}@goauthentik.io",
}
],
"displayName": f"{uid} {uid}",
"externalId": user.uid,
"name": {
"familyName": uid,
"formatted": f"{uid} {uid}",
"givenName": uid,
},
"userName": uid,
},
)
conn = SCIMProviderUser.objects.filter(user=user).first()
conn.attributes = {
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"active": True,
"emails": [
{
"primary": True,
"type": "other",
"value": f"{uid}@goauthentik.io",
}
],
"displayName": f"{uid} {uid}",
"externalId": user.uid,
"name": {
"familyName": uid,
"formatted": f"{uid} {uid}",
"givenName": uid,
},
"userName": uid,
"id": scim_id,
}
conn.save()
user.save()
self.assertEqual(mock.call_count, 3)
self.assertEqual(mock.request_history[0].method, "GET")
self.assertEqual(mock.request_history[1].method, "POST")
self.assertEqual(mock.request_history[2].method, "GET")
# No PUT request

View File

@@ -61,6 +61,22 @@ class SessionMiddleware(UpstreamSessionMiddleware):
pass
return session_key
@staticmethod
def encode_session(session_key: str, user: User):
payload = {
"sid": session_key,
"iss": "authentik",
"sub": "anonymous",
"authenticated": user.is_authenticated,
"acr": ACR_AUTHENTIK_SESSION,
}
if user.is_authenticated:
payload["sub"] = user.uid
value = encode(payload=payload, key=SIGNING_HASH)
if settings.TEST:
value = session_key
return value
def process_request(self, request: HttpRequest):
raw_session = request.COOKIES.get(settings.SESSION_COOKIE_NAME)
session_key = SessionMiddleware.decode_session_key(raw_session)
@@ -117,21 +133,9 @@ class SessionMiddleware(UpstreamSessionMiddleware):
"request completed. The user may have logged "
"out in a concurrent request, for example."
) from None
payload = {
"sid": request.session.session_key,
"iss": "authentik",
"sub": "anonymous",
"authenticated": request.user.is_authenticated,
"acr": ACR_AUTHENTIK_SESSION,
}
if request.user.is_authenticated:
payload["sub"] = request.user.uid
value = encode(payload=payload, key=SIGNING_HASH)
if settings.TEST:
value = request.session.session_key
response.set_cookie(
settings.SESSION_COOKIE_NAME,
value,
SessionMiddleware.encode_session(request.session.session_key, request.user),
max_age=max_age,
expires=expires,
domain=settings.SESSION_COOKIE_DOMAIN,

View File

@@ -13,7 +13,7 @@ from rest_framework.exceptions import ValidationError
from authentik.core.middleware import SESSION_KEY_IMPERSONATE_USER
from authentik.core.models import USER_ATTRIBUTE_SOURCES, User, UserSourceConnection, UserTypes
from authentik.core.sources.stage import PLAN_CONTEXT_SOURCES_CONNECTION
from authentik.events.utils import sanitize_item
from authentik.events.utils import sanitize_dict, sanitize_item
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER
from authentik.flows.stage import StageView
from authentik.flows.views.executor import FlowExecutorView
@@ -115,7 +115,10 @@ class UserWriteStageView(StageView):
continue
# For exact attributes match, update the dictionary in place
elif key == "attributes":
user.attributes.update(value)
if isinstance(value, dict):
user.attributes.update(sanitize_dict(value))
else:
raise ValidationError("Attempt to overwrite complete attributes")
# If using dot notation, use the correct helper to update the nested value
elif key.startswith("attributes.") or key.startswith("attributes_"):
UserWriteStageView.write_attribute(user, key, value)

View File

@@ -3,6 +3,7 @@
from unittest.mock import patch
from django.urls import reverse
from django.utils.timezone import now
from authentik.core.models import (
USER_ATTRIBUTE_SOURCES,
@@ -128,6 +129,34 @@ class TestUserWriteStage(FlowTestCase):
self.assertEqual(user_qs.first().attributes["foo"], "bar")
self.assertEqual(user_qs.first().attributes["some_custom_attribute"], "test")
def test_user_update_complex(self):
"""Test update of existing user"""
new_password = generate_key()
plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
plan.context[PLAN_CONTEXT_PENDING_USER] = User.objects.create(
username="unittest", email="test@goauthentik.io"
)
time = now()
plan.context[PLAN_CONTEXT_PROMPT] = {
"username": "test-user-new",
"password": new_password,
"attributes.foo": time,
}
session = self.client.session
session[SESSION_KEY_PLAN] = plan
session.save()
response = self.client.post(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
)
self.assertEqual(response.status_code, 200)
self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
user_qs = User.objects.filter(username=plan.context[PLAN_CONTEXT_PROMPT]["username"])
self.assertTrue(user_qs.exists())
self.assertTrue(user_qs.first().check_password(new_password))
self.assertEqual(user_qs.first().attributes["foo"], time.isoformat()[:-6] + "Z")
def test_user_update_source(self):
"""Test update of existing user with a source"""
new_password = generate_key()

View File

@@ -5278,50 +5278,58 @@
"authentik_crypto.view_certificatekeypair",
"authentik_endpoints.add_connector",
"authentik_endpoints.add_device",
"authentik_endpoints.add_deviceaccessgroup",
"authentik_endpoints.add_deviceconnection",
"authentik_endpoints.add_devicefactsnapshot",
"authentik_endpoints.add_devicegroup",
"authentik_endpoints.add_deviceuserbinding",
"authentik_endpoints.add_endpointstage",
"authentik_endpoints.change_connector",
"authentik_endpoints.change_device",
"authentik_endpoints.change_deviceaccessgroup",
"authentik_endpoints.change_deviceconnection",
"authentik_endpoints.change_devicefactsnapshot",
"authentik_endpoints.change_devicegroup",
"authentik_endpoints.change_deviceuserbinding",
"authentik_endpoints.change_endpointstage",
"authentik_endpoints.delete_connector",
"authentik_endpoints.delete_device",
"authentik_endpoints.delete_deviceaccessgroup",
"authentik_endpoints.delete_deviceconnection",
"authentik_endpoints.delete_devicefactsnapshot",
"authentik_endpoints.delete_devicegroup",
"authentik_endpoints.delete_deviceuserbinding",
"authentik_endpoints.delete_endpointstage",
"authentik_endpoints.view_connector",
"authentik_endpoints.view_device",
"authentik_endpoints.view_deviceaccessgroup",
"authentik_endpoints.view_deviceconnection",
"authentik_endpoints.view_devicefactsnapshot",
"authentik_endpoints.view_devicegroup",
"authentik_endpoints.view_deviceuserbinding",
"authentik_endpoints.view_endpointstage",
"authentik_endpoints_connectors_agent.add_agentconnector",
"authentik_endpoints_connectors_agent.add_agentdeviceconnection",
"authentik_endpoints_connectors_agent.add_agentdeviceuserbinding",
"authentik_endpoints_connectors_agent.add_applenonce",
"authentik_endpoints_connectors_agent.add_deviceauthenticationtoken",
"authentik_endpoints_connectors_agent.add_devicetoken",
"authentik_endpoints_connectors_agent.add_enrollmenttoken",
"authentik_endpoints_connectors_agent.change_agentconnector",
"authentik_endpoints_connectors_agent.change_agentdeviceconnection",
"authentik_endpoints_connectors_agent.change_agentdeviceuserbinding",
"authentik_endpoints_connectors_agent.change_applenonce",
"authentik_endpoints_connectors_agent.change_deviceauthenticationtoken",
"authentik_endpoints_connectors_agent.change_devicetoken",
"authentik_endpoints_connectors_agent.change_enrollmenttoken",
"authentik_endpoints_connectors_agent.delete_agentconnector",
"authentik_endpoints_connectors_agent.delete_agentdeviceconnection",
"authentik_endpoints_connectors_agent.delete_agentdeviceuserbinding",
"authentik_endpoints_connectors_agent.delete_applenonce",
"authentik_endpoints_connectors_agent.delete_deviceauthenticationtoken",
"authentik_endpoints_connectors_agent.delete_devicetoken",
"authentik_endpoints_connectors_agent.delete_enrollmenttoken",
"authentik_endpoints_connectors_agent.view_agentconnector",
"authentik_endpoints_connectors_agent.view_agentdeviceconnection",
"authentik_endpoints_connectors_agent.view_agentdeviceuserbinding",
"authentik_endpoints_connectors_agent.view_applenonce",
"authentik_endpoints_connectors_agent.view_deviceauthenticationtoken",
"authentik_endpoints_connectors_agent.view_devicetoken",
"authentik_endpoints_connectors_agent.view_enrollment_token_key",
"authentik_endpoints_connectors_agent.view_enrollmenttoken",
@@ -5973,6 +5981,25 @@
"minLength": 1,
"title": "Snapshot expiry"
},
"auth_session_duration": {
"type": "string",
"minLength": 1,
"title": "Auth session duration"
},
"auth_terminate_session_on_expiry": {
"type": "boolean",
"title": "Auth terminate session on expiry"
},
"refresh_interval": {
"type": "string",
"minLength": 1,
"title": "Refresh interval"
},
"authorization_flow": {
"type": "string",
"format": "uuid",
"title": "Authorization flow"
},
"nss_uid_offset": {
"type": "integer",
"minimum": 0,
@@ -5985,24 +6012,17 @@
"maximum": 2147483647,
"title": "Nss gid offset"
},
"auth_terminate_session_on_expiry": {
"type": "boolean",
"title": "Auth terminate session on expiry"
},
"refresh_interval": {
"type": "string",
"minLength": 1,
"title": "Refresh interval"
},
"authentication_flow": {
"type": "string",
"format": "uuid",
"title": "Authentication flow"
},
"challenge_key": {
"type": "string",
"format": "uuid",
"title": "Challenge key"
},
"jwt_federation_providers": {
"type": "array",
"items": {
"type": "integer"
},
"title": "Jwt federation providers"
}
},
"required": []
@@ -6267,9 +6287,7 @@
},
"slug": {
"type": "string",
"maxLength": 50,
"minLength": 1,
"pattern": "^[-a-zA-Z0-9_]+$",
"title": "Slug",
"description": "Visible in the URL."
},
@@ -10558,50 +10576,58 @@
"authentik_crypto.view_certificatekeypair",
"authentik_endpoints.add_connector",
"authentik_endpoints.add_device",
"authentik_endpoints.add_deviceaccessgroup",
"authentik_endpoints.add_deviceconnection",
"authentik_endpoints.add_devicefactsnapshot",
"authentik_endpoints.add_devicegroup",
"authentik_endpoints.add_deviceuserbinding",
"authentik_endpoints.add_endpointstage",
"authentik_endpoints.change_connector",
"authentik_endpoints.change_device",
"authentik_endpoints.change_deviceaccessgroup",
"authentik_endpoints.change_deviceconnection",
"authentik_endpoints.change_devicefactsnapshot",
"authentik_endpoints.change_devicegroup",
"authentik_endpoints.change_deviceuserbinding",
"authentik_endpoints.change_endpointstage",
"authentik_endpoints.delete_connector",
"authentik_endpoints.delete_device",
"authentik_endpoints.delete_deviceaccessgroup",
"authentik_endpoints.delete_deviceconnection",
"authentik_endpoints.delete_devicefactsnapshot",
"authentik_endpoints.delete_devicegroup",
"authentik_endpoints.delete_deviceuserbinding",
"authentik_endpoints.delete_endpointstage",
"authentik_endpoints.view_connector",
"authentik_endpoints.view_device",
"authentik_endpoints.view_deviceaccessgroup",
"authentik_endpoints.view_deviceconnection",
"authentik_endpoints.view_devicefactsnapshot",
"authentik_endpoints.view_devicegroup",
"authentik_endpoints.view_deviceuserbinding",
"authentik_endpoints.view_endpointstage",
"authentik_endpoints_connectors_agent.add_agentconnector",
"authentik_endpoints_connectors_agent.add_agentdeviceconnection",
"authentik_endpoints_connectors_agent.add_agentdeviceuserbinding",
"authentik_endpoints_connectors_agent.add_applenonce",
"authentik_endpoints_connectors_agent.add_deviceauthenticationtoken",
"authentik_endpoints_connectors_agent.add_devicetoken",
"authentik_endpoints_connectors_agent.add_enrollmenttoken",
"authentik_endpoints_connectors_agent.change_agentconnector",
"authentik_endpoints_connectors_agent.change_agentdeviceconnection",
"authentik_endpoints_connectors_agent.change_agentdeviceuserbinding",
"authentik_endpoints_connectors_agent.change_applenonce",
"authentik_endpoints_connectors_agent.change_deviceauthenticationtoken",
"authentik_endpoints_connectors_agent.change_devicetoken",
"authentik_endpoints_connectors_agent.change_enrollmenttoken",
"authentik_endpoints_connectors_agent.delete_agentconnector",
"authentik_endpoints_connectors_agent.delete_agentdeviceconnection",
"authentik_endpoints_connectors_agent.delete_agentdeviceuserbinding",
"authentik_endpoints_connectors_agent.delete_applenonce",
"authentik_endpoints_connectors_agent.delete_deviceauthenticationtoken",
"authentik_endpoints_connectors_agent.delete_devicetoken",
"authentik_endpoints_connectors_agent.delete_enrollmenttoken",
"authentik_endpoints_connectors_agent.view_agentconnector",
"authentik_endpoints_connectors_agent.view_agentdeviceconnection",
"authentik_endpoints_connectors_agent.view_agentdeviceuserbinding",
"authentik_endpoints_connectors_agent.view_applenonce",
"authentik_endpoints_connectors_agent.view_deviceauthenticationtoken",
"authentik_endpoints_connectors_agent.view_devicetoken",
"authentik_endpoints_connectors_agent.view_enrollment_token_key",
"authentik_endpoints_connectors_agent.view_enrollmenttoken",

4
go.mod
View File

@@ -9,7 +9,7 @@ require (
beryju.io/radius-eap v0.1.0
github.com/avast/retry-go/v4 v4.7.0
github.com/coreos/go-oidc/v3 v3.17.0
github.com/getsentry/sentry-go v0.39.0
github.com/getsentry/sentry-go v0.40.0
github.com/go-http-utils/etag v0.0.0-20161124023236-513ea8f21eb1
github.com/go-ldap/ldap/v3 v3.4.12
github.com/go-openapi/runtime v0.29.2
@@ -32,7 +32,7 @@ require (
github.com/spf13/cobra v1.10.1
github.com/stretchr/testify v1.11.1
github.com/wwt/guac v1.3.2
goauthentik.io/api/v3 v3.2025120.7
goauthentik.io/api/v3 v3.2025120.11
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab
golang.org/x/oauth2 v0.33.0
golang.org/x/sync v0.18.0

8
go.sum
View File

@@ -20,8 +20,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/getsentry/sentry-go v0.39.0 h1:uhnexj8PNCyCve37GSqxXOeXHh4cJNLNNB4w70Jtgo0=
github.com/getsentry/sentry-go v0.39.0/go.mod h1:eRXCoh3uvmjQLY6qu63BjUZnaBu5L5WhMV1RwYO8W5s=
github.com/getsentry/sentry-go v0.40.0 h1:VTJMN9zbTvqDqPwheRVLcp0qcUcM+8eFivvGocAaSbo=
github.com/getsentry/sentry-go v0.40.0/go.mod h1:eRXCoh3uvmjQLY6qu63BjUZnaBu5L5WhMV1RwYO8W5s=
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo=
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
@@ -214,8 +214,8 @@ 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.2025120.7 h1:kcMcIm1l0ndbmEu4JlIHL2pscmdN2x4kAn0t8Zd+RNI=
goauthentik.io/api/v3 v3.2025120.7/go.mod h1:82lqAz4jxzl6Cg0YDbhNtvvTG2rm6605ZhdJFnbbsl8=
goauthentik.io/api/v3 v3.2025120.11 h1:qKKIj6FzWMG5x3suQyKJdD4ziIlqtp6Lq8SCjjQAMf0=
goauthentik.io/api/v3 v3.2025120.11/go.mod h1:82lqAz4jxzl6Cg0YDbhNtvvTG2rm6605ZhdJFnbbsl8=
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=

View File

@@ -31,7 +31,7 @@ RUN --mount=type=cache,sharing=locked,target=/go/pkg/mod \
go build -o /go/ldap ./cmd/ldap
# Stage 2: Run
FROM ghcr.io/goauthentik/fips-debian:trixie-slim-fips@sha256:ac4c80b43351a3a10c0307d191fe6a6386359bc36e2aa3ccb1b23abd1f0a9e4f
FROM ghcr.io/goauthentik/fips-debian:trixie-slim-fips@sha256:c718f608885a76a8940700b0c9feab75b1c967e1c291adf52dcd3ad6f6a86a66
ARG VERSION
ARG GIT_BUILD_HASH

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -27,27 +27,28 @@
# Stefan Werner, 2024
# Jonas, 2025
# Niklas Kroese, 2025
# 97cce0ae0cad2a2cc552d3165d04643e_de3d740, 2025
# Dominic Wagner <mail@dominic-wagner.de>, 2025
# Till-Frederik Riechard, 2025
# Alexander Mnich, 2025
# Ben, 2025
# datenschmutz, 2025
# Ulrich Stark, 2025
# Benjamin Böhmke, 2025
# 97cce0ae0cad2a2cc552d3165d04643e_de3d740, 2025
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-10-20 00:11+0000\n"
"POT-Creation-Date: 2025-12-01 15:07+0000\n"
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
"Last-Translator: Benjamin Böhmke, 2025\n"
"Last-Translator: 97cce0ae0cad2a2cc552d3165d04643e_de3d740, 2025\n"
"Language-Team: German (https://app.transifex.com/authentik/teams/119923/de/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: de\n"
"Language: de_DE\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: authentik/admin/models.py
@@ -419,6 +420,12 @@ msgstr "Quellname"
msgid "Internal source name, used in URLs."
msgstr "Interner Quellname, genutzt für URLs"
#: authentik/core/models.py
msgid ""
"When enabled, this source will be displayed as a prominent button on the "
"login page, instead of a small icon."
msgstr ""
#: authentik/core/models.py
msgid "Flow to use when authenticating existing users."
msgstr "Flow der zur Authorisierung bereits ersteller Nutzer verwendet wird"
@@ -451,7 +458,7 @@ msgstr "Token"
msgid "Tokens"
msgstr "Tokens"
#: authentik/core/models.py
#: authentik/core/models.py authentik/endpoints/connectors/agent/models.py
msgid "View token's key"
msgstr "Schlüssel des Tokens anzeigen"
@@ -524,6 +531,7 @@ msgid "Remove temporary users created by SAML Sources."
msgstr "Temporäre Benutzer entfernen, die von SAML-Sources erstellt wurden."
#: authentik/core/templates/if/error.html
#: authentik/policies/templates/policies/denied.html
msgid "Go home"
msgstr "Zur Startseite"
@@ -555,6 +563,26 @@ msgstr "RSA"
msgid "ecdsa"
msgstr "ECDSA"
#: authentik/crypto/models.py
msgid "RSA"
msgstr ""
#: authentik/crypto/models.py
msgid "Elliptic Curve"
msgstr ""
#: authentik/crypto/models.py
msgid "DSA"
msgstr ""
#: authentik/crypto/models.py
msgid "Ed25519"
msgstr ""
#: authentik/crypto/models.py
msgid "Ed448"
msgstr ""
#: authentik/crypto/models.py
msgid "PEM-encoded Certificate data"
msgstr "PEM-verschlüsselte Zertifikatsdaten"
@@ -579,6 +607,104 @@ msgstr "Zertifikat-Schlüsselpaare"
msgid "Discover, import and update certificates from the filesystem."
msgstr "Zertifikate vom Dateisystem entdecken, importieren und aktualisieren."
#: authentik/endpoints/connectors/agent/api/connectors.py
msgid "Selected platform not supported"
msgstr ""
#: authentik/endpoints/connectors/agent/api/connectors.py
msgid "Token is expired"
msgstr ""
#: authentik/endpoints/connectors/agent/api/connectors.py
msgid "Invalid token for connector"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Agent Connector"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Agent Connectors"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
#: authentik/providers/oauth2/models.py
msgid "Device Token"
msgstr "Geräte-Token"
#: authentik/endpoints/connectors/agent/models.py
#: authentik/providers/oauth2/models.py
msgid "Device Tokens"
msgstr "Geräte-Token"
#: authentik/endpoints/connectors/agent/models.py
msgid "Enrollment Token"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Enrollment Tokens"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Device authentication token"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Device authentication tokens"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Apple Nonce"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Apple Nonces"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device"
msgstr "Gerät"
#: authentik/endpoints/models.py
msgid "Devices"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device User binding"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device User bindings"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device connection"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device connections"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device fact snapshot"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device fact snapshots"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device access group"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device access groups"
msgstr ""
#: authentik/endpoints/tasks.py
msgid "Sync endpoints."
msgstr ""
#: authentik/enterprise/api.py
msgid "Enterprise is required to create/update this object."
msgstr ""
@@ -1250,10 +1376,22 @@ msgstr "Flow-Token"
msgid "Flow Tokens"
msgstr "Flow-Tokens"
#: authentik/flows/templates/if/flow.html
msgid "Site footer"
msgstr ""
#: authentik/flows/views/executor.py
msgid "Invalid next URL"
msgstr "Ungültige nächste URL"
#: authentik/lib/sync/outgoing/models.py
msgid "Controls the number of objects synced in a single task"
msgstr ""
#: authentik/lib/sync/outgoing/models.py
msgid "Timeout for synchronization of a single page"
msgstr ""
#: authentik/lib/sync/outgoing/models.py
msgid ""
"When enabled, provider will not modify or create objects in the remote "
@@ -1657,6 +1795,10 @@ msgstr "Avatar des Benutzers"
msgid "Not you?"
msgstr "Nicht Sie?"
#: authentik/policies/templates/policies/denied.html
msgid "Error"
msgstr "Fehler"
#: authentik/policies/templates/policies/denied.html
msgid "Request has been denied."
msgstr "Anfrage wurde verweigert"
@@ -2040,14 +2182,6 @@ msgstr "OAuth2-Aktualisierungs-Token"
msgid "OAuth2 Refresh Tokens"
msgstr "OAuth2-Aktualisierungs-Token"
#: authentik/providers/oauth2/models.py
msgid "Device Token"
msgstr "Geräte-Token"
#: authentik/providers/oauth2/models.py
msgid "Device Tokens"
msgstr "Geräte-Token"
#: authentik/providers/oauth2/tasks.py
msgid "Send a back-channel logout request to the registered client"
msgstr "Eine Back-Channel-Logout-Anfrage an den registrierten Client senden."
@@ -2062,7 +2196,7 @@ msgstr ""
#: authentik/providers/saml/views/flows.py
#, python-brace-format
msgid "Redirecting to {app}..."
msgstr "Umleitung zu {app}..."
msgstr "Weiterleitung zu {app}..."
#: authentik/providers/oauth2/views/device_init.py
msgid "Invalid code"
@@ -2285,18 +2419,6 @@ msgstr "Der Import von Metadaten ist fehlgeschlagen: {messages}"
msgid "ACS URL"
msgstr "ACS URL"
#: authentik/providers/saml/models.py
msgid ""
"Value of the audience restriction field of the assertion. When left empty, "
"no audience restriction will be added."
msgstr ""
"Wert des Feldes für die Zielgruppenbeschränkung in der Prüfung. Bleibt das "
"Feld leer, wird keine Zielgruppenbeschränkung hinzugefügt."
#: authentik/providers/saml/models.py
msgid "Also known as EntityID"
msgstr "Auch bekannt als EntityID"
#: authentik/providers/saml/models.py
msgid "Service Provider Binding"
msgstr "Service Provider zuordung"
@@ -2309,6 +2431,18 @@ msgstr ""
"Damit wird festgelegt, wie Authentik die Antwort an den Dienstanbieter "
"zurücksendet."
#: authentik/providers/saml/models.py
msgid ""
"Value of the audience restriction field of the assertion. When left empty, "
"no audience restriction will be added."
msgstr ""
"Wert des Feldes für die Zielgruppenbeschränkung in der Prüfung. Bleibt das "
"Feld leer, wird keine Zielgruppenbeschränkung hinzugefügt."
#: authentik/providers/saml/models.py
msgid "Also known as EntityID"
msgstr "Auch bekannt als EntityID"
#: authentik/providers/saml/models.py
msgid "SLS URL"
msgstr ""

Binary file not shown.

View File

@@ -8,15 +8,14 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-11-25 00:09+0000\n"
"POT-Creation-Date: 2025-12-01 17:05+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"Language: en\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: authentik/admin/models.py
msgid "Version history"
@@ -348,6 +347,12 @@ msgstr ""
msgid "Internal source name, used in URLs."
msgstr ""
#: authentik/core/models.py
msgid ""
"When enabled, this source will be displayed as a prominent button on the "
"login page, instead of a small icon."
msgstr ""
#: authentik/core/models.py
msgid "Flow to use when authenticating existing users."
msgstr ""
@@ -540,6 +545,16 @@ msgstr ""
msgid "Agent Connectors"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
#: authentik/providers/oauth2/models.py
msgid "Device Token"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
#: authentik/providers/oauth2/models.py
msgid "Device Tokens"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Enrollment Token"
msgstr ""
@@ -548,6 +563,62 @@ msgstr ""
msgid "Enrollment Tokens"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Device authentication token"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Device authentication tokens"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Apple Nonce"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Apple Nonces"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device"
msgstr ""
#: authentik/endpoints/models.py
msgid "Devices"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device User binding"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device User bindings"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device connection"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device connections"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device fact snapshot"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device fact snapshots"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device access group"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device access groups"
msgstr ""
#: authentik/endpoints/tasks.py
msgid "Sync endpoints."
msgstr ""
@@ -1873,14 +1944,6 @@ msgstr ""
msgid "OAuth2 Refresh Tokens"
msgstr ""
#: authentik/providers/oauth2/models.py
msgid "Device Token"
msgstr ""
#: authentik/providers/oauth2/models.py
msgid "Device Tokens"
msgstr ""
#: authentik/providers/oauth2/tasks.py
msgid "Send a back-channel logout request to the registered client"
msgstr ""

Binary file not shown.

Binary file not shown.

View File

@@ -16,14 +16,14 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-10-20 00:11+0000\n"
"POT-Creation-Date: 2025-12-01 15:07+0000\n"
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
"Last-Translator: Jens L. <jens@goauthentik.io>, 2025\n"
"Language-Team: Spanish (https://app.transifex.com/authentik/teams/119923/es/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: es\n"
"Language: es_ES\n"
"Plural-Forms: nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n"
#: authentik/admin/models.py
@@ -394,6 +394,12 @@ msgstr "Nombre de visualización de la fuente."
msgid "Internal source name, used in URLs."
msgstr "Nombre de origen interno, utilizado en las URL."
#: authentik/core/models.py
msgid ""
"When enabled, this source will be displayed as a prominent button on the "
"login page, instead of a small icon."
msgstr ""
#: authentik/core/models.py
msgid "Flow to use when authenticating existing users."
msgstr "Flujo que se utilizará al autenticar a los usuarios existentes."
@@ -426,7 +432,7 @@ msgstr "Token"
msgid "Tokens"
msgstr "Tokens"
#: authentik/core/models.py
#: authentik/core/models.py authentik/endpoints/connectors/agent/models.py
msgid "View token's key"
msgstr "Ver llave del token"
@@ -498,6 +504,7 @@ msgid "Remove temporary users created by SAML Sources."
msgstr "Eliminar usuarios temporales creados por SAML Sources."
#: authentik/core/templates/if/error.html
#: authentik/policies/templates/policies/denied.html
msgid "Go home"
msgstr "Ir al inicio"
@@ -529,6 +536,26 @@ msgstr "rsa"
msgid "ecdsa"
msgstr "ecdsa"
#: authentik/crypto/models.py
msgid "RSA"
msgstr ""
#: authentik/crypto/models.py
msgid "Elliptic Curve"
msgstr ""
#: authentik/crypto/models.py
msgid "DSA"
msgstr ""
#: authentik/crypto/models.py
msgid "Ed25519"
msgstr ""
#: authentik/crypto/models.py
msgid "Ed448"
msgstr ""
#: authentik/crypto/models.py
msgid "PEM-encoded Certificate data"
msgstr "Datos de certificados codificados en PEM"
@@ -554,6 +581,104 @@ msgid "Discover, import and update certificates from the filesystem."
msgstr ""
"Descubra, importe y actualice certificados desde el sistema de archivos."
#: authentik/endpoints/connectors/agent/api/connectors.py
msgid "Selected platform not supported"
msgstr ""
#: authentik/endpoints/connectors/agent/api/connectors.py
msgid "Token is expired"
msgstr ""
#: authentik/endpoints/connectors/agent/api/connectors.py
msgid "Invalid token for connector"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Agent Connector"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Agent Connectors"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
#: authentik/providers/oauth2/models.py
msgid "Device Token"
msgstr "Token de Dispositivo"
#: authentik/endpoints/connectors/agent/models.py
#: authentik/providers/oauth2/models.py
msgid "Device Tokens"
msgstr "Tokens de Dispositivo"
#: authentik/endpoints/connectors/agent/models.py
msgid "Enrollment Token"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Enrollment Tokens"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Device authentication token"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Device authentication tokens"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Apple Nonce"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Apple Nonces"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device"
msgstr "Dispositivo"
#: authentik/endpoints/models.py
msgid "Devices"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device User binding"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device User bindings"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device connection"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device connections"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device fact snapshot"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device fact snapshots"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device access group"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device access groups"
msgstr ""
#: authentik/endpoints/tasks.py
msgid "Sync endpoints."
msgstr ""
#: authentik/enterprise/api.py
msgid "Enterprise is required to create/update this object."
msgstr "Se requiere de Enterprise para crear/actualizar este objeto."
@@ -1211,10 +1336,22 @@ msgstr "Token de flujo"
msgid "Flow Tokens"
msgstr "Tokens de flujo"
#: authentik/flows/templates/if/flow.html
msgid "Site footer"
msgstr ""
#: authentik/flows/views/executor.py
msgid "Invalid next URL"
msgstr "Siguiente URL invalida"
#: authentik/lib/sync/outgoing/models.py
msgid "Controls the number of objects synced in a single task"
msgstr ""
#: authentik/lib/sync/outgoing/models.py
msgid "Timeout for synchronization of a single page"
msgstr ""
#: authentik/lib/sync/outgoing/models.py
msgid ""
"When enabled, provider will not modify or create objects in the remote "
@@ -1615,6 +1752,10 @@ msgstr "Avatar del usuario"
msgid "Not you?"
msgstr "¿No eres tú?"
#: authentik/policies/templates/policies/denied.html
msgid "Error"
msgstr "Error"
#: authentik/policies/templates/policies/denied.html
msgid "Request has been denied."
msgstr "Se ha denegado la solicitud."
@@ -1995,14 +2136,6 @@ msgstr "Token de Actualización OAuth2"
msgid "OAuth2 Refresh Tokens"
msgstr "Tokens de Actualización OAuth2"
#: authentik/providers/oauth2/models.py
msgid "Device Token"
msgstr "Token de Dispositivo"
#: authentik/providers/oauth2/models.py
msgid "Device Tokens"
msgstr "Tokens de Dispositivo"
#: authentik/providers/oauth2/tasks.py
msgid "Send a back-channel logout request to the registered client"
msgstr ""
@@ -2245,6 +2378,17 @@ msgstr "No se pudieron importar los Metadatos: {messages}"
msgid "ACS URL"
msgstr "URL"
#: authentik/providers/saml/models.py
msgid "Service Provider Binding"
msgstr "Vinculación de Proveedor de Servicio"
#: authentik/providers/saml/models.py
msgid ""
"This determines how authentik sends the response back to the Service "
"Provider."
msgstr ""
"Esto determina cómo authentik envía la respuesta al Proveedor de Servicios."
#: authentik/providers/saml/models.py
msgid ""
"Value of the audience restriction field of the assertion. When left empty, "
@@ -2257,17 +2401,6 @@ msgstr ""
msgid "Also known as EntityID"
msgstr "También conocido como EntityID"
#: authentik/providers/saml/models.py
msgid "Service Provider Binding"
msgstr "Vinculación de Proveedor de Servicio"
#: authentik/providers/saml/models.py
msgid ""
"This determines how authentik sends the response back to the Service "
"Provider."
msgstr ""
"Esto determina cómo authentik envía la respuesta al Proveedor de Servicios."
#: authentik/providers/saml/models.py
msgid "SLS URL"
msgstr ""

View File

@@ -15,14 +15,14 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-11-13 03:19+0000\n"
"POT-Creation-Date: 2025-12-01 15:07+0000\n"
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
"Last-Translator: Skyler Mäntysaari, 2025\n"
"Language-Team: Finnish (https://app.transifex.com/authentik/teams/119923/fi/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: fi\n"
"Language: fi_FI\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: authentik/admin/models.py
@@ -382,6 +382,12 @@ msgstr "Lähteen näytettävä nimi."
msgid "Internal source name, used in URLs."
msgstr "Lähteen sisäinen nimi, käytetään URLeissa."
#: authentik/core/models.py
msgid ""
"When enabled, this source will be displayed as a prominent button on the "
"login page, instead of a small icon."
msgstr ""
#: authentik/core/models.py
msgid "Flow to use when authenticating existing users."
msgstr "Prosessi, jota käytetään kun todennetaan olemassa olevia käyttäjiä."
@@ -414,7 +420,7 @@ msgstr "Tunniste"
msgid "Tokens"
msgstr "Tunnisteet"
#: authentik/core/models.py
#: authentik/core/models.py authentik/endpoints/connectors/agent/models.py
msgid "View token's key"
msgstr "Näytä tunnisteen avain"
@@ -562,6 +568,104 @@ msgstr "Sertifikaatti-avainparit"
msgid "Discover, import and update certificates from the filesystem."
msgstr "Havaitse, tuo ja päivitä sertifikaatteja levyjärjestelmästä."
#: authentik/endpoints/connectors/agent/api/connectors.py
msgid "Selected platform not supported"
msgstr ""
#: authentik/endpoints/connectors/agent/api/connectors.py
msgid "Token is expired"
msgstr ""
#: authentik/endpoints/connectors/agent/api/connectors.py
msgid "Invalid token for connector"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Agent Connector"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Agent Connectors"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
#: authentik/providers/oauth2/models.py
msgid "Device Token"
msgstr "Laitetunniste"
#: authentik/endpoints/connectors/agent/models.py
#: authentik/providers/oauth2/models.py
msgid "Device Tokens"
msgstr "Laitetunnisteet"
#: authentik/endpoints/connectors/agent/models.py
msgid "Enrollment Token"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Enrollment Tokens"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Device authentication token"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Device authentication tokens"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Apple Nonce"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Apple Nonces"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device"
msgstr "Laite"
#: authentik/endpoints/models.py
msgid "Devices"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device User binding"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device User bindings"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device connection"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device connections"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device fact snapshot"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device fact snapshots"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device access group"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device access groups"
msgstr ""
#: authentik/endpoints/tasks.py
msgid "Sync endpoints."
msgstr ""
#: authentik/enterprise/api.py
msgid "Enterprise is required to create/update this object."
msgstr "Tämän objektin luontiin/päivittämiseen tarvitaan Enterprise-versiota."
@@ -1215,6 +1319,10 @@ msgstr "Prosessin tunniste"
msgid "Flow Tokens"
msgstr "Prosessin tunnisteet"
#: authentik/flows/templates/if/flow.html
msgid "Site footer"
msgstr ""
#: authentik/flows/views/executor.py
msgid "Invalid next URL"
msgstr "Virheellinen seuraava URL"
@@ -2012,14 +2120,6 @@ msgstr "OAuth2-päivitystunnus"
msgid "OAuth2 Refresh Tokens"
msgstr "OAuth2-päivitystunnukset"
#: authentik/providers/oauth2/models.py
msgid "Device Token"
msgstr "Laitetunniste"
#: authentik/providers/oauth2/models.py
msgid "Device Tokens"
msgstr "Laitetunnisteet"
#: authentik/providers/oauth2/tasks.py
msgid "Send a back-channel logout request to the registered client"
msgstr ""

View File

@@ -9,24 +9,24 @@
# Kyllian Delaye-Maillot, 2023
# Manuel Viens, 2023
# Mordecai, 2023
# Charles Leclerc, 2025
# Tina, 2025
# nerdinator <florian.dupret@gmail.com>, 2025
# Marc Schmitt, 2025
# Charles Leclerc, 2025
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-10-20 00:11+0000\n"
"POT-Creation-Date: 2025-12-01 15:07+0000\n"
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
"Last-Translator: Marc Schmitt, 2025\n"
"Last-Translator: Charles Leclerc, 2025\n"
"Language-Team: French (https://app.transifex.com/authentik/teams/119923/fr/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: fr\n"
"Language: fr_FR\n"
"Plural-Forms: nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n"
#: authentik/admin/models.py
@@ -397,6 +397,12 @@ msgstr "Nom d'affichage de la source."
msgid "Internal source name, used in URLs."
msgstr "Nom interne de la source, utilisé dans les URLs."
#: authentik/core/models.py
msgid ""
"When enabled, this source will be displayed as a prominent button on the "
"login page, instead of a small icon."
msgstr ""
#: authentik/core/models.py
msgid "Flow to use when authenticating existing users."
msgstr "Flux à utiliser pour authentifier les utilisateurs existants."
@@ -429,7 +435,7 @@ msgstr "Jeton"
msgid "Tokens"
msgstr "Jetons"
#: authentik/core/models.py
#: authentik/core/models.py authentik/endpoints/connectors/agent/models.py
msgid "View token's key"
msgstr "Voir la clé du jeton"
@@ -501,6 +507,7 @@ msgid "Remove temporary users created by SAML Sources."
msgstr "Supprime les utilisateurs temporaires créés par les sources SAML."
#: authentik/core/templates/if/error.html
#: authentik/policies/templates/policies/denied.html
msgid "Go home"
msgstr "Retourner à l'accueil"
@@ -532,6 +539,26 @@ msgstr "rsa"
msgid "ecdsa"
msgstr "ecdsa"
#: authentik/crypto/models.py
msgid "RSA"
msgstr ""
#: authentik/crypto/models.py
msgid "Elliptic Curve"
msgstr ""
#: authentik/crypto/models.py
msgid "DSA"
msgstr ""
#: authentik/crypto/models.py
msgid "Ed25519"
msgstr ""
#: authentik/crypto/models.py
msgid "Ed448"
msgstr ""
#: authentik/crypto/models.py
msgid "PEM-encoded Certificate data"
msgstr "Données du certificat au format PEM"
@@ -558,6 +585,104 @@ msgstr ""
"Découvre, importe et met à jour les certificats depuis le système de "
"fichiers."
#: authentik/endpoints/connectors/agent/api/connectors.py
msgid "Selected platform not supported"
msgstr ""
#: authentik/endpoints/connectors/agent/api/connectors.py
msgid "Token is expired"
msgstr ""
#: authentik/endpoints/connectors/agent/api/connectors.py
msgid "Invalid token for connector"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Agent Connector"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Agent Connectors"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
#: authentik/providers/oauth2/models.py
msgid "Device Token"
msgstr "Jeton d'équipement"
#: authentik/endpoints/connectors/agent/models.py
#: authentik/providers/oauth2/models.py
msgid "Device Tokens"
msgstr "Jetons d'équipement"
#: authentik/endpoints/connectors/agent/models.py
msgid "Enrollment Token"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Enrollment Tokens"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Device authentication token"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Device authentication tokens"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Apple Nonce"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Apple Nonces"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device"
msgstr "Équipement"
#: authentik/endpoints/models.py
msgid "Devices"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device User binding"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device User bindings"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device connection"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device connections"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device fact snapshot"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device fact snapshots"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device access group"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device access groups"
msgstr ""
#: authentik/endpoints/tasks.py
msgid "Sync endpoints."
msgstr ""
#: authentik/enterprise/api.py
msgid "Enterprise is required to create/update this object."
msgstr "Entreprise est requis pour créer/mettre à jour cet objet."
@@ -1225,10 +1350,22 @@ msgstr "Jeton du flux"
msgid "Flow Tokens"
msgstr "Jetons du flux"
#: authentik/flows/templates/if/flow.html
msgid "Site footer"
msgstr ""
#: authentik/flows/views/executor.py
msgid "Invalid next URL"
msgstr "URL suivante invalide"
#: authentik/lib/sync/outgoing/models.py
msgid "Controls the number of objects synced in a single task"
msgstr ""
#: authentik/lib/sync/outgoing/models.py
msgid "Timeout for synchronization of a single page"
msgstr ""
#: authentik/lib/sync/outgoing/models.py
msgid ""
"When enabled, provider will not modify or create objects in the remote "
@@ -1633,6 +1770,10 @@ msgstr "Avatar de l'utilisateu"
msgid "Not you?"
msgstr "Pas vous ?"
#: authentik/policies/templates/policies/denied.html
msgid "Error"
msgstr "Erreur"
#: authentik/policies/templates/policies/denied.html
msgid "Request has been denied."
msgstr "La requête a été refusée."
@@ -2019,14 +2160,6 @@ msgstr "Jeton de rafraîchissement OAuth2"
msgid "OAuth2 Refresh Tokens"
msgstr "Jetons de rafraîchissement OAuth2"
#: authentik/providers/oauth2/models.py
msgid "Device Token"
msgstr "Jeton d'équipement"
#: authentik/providers/oauth2/models.py
msgid "Device Tokens"
msgstr "Jetons d'équipement"
#: authentik/providers/oauth2/tasks.py
msgid "Send a back-channel logout request to the registered client"
msgstr "Envoyer une requête de déconnexion Back-Channel au client enregistré"
@@ -2266,18 +2399,6 @@ msgstr "Échec d'import des métadonnées : {messages}"
msgid "ACS URL"
msgstr "ACS URL"
#: authentik/providers/saml/models.py
msgid ""
"Value of the audience restriction field of the assertion. When left empty, "
"no audience restriction will be added."
msgstr ""
"Valeur du champ de restriction d'audience de l'assertion. Si vide, aucune "
"restriction d'audience ne sera ajoutée."
#: authentik/providers/saml/models.py
msgid "Also known as EntityID"
msgstr "Aussi appelé EntityID"
#: authentik/providers/saml/models.py
msgid "Service Provider Binding"
msgstr "Liaison du fournisseur de services"
@@ -2290,6 +2411,18 @@ msgstr ""
"Cela détermine la manière dont authentik renvoie la réponse au fournisseur "
"de services."
#: authentik/providers/saml/models.py
msgid ""
"Value of the audience restriction field of the assertion. When left empty, "
"no audience restriction will be added."
msgstr ""
"Valeur du champ de restriction d'audience de l'assertion. Si vide, aucune "
"restriction d'audience ne sera ajoutée."
#: authentik/providers/saml/models.py
msgid "Also known as EntityID"
msgstr "Aussi appelé EntityID"
#: authentik/providers/saml/models.py
msgid "SLS URL"
msgstr "URL SLS"

Binary file not shown.

Binary file not shown.

View File

@@ -11,23 +11,23 @@
# Nicola Mersi, 2024
# tmassimi, 2024
# Marc Schmitt, 2024
# Kowalski Dragon (kowalski7cc) <kowalski.7cc@gmail.com>, 2025
# Matteo Piccina <altermatte@gmail.com>, 2025
# albanobattistella <albanobattistella@gmail.com>, 2025
# Kowalski Dragon (kowalski7cc) <kowalski.7cc@gmail.com>, 2025
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-10-20 00:11+0000\n"
"POT-Creation-Date: 2025-12-01 15:07+0000\n"
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
"Last-Translator: albanobattistella <albanobattistella@gmail.com>, 2025\n"
"Last-Translator: Kowalski Dragon (kowalski7cc) <kowalski.7cc@gmail.com>, 2025\n"
"Language-Team: Italian (https://app.transifex.com/authentik/teams/119923/it/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: it\n"
"Language: it_IT\n"
"Plural-Forms: nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n"
#: authentik/admin/models.py
@@ -389,6 +389,12 @@ msgstr "Nome visualizzato della sorgente."
msgid "Internal source name, used in URLs."
msgstr "Nome interno della sorgente, utilizzato negli URL."
#: authentik/core/models.py
msgid ""
"When enabled, this source will be displayed as a prominent button on the "
"login page, instead of a small icon."
msgstr ""
#: authentik/core/models.py
msgid "Flow to use when authenticating existing users."
msgstr "Flusso da usare per autenticare utenti esistenti."
@@ -421,7 +427,7 @@ msgstr "Token"
msgid "Tokens"
msgstr "Tokens"
#: authentik/core/models.py
#: authentik/core/models.py authentik/endpoints/connectors/agent/models.py
msgid "View token's key"
msgstr "Visualizza la chiave token"
@@ -493,6 +499,7 @@ msgid "Remove temporary users created by SAML Sources."
msgstr "Rimuovi gli utenti temporanei creati da SAML Sources."
#: authentik/core/templates/if/error.html
#: authentik/policies/templates/policies/denied.html
msgid "Go home"
msgstr "Vai alla pagina iniziale"
@@ -524,6 +531,26 @@ msgstr "rsa"
msgid "ecdsa"
msgstr "ecdsa"
#: authentik/crypto/models.py
msgid "RSA"
msgstr ""
#: authentik/crypto/models.py
msgid "Elliptic Curve"
msgstr ""
#: authentik/crypto/models.py
msgid "DSA"
msgstr ""
#: authentik/crypto/models.py
msgid "Ed25519"
msgstr ""
#: authentik/crypto/models.py
msgid "Ed448"
msgstr ""
#: authentik/crypto/models.py
msgid "PEM-encoded Certificate data"
msgstr "Dati del certificato in codifica PEM"
@@ -548,6 +575,104 @@ msgstr "Coppie certificato-chiave"
msgid "Discover, import and update certificates from the filesystem."
msgstr "Scopri, importa e aggiorna i certificati dal file system."
#: authentik/endpoints/connectors/agent/api/connectors.py
msgid "Selected platform not supported"
msgstr ""
#: authentik/endpoints/connectors/agent/api/connectors.py
msgid "Token is expired"
msgstr ""
#: authentik/endpoints/connectors/agent/api/connectors.py
msgid "Invalid token for connector"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Agent Connector"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Agent Connectors"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
#: authentik/providers/oauth2/models.py
msgid "Device Token"
msgstr "Token Dispositivo"
#: authentik/endpoints/connectors/agent/models.py
#: authentik/providers/oauth2/models.py
msgid "Device Tokens"
msgstr "Token Dispositivi"
#: authentik/endpoints/connectors/agent/models.py
msgid "Enrollment Token"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Enrollment Tokens"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Device authentication token"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Device authentication tokens"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Apple Nonce"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Apple Nonces"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device"
msgstr "Dispositivo"
#: authentik/endpoints/models.py
msgid "Devices"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device User binding"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device User bindings"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device connection"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device connections"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device fact snapshot"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device fact snapshots"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device access group"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device access groups"
msgstr ""
#: authentik/endpoints/tasks.py
msgid "Sync endpoints."
msgstr ""
#: authentik/enterprise/api.py
msgid "Enterprise is required to create/update this object."
msgstr "Versione Enterprise richiesta per creare/aggiornare questo oggetto"
@@ -1211,10 +1336,22 @@ msgstr "Token del flusso"
msgid "Flow Tokens"
msgstr "Tokens del flusso"
#: authentik/flows/templates/if/flow.html
msgid "Site footer"
msgstr ""
#: authentik/flows/views/executor.py
msgid "Invalid next URL"
msgstr "URL successivo non valido"
#: authentik/lib/sync/outgoing/models.py
msgid "Controls the number of objects synced in a single task"
msgstr ""
#: authentik/lib/sync/outgoing/models.py
msgid "Timeout for synchronization of a single page"
msgstr ""
#: authentik/lib/sync/outgoing/models.py
msgid ""
"When enabled, provider will not modify or create objects in the remote "
@@ -1613,6 +1750,10 @@ msgstr "Avatar utente"
msgid "Not you?"
msgstr "Non sei tu?"
#: authentik/policies/templates/policies/denied.html
msgid "Error"
msgstr "Errore"
#: authentik/policies/templates/policies/denied.html
msgid "Request has been denied."
msgstr "La richiesta è stata negata."
@@ -1994,14 +2135,6 @@ msgstr "Token di aggiornamento OAuth2"
msgid "OAuth2 Refresh Tokens"
msgstr "Tokens di aggiornamento OAuth2"
#: authentik/providers/oauth2/models.py
msgid "Device Token"
msgstr "Token Dispositivo"
#: authentik/providers/oauth2/models.py
msgid "Device Tokens"
msgstr "Token Dispositivi"
#: authentik/providers/oauth2/tasks.py
msgid "Send a back-channel logout request to the registered client"
msgstr ""
@@ -2238,18 +2371,6 @@ msgstr "Impossibile importare i metadati: {messages}"
msgid "ACS URL"
msgstr "URL ACS"
#: authentik/providers/saml/models.py
msgid ""
"Value of the audience restriction field of the assertion. When left empty, "
"no audience restriction will be added."
msgstr ""
"Valore del campo di limitazione del pubblico dell'asserzione. Se lasciato "
"vuoto, non verrà aggiunta alcuna restrizione sul pubblico."
#: authentik/providers/saml/models.py
msgid "Also known as EntityID"
msgstr "Conosciuto anche come EntityID"
#: authentik/providers/saml/models.py
msgid "Service Provider Binding"
msgstr "Associazione fornitore di servizi"
@@ -2262,6 +2383,18 @@ msgstr ""
"Ciò determina il modo in cui authentik invia la risposta al fornitore di "
"servizi."
#: authentik/providers/saml/models.py
msgid ""
"Value of the audience restriction field of the assertion. When left empty, "
"no audience restriction will be added."
msgstr ""
"Valore del campo di limitazione del pubblico dell'asserzione. Se lasciato "
"vuoto, non verrà aggiunta alcuna restrizione sul pubblico."
#: authentik/providers/saml/models.py
msgid "Also known as EntityID"
msgstr "Conosciuto anche come EntityID"
#: authentik/providers/saml/models.py
msgid "SLS URL"
msgstr ""

View File

@@ -19,14 +19,14 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-11-10 00:11+0000\n"
"POT-Creation-Date: 2025-12-01 15:07+0000\n"
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
"Last-Translator: Teffen Ellis, 2025\n"
"Language-Team: Japanese (https://app.transifex.com/authentik/teams/119923/ja/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: ja\n"
"Language: ja_JP\n"
"Plural-Forms: nplurals=1; plural=0;\n"
#: authentik/admin/models.py
@@ -363,6 +363,12 @@ msgstr "ソースの表示名。"
msgid "Internal source name, used in URLs."
msgstr "URLで使用される内部ソース名。"
#: authentik/core/models.py
msgid ""
"When enabled, this source will be displayed as a prominent button on the "
"login page, instead of a small icon."
msgstr ""
#: authentik/core/models.py
msgid "Flow to use when authenticating existing users."
msgstr "フローは既存のユーザーを認証するときに使用されます。"
@@ -391,7 +397,7 @@ msgstr "トークン"
msgid "Tokens"
msgstr "トークン"
#: authentik/core/models.py
#: authentik/core/models.py authentik/endpoints/connectors/agent/models.py
msgid "View token's key"
msgstr "トークンのキーを表示"
@@ -461,6 +467,7 @@ msgid "Remove temporary users created by SAML Sources."
msgstr "SAMLで作成された一時ユーザを削除。"
#: authentik/core/templates/if/error.html
#: authentik/policies/templates/policies/denied.html
msgid "Go home"
msgstr "ホームに戻る"
@@ -534,6 +541,104 @@ msgstr "証明書とキーのペア"
msgid "Discover, import and update certificates from the filesystem."
msgstr "証明書をファイルシステムから検出、インポート、更新する。"
#: authentik/endpoints/connectors/agent/api/connectors.py
msgid "Selected platform not supported"
msgstr ""
#: authentik/endpoints/connectors/agent/api/connectors.py
msgid "Token is expired"
msgstr ""
#: authentik/endpoints/connectors/agent/api/connectors.py
msgid "Invalid token for connector"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Agent Connector"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Agent Connectors"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
#: authentik/providers/oauth2/models.py
msgid "Device Token"
msgstr "デバイストークン"
#: authentik/endpoints/connectors/agent/models.py
#: authentik/providers/oauth2/models.py
msgid "Device Tokens"
msgstr "デバイストークン"
#: authentik/endpoints/connectors/agent/models.py
msgid "Enrollment Token"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Enrollment Tokens"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Device authentication token"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Device authentication tokens"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Apple Nonce"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Apple Nonces"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device"
msgstr ""
#: authentik/endpoints/models.py
msgid "Devices"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device User binding"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device User bindings"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device connection"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device connections"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device fact snapshot"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device fact snapshots"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device access group"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device access groups"
msgstr ""
#: authentik/endpoints/tasks.py
msgid "Sync endpoints."
msgstr ""
#: authentik/enterprise/api.py
msgid "Enterprise is required to create/update this object."
msgstr "このオブジェクトの作成/更新にはエンタープライズ契約が必要です。"
@@ -1143,6 +1248,10 @@ msgstr "フロートークン"
msgid "Flow Tokens"
msgstr "フロートークン"
#: authentik/flows/templates/if/flow.html
msgid "Site footer"
msgstr ""
#: authentik/flows/views/executor.py
msgid "Invalid next URL"
msgstr "無効なネクスト URL"
@@ -1522,6 +1631,10 @@ msgstr "アバター"
msgid "Not you?"
msgstr "あなたではありませんか?"
#: authentik/policies/templates/policies/denied.html
msgid "Error"
msgstr ""
#: authentik/policies/templates/policies/denied.html
msgid "Request has been denied."
msgstr "リクエストは拒否されました。"
@@ -1877,14 +1990,6 @@ msgstr "OAuth2 リフレッシュトークン"
msgid "OAuth2 Refresh Tokens"
msgstr "OAuth2 リフレッシュトークン"
#: authentik/providers/oauth2/models.py
msgid "Device Token"
msgstr "デバイストークン"
#: authentik/providers/oauth2/models.py
msgid "Device Tokens"
msgstr "デバイストークン"
#: authentik/providers/oauth2/tasks.py
msgid "Send a back-channel logout request to the registered client"
msgstr "登録されたクライアントにバックチャネルログアウトリクエストを送信"

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

View File

@@ -11,22 +11,22 @@
# Sjors Wortelboer, 2024
# Marc Schmitt, 2025
# Taeke <transifex.net@taeke.eu>, 2025
# Alex Kruidenberg <alexkruidenberg@hotmail.com>, 2025
# Dany Sluijk, 2025
# Alex Kruidenberg <alexkruidenberg@hotmail.com>, 2025
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-10-20 00:11+0000\n"
"POT-Creation-Date: 2025-12-01 15:07+0000\n"
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
"Last-Translator: Dany Sluijk, 2025\n"
"Last-Translator: Alex Kruidenberg <alexkruidenberg@hotmail.com>, 2025\n"
"Language-Team: Dutch (https://app.transifex.com/authentik/teams/119923/nl/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: nl\n"
"Language: nl_NL\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: authentik/admin/models.py
@@ -388,6 +388,12 @@ msgstr "Weergavenaam van de bron."
msgid "Internal source name, used in URLs."
msgstr "Interne naam van de bron, gebruikt in URL's."
#: authentik/core/models.py
msgid ""
"When enabled, this source will be displayed as a prominent button on the "
"login page, instead of a small icon."
msgstr ""
#: authentik/core/models.py
msgid "Flow to use when authenticating existing users."
msgstr "Flow om te gebruiken bij het authenticeren van bestaande gebruikers."
@@ -420,7 +426,7 @@ msgstr "Token"
msgid "Tokens"
msgstr "Tokens"
#: authentik/core/models.py
#: authentik/core/models.py authentik/endpoints/connectors/agent/models.py
msgid "View token's key"
msgstr "Toon token sleutel"
@@ -492,6 +498,7 @@ msgid "Remove temporary users created by SAML Sources."
msgstr ""
#: authentik/core/templates/if/error.html
#: authentik/policies/templates/policies/denied.html
msgid "Go home"
msgstr "Ga naar startpagina"
@@ -523,6 +530,26 @@ msgstr "rsa"
msgid "ecdsa"
msgstr "ecdsa"
#: authentik/crypto/models.py
msgid "RSA"
msgstr ""
#: authentik/crypto/models.py
msgid "Elliptic Curve"
msgstr ""
#: authentik/crypto/models.py
msgid "DSA"
msgstr ""
#: authentik/crypto/models.py
msgid "Ed25519"
msgstr ""
#: authentik/crypto/models.py
msgid "Ed448"
msgstr ""
#: authentik/crypto/models.py
msgid "PEM-encoded Certificate data"
msgstr "PEM-gecodeerde certificaatgegevens"
@@ -547,6 +574,104 @@ msgstr "Certificaat-Sleutelparen"
msgid "Discover, import and update certificates from the filesystem."
msgstr ""
#: authentik/endpoints/connectors/agent/api/connectors.py
msgid "Selected platform not supported"
msgstr ""
#: authentik/endpoints/connectors/agent/api/connectors.py
msgid "Token is expired"
msgstr ""
#: authentik/endpoints/connectors/agent/api/connectors.py
msgid "Invalid token for connector"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Agent Connector"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Agent Connectors"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
#: authentik/providers/oauth2/models.py
msgid "Device Token"
msgstr "Apparaattoken"
#: authentik/endpoints/connectors/agent/models.py
#: authentik/providers/oauth2/models.py
msgid "Device Tokens"
msgstr "Apparaattokens"
#: authentik/endpoints/connectors/agent/models.py
msgid "Enrollment Token"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Enrollment Tokens"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Device authentication token"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Device authentication tokens"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Apple Nonce"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Apple Nonces"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device"
msgstr ""
#: authentik/endpoints/models.py
msgid "Devices"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device User binding"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device User bindings"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device connection"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device connections"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device fact snapshot"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device fact snapshots"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device access group"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device access groups"
msgstr ""
#: authentik/endpoints/tasks.py
msgid "Sync endpoints."
msgstr ""
#: authentik/enterprise/api.py
msgid "Enterprise is required to create/update this object."
msgstr "Enterprise is benodigd om dit object te aan te maken/bijwerken."
@@ -1177,10 +1302,22 @@ msgstr "Flowtoken"
msgid "Flow Tokens"
msgstr "Flowtokens"
#: authentik/flows/templates/if/flow.html
msgid "Site footer"
msgstr ""
#: authentik/flows/views/executor.py
msgid "Invalid next URL"
msgstr "Invalide volgend URL"
#: authentik/lib/sync/outgoing/models.py
msgid "Controls the number of objects synced in a single task"
msgstr ""
#: authentik/lib/sync/outgoing/models.py
msgid "Timeout for synchronization of a single page"
msgstr ""
#: authentik/lib/sync/outgoing/models.py
msgid ""
"When enabled, provider will not modify or create objects in the remote "
@@ -1573,6 +1710,10 @@ msgstr "Avatar van de gebruiker"
msgid "Not you?"
msgstr "Niet jij?"
#: authentik/policies/templates/policies/denied.html
msgid "Error"
msgstr "Fout"
#: authentik/policies/templates/policies/denied.html
msgid "Request has been denied."
msgstr "Verzoek is geweigerd."
@@ -1953,14 +2094,6 @@ msgstr "OAuth2 Verversingstoken"
msgid "OAuth2 Refresh Tokens"
msgstr "OAuth2 Verversingstokens"
#: authentik/providers/oauth2/models.py
msgid "Device Token"
msgstr "Apparaattoken"
#: authentik/providers/oauth2/models.py
msgid "Device Tokens"
msgstr "Apparaattokens"
#: authentik/providers/oauth2/tasks.py
msgid "Send a back-channel logout request to the registered client"
msgstr ""
@@ -2193,6 +2326,17 @@ msgstr ""
msgid "ACS URL"
msgstr "ACS-URL"
#: authentik/providers/saml/models.py
msgid "Service Provider Binding"
msgstr "Serviceproviderbinding"
#: authentik/providers/saml/models.py
msgid ""
"This determines how authentik sends the response back to the Service "
"Provider."
msgstr ""
"Dit bepaalt hoe authentik de reactie terugstuurt naar de serviceprovider."
#: authentik/providers/saml/models.py
msgid ""
"Value of the audience restriction field of the assertion. When left empty, "
@@ -2205,17 +2349,6 @@ msgstr ""
msgid "Also known as EntityID"
msgstr "Ook bekend als EntityID"
#: authentik/providers/saml/models.py
msgid "Service Provider Binding"
msgstr "Serviceproviderbinding"
#: authentik/providers/saml/models.py
msgid ""
"This determines how authentik sends the response back to the Service "
"Provider."
msgstr ""
"Dit bepaalt hoe authentik de reactie terugstuurt naar de serviceprovider."
#: authentik/providers/saml/models.py
msgid "SLS URL"
msgstr ""

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -11,18 +11,18 @@
# Hacklab, 2025
# Victor Haddad, 2025
# Josenivaldo Benito Junior, 2025
# Rafael Mundel, 2025
# Hudson Oliveira, 2025
# Wagner Santos, 2025
# Rafael Mundel, 2025
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-10-20 00:11+0000\n"
"POT-Creation-Date: 2025-12-01 15:07+0000\n"
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
"Last-Translator: Wagner Santos, 2025\n"
"Last-Translator: Rafael Mundel, 2025\n"
"Language-Team: Portuguese (Brazil) (https://app.transifex.com/authentik/teams/119923/pt_BR/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -390,6 +390,12 @@ msgstr "Nome de exibição da fonte."
msgid "Internal source name, used in URLs."
msgstr "Nome da fonte interna, usado em URLs."
#: authentik/core/models.py
msgid ""
"When enabled, this source will be displayed as a prominent button on the "
"login page, instead of a small icon."
msgstr ""
#: authentik/core/models.py
msgid "Flow to use when authenticating existing users."
msgstr "Fluxo a ser usado ao autenticar usuários existentes."
@@ -422,7 +428,7 @@ msgstr "Token"
msgid "Tokens"
msgstr "Tokens"
#: authentik/core/models.py
#: authentik/core/models.py authentik/endpoints/connectors/agent/models.py
msgid "View token's key"
msgstr "Ver chaves do token"
@@ -494,6 +500,7 @@ msgid "Remove temporary users created by SAML Sources."
msgstr "Remover usuários temporários criados por Fontes SAML."
#: authentik/core/templates/if/error.html
#: authentik/policies/templates/policies/denied.html
msgid "Go home"
msgstr "Ir para casa"
@@ -525,6 +532,26 @@ msgstr "rsa"
msgid "ecdsa"
msgstr "ecdsa"
#: authentik/crypto/models.py
msgid "RSA"
msgstr ""
#: authentik/crypto/models.py
msgid "Elliptic Curve"
msgstr ""
#: authentik/crypto/models.py
msgid "DSA"
msgstr ""
#: authentik/crypto/models.py
msgid "Ed25519"
msgstr ""
#: authentik/crypto/models.py
msgid "Ed448"
msgstr ""
#: authentik/crypto/models.py
msgid "PEM-encoded Certificate data"
msgstr "Dados de certificado codificados por PEM"
@@ -549,6 +576,104 @@ msgstr "Pares de chave de certificado"
msgid "Discover, import and update certificates from the filesystem."
msgstr "Descobrir, importar e atualizar certificados do sistema de arquivos."
#: authentik/endpoints/connectors/agent/api/connectors.py
msgid "Selected platform not supported"
msgstr ""
#: authentik/endpoints/connectors/agent/api/connectors.py
msgid "Token is expired"
msgstr ""
#: authentik/endpoints/connectors/agent/api/connectors.py
msgid "Invalid token for connector"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Agent Connector"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Agent Connectors"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
#: authentik/providers/oauth2/models.py
msgid "Device Token"
msgstr "Token do dispositivo"
#: authentik/endpoints/connectors/agent/models.py
#: authentik/providers/oauth2/models.py
msgid "Device Tokens"
msgstr "Tokens de dispositivo"
#: authentik/endpoints/connectors/agent/models.py
msgid "Enrollment Token"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Enrollment Tokens"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Device authentication token"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Device authentication tokens"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Apple Nonce"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Apple Nonces"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device"
msgstr "Dispositivo"
#: authentik/endpoints/models.py
msgid "Devices"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device User binding"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device User bindings"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device connection"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device connections"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device fact snapshot"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device fact snapshots"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device access group"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device access groups"
msgstr ""
#: authentik/endpoints/tasks.py
msgid "Sync endpoints."
msgstr ""
#: authentik/enterprise/api.py
msgid "Enterprise is required to create/update this object."
msgstr "Enterprise é necessário para criar/atualizar esse objeto."
@@ -1201,10 +1326,22 @@ msgstr "Token de Fluxo"
msgid "Flow Tokens"
msgstr "Tokens de Fluxo"
#: authentik/flows/templates/if/flow.html
msgid "Site footer"
msgstr ""
#: authentik/flows/views/executor.py
msgid "Invalid next URL"
msgstr "URL de próximo passo inválida"
#: authentik/lib/sync/outgoing/models.py
msgid "Controls the number of objects synced in a single task"
msgstr ""
#: authentik/lib/sync/outgoing/models.py
msgid "Timeout for synchronization of a single page"
msgstr ""
#: authentik/lib/sync/outgoing/models.py
msgid ""
"When enabled, provider will not modify or create objects in the remote "
@@ -1599,6 +1736,10 @@ msgstr "Avatar do usuário"
msgid "Not you?"
msgstr "Não é você?"
#: authentik/policies/templates/policies/denied.html
msgid "Error"
msgstr "Erro"
#: authentik/policies/templates/policies/denied.html
msgid "Request has been denied."
msgstr "A solicitação foi negada."
@@ -1978,14 +2119,6 @@ msgstr "Token de Atualização OAuth2"
msgid "OAuth2 Refresh Tokens"
msgstr "Tokens de Atualização OAuth2"
#: authentik/providers/oauth2/models.py
msgid "Device Token"
msgstr "Token do dispositivo"
#: authentik/providers/oauth2/models.py
msgid "Device Tokens"
msgstr "Tokens de dispositivo"
#: authentik/providers/oauth2/tasks.py
msgid "Send a back-channel logout request to the registered client"
msgstr ""
@@ -2223,18 +2356,6 @@ msgstr "Falha ao importar Metadata: {messages}"
msgid "ACS URL"
msgstr "URL ACS"
#: authentik/providers/saml/models.py
msgid ""
"Value of the audience restriction field of the assertion. When left empty, "
"no audience restriction will be added."
msgstr ""
"Valor do campo de restrição de público da asserção. Quando deixado em "
"branco, nenhuma restrição de público será adicionada."
#: authentik/providers/saml/models.py
msgid "Also known as EntityID"
msgstr "Também conhecido como EntityID"
#: authentik/providers/saml/models.py
msgid "Service Provider Binding"
msgstr "Vinculação do Provedor de Serviços"
@@ -2247,6 +2368,18 @@ msgstr ""
"Isso determina como o authentik envia a resposta de volta ao provedor de "
"serviços."
#: authentik/providers/saml/models.py
msgid ""
"Value of the audience restriction field of the assertion. When left empty, "
"no audience restriction will be added."
msgstr ""
"Valor do campo de restrição de público da asserção. Quando deixado em "
"branco, nenhuma restrição de público será adicionada."
#: authentik/providers/saml/models.py
msgid "Also known as EntityID"
msgstr "Também conhecido como EntityID"
#: authentik/providers/saml/models.py
msgid "SLS URL"
msgstr "URLs SLS"

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -18,14 +18,14 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-10-20 00:11+0000\n"
"POT-Creation-Date: 2025-12-01 15:07+0000\n"
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
"Last-Translator: Marc Schmitt, 2025\n"
"Language-Team: Russian (https://app.transifex.com/authentik/teams/119923/ru/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: ru\n"
"Language: ru_RU\n"
"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);\n"
#: authentik/admin/models.py
@@ -388,6 +388,12 @@ msgstr "Отображаемое имя источника."
msgid "Internal source name, used in URLs."
msgstr "Внутреннее имя источника, используемое в URL-адресах."
#: authentik/core/models.py
msgid ""
"When enabled, this source will be displayed as a prominent button on the "
"login page, instead of a small icon."
msgstr ""
#: authentik/core/models.py
msgid "Flow to use when authenticating existing users."
msgstr "Поток, используемый при аутентификации существующих пользователей."
@@ -420,7 +426,7 @@ msgstr "Токен"
msgid "Tokens"
msgstr "Токены"
#: authentik/core/models.py
#: authentik/core/models.py authentik/endpoints/connectors/agent/models.py
msgid "View token's key"
msgstr "Просмотр ключа токена "
@@ -493,6 +499,7 @@ msgid "Remove temporary users created by SAML Sources."
msgstr ""
#: authentik/core/templates/if/error.html
#: authentik/policies/templates/policies/denied.html
msgid "Go home"
msgstr "Домой"
@@ -524,6 +531,26 @@ msgstr "rsa"
msgid "ecdsa"
msgstr "ecdsa"
#: authentik/crypto/models.py
msgid "RSA"
msgstr ""
#: authentik/crypto/models.py
msgid "Elliptic Curve"
msgstr ""
#: authentik/crypto/models.py
msgid "DSA"
msgstr ""
#: authentik/crypto/models.py
msgid "Ed25519"
msgstr ""
#: authentik/crypto/models.py
msgid "Ed448"
msgstr ""
#: authentik/crypto/models.py
msgid "PEM-encoded Certificate data"
msgstr "Данные сертификата, закодированные в формате PEM"
@@ -548,6 +575,104 @@ msgstr "Пары сертификат-ключ"
msgid "Discover, import and update certificates from the filesystem."
msgstr ""
#: authentik/endpoints/connectors/agent/api/connectors.py
msgid "Selected platform not supported"
msgstr ""
#: authentik/endpoints/connectors/agent/api/connectors.py
msgid "Token is expired"
msgstr ""
#: authentik/endpoints/connectors/agent/api/connectors.py
msgid "Invalid token for connector"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Agent Connector"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Agent Connectors"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
#: authentik/providers/oauth2/models.py
msgid "Device Token"
msgstr "Токен устройства"
#: authentik/endpoints/connectors/agent/models.py
#: authentik/providers/oauth2/models.py
msgid "Device Tokens"
msgstr "Токены устройства"
#: authentik/endpoints/connectors/agent/models.py
msgid "Enrollment Token"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Enrollment Tokens"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Device authentication token"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Device authentication tokens"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Apple Nonce"
msgstr ""
#: authentik/endpoints/connectors/agent/models.py
msgid "Apple Nonces"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device"
msgstr ""
#: authentik/endpoints/models.py
msgid "Devices"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device User binding"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device User bindings"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device connection"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device connections"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device fact snapshot"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device fact snapshots"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device access group"
msgstr ""
#: authentik/endpoints/models.py
msgid "Device access groups"
msgstr ""
#: authentik/endpoints/tasks.py
msgid "Sync endpoints."
msgstr ""
#: authentik/enterprise/api.py
msgid "Enterprise is required to create/update this object."
msgstr "Для создания/обновления этого объекта требуется Enterprise."
@@ -1176,10 +1301,22 @@ msgstr "Токен потока"
msgid "Flow Tokens"
msgstr "Токены потока"
#: authentik/flows/templates/if/flow.html
msgid "Site footer"
msgstr ""
#: authentik/flows/views/executor.py
msgid "Invalid next URL"
msgstr "Недопустимый следующий URL-адрес"
#: authentik/lib/sync/outgoing/models.py
msgid "Controls the number of objects synced in a single task"
msgstr ""
#: authentik/lib/sync/outgoing/models.py
msgid "Timeout for synchronization of a single page"
msgstr ""
#: authentik/lib/sync/outgoing/models.py
msgid ""
"When enabled, provider will not modify or create objects in the remote "
@@ -1574,6 +1711,10 @@ msgstr "Аватар пользователя"
msgid "Not you?"
msgstr "Не вы?"
#: authentik/policies/templates/policies/denied.html
msgid "Error"
msgstr ""
#: authentik/policies/templates/policies/denied.html
msgid "Request has been denied."
msgstr "В произведении запроса было отказано."
@@ -1952,14 +2093,6 @@ msgstr "OAuth2 Refresh токен"
msgid "OAuth2 Refresh Tokens"
msgstr "OAuth2 Refresh токены"
#: authentik/providers/oauth2/models.py
msgid "Device Token"
msgstr "Токен устройства"
#: authentik/providers/oauth2/models.py
msgid "Device Tokens"
msgstr "Токены устройства"
#: authentik/providers/oauth2/tasks.py
msgid "Send a back-channel logout request to the registered client"
msgstr ""
@@ -2193,6 +2326,17 @@ msgstr "Не удалось импортировать метаданные: {me
msgid "ACS URL"
msgstr "URL-адрес ACS"
#: authentik/providers/saml/models.py
msgid "Service Provider Binding"
msgstr "Привязка провайдера услуг"
#: authentik/providers/saml/models.py
msgid ""
"This determines how authentik sends the response back to the Service "
"Provider."
msgstr ""
"Это определяет, как authentik отправляет ответ обратно провайдеру услуг."
#: authentik/providers/saml/models.py
msgid ""
"Value of the audience restriction field of the assertion. When left empty, "
@@ -2205,17 +2349,6 @@ msgstr ""
msgid "Also known as EntityID"
msgstr "Также известен как EntityID"
#: authentik/providers/saml/models.py
msgid "Service Provider Binding"
msgstr "Привязка провайдера услуг"
#: authentik/providers/saml/models.py
msgid ""
"This determines how authentik sends the response back to the Service "
"Provider."
msgstr ""
"Это определяет, как authentik отправляет ответ обратно провайдеру услуг."
#: authentik/providers/saml/models.py
msgid "SLS URL"
msgstr ""

Binary file not shown.

Binary file not shown.

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