Compare commits

..

101 Commits

Author SHA1 Message Date
James Elliott 53d3cdb271
Merge remote-tracking branch 'origin/master' into feat-settings-ui
# Conflicts:
#	web/package.json
#	web/pnpm-lock.yaml
2023-05-30 09:15:20 +10:00
James Elliott b4083df061
Merge remote-tracking branch 'origin/master' into feat-settings-ui 2023-05-20 10:26:42 +10:00
James Elliott 1334e9e007
Merge remote-tracking branch 'origin/master' into feat-settings-ui 2023-05-20 10:26:23 +10:00
James Elliott 91ebdd6bc6
docs: update webauthn docs
Signed-off-by: James Elliott <james-d-elliott@users.noreply.github.com>
2023-05-20 10:26:11 +10:00
James Elliott ecbd6511e1
Merge remote-tracking branch 'origin/master' into feat-settings-ui
# Conflicts:
#	web/package.json
#	web/pnpm-lock.yaml
2023-05-19 22:53:24 +10:00
James Elliott 5e9fe907c8
Merge remote-tracking branch 'origin/master' into feat-settings-ui
# Conflicts:
#	web/package.json
#	web/pnpm-lock.yaml
2023-05-05 22:49:23 +10:00
James Elliott bb563f4baa
Merge remote-tracking branch 'origin/master' into feat-settings-ui
# Conflicts:
#	web/package.json
#	web/pnpm-lock.yaml
2023-05-05 14:34:49 +10:00
James Elliott 5faffbe46b
docs: add alert for configuration sections
Signed-off-by: James Elliott <james-d-elliott@users.noreply.github.com>
2023-05-04 21:21:03 +10:00
James Elliott 12443920e6
Merge remote-tracking branch 'origin/master' into feat-settings-ui
Signed-off-by: James Elliott <james-d-elliott@users.noreply.github.com>
2023-05-04 20:58:34 +10:00
James Elliott 873749a28f
Merge remote-tracking branch 'origin/master' into feat-settings-ui
# Conflicts:
#	web/package.json
#	web/pnpm-lock.yaml
2023-04-21 21:32:32 +10:00
James Elliott 79973414e4
fix: misc translations
Signed-off-by: James Elliott <james-d-elliott@users.noreply.github.com>
2023-04-16 11:31:19 +10:00
James Elliott 7c69152a86
fix: null transports
Signed-off-by: James Elliott <james-d-elliott@users.noreply.github.com>
2023-04-16 11:23:25 +10:00
James Elliott c3e785872d
Merge remote-tracking branch 'origin/master' into feat-settings-ui 2023-04-16 07:59:08 +10:00
James Elliott a8a8089f33
fix: missing files
Signed-off-by: James Elliott <james-d-elliott@users.noreply.github.com>
2023-04-15 16:21:42 +10:00
James Elliott 29ddc73012
Merge remote-tracking branch 'origin/master' into feat-settings-ui
# Conflicts:
#	internal/suites/scenario_backend_protection_test.go
2023-04-15 15:05:09 +10:00
James Elliott e464295c8b
fix: more webauthn consistency fixes
Signed-off-by: James Elliott <james-d-elliott@users.noreply.github.com>
2023-04-15 11:00:52 +10:00
James Elliott 1341ef79d6
Merge remote-tracking branch 'origin/master' into feat-settings-ui 2023-04-15 10:52:49 +10:00
James Elliott 53668e0c60
feat: backport changes
Signed-off-by: James Elliott <james-d-elliott@users.noreply.github.com>
2023-04-15 10:03:19 +10:00
James Elliott 6c89ee1f9c
Merge orgin/master into feat-settings-ui
Signed-off-by: James Elliott <james-d-elliott@users.noreply.github.com>
2023-04-15 03:12:55 +10:00
James Elliott 86b525ce21
Merge remote tracking branch origin/master into feat-settings-ui
Signed-off-by: James Elliott <james-d-elliott@users.noreply.github.com>
2023-04-15 02:14:23 +10:00
James Elliott d97c0eb0ea
fix: remove gen files
Signed-off-by: James Elliott <james-d-elliott@users.noreply.github.com>
2023-04-15 01:28:47 +10:00
James Elliott f549afd480
Merge remote-tracking branch 'origin/master' into feat-settings-ui
# Conflicts:
#	internal/authentication/ldap_client_mock.go
#	internal/authentication/types.go
2023-04-14 21:43:04 +10:00
James Elliott f35e49a1fd
fix: misc
Signed-off-by: James Elliott <james-d-elliott@users.noreply.github.com>
2023-04-14 21:11:03 +10:00
James Elliott 774f64a932
Merge remote tracking branch 'origin/master' into feat-settings-ui
Signed-off-by: James Elliott <james-d-elliott@users.noreply.github.com>
2023-04-14 20:58:49 +10:00
James Elliott f3d447d76a
Merge remote-tracking branch 'origin/master' into feat-settings-ui
# Conflicts:
#	web/package.json
#	web/pnpm-lock.yaml
2023-04-12 14:43:32 +10:00
James Elliott 51e1f41620
Merge remote-tracking branch 'origin/master' into feat-settings-ui
Signed-off-by: James Elliott <james-d-elliott@users.noreply.github.com>
2023-04-11 22:21:00 +10:00
James Elliott 7fdcc351d4
Merge remote-tracking branch 'origin/master' into feat-settings-ui
Signed-off-by: James Elliott <james-d-elliott@users.noreply.github.com>

# Conflicts:
#	internal/handlers/handler_register_webauthn.go
#	internal/handlers/webauthn.go
#	internal/handlers/webauthn_test.go
#	internal/mocks/storage.go
#	internal/model/webauthn.go
#	internal/storage/provider.go
#	internal/storage/sql_provider.go
#	web/package.json
#	web/pnpm-lock.yaml
#	web/src/layouts/LoginLayout.tsx
2023-04-11 21:34:45 +10:00
James Elliott 928df8a698
Merge remote-tracking branch 'origin/master' into feat-oidc-auth-mode
Signed-off-by: James Elliott <james-d-elliott@users.noreply.github.com>

# Conflicts:
#	internal/configuration/validator/const.go
2023-04-09 13:19:29 +10:00
James Elliott 904b659fcb
Merge remote-tracking branch 'origin/master' into feat-settings-ui
Signed-off-by: James Elliott <james-d-elliott@users.noreply.github.com>
2023-04-02 16:12:08 +10:00
James Elliott 54509bcc60
build(deps): remove @types/qrcode.react
Signed-off-by: James Elliott <james-d-elliott@users.noreply.github.com>
2023-04-02 14:21:59 +10:00
James Elliott 1ba4f705f0
Merge remote-tracking branch 'origin/master' into feat-settings-ui
Signed-off-by: James Elliott <james-d-elliott@users.noreply.github.com>
2023-04-02 14:14:29 +10:00
James Elliott 4f46514fdf
Merge remote-tracking branch 'origin/master' into feat-settings-ui
# Conflicts:
#	web/package.json
#	web/pnpm-lock.yaml
2023-03-19 08:09:17 +11:00
James Elliott e584e0c4a3
Merge remote-tracking branch 'origin/master' into feat-settings-ui 2023-03-12 11:41:31 +11:00
James Elliott 7ef1ba23df
Merge remote-tracking branch 'origin/master' into feat-settings-ui
# Conflicts:
#	docs/package.json
#	docs/pnpm-lock.yaml
#	internal/configuration/validator/identity_providers_test.go
#	web/package.json
#	web/pnpm-lock.yaml
2023-03-12 00:09:42 +11:00
James Elliott b6883a337f
Merge origin/master into feat-settings-ui 2023-03-07 10:12:49 +11:00
James Elliott e64661af3f
Merge remote-tracking branch 'origin/master' into feat-settings-ui 2023-02-28 20:40:51 +11:00
James Elliott 8b8d6ce417
Merge remote-tracking branch origin/master into feat-settings-ui 2023-02-28 20:07:42 +11:00
James Elliott e6ef74fd8e
Merge remote-tracking branch 'origin/master' into feat-settings-ui
# Conflicts:
#	go.mod
#	web/package.json
#	web/pnpm-lock.yaml
2023-02-25 13:46:06 +11:00
James Elliott f2e40a72e7
build(deps): bump 2023-02-25 13:32:32 +11:00
James Elliott ea2350f0e4
refactor: down migrations 2023-02-19 14:59:45 +11:00
James Elliott a3d7212f23
test: fix test 2023-02-19 14:08:18 +11:00
James Elliott 257bd2a25a
test: fix test 2023-02-19 12:48:11 +11:00
James Elliott 3e53ae7b2e
test: fix test 2023-02-19 12:11:33 +11:00
James Elliott a6cc022e5c
Merge remote tracking branch origin/master into feat-settings-ui 2023-02-19 11:53:11 +11:00
James Elliott a13a3c45f2
fix: encoding 2023-02-19 11:48:35 +11:00
James Elliott e5cdb175b4
feat: cred props 2023-02-18 15:36:58 +11:00
James Elliott 5be5de02d8
feat: webauthn users 2023-02-17 06:40:40 +11:00
James Elliott e84ca4956a
refactor: sql updates 2023-02-14 23:35:15 +11:00
James Elliott 236fcb1e37
Merge remote-tracking branch 'origin/master' into feat-settings-ui 2023-02-13 18:53:54 +11:00
James Elliott ee56740f46
Merge remote-tracking branch 'origin/master' into feat-settings-ui 2023-02-13 06:33:46 +11:00
James Elliott 130a28a430
fix: misc 2023-02-12 23:57:43 +11:00
James Elliott 526dd8347d
fix: misc 2023-02-12 23:12:31 +11:00
James Elliott ba1ed1252c
fix: tests 2023-02-12 22:11:00 +11:00
James Elliott 515309c10e
feat: translate all the things 2023-02-12 21:57:45 +11:00
James Elliott 7e56cf2d15
test(suites): fix postgres 2023-02-12 12:48:39 +11:00
James Elliott d0160edc70
test(suites): fix standalone 2023-02-12 12:39:17 +11:00
James Elliott be21d73c72
fix: sql migration 2023-02-12 12:25:15 +11:00
James Elliott 40e247fcee
Merge branch 'master' into feat-settings-ui 2023-02-12 03:02:26 +11:00
James Elliott f920ef9dd9
build: update lockfiles 2023-02-12 03:01:51 +11:00
James Elliott 62fa7a6244
Merge remote-tracking branch 'origin/master' into feat-settings-ui
# Conflicts:
#	web/pnpm-lock.yaml
2023-02-12 02:51:34 +11:00
James Elliott 3b6f5482b8
fix: multi-cookie domain webauthn 2023-02-12 02:47:03 +11:00
James Elliott 8c057f65a5
Merge remote-tracking branch 'origin/master' into feat-settings-ui 2023-02-11 21:53:34 +11:00
James Elliott 852dc808bd
Merge remote-tracking branch 'origin/master' into feat-settings-ui 2023-02-11 14:13:18 +11:00
James Elliott 1f1210c6ac
Merge remote-tracking branch 'origin/master' into feat-settings-ui 2023-02-08 13:52:07 +11:00
James Elliott dc334234a8
build: fix transform ignores 2023-02-06 13:13:47 +11:00
James Elliott 9e5aa1c1a9
Merge remote-tracking branch 'origin/master' into feat-settings-ui
# Conflicts:
#	web/package.json
#	web/pnpm-lock.yaml
2023-02-05 20:19:40 +11:00
James Elliott d7be1c1359
refactor: reduce complexity 2023-02-01 22:10:38 +11:00
James Elliott 3af20a7daf
build(deps): use @simplewebauthn/browser 2023-01-30 16:37:53 +11:00
James Elliott a36c45f1e1
Merge remote-tracking branch 'origin/master' into feat-settings-ui 2023-01-30 09:52:33 +11:00
James Elliott 4bed5d2461
Merge branch 'master' into feat-settings-ui 2023-01-27 11:27:12 +11:00
James Elliott 7d17c39c52
Merge origin/master into feat-settings-ui 2023-01-25 22:11:41 +11:00
James Elliott 25244c42f1
fix: unused import 2023-01-21 14:48:33 +11:00
James Elliott bd279900ca
Merge remote-tracking branch 'origin/master' into feat-settings-ui 2023-01-20 17:56:06 +11:00
James Elliott 49d421e910
Merge remote-tracking branch 'origin/master' into feat-settings-ui
# Conflicts:
#	api/openapi.yml
#	web/src/views/DeviceRegistration/RegisterWebauthn.tsx
#	web/src/views/LoginPortal/SecondFactor/WebauthnMethod.tsx
2023-01-07 11:50:19 +11:00
James Elliott 917ac89e38
refactor: 2fa api 2023-01-01 22:16:28 +11:00
James Elliott dd781ffc51
refactor: adjust settings components 2022-12-31 18:27:43 +11:00
James Elliott 4239db6171
refactor: adjust settings components 2022-12-31 16:28:46 +11:00
James Elliott f2ee86472d
revert: 2fa skip 2022-12-30 23:51:52 +11:00
James Elliott 0e2770e72d
Merge remote-tracking branch 'origin/master' into feat-settings-ui 2022-12-27 20:05:02 +11:00
James Elliott 4a2fd3dea7
Merge remote-tracking branch 'origin/master' into feat-settings-ui 2022-12-23 16:08:47 +11:00
James Elliott a186dca3bf
Merge remote-tracking branch 'origin/master' into feat-settings-ui
# Conflicts:
#	api/openapi.yml
2022-12-17 15:47:34 +11:00
James Elliott 67381b1318
fix: no webauthn devices doesn't display correctly (#4537)
* fix: no webauthn devices doesn't display correctly

* refactor: factorize
2022-12-12 12:21:27 +11:00
Stephen Kent 326ed60a65
refactor: retitle settings appbar to authelia settings (#4454) 2022-12-03 16:55:16 +11:00
James Elliott 133f1626ab
Merge remote tracking branch 'origin/master' into feat-settings-ui 2022-11-30 10:00:33 +11:00
Stephen Kent d6f1365d42
feat: rework webauthn devices list ui (#4435)
* feat: add loading skeleton to webauthn devices list in settings ui

* refactor: move webauthn device index knowledge out of webauthndeviceitem

* feat: implement webauthn device delete confirmation

* fix: don't unset deleting idx for dialog on webauthn device delete

* feat: implement webauthn device rename with dialog

* refactor: remove `@root` from import paths

* refactor: remove `@root` from import paths

* feat: rework webauthn devices list ui
2022-11-28 18:39:08 +11:00
Stephen Kent 33520daa10
feat: implement webauthn device rename with dialog (#4427)
* feat: add loading skeleton to webauthn devices list in settings ui

* refactor: move webauthn device index knowledge out of webauthndeviceitem

* feat: implement webauthn device delete confirmation

* fix: don't unset deleting idx for dialog on webauthn device delete

* feat: implement webauthn device rename with dialog

* refactor: remove `@root` from import paths

* refactor: remove `@root` from import paths
2022-11-27 11:08:13 +11:00
Stephen Kent 24d947624b
feat: implement webauthn device delete confirmation (#4426)
* refactor: move webauthn device index knowledge out of webauthndeviceitem

* feat: implement webauthn device delete confirmation

* fix: don't unset deleting idx for dialog on webauthn device delete

* refactor: remove `@root` from import paths
2022-11-27 10:46:24 +11:00
Stephen Kent b842a22236
feat: add loading skeleton to webauthn devices list in settings ui (#4406) 2022-11-26 17:22:40 +11:00
Stephen Kent 2967500401
feat: hide empty webauthn devices table when there are no devices (#4405) 2022-11-19 19:31:08 +11:00
James Elliott 6f8b6adfb5
Merge remote-tracking branch 'origin/master' into feat-settings-ui
# Conflicts:
#	internal/model/webauthn.go
2022-11-19 17:43:59 +11:00
James Elliott 5d1b840e2b
refactor: merge master and fix missing rebinds (#4404)
* build(deps): update module github.com/jackc/pgx/v5 to v5.1.0 (#4365)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* docs: add smkent as a contributor for code, design, and ideas (#4367)

* update README.md

* update .all-contributorsrc

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>

* build(deps): update module github.com/ory/fosite to v0.43.0 (#4269)

This updates fosite and refactors our usage out of compose.

* refactor(cmd): restrict bootstrap pnpm tasks to dev environment (#4370)

* build(deps): update alpine docker tag to v3.16.3 (#4362)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* build(deps): update module github.com/ory/x to v0.0.514 (#4368)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* refactor: sql formatting (#4371)

* refactor: sql spacing

* refactor editor config

* docs: clarify cloudflare docs (#4373)

* build(deps): update dependency @types/react-dom to v18.0.9 (#4379)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* build(deps): update typescript-eslint monorepo to v5.43.0 (#4380)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* build(deps): update dependency @types/jest to v29.2.3 (#4381)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* build(deps): update dependency esbuild to v0.15.14 (#4383)

* build(deps): update material-ui monorepo to v5.10.14 (#4385)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* build(deps): update dependency vite to v3.2.4 (#4386)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* build(deps): update font awesome to v6.2.1 (#4389)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* build(deps): update dependency typescript to v4.9.3 (#4390)

* docs: adjust issue templates (#4391)

* docs: adjust issue templates

* docs: adjust wording

* build(deps): update dependency jest-watch-typeahead to v2.2.1 (#4392)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* build(deps): update dependency i18next to v22.0.6 (#4395)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* build(deps): update github.com/duosecurity/duo_api_golang digest to 091daa0 (#4396)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* build(deps): update traefik docker tag to v2.9.5 (#4398)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* build(deps): update module github.com/jackc/pgx/v5 to v5.1.1 (#4400)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* build(deps): update mariadb docker tag to v10.10.2 (#4399)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* build(deps): update dependency eslint-plugin-react to v7.31.11 (#4401)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* build(deps): update dependency eslint to v8.28.0 (#4402)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* fix(storage): schema inconsistency (#4262)

* fix: missing pg rebinds

* fix: refactoring issues

* fix: refactoring issues

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
Co-authored-by: Amir Zarrinkafsh <nightah@me.com>
2022-11-19 17:42:03 +11:00
Stephen Kent 2584e3d328
feat: move webauthn device enrollment flow to new settings ui (#4376)
The current 2-factor authentication method registration flow requires
email verification for both initial 2FA registration, and 2FA
re-registration even if the user is already logged in with 2FA.

This change removes email ID verification for users who are already
logged in with 2-factor authentication. Users who have only completed
first factor authentication (password) are still required to complete
email ID verification.
2022-11-19 16:48:47 +11:00
James Elliott ff26673659
feat: better menu matching and overview page (#4384) 2022-11-15 19:26:09 +11:00
James Elliott 0f8de33f2f
feat: settings router (#4377) 2022-11-14 22:13:10 +11:00
Stephen Kent dcd65515fc
fix: add toolbar below appbar in settings page to avoid content overlap (#4375) 2022-11-14 16:38:06 +11:00
James Elliott 164fc5e80d
feat: settings i18n [skip test] (#4372) 2022-11-14 14:49:34 +11:00
James Elliott 1a1b85489c
feat: settings ui device details (#4369)
This adds details to the settings ui.
2022-11-14 13:19:18 +11:00
Stephen Kent 92b3a5804b
feat: provide webauthn device description from frontend on registration (#4363) 2022-11-13 18:59:21 +11:00
James Elliott bbc9e6422e
fix: lint 2022-11-13 10:18:57 +11:00
James Elliott 9b66bb4fe2
Merge remote-tracking branch 'origin/master' into feat-settings-ui
# Conflicts:
#	internal/model/webauthn.go
2022-11-13 09:19:22 +11:00
Clément Michaud a69ba22f46 feat: implement a ui for supporting multiple u2f devices 2022-10-30 09:52:49 +01:00
211 changed files with 6141 additions and 7910 deletions

View File

@ -7,4 +7,3 @@
!entrypoint.sh !entrypoint.sh
!healthcheck.sh !healthcheck.sh
!.healthcheck.env !.healthcheck.env
!dist/public_html/

View File

@ -29,9 +29,9 @@ body:
id: 'version' id: 'version'
attributes: attributes:
label: | label: |
Version Version
description: | description: |
What version(s) of Authelia can you reproduce this bug on? What version(s) of Authelia can you reproduce this bug on?
multiple: true multiple: true
options: options:
- 'v4.37.5' - 'v4.37.5'
@ -76,9 +76,9 @@ body:
id: 'deployment' id: 'deployment'
attributes: attributes:
label: | label: |
Deployment Method Deployment Method
description: | description: |
How are you deploying Authelia? How are you deploying Authelia?
options: options:
- 'Docker' - 'Docker'
- 'Kubernetes' - 'Kubernetes'
@ -90,9 +90,9 @@ body:
id: 'proxy' id: 'proxy'
attributes: attributes:
label: | label: |
Reverse Proxy Reverse Proxy
description: | description: |
What reverse proxy are you using? What reverse proxy are you using?
options: options:
- 'Caddy' - 'Caddy'
- 'Traefik' - 'Traefik'
@ -108,9 +108,9 @@ body:
id: 'proxy-version' id: 'proxy-version'
attributes: attributes:
label: | label: |
Reverse Proxy Version Reverse Proxy Version
description: | description: |
What is the version of your reverse proxy? What is the version of your reverse proxy?
placeholder: 'x.x.x' placeholder: 'x.x.x'
validations: validations:
required: false required: false
@ -118,36 +118,36 @@ body:
id: 'description' id: 'description'
attributes: attributes:
label: | label: |
Description Description
description: | description: |
Describe the bug. Describe the bug.
validations: validations:
required: true required: true
- type: 'textarea' - type: 'textarea'
id: 'reproduction' id: 'reproduction'
attributes: attributes:
label: | label: |
Reproduction Reproduction
description: | description: |
Describe how we can reproduce this issue. This should be step by step and should include detailed and specific information. Abstract or generic information should be avoided. For example this should include specific application names and versions if relevant. Reproducing the issue is important so we can verify it exists, add relevant tests, and verify it is solved. Describe how we can reproduce this issue. This should be step by step and should include detailed and specific information. Abstract or generic information should be avoided. For example this should include specific application names and versions if relevant. Reproducing the issue is important so we can verify it exists, add relevant tests, and verify it is solved.
validations: validations:
required: true required: true
- type: 'textarea' - type: 'textarea'
id: 'expectations' id: 'expectations'
attributes: attributes:
label: | label: |
Expectations Expectations
description: | description: |
Describe the desired or expected results. Describe the desired or expected results.
validations: validations:
required: false required: false
- type: 'textarea' - type: 'textarea'
id: 'configuration' id: 'configuration'
attributes: attributes:
label: | label: |
Configuration (Authelia) Configuration (Authelia)
description: | description: |
Provide a complete configuration file (the template will automatically put this content in a code block). Provide a complete configuration file (the template will automatically put this content in a code block).
render: 'yaml' render: 'yaml'
validations: validations:
required: false required: false
@ -155,7 +155,7 @@ body:
id: 'logs' id: 'logs'
attributes: attributes:
label: | label: |
Logs (Authelia) Logs (Authelia)
description: | description: |
Provide complete logs with the log level set to debug or trace. Complete means from application start until the issue occurring. This is clearly explained in the [Logs](https://www.authelia.com/r/troubleshooting#logs) section of the troubleshooting guide. Provide complete logs with the log level set to debug or trace. Complete means from application start until the issue occurring. This is clearly explained in the [Logs](https://www.authelia.com/r/troubleshooting#logs) section of the troubleshooting guide.
@ -167,9 +167,9 @@ body:
id: 'logs-other' id: 'logs-other'
attributes: attributes:
label: | label: |
Logs (Proxy / Application) Logs (Proxy / Application)
description: | description: |
Provide complete debug logs for the affected proxy and/or application if available and relevant (the template will automatically put this content in a code block). Provide complete debug logs for the affected proxy and/or application if available and relevant (the template will automatically put this content in a code block).
render: 'shell' render: 'shell'
validations: validations:
required: false required: false
@ -177,38 +177,38 @@ body:
id: 'documentation' id: 'documentation'
attributes: attributes:
label: | label: |
Documentation Documentation
description: | description: |
Provide any relevant specification or other documentation if applicable. Provide any relevant specification or other documentation if applicable.
validations: validations:
required: false required: false
- type: 'checkboxes' - type: 'checkboxes'
id: 'checklist' id: 'checklist'
attributes: attributes:
label: | label: |
Pre-Submission Checklist Pre-Submission Checklist
description: | description: |
By submitting this issue confirm all of the following. By submitting this issue confirm all of the following.
options: options:
- label: | - label: |
I agree to follow the [Code of Conduct](http://www.authelia.com/code-of-conduct) I agree to follow the [Code of Conduct](http://www.authelia.com/code-of-conduct)
required: true required: true
- label: | - label: |
This is a bug report and not a support request This is a bug report and not a support request
required: true required: true
- label: | - label: |
I have read the security policy and this bug report is not a security issue or security related issue I have read the security policy and this bug report is not a security issue or security related issue
required: true required: true
- label: | - label: |
I have either included the complete configuration file or I am sure it's unrelated to the configuration I have either included the complete configuration file or I am sure it's unrelated to the configuration
required: true required: true
- label: | - label: |
I have provided all of the required information in full with the only alteration being reasonable sanitization in accordance with the [Troubleshooting Sanitization](https://www.authelia.com/r/sanitize) reference guide I have provided all of the required information in full with the only alteration being reasonable sanitization in accordance with the [Troubleshooting Sanitization](https://www.authelia.com/r/sanitize) reference guide
required: true required: true
- label: | - label: |
I have checked for related proxy or application logs and included them if available I have checked for related proxy or application logs and included them if available
required: true required: true
- label: | - label: |
I have checked for related issues and checked the documentation I have checked for related issues and checked the documentation
required: true required: true
... ...

View File

@ -20,50 +20,50 @@ body:
id: 'description' id: 'description'
attributes: attributes:
label: | label: |
Description Description
description: | description: |
Describe the feature Describe the feature
validations: validations:
required: true required: true
- type: 'textarea' - type: 'textarea'
id: 'use-case' id: 'use-case'
attributes: attributes:
label: | label: |
Use Case Use Case
description: | description: |
Provide a use case Provide a use case
validations: validations:
required: true required: true
- type: 'textarea' - type: 'textarea'
id: 'details' id: 'details'
attributes: attributes:
label: | label: |
Details Details
description: | description: |
Describe the feature in detail Describe the feature in detail
validations: validations:
required: false required: false
- type: 'textarea' - type: 'textarea'
id: 'documentation' id: 'documentation'
attributes: attributes:
label: | label: |
Documentation Documentation
description: | description: |
Provide any relevant specification or other documentation if applicable Provide any relevant specification or other documentation if applicable
validations: validations:
required: false required: false
- type: 'checkboxes' - type: 'checkboxes'
id: 'checklist' id: 'checklist'
attributes: attributes:
label: | label: |
Pre-Submission Checklist Pre-Submission Checklist
description: | description: |
By submitting this issue confirm all of the following By submitting this issue confirm all of the following
options: options:
- label: | - label: |
I agree to follow the [Code of Conduct](http://www.authelia.com/code-of-conduct) I agree to follow the [Code of Conduct](http://www.authelia.com/code-of-conduct)
required: true required: true
- label: | - label: |
I have checked for related issues and checked the documentation I have checked for related issues and checked the documentation
required: true required: true
... ...

View File

@ -1,7 +1,7 @@
# =================================== # ===================================
# ===== Authelia official image ===== # ===== Authelia official image =====
# =================================== # ===================================
FROM alpine:3.18.2 FROM alpine:3.18.0
ARG TARGETOS ARG TARGETOS
ARG TARGETARCH ARG TARGETARCH

View File

@ -15,7 +15,7 @@ RUN yarn global add pnpm && \
# ======================================= # =======================================
# ===== Build image for the backend ===== # ===== Build image for the backend =====
# ======================================= # =======================================
FROM golang:1.20.5-alpine AS builder-backend FROM golang:1.20.4-alpine AS builder-backend
WORKDIR /go/src/app WORKDIR /go/src/app
@ -46,7 +46,7 @@ RUN \
# =================================== # ===================================
# ===== Authelia official image ===== # ===== Authelia official image =====
# =================================== # ===================================
FROM alpine:3.18.2 FROM alpine:3.18.0
RUN apk --no-cache add ca-certificates tzdata RUN apk --no-cache add ca-certificates tzdata

View File

@ -13,7 +13,7 @@ RUN yarn install --frozen-lockfile && yarn build
# ======================================= # =======================================
# ===== Build image for the backend ===== # ===== Build image for the backend =====
# ======================================= # =======================================
FROM golang:1.20.5-alpine AS builder-backend FROM golang:1.20.4-alpine AS builder-backend
WORKDIR /go/src/app WORKDIR /go/src/app
@ -43,7 +43,7 @@ RUN \
# =================================== # ===================================
# ===== Authelia official image ===== # ===== Authelia official image =====
# =================================== # ===================================
FROM alpine:3.18.2 FROM alpine:3.18.0
WORKDIR /app WORKDIR /app

View File

@ -1,95 +0,0 @@
# Ausführen
Um die Anwendung lokal auszuführen, können die folgenden Befehle verwendet werden.
```
export GOPATH=/tmp
source bootstrap.sh
authelia-scripts suites setup Standalone
```
Nun sollte der "Haupt-Enpunkt" unter `https://home.example.com:8080` und die API unter `https://authelia.example.com:9091` erreichbar sein. Achtung: es wird ein selbstsigniertes Zertifikat verwendet!
Mithilfe der Hot-Reload kann jetzt gecoded werden.
---
Nach der Entwicklung kann die Testumgebung durch den folgenden Befehl wieder zurückgesetzt werden.
```
go run ./cmd/authelia-scripts/ suites teardown Standalone
```
## Benutzerdefinierte Zertifikate
Um ein benutzerdefiniertes Zertifikat für die Ausführung zu verwenden, muss die Datai `public.backend.crt` und `private.bakend.pem` unter [diesem](/internal/suites/common/pki/) Verzeichnis abgeändert werden.
Um die Gültigkeit zu testen, kann der folgendende Befehl ausgeführt werden.
```
curl https://auth.rpjosh.de:9091 --connect-to 'auth.rpjosh.de:9091:authelia.example.com:9091'
```
## Externe erreichbarkeit
Im aktuellen Zustand sind die Endpunkte nur unter den Docker internen IP-Adressen erreichbar. Daher muss noch ein NAT Regel angelegt werden.
```
ip=$(ping -c 1 authelia.example.com | gawk -F'[()]' '/PING/{print $2}')
sudo iptables -t nat -A PREROUTING -p tcp --dport 9091 -d 192.168.0.15 -j DNAT --to-destination 192.168.240.50:9091 -m comment --comment "Authelia-Test"
sudo iptables -t nat -A PREROUTING -p tcp --dport 9092 -d 192.168.0.15 -j DNAT --to-destination 192.168.240.50:9092 -m comment --comment "Authelia-Test"
sudo iptables -t nat -I OUTPUT -p tcp -o lo --dport 9091 -j DNAT --to-destination 192.168.240.50:9091
```
# Customizations
Für das Starten des *gRPC* Servers müssen die folgenden Abhängigkeiten installiert werden.
```
go get github.com/envoyproxy/go-control-plane
go get github.com/envoyproxy/go-control-plane/envoy/config/core/v3
go get github.com/gogo/googleapis/google/rpc
go get google.golang.org/grpc
```
## Konfiguration ändern
Wenn die Konfiguration geändert wurde, müssen die Keys zur Validierung wieder erneut gebaut werden.
```
go run ./cmd/authelia-gen code keys
```
## Mocks abgeändert
Wenn interfaces von den Mocks geändert werden, muss folgendes wieder ausgeführt werden:
```
export PATH=$PATH:$(go env GOPATH)/bin
go generate ./...
```
## Bauen
Um ein Docker Image für authelia zu bauen, müssen die folgenden Befehle ausgeführt werden.
```sh
# Dieser Befehle funktionieren aktuell nicht
authelia-scripts docker build
authelia-scripts build
# => Manuell bauen
export CC=musl-gcc
authelia-scripts build
cp -r dist/public_html internal/server/
go build -buildmode=pie -ldflags "-linkmode=external -s -w" -trimpath -buildmode=pie -o authelia ./cmd/authelia
mv authelia authelia-linux-amd64-musl
# Build docker image
docker build --tag git.rpjosh.de/rpjosh/authelia/authelia:4.38.0-dev .
docker push git.rpjosh.de/rpjosh/authelia/authelia:4.38.0-dev
# Cleanup
rm -rf internal/server/public_html/ ./authelia-linux-amd64-musl
```
# gRCP
Um einen gRCP Endpunkt nutzen zu können, brauch mein eine *.proto* Datei. Für Envoy sieht diese wie in [dieser Datei](/ext-auth.proto) folgendermaßen aus.

View File

@ -849,6 +849,45 @@ paths:
$ref: '#/components/schemas/middlewares.OkResponse' $ref: '#/components/schemas/middlewares.OkResponse'
security: security:
- authelia_auth: [] - authelia_auth: []
/api/secondfactor/webauthn/devices/{deviceID}:
delete:
tags:
- Second Factor
summary: WebAuthn Device Deletion
description: This endpoint deletes the specified WebAuthn credential.
responses:
"200":
description: Successful Operation
content:
application/json:
schema:
$ref: '#/components/schemas/middlewares.OkResponse'
security:
- authelia_auth: []
parameters:
- $ref: '#/components/parameters/deviceID'
put:
tags:
- Second Factor
summary: WebAuthn Device Update
description: This endpoint updates the description of the specified WebAuthn credential.
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/webauthn.DeviceUpdateRequest'
responses:
"200":
description: Successful Operation
content:
application/json:
schema:
$ref: '#/components/schemas/middlewares.OkResponse'
security:
- authelia_auth: []
parameters:
- $ref: '#/components/parameters/deviceID'
{{- end }} {{- end }}
{{- if .Duo }} {{- if .Duo }}
/api/secondfactor/duo: /api/secondfactor/duo:
@ -1433,6 +1472,13 @@ paths:
{{- end }} {{- end }}
components: components:
parameters: parameters:
deviceID:
in: path
name: deviceID
schema:
type: integer
required: true
description: Numeric WebAuthn Device ID
originalMethodParam: originalMethodParam:
name: X-Original-Method name: X-Original-Method
in: header in: header
@ -1917,23 +1963,28 @@ components:
type: string type: string
format: byte format: byte
webauthn.CredentialAttestationResponse: webauthn.CredentialAttestationResponse:
allOf: type: object
- $ref: '#/components/schemas/webauthn.PublicKeyCredential' properties:
- type: object credential:
properties: allOf:
clientExtensionResults: - $ref: '#/components/schemas/webauthn.PublicKeyCredential'
type: object - type: object
properties: properties:
appidExclude: clientExtensionResults:
type: boolean type: object
response:
allOf:
- $ref: '#/components/schemas/webauthn.AuthenticatorResponse'
- type: object
properties: properties:
attestationObject: appidExclude:
type: string type: boolean
format: byte response:
allOf:
- $ref: '#/components/schemas/webauthn.AuthenticatorResponse'
- type: object
properties:
attestationObject:
type: string
format: byte
description:
type: string
webauthn.CredentialAssertionResponse: webauthn.CredentialAssertionResponse:
allOf: allOf:
- $ref: '#/components/schemas/webauthn.PublicKeyCredential' - $ref: '#/components/schemas/webauthn.PublicKeyCredential'
@ -1971,6 +2022,11 @@ components:
format: uuid format: uuid
pattern: '^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$' pattern: '^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$'
example: '3ebcfbc5-b0fd-4ee0-9d3c-080ae1e7298c' example: '3ebcfbc5-b0fd-4ee0-9d3c-080ae1e7298c'
webauthn.DeviceUpdateRequest:
type: object
properties:
description:
type: string
webauthn.PublicKeyCredentialCreationOptions: webauthn.PublicKeyCredentialCreationOptions:
type: object type: object
properties: properties:

View File

@ -61,24 +61,6 @@ func rootSubCommandsRunE(cmd *cobra.Command, args []string) (err error) {
return err return err
} }
subCmds := sortCmds(cmd)
for _, subCmd := range subCmds {
if subCmd.Use == cmdUseCompletion || strings.HasPrefix(subCmd.Use, "help ") || utils.IsStringSliceContainsAny([]string{resolveCmdName(subCmd), subCmd.Use}, exclude) {
continue
}
rootCmd.SetArgs(rootCmdGetArgs(subCmd, args))
if err = rootCmd.Execute(); err != nil {
return err
}
}
return nil
}
func sortCmds(cmd *cobra.Command) []*cobra.Command {
subCmds := cmd.Commands() subCmds := cmd.Commands()
switch cmd.Use { switch cmd.Use {
@ -108,7 +90,19 @@ func sortCmds(cmd *cobra.Command) []*cobra.Command {
}) })
} }
return subCmds for _, subCmd := range subCmds {
if subCmd.Use == cmdUseCompletion || strings.HasPrefix(subCmd.Use, "help ") || utils.IsStringSliceContainsAny([]string{resolveCmdName(subCmd), subCmd.Use}, exclude) {
continue
}
rootCmd.SetArgs(rootCmdGetArgs(subCmd, args))
if err = rootCmd.Execute(); err != nil {
return err
}
}
return nil
} }
func resolveCmdName(cmd *cobra.Command) string { func resolveCmdName(cmd *cobra.Command) string {
@ -123,7 +117,7 @@ func resolveCmdName(cmd *cobra.Command) string {
func rootCmdGetArgs(cmd *cobra.Command, args []string) []string { func rootCmdGetArgs(cmd *cobra.Command, args []string) []string {
for { for {
if cmd == nil || cmd == rootCmd { if cmd == rootCmd {
break break
} }

View File

@ -1,125 +0,0 @@
package main
import (
"testing"
"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestResolveCmdName(t *testing.T) {
testCases := []struct {
name string
have *cobra.Command
expected string
}{
{
"ShouldResolveRootCmd",
newRootCmd(),
"authelia-gen",
},
{
"ShouldResolveDocsCmd",
newDocsCmd(),
"docs",
},
{
"ShouldResolveDocsSubCmd",
newRootCmd().Commands()[0].Commands()[0],
"code.keys",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
assert.Equal(t, tc.expected, resolveCmdName(tc.have))
})
}
}
func TestRootCmdGetArgs(t *testing.T) {
testCases := []struct {
name string
have func() *cobra.Command
args []string
expected []string
}{
{
"ShouldReturnRootCmdArgs",
func() *cobra.Command {
cmd := newRootCmd()
cmd.SetArgs([]string{"a", "b"})
return cmd.Commands()[0]
},
[]string{"c", "d"},
[]string{"authelia-gen", "code", "c", "d"},
},
{
"ShouldReturnRootCmdWithoutArgs",
func() *cobra.Command {
cmd := newRootCmd()
return cmd.Commands()[0]
},
nil,
[]string{"authelia-gen", "code"},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
assert.Equal(t, tc.expected, rootCmdGetArgs(tc.have(), tc.args))
})
}
}
func TestSortCmds(t *testing.T) {
testCases := []struct {
name string
have *cobra.Command
expected []string
}{
{
"ShouldSortRootCmd",
newRootCmd(),
[]string{"code", "commit-lint", "github", "locales", "docs"},
},
{
"ShouldSortDocsCmd",
newDocsCmd(),
[]string{"cli", "data", "date"},
},
{
"ShouldSortGitHubCmd",
newGitHubCmd(),
[]string{"issue-templates"},
},
{
"ShouldSortLocalesCmd",
newLocalesCmd(),
nil,
},
{
"ShouldSortDocsDataCmd",
newDocsDataCmd(),
[]string{"keys", "misc"},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
actual := sortCmds(tc.have)
n := len(tc.expected)
require.Len(t, actual, n)
for i := 0; i < n; i++ {
assert.Equal(t, tc.expected[i], actual[i].Use)
}
})
}
}

View File

@ -1,145 +0,0 @@
package main
import (
"net/mail"
"reflect"
"testing"
"github.com/spf13/pflag"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/authelia/authelia/v4/internal/configuration/schema"
)
func TestGetPFlagPath(t *testing.T) {
testCases := []struct {
name string
have func(t *testing.T) *pflag.FlagSet
names []string
expected string
err string
}{
{
"ShouldFailEmptyFlagSet",
func(t *testing.T) *pflag.FlagSet {
return pflag.NewFlagSet("example", pflag.ContinueOnError)
},
[]string{"abc", "123"},
"",
"failed to lookup flag 'abc': flag accessed but not defined: abc",
},
{
"ShouldFailEmptyFlagNames",
func(t *testing.T) *pflag.FlagSet {
return pflag.NewFlagSet("example", pflag.ContinueOnError)
},
nil,
"",
"no flag names",
},
{
"ShouldLookupFlagNames",
func(t *testing.T) *pflag.FlagSet {
flagset := pflag.NewFlagSet("example", pflag.ContinueOnError)
flagset.String("dir.one", "", "")
flagset.String("dir.two", "", "")
flagset.String("file.name", "", "")
require.NoError(t, flagset.Parse([]string{"--dir.one=abc", "--dir.two=123", "--file.name=path.txt"}))
return flagset
},
[]string{"dir.one", "dir.two", "file.name"},
"abc/123/path.txt",
"",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
actual, theError := getPFlagPath(tc.have(t), tc.names...)
if tc.err == "" {
assert.NoError(t, theError)
assert.Equal(t, tc.expected, actual)
} else {
assert.EqualError(t, theError, tc.err)
assert.Equal(t, "", actual)
}
})
}
}
func TestBuildCSP(t *testing.T) {
testCases := []struct {
name string
have string
ruleSets [][]CSPValue
expected string
}{
{
"ShouldParseDefault",
codeCSPProductionDefaultSrc,
[][]CSPValue{
codeCSPValuesCommon,
codeCSPValuesProduction,
},
"default-src 'self'; frame-src 'none'; object-src 'none'; style-src 'self' 'nonce-%s'; frame-ancestors 'none'; base-uri 'self'",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
assert.Equal(t, tc.expected, buildCSP(tc.have, tc.ruleSets...))
})
}
}
func TestContainsType(t *testing.T) {
astring := ""
testCases := []struct {
name string
have any
expected bool
}{
{
"ShouldContainMailAddress",
mail.Address{},
true,
},
{
"ShouldContainSchemaAddressPtr",
&schema.Address{},
true,
},
{
"ShouldNotContainString",
astring,
false,
},
{
"ShouldNotContainStringPtr",
&astring,
false,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
assert.Equal(t, tc.expected, containsType(reflect.TypeOf(tc.have), decodedTypes))
})
}
}
func TestReadTags(t *testing.T) {
assert.NotPanics(t, func() {
readTags("", reflect.TypeOf(schema.Configuration{}), false)
})
assert.NotPanics(t, func() {
readTags("", reflect.TypeOf(schema.Configuration{}), true)
})
}

View File

@ -29,23 +29,23 @@ body:
id: 'version' id: 'version'
attributes: attributes:
label: | label: |
Version Version
description: | description: |
What version(s) of Authelia can you reproduce this bug on? What version(s) of Authelia can you reproduce this bug on?
multiple: true multiple: true
options: options:
{{- range .Versions }} {{- range .Versions }}
- '{{ . }}' - '{{ . }}'
{{- end }} {{- end }}
validations: validations:
required: true required: true
- type: 'dropdown' - type: 'dropdown'
id: 'deployment' id: 'deployment'
attributes: attributes:
label: | label: |
Deployment Method Deployment Method
description: | description: |
How are you deploying Authelia? How are you deploying Authelia?
options: options:
- 'Docker' - 'Docker'
- 'Kubernetes' - 'Kubernetes'
@ -57,9 +57,9 @@ body:
id: 'proxy' id: 'proxy'
attributes: attributes:
label: | label: |
Reverse Proxy Reverse Proxy
description: | description: |
What reverse proxy are you using? What reverse proxy are you using?
options: options:
{{- range .Proxies }} {{- range .Proxies }}
- '{{ . }}' - '{{ . }}'
@ -70,9 +70,9 @@ body:
id: 'proxy-version' id: 'proxy-version'
attributes: attributes:
label: | label: |
Reverse Proxy Version Reverse Proxy Version
description: | description: |
What is the version of your reverse proxy? What is the version of your reverse proxy?
placeholder: 'x.x.x' placeholder: 'x.x.x'
validations: validations:
required: false required: false
@ -80,36 +80,36 @@ body:
id: 'description' id: 'description'
attributes: attributes:
label: | label: |
Description Description
description: | description: |
Describe the bug. Describe the bug.
validations: validations:
required: true required: true
- type: 'textarea' - type: 'textarea'
id: 'reproduction' id: 'reproduction'
attributes: attributes:
label: | label: |
Reproduction Reproduction
description: | description: |
Describe how we can reproduce this issue. This should be step by step and should include detailed and specific information. Abstract or generic information should be avoided. For example this should include specific application names and versions if relevant. Reproducing the issue is important so we can verify it exists, add relevant tests, and verify it is solved. Describe how we can reproduce this issue. This should be step by step and should include detailed and specific information. Abstract or generic information should be avoided. For example this should include specific application names and versions if relevant. Reproducing the issue is important so we can verify it exists, add relevant tests, and verify it is solved.
validations: validations:
required: true required: true
- type: 'textarea' - type: 'textarea'
id: 'expectations' id: 'expectations'
attributes: attributes:
label: | label: |
Expectations Expectations
description: | description: |
Describe the desired or expected results. Describe the desired or expected results.
validations: validations:
required: false required: false
- type: 'textarea' - type: 'textarea'
id: 'configuration' id: 'configuration'
attributes: attributes:
label: | label: |
Configuration (Authelia) Configuration (Authelia)
description: | description: |
Provide a complete configuration file (the template will automatically put this content in a code block). Provide a complete configuration file (the template will automatically put this content in a code block).
render: 'yaml' render: 'yaml'
validations: validations:
required: false required: false
@ -117,7 +117,7 @@ body:
id: 'logs' id: 'logs'
attributes: attributes:
label: | label: |
Logs (Authelia) Logs (Authelia)
description: | description: |
Provide complete logs with the log level set to debug or trace. Complete means from application start until the issue occurring. This is clearly explained in the [Logs](https://www.authelia.com/r/troubleshooting#logs) section of the troubleshooting guide. Provide complete logs with the log level set to debug or trace. Complete means from application start until the issue occurring. This is clearly explained in the [Logs](https://www.authelia.com/r/troubleshooting#logs) section of the troubleshooting guide.
@ -129,9 +129,9 @@ body:
id: 'logs-other' id: 'logs-other'
attributes: attributes:
label: | label: |
Logs (Proxy / Application) Logs (Proxy / Application)
description: | description: |
Provide complete debug logs for the affected proxy and/or application if available and relevant (the template will automatically put this content in a code block). Provide complete debug logs for the affected proxy and/or application if available and relevant (the template will automatically put this content in a code block).
render: 'shell' render: 'shell'
validations: validations:
required: false required: false
@ -139,38 +139,38 @@ body:
id: 'documentation' id: 'documentation'
attributes: attributes:
label: | label: |
Documentation Documentation
description: | description: |
Provide any relevant specification or other documentation if applicable. Provide any relevant specification or other documentation if applicable.
validations: validations:
required: false required: false
- type: 'checkboxes' - type: 'checkboxes'
id: 'checklist' id: 'checklist'
attributes: attributes:
label: | label: |
Pre-Submission Checklist Pre-Submission Checklist
description: | description: |
By submitting this issue confirm all of the following. By submitting this issue confirm all of the following.
options: options:
- label: | - label: |
I agree to follow the [Code of Conduct](http://www.authelia.com/code-of-conduct) I agree to follow the [Code of Conduct](http://www.authelia.com/code-of-conduct)
required: true required: true
- label: | - label: |
This is a bug report and not a support request This is a bug report and not a support request
required: true required: true
- label: | - label: |
I have read the security policy and this bug report is not a security issue or security related issue I have read the security policy and this bug report is not a security issue or security related issue
required: true required: true
- label: | - label: |
I have either included the complete configuration file or I am sure it's unrelated to the configuration I have either included the complete configuration file or I am sure it's unrelated to the configuration
required: true required: true
- label: | - label: |
I have provided all of the required information in full with the only alteration being reasonable sanitization in accordance with the [Troubleshooting Sanitization](https://www.authelia.com/r/sanitize) reference guide I have provided all of the required information in full with the only alteration being reasonable sanitization in accordance with the [Troubleshooting Sanitization](https://www.authelia.com/r/sanitize) reference guide
required: true required: true
- label: | - label: |
I have checked for related proxy or application logs and included them if available I have checked for related proxy or application logs and included them if available
required: true required: true
- label: | - label: |
I have checked for related issues and checked the documentation I have checked for related issues and checked the documentation
required: true required: true
... ...

View File

@ -20,50 +20,50 @@ body:
id: 'description' id: 'description'
attributes: attributes:
label: | label: |
Description Description
description: | description: |
Describe the feature Describe the feature
validations: validations:
required: true required: true
- type: 'textarea' - type: 'textarea'
id: 'use-case' id: 'use-case'
attributes: attributes:
label: | label: |
Use Case Use Case
description: | description: |
Provide a use case Provide a use case
validations: validations:
required: true required: true
- type: 'textarea' - type: 'textarea'
id: 'details' id: 'details'
attributes: attributes:
label: | label: |
Details Details
description: | description: |
Describe the feature in detail Describe the feature in detail
validations: validations:
required: false required: false
- type: 'textarea' - type: 'textarea'
id: 'documentation' id: 'documentation'
attributes: attributes:
label: | label: |
Documentation Documentation
description: | description: |
Provide any relevant specification or other documentation if applicable Provide any relevant specification or other documentation if applicable
validations: validations:
required: false required: false
- type: 'checkboxes' - type: 'checkboxes'
id: 'checklist' id: 'checklist'
attributes: attributes:
label: | label: |
Pre-Submission Checklist Pre-Submission Checklist
description: | description: |
By submitting this issue confirm all of the following By submitting this issue confirm all of the following
options: options:
- label: | - label: |
I agree to follow the [Code of Conduct](http://www.authelia.com/code-of-conduct) I agree to follow the [Code of Conduct](http://www.authelia.com/code-of-conduct)
required: true required: true
- label: | - label: |
I have checked for related issues and checked the documentation I have checked for related issues and checked the documentation
required: true required: true
... ...

View File

@ -1,13 +0,0 @@
package main
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestShouldFailToLoadBadTemplate(t *testing.T) {
assert.Panics(t, func() {
mustLoadTmplFS("bad tmpl")
})
}

View File

@ -120,11 +120,6 @@ const (
labelAreaPrefixStatus = "status" labelAreaPrefixStatus = "status"
) )
type label interface {
String() string
LabelDescription() string
}
type labelPriority int type labelPriority int
//nolint:deadcode,varcheck // Kept for future use. //nolint:deadcode,varcheck // Kept for future use.
@ -134,7 +129,6 @@ const (
labelPriorityMedium labelPriorityMedium
labelPriorityNormal labelPriorityNormal
labelPriorityLow labelPriorityLow
labelPriorityVeryLow
) )
var labelPriorityDescriptions = [...]string{ var labelPriorityDescriptions = [...]string{
@ -143,15 +137,14 @@ var labelPriorityDescriptions = [...]string{
"Medium", "Medium",
"Normal", "Normal",
"Low", "Low",
"Very Low",
} }
func (l labelPriority) String() string { func (p labelPriority) String() string {
return fmt.Sprintf("%s/%d/%s", labelAreaPrefixPriority, l+1, labelFormatString(labelPriorityDescriptions[l])) return fmt.Sprintf("%s/%d/%s", labelAreaPrefixPriority, p+1, strings.ToLower(labelPriorityDescriptions[p]))
} }
func (l labelPriority) LabelDescription() string { func (p labelPriority) Description() string {
return labelPriorityDescriptions[l] return labelPriorityDescriptions[p]
} }
type labelStatus int type labelStatus int
@ -162,16 +155,12 @@ const (
) )
var labelStatusDescriptions = [...]string{ var labelStatusDescriptions = [...]string{
"Needs Design", "needs-design",
"Needs Triage", "needs-triage",
} }
func (l labelStatus) String() string { func (s labelStatus) String() string {
return fmt.Sprintf("%s/%s", labelAreaPrefixStatus, labelFormatString(labelStatusDescriptions[l])) return fmt.Sprintf("%s/%s", labelAreaPrefixStatus, labelStatusDescriptions[s])
}
func (l labelStatus) LabelDescription() string {
return labelStatusDescriptions[l]
} }
type labelType int type labelType int
@ -184,24 +173,13 @@ const (
) )
var labelTypeDescriptions = [...]string{ var labelTypeDescriptions = [...]string{
"Feature", "feature",
"Bug: Unconfirmed", "bug/unconfirmed",
"Bug", "bug",
} }
func (l labelType) String() string { func (t labelType) String() string {
return fmt.Sprintf("%s/%s", labelAreaPrefixType, labelFormatString(labelTypeDescriptions[l])) return fmt.Sprintf("%s/%s", labelAreaPrefixType, labelTypeDescriptions[t])
}
func (l labelType) LabelDescription() string {
return labelTypeDescriptions[l]
}
func labelFormatString(in string) string {
in = strings.ReplaceAll(in, ": ", "/")
in = strings.ReplaceAll(in, " ", "-")
return strings.ToLower(in)
} }
// CSPValue represents individual CSP values. // CSPValue represents individual CSP values.

View File

@ -1,90 +0,0 @@
package main
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestLabels(t *testing.T) {
testCases := []struct {
name string
have label
expectedDescription string
expectedString string
}{
{
"ShouldShowCorrectPriorityCriticalValues",
labelPriorityCritical,
"Critical",
"priority/1/critical",
},
{
"ShouldShowCorrectPriorityHighValues",
labelPriorityHigh,
"High",
"priority/2/high",
},
{
"ShouldShowCorrectPriorityMediumValues",
labelPriorityMedium,
"Medium",
"priority/3/medium",
},
{
"ShouldShowCorrectPriorityNormalValues",
labelPriorityNormal,
"Normal",
"priority/4/normal",
},
{
"ShouldShowCorrectPriorityLowValues",
labelPriorityLow,
"Low",
"priority/5/low",
},
{
"ShouldShowCorrectPriorityVeryLowValues",
labelPriorityVeryLow,
"Very Low",
"priority/6/very-low",
},
{
"ShouldShowCorrectStatusNeedsDesignValues",
labelStatusNeedsDesign,
"Needs Design",
"status/needs-design",
},
{
"ShouldShowCorrectStatusNeedsTriageValues",
labelStatusNeedsTriage,
"Needs Triage",
"status/needs-triage",
},
{
"ShouldShowCorrectTypeFeatureValues",
labelTypeFeature,
"Feature",
"type/feature",
},
{
"ShouldShowCorrectTypeBugUnconfirmedValues",
labelTypeBugUnconfirmed,
"Bug: Unconfirmed",
"type/bug/unconfirmed",
},
{
"ShouldShowCorrectTypeBugValues",
labelTypeBug,
"Bug",
"type/bug",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
assert.Equal(t, tc.expectedString, tc.have.String())
assert.Equal(t, tc.expectedDescription, tc.have.LabelDescription())
})
}
}

View File

@ -7,5 +7,5 @@
package cmd package cmd
const ( const (
versionSwaggerUI = "5.0.0" versionSwaggerUI = "4.18.3"
) )

View File

@ -39,7 +39,7 @@ default_redirection_url: 'https://home.example.com/'
## Set the default 2FA method for new users and for when a user has a preferred method configured that has been ## Set the default 2FA method for new users and for when a user has a preferred method configured that has been
## disabled. This setting must be a method that is enabled. ## disabled. This setting must be a method that is enabled.
## Options are totp, webauthn, mobile_push. ## Options are totp, webauthn, mobile_push.
# default_2fa_method: '' default_2fa_method: ''
## ##
## Server Configuration ## Server Configuration
@ -47,12 +47,15 @@ default_redirection_url: 'https://home.example.com/'
server: server:
## The address for the Main server to listen on in the address common syntax. ## The address for the Main server to listen on in the address common syntax.
## Formats: ## Formats:
## - [<scheme>://]<hostname>[:<port>][/<path>] ## - [<scheme>://]<hostname>[:<port>]
## - [<scheme>://][hostname]:<port>[/<path>] ## - [<scheme>://][hostname]:<port>
## Square brackets indicate optional portions of the format. Scheme must be 'tcp', 'tcp4', 'tcp6', or 'unix'. ## Square brackets indicate optional portions of the format. Scheme must be 'tcp', 'tcp4', 'tcp6', or 'unix'.
## The default scheme is 'unix' if the address is an absolute path otherwise it's 'tcp'. The default port is '9091'. ## The default scheme is 'unix' if the address is an absolute path otherwise it's 'tcp'. The default port is '9091'.
## If the path is specified this configures the router to handle both the `/` path and the configured path. address: 'tcp://:9091'
address: 'tcp://:9091/'
## Set the single level path Authelia listens on.
## Must be alphanumeric chars and should not contain any slashes.
path: ''
## Set the path on disk to Authelia assets. ## Set the path on disk to Authelia assets.
## Useful to allow overriding of specific static assets. ## Useful to allow overriding of specific static assets.
@ -62,10 +65,6 @@ server:
## This is disabled by default if either /app/.healthcheck.env or /app/healthcheck.sh do not exist. ## This is disabled by default if either /app/.healthcheck.env or /app/healthcheck.sh do not exist.
disable_healthcheck: false disable_healthcheck: false
## If a request over the insecure http protocol is received from authelias gRPC endpoint (only for envoy),
## the request is by default redirected to the matching https URL (301)
disable_autho_https_redirect: false
## Authelia by default doesn't accept TLS communication on the server port. This section overrides this behaviour. ## Authelia by default doesn't accept TLS communication on the server port. This section overrides this behaviour.
tls: tls:
## The path to the DER base64/PEM format private key. ## The path to the DER base64/PEM format private key.
@ -77,17 +76,6 @@ server:
## The list of certificates for client authentication. ## The list of certificates for client authentication.
client_certificates: [] client_certificates: []
## Enable the support for gRPC ext authentication for envoy. If TLS is enabled in the above section,
## the defined certificates will also be used for the gRPC endpoint
grpc:
address: 'tcp://:9092'
# Even if TLS is configured in the server setting (under server.tls), the grcp server won't use TLS
disableTLS: false
# By default the ban is issued for the user. With this options the IP instead of the user will be banned
use_ip_for_ban: true
## Server headers configuration/customization. ## Server headers configuration/customization.
headers: headers:
@ -174,12 +162,11 @@ telemetry:
## The address for the Metrics server to listen on in the address common syntax. ## The address for the Metrics server to listen on in the address common syntax.
## Formats: ## Formats:
## - [<scheme>://]<hostname>[:<port>][/<path>] ## - [<scheme>://]<hostname>[:<port>]
## - [<scheme>://][hostname]:<port>[/<path>] ## - [<scheme>://][hostname]:<port>
## Square brackets indicate optional portions of the format. Scheme must be 'tcp', 'tcp4', 'tcp6', or 'unix'. ## Square brackets indicate optional portions of the format. Scheme must be 'tcp', 'tcp4', 'tcp6', or 'unix'.
## The default scheme is 'unix' if the address is an absolute path otherwise it's 'tcp'. The default port is '9959'. ## The default scheme is 'unix' if the address is an absolute path otherwise it's 'tcp'. The default port is '9959'.
## If the path is not specified it defaults to `/metrics`. address: 'tcp://:9959'
address: 'tcp://:9959/metrics'
## Metrics Server Buffers configuration. ## Metrics Server Buffers configuration.
# buffers: # buffers:
@ -216,7 +203,7 @@ totp:
## The TOTP algorithm to use. ## The TOTP algorithm to use.
## It is CRITICAL you read the documentation before changing this option: ## It is CRITICAL you read the documentation before changing this option:
## https://www.authelia.com/c/totp#algorithm ## https://www.authelia.com/c/totp#algorithm
algorithm: 'SHA1' algorithm: 'sha1'
## The number of digits a user has to input. Must either be 6 or 8. ## The number of digits a user has to input. Must either be 6 or 8.
## Changing this option only affects newly generated TOTP configurations. ## Changing this option only affects newly generated TOTP configurations.
@ -313,7 +300,7 @@ authentication_backend:
## External reset password url that redirects the user to an external reset portal. This disables the internal reset ## External reset password url that redirects the user to an external reset portal. This disables the internal reset
## functionality. ## functionality.
# custom_url: '' custom_url: ''
## The amount of time to wait before we refresh data from the authentication backend in the duration common syntax. ## The amount of time to wait before we refresh data from the authentication backend in the duration common syntax.
## To disable this feature set it to 'disable', this will slightly reduce security because for Authelia, users will ## To disable this feature set it to 'disable', this will slightly reduce security because for Authelia, users will
@ -330,7 +317,7 @@ authentication_backend:
## because it allows Authelia to offload the stateful operations ## because it allows Authelia to offload the stateful operations
## onto the LDAP service. ## onto the LDAP service.
# ldap: # ldap:
## The address of the directory server to connect to in the address common syntax. ## The address of the LDAP server to connect to in the address common syntax.
## Format: [<scheme>://]<hostname>[:<port>]. ## Format: [<scheme>://]<hostname>[:<port>].
## Square brackets indicate optional portions of the format. Scheme must be 'ldap', 'ldaps', or 'ldapi`. ## Square brackets indicate optional portions of the format. Scheme must be 'ldap', 'ldaps', or 'ldapi`.
## The default scheme is 'ldapi' if the address is an absolute path otherwise it's 'ldaps'. ## The default scheme is 'ldapi' if the address is an absolute path otherwise it's 'ldaps'.
@ -409,13 +396,23 @@ authentication_backend:
# SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH # SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH
# ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg # ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg
# 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg # 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg
# HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRs_DO_NOT_USE= # HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRsDO_NOT_USE=
# -----END RSA PRIVATE KEY----- # -----END RSA PRIVATE KEY-----
## The distinguished name of the container searched for objects in the directory information tree. ## The distinguished name of the container searched for objects in the directory information tree.
## See also: additional_users_dn, additional_groups_dn. ## See also: additional_users_dn, additional_groups_dn.
# base_dn: 'dc=example,dc=com' # base_dn: 'dc=example,dc=com'
## The attribute holding the username of the user. This attribute is used to populate the username in the session
## information. For your information, Microsoft Active Directory usually uses 'sAMAccountName' and OpenLDAP usually
## uses 'uid'. Beware that this attribute holds the unique identifiers for the users binding the user and the
## configuration stored in database. Therefore only single value attributes are allowed and the value must never be
## changed once attributed to a user otherwise it would break the configuration for that user. Technically,
## non-unique attributes like 'mail' can also be used but we don't recommend using them, we instead advise to use
## a filter to perform alternative lookups and the attributes mentioned above (sAMAccountName and uid) to
## follow https://datatracker.ietf.org/doc/html/rfc2307.
# username_attribute: 'uid'
## The additional_users_dn is prefixed to base_dn and delimited by a comma when searching for users. ## The additional_users_dn is prefixed to base_dn and delimited by a comma when searching for users.
## i.e. with this set to OU=Users and base_dn set to DC=a,DC=com; OU=Users,DC=a,DC=com is searched for users. ## i.e. with this set to OU=Users and base_dn set to DC=a,DC=com; OU=Users,DC=a,DC=com is searched for users.
# additional_users_dn: 'ou=users' # additional_users_dn: 'ou=users'
@ -446,9 +443,15 @@ authentication_backend:
## (&(uniqueMember={dn})(objectClass=groupOfUniqueNames)) ## (&(uniqueMember={dn})(objectClass=groupOfUniqueNames))
# groups_filter: '(&(member={dn})(objectClass=groupOfNames))' # groups_filter: '(&(member={dn})(objectClass=groupOfNames))'
## The group search mode to use. Options are 'filter' or 'memberof'. It's essential to read the docs if you wish to ## The attribute holding the name of the group.
## use 'memberof'. Also 'filter' is the best choice for most use cases. # group_name_attribute: 'cn'
# group_search_mode: 'filter'
## The attribute holding the mail address of the user. If multiple email addresses are defined for a user, only the
## first one returned by the LDAP server is used.
# mail_attribute: 'mail'
## The attribute holding the display name of the user. This will be used to greet an authenticated user.
# display_name_attribute: 'displayName'
## Follow referrals returned by the server. ## Follow referrals returned by the server.
## This is especially useful for environments where read-only servers exist. Only implemented for write operations. ## This is especially useful for environments where read-only servers exist. Only implemented for write operations.
@ -459,37 +462,6 @@ authentication_backend:
## Password can also be set using a secret: https://www.authelia.com/c/secrets ## Password can also be set using a secret: https://www.authelia.com/c/secrets
# password: 'password' # password: 'password'
## The attributes for users and objects from the directory server.
# attributes:
## The distinguished name attribute if your directory server supports it. Users should read the docs before
## configuring. Only used for the 'memberof' group search mode.
# distinguished_name: ''
## The attribute holding the username of the user. This attribute is used to populate the username in the session
## information. For your information, Microsoft Active Directory usually uses 'sAMAccountName' and OpenLDAP
## usually uses 'uid'. Beware that this attribute holds the unique identifiers for the users binding the user and
## the configuration stored in database; therefore only single value attributes are allowed and the value must
## never be changed once attributed to a user otherwise it would break the configuration for that user.
## Technically non-unique attributes like 'mail' can also be used but we don't recommend using them, we instead
## advise to use a filter to perform alternative lookups and the attributes mentioned above
## (sAMAccountName and uid) to follow https://datatracker.ietf.org/doc/html/rfc2307.
# username: 'uid'
## The attribute holding the display name of the user. This will be used to greet an authenticated user.
# display_name: 'displayName'
## The attribute holding the mail address of the user. If multiple email addresses are defined for a user, only
## the first one returned by the directory server is used.
# mail: 'mail'
## The attribute which provides distinguished names of groups an object is a member of.
## Only used for the 'memberof' group search mode.
# member_of: 'memberOf'
## The attribute holding the name of the group.
# group_name: 'cn'
## ##
## File (Authentication Provider) ## File (Authentication Provider)
## ##
@ -848,7 +820,7 @@ session:
# SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH # SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH
# ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg # ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg
# 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg # 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg
# HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRs_DO_NOT_USE= # HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRsDO_NOT_USE=
# -----END RSA PRIVATE KEY----- # -----END RSA PRIVATE KEY-----
## The Redis HA configuration options. ## The Redis HA configuration options.
@ -993,7 +965,7 @@ regulation:
# SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH # SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH
# ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg # ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg
# 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg # 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg
# HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRs_DO_NOT_USE= # HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRsDO_NOT_USE=
# -----END RSA PRIVATE KEY----- # -----END RSA PRIVATE KEY-----
## ##
@ -1076,8 +1048,8 @@ regulation:
# SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH # SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH
# ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg # ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg
# 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg # 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg
# HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRs_DO_NOT_USE= # HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRsDO_NOT_USE=
# -----END RSA PRIVATE KEY----- # -----END RSA PRIVATE KEY-----
## ##
## Notification Provider ## Notification Provider
@ -1194,7 +1166,7 @@ notifier:
# SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH # SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH
# ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg # ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg
# 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg # 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg
# HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRs_DO_NOT_USE= # HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRsDO_NOT_USE=
# -----END RSA PRIVATE KEY----- # -----END RSA PRIVATE KEY-----
## ##
@ -1236,7 +1208,7 @@ notifier:
# SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH # SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH
# ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg # ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg
# 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg # 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg
# HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRs_DO_NOT_USE= # HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRsDO_NOT_USE=
# -----END RSA PRIVATE KEY----- # -----END RSA PRIVATE KEY-----
@ -1266,7 +1238,7 @@ notifier:
# SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH # SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH
# ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg # ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg
# 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg # 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg
# HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRs_DO_NOT_USE= # HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRsDO_NOT_USE=
# -----END RSA PRIVATE KEY----- # -----END RSA PRIVATE KEY-----
## Optional matching certificate chain in PEM DER form that matches the issuer_private_key. All certificates within ## Optional matching certificate chain in PEM DER form that matches the issuer_private_key. All certificates within
@ -1315,7 +1287,6 @@ notifier:
## List of endpoints in addition to the metadata endpoints to permit cross-origin requests on. ## List of endpoints in addition to the metadata endpoints to permit cross-origin requests on.
# endpoints: # endpoints:
# - 'authorization' # - 'authorization'
# - 'pushed-authorization-request'
# - 'token' # - 'token'
# - 'revocation' # - 'revocation'
# - 'introspection' # - 'introspection'
@ -1354,7 +1325,7 @@ notifier:
## Redirect URI's specifies a list of valid case-sensitive callbacks for this client. ## Redirect URI's specifies a list of valid case-sensitive callbacks for this client.
# redirect_uris: # redirect_uris:
# - 'https://oidc.example.com:8080/oauth2/callback' # - 'https://oidc.example.com:8080/oauth2/callback'
## Audience this client is allowed to request. ## Audience this client is allowed to request.
# audience: [] # audience: []
@ -1369,7 +1340,7 @@ notifier:
## Grant Types configures which grants this client can obtain. ## Grant Types configures which grants this client can obtain.
## It's not recommended to define this unless you know what you're doing. ## It's not recommended to define this unless you know what you're doing.
# grant_types: # grant_types:
# - 'authorization_code' # - 'authorization_code'
## Response Types configures which responses this client can be sent. ## Response Types configures which responses this client can be sent.
## It's not recommended to define this unless you know what you're doing. ## It's not recommended to define this unless you know what you're doing.
@ -1441,7 +1412,7 @@ notifier:
# key: | # key: |
# -----BEGIN RSA PUBLIC KEY----- # -----BEGIN RSA PUBLIC KEY-----
# MEgCQQDAwV26ZA1lodtOQxNrJ491gWT+VzFum9IeZ+WTmMypYWyW1CzXKwsvTHDz # MEgCQQDAwV26ZA1lodtOQxNrJ491gWT+VzFum9IeZ+WTmMypYWyW1CzXKwsvTHDz
# 9ec+jserR3EMQ0Rr24lj13FL1ib5AgMBAAE_DO_NOT_USE= # 9ec+jserR3EMQ0Rr24lj13FL1ib5AgMBAAE=
# -----END RSA PUBLIC KEY---- # -----END RSA PUBLIC KEY----
## The matching certificate chain in PEM DER form that matches the key if available. ## The matching certificate chain in PEM DER form that matches the key if available.

View File

@ -101,20 +101,16 @@ authentication_backend:
base_dn: 'DC=example,DC=com' base_dn: 'DC=example,DC=com'
additional_users_dn: 'OU=users' additional_users_dn: 'OU=users'
users_filter: '(&({username_attribute}={input})(objectClass=person))' users_filter: '(&({username_attribute}={input})(objectClass=person))'
username_attribute: 'uid'
mail_attribute: 'mail'
display_name_attribute: 'displayName'
additional_groups_dn: 'OU=groups' additional_groups_dn: 'OU=groups'
groups_filter: '(&(member={dn})(objectClass=groupOfNames))' groups_filter: '(&(member={dn})(objectClass=groupOfNames))'
group_search_mode: 'filter' group_name_attribute: 'cn'
permit_referrals: false permit_referrals: false
permit_unauthenticated_bind: false permit_unauthenticated_bind: false
user: 'CN=admin,DC=example,DC=com' user: 'CN=admin,DC=example,DC=com'
password: 'password' password: 'password'
attributes:
distinguished_name: 'distinguishedName'
username: 'uid'
display_name: 'displayName'
mail: 'mail'
member_of: 'memberOf'
group_name: 'cn'
``` ```
## Options ## Options
@ -213,33 +209,66 @@ The LDAP filter to narrow down which users are valid. This is important to set c
The default value is dependent on the [implementation](#implementation), refer to the The default value is dependent on the [implementation](#implementation), refer to the
[attribute defaults](../../reference/guides/ldap.md#attribute-defaults) for more information. [attribute defaults](../../reference/guides/ldap.md#attribute-defaults) for more information.
### username_attribute
{{< confkey type="string" required="situational" >}}
*__Note:__ This option is technically required however the [implementation](#implementation) option can implicitly set a
default negating this requirement. Refer to the [attribute defaults](../../reference/guides/ldap.md#attribute-defaults)
for more information.*
The LDAP attribute that maps to the username in *Authelia*. This must contain the `{username_attribute}`
[placeholder](../../reference/guides/ldap.md#users-filter-replacements).
### mail_attribute
{{< confkey type="string" required="situational" >}}
*__Note:__ This option is technically required however the [implementation](#implementation) option can implicitly set a
default negating this requirement. Refer to the [attribute defaults](../../reference/guides/ldap.md#attribute-defaults)
for more information.*
The attribute to retrieve which contains the users email addresses. This is important for the device registration and
password reset processes. The user must have an email address in order for Authelia to perform identity verification
when a user attempts to reset their password or register a second factor device.
### display_name_attribute
{{< confkey type="string" required="situational" >}}
*__Note:__ This option is technically required however the [implementation](#implementation) option can implicitly set a
default negating this requirement. Refer to the [attribute defaults](#attribute-defaults) for more information.*
The attribute to retrieve which is shown on the Web UI to the user when they log in.
### additional_groups_dn ### additional_groups_dn
{{< confkey type="string" required="no" >}} {{< confkey type="string" required="no" >}}
Similar to [additional_users_dn](#additionalusersdn) but it applies to group searches. Similar to [additional_users_dn](#additional_users_dn) but it applies to group searches.
### groups_filter ### groups_filter
{{< confkey type="string" required="situational" >}} {{< confkey type="string" required="situational" >}}
*__Note:__ This option is technically required however the [implementation](#implementation) option can implicitly set a *__Note:__ This option is technically required however the [implementation](#implementation) option can implicitly set a
default negating this requirement. Refer to the [filter defaults](../../reference/guides/ldap.md#filter-defaults) for default negating this requirement. Refer to the [filter defaults](#filter-defaults) for more information.*
more information.*
Similar to [users_filter](#usersfilter) but it applies to group searches. In order to include groups the member is not Similar to [users_filter](#users_filter) but it applies to group searches. In order to include groups the member is not
a direct member of, but is a member of another group that is a member of those (i.e. recursive groups), you may try a direct member of, but is a member of another group that is a member of those (i.e. recursive groups), you may try
using the following filter which is currently only tested against Microsoft Active Directory: using the following filter which is currently only tested against Microsoft Active Directory:
`(&(member:1.2.840.113556.1.4.1941:={dn})(objectClass=group)(objectCategory=group))` `(&(member:1.2.840.113556.1.4.1941:={dn})(objectClass=group)(objectCategory=group))`
### group_search_mode ### group_name_attribute
{{< confkey type="string" default="filter" required="no" >}} {{< confkey type="string" required="situational" >}}
The group search mode controls how user groups are discovered. The default of `filter` directly uses the filter to *__Note:__ This option is technically required however the [implementation](#implementation) option can implicitly set a
determine the result. The `memberof` experimental mode does another special filtered search. See the default negating this requirement. Refer to the [attribute defaults](#attribute-defaults) for more
[Reference Documentation](../../reference/guides/ldap.md#group-search-modes) for more information. information.*
The LDAP attribute that is used by Authelia to determine the group name.
### permit_referrals ### permit_referrals
@ -284,71 +313,6 @@ It's __strongly recommended__ this is a
[Random Alphanumeric String](../../reference/guides/generating-secure-values.md#generating-a-random-alphanumeric-string) with 64 or more [Random Alphanumeric String](../../reference/guides/generating-secure-values.md#generating-a-random-alphanumeric-string) with 64 or more
characters and the user password is changed to this value. characters and the user password is changed to this value.
### attributes
The following options configure The directory server attribute mappings.
#### distinguished_name
{{< confkey type="string" required="situational" >}}
*__Note:__ This option is technically not required however it is required when using the group search mode
`memberof` replacement `{memberof:dn}`.*
The directory server attribute which contains the distinguished name, primarily used to perform filtered searches. There
is a clear distinction between the actual distinguished name and a distinguished name attribute, all directories have
distinguished names for objects, but not all have an attribute representing this that can be searched on.
The only known support at this time is with Active Directory.
#### username
{{< confkey type="string" required="situational" >}}
*__Note:__ This option is technically required however the [implementation](#implementation) option can implicitly set a
default negating this requirement. Refer to the [attribute defaults] for more information.*
The directory server attribute that maps to the username in *Authelia*. This must contain the `{username_attribute}` [placeholder].
#### display_name
{{< confkey type="string" required="situational" >}}
*__Note:__ This option is technically required however the [implementation](#implementation) option can implicitly set a
default negating this requirement. Refer to the [attribute defaults] for more information.*
The directory server attribute to retrieve which is shown on the Web UI to the user when they log in.
#### mail
{{< confkey type="string" required="situational" >}}
*__Note:__ This option is technically required however the [implementation](#implementation) option can implicitly set a
default negating this requirement. Refer to the [attribute defaults] for more information.*
The directory server attribute to retrieve which contains the users email addresses. This is important for the device
registration and password reset processes. The user must have an email address in order for Authelia to perform
identity verification when a user attempts to reset their password or register a second factor device.
#### member_of
{{< confkey type="string" required="situational" >}}
*__Note:__ This option is technically required however the [implementation](#implementation) option can implicitly set a
default negating this requirement. Refer to the [attribute defaults] for more information.*
The directory server attribute which contains the groups a user is a member of. This is currently only used for the
`memberof` group search mode.
#### group_name
{{< confkey type="string" required="situational" >}}
*__Note:__ This option is technically required however the [implementation](#implementation) option can implicitly set a
default negating this requirement. Refer to the [attribute defaults] for more information.*
The directory server attribute that is used by Authelia to determine the group name.
## Refresh Interval ## Refresh Interval
It's recommended you either use the default [refresh interval](introduction.md#refreshinterval) or configure this to It's recommended you either use the default [refresh interval](introduction.md#refreshinterval) or configure this to
@ -368,8 +332,6 @@ for your users.
- [LDAP Reference Guide](../../reference/guides/ldap.md) - [LDAP Reference Guide](../../reference/guides/ldap.md)
[username attribute]: #username [username attribute]: #usernameattribute
[TechNet wiki]: https://social.technet.microsoft.com/wiki/contents/articles/5392.active-directory-ldap-syntax-filters.aspx [TechNet wiki]: https://social.technet.microsoft.com/wiki/contents/articles/5392.active-directory-ldap-syntax-filters.aspx
[RFC2307]: https://datatracker.ietf.org/doc/html/rfc2307 [RFC2307]: https://datatracker.ietf.org/doc/html/rfc2307
[attribute defaults]: ../../reference/guides/ldap.md#attribute-defaults
[placeholder]: ../../reference/guides/ldap.md#users-filter-replacements

View File

@ -21,7 +21,8 @@ aliases:
```yaml ```yaml
server: server:
address: 'tcp://:9091/' address: 'tcp://:9091'
path: ''
disable_healthcheck: false disable_healthcheck: false
tls: tls:
key: '' key: ''
@ -58,25 +59,19 @@ server:
### address ### address
{{< confkey type="address" default="tcp://:9091/" required="no" >}} {{< confkey type="address" default="tcp://:9091" required="no" >}}
*__Reference Note:__ This configuration option uses the [address common syntax](../prologue/common.md#address). Please *__Reference Note:__ This configuration option uses the [address common syntax](../prologue/common.md#address). Please
see the [documentation](../prologue/common.md#address) on this format for more information.* see the [documentation](../prologue/common.md#address) on this format for more information.*
Configures the listener address for the Main HTTP Server. The address itself is a listener and the scheme must either be Configures the listener address for the Main HTTP Server. The address itself is a listener and the scheme must either be
the `unix` scheme or one of the `tcp` schemes. It can configure the host, port, and path the listener responds to. If the `unix` scheme or one of the `tcp` schemes.
the path is configured to anything other than `/` Authelia will handle requests for both `/` and the configured path.
__Examples:__ __Examples:__
```yaml ```yaml
server: server:
address: tcp://127.0.0.1:9091/ address: tcp://127.0.0.1:9091
```
```yaml
server:
address: tcp://127.0.0.1:9091/subpath
``` ```
```yaml ```yaml
@ -84,6 +79,34 @@ server:
address: unix:///var/run/authelia.sock address: unix:///var/run/authelia.sock
``` ```
### path
{{< confkey type="string " required="no" >}}
Authelia by default is served from the root `/` location, either via its own domain or subdomain.
Modifying this setting will allow you to serve Authelia out from a specified base path. Please note
that currently only a single level path is supported meaning slashes are not allowed, and only
alphanumeric characters are supported.
__Example:__
```yaml
server:
path: ""
```
*Works for https://auth.example.com/, https://example.com/, etc*.
__Example:__
```yaml
server:
path: authelia
```
*Works for https://auth.example.com/authelia/, https://example.com/authelia/, etc*.
### asset_path ### asset_path
{{< confkey type="string " required="no" >}} {{< confkey type="string " required="no" >}}

View File

@ -88,7 +88,7 @@ Refer to the individual documentation for an option for clarity. In this format
are optional. The default for these when not provided varies. are optional. The default for these when not provided varies.
```text ```text
[<scheme>://]<hostname>[:<port>][/<path>] [<scheme>://]<hostname>[:<port>]
``` ```
##### Port ##### Port
@ -99,7 +99,7 @@ hostname are optional. The default for the scheme when not provided varies, and
available addresses when not provided. available addresses when not provided.
```text ```text
[<scheme>://][hostname]:<port>[/<path>] [<scheme>://][hostname]:<port>
``` ```
##### Unix Domain Socket ##### Unix Domain Socket
@ -131,11 +131,8 @@ Various examples for these formats.
```text ```text
0.0.0.0 0.0.0.0
tcp://0.0.0.0 tcp://0.0.0.0
tcp://0.0.0.0/subpath
tcp://0.0.0.0:9091 tcp://0.0.0.0:9091
tcp://0.0.0.0:9091/subpath
tcp://:9091 tcp://:9091
tcp://:9091/subpath
0.0.0.0:9091 0.0.0.0:9091
udp://0.0.0.0:123 udp://0.0.0.0:123

View File

@ -38,3 +38,4 @@ this instance if you wanted to downgrade to pre1 you would need to use an Authel
| 7 | 4.37.3 | Fixed some schema inconsistencies most notably the MySQL/MariaDB Engine and Collation | | 7 | 4.37.3 | Fixed some schema inconsistencies most notably the MySQL/MariaDB Engine and Collation |
| 8 | 4.38.0 | OpenID Connect 1.0 Pushed Authorization Requests | | 8 | 4.38.0 | OpenID Connect 1.0 Pushed Authorization Requests |
| 9 | 4.38.0 | Fix a PostgreSQL NOT NULL constraint issue on the `aaguid` column of the `webauthn_devices` table | | 9 | 4.38.0 | Fix a PostgreSQL NOT NULL constraint issue on the `aaguid` column of the `webauthn_devices` table |
| 10 | 4.38.0 | WebAuthn adjustments for multi-cookie domain changes |

View File

@ -22,7 +22,7 @@ toc: true
telemetry: telemetry:
metrics: metrics:
enabled: false enabled: false
address: 'tcp://:9959/' address: 'tcp://:9959'
buffers: buffers:
read: 4096 read: 4096
write: 4096 write: 4096
@ -44,7 +44,7 @@ Determines if the [Prometheus] HTTP Metrics Exporter is enabled.
### address ### address
{{< confkey type="address" default="tcp://:9959/" required="no" >}} {{< confkey type="address" default="tcp://:9959" required="no" >}}
*__Reference Note:__ This configuration option uses the [address common syntax](../prologue/common.md#address). Please *__Reference Note:__ This configuration option uses the [address common syntax](../prologue/common.md#address). Please
see the [documentation](../prologue/common.md#address) on this format for more information.* see the [documentation](../prologue/common.md#address) on this format for more information.*

View File

@ -2,7 +2,7 @@
title: "Database Schema" title: "Database Schema"
description: "Authelia Development Database Schema Guidelines" description: "Authelia Development Database Schema Guidelines"
lead: "This section covers the database schema guidelines we use for development." lead: "This section covers the database schema guidelines we use for development."
date: 2022-11-19T16:47:09+11:00 date: 2022-11-19T17:42:03+11:00
draft: false draft: false
images: [] images: []
menu: menu:

View File

@ -92,35 +92,35 @@ Use this [Standalone Example](#standalone-example) if you want to use
version: "3.8" version: "3.8"
secrets: secrets:
JWT_SECRET: JWT_SECRET:
file: '${PWD}/data/authelia/secrets/JWT_SECRET' file: ${PWD}/data/authelia/secrets/JWT_SECRET
SESSION_SECRET: SESSION_SECRET:
file: '${PWD}/data/authelia/secrets/SESSION_SECRET' file: ${PWD}/data/authelia/secrets/SESSION_SECRET
STORAGE_PASSWORD: STORAGE_PASSWORD:
file: '${PWD}/data/authelia/secrets/STORAGE_PASSWORD' file: ${PWD}/data/authelia/secrets/STORAGE_PASSWORD
STORAGE_ENCRYPTION_KEY: STORAGE_ENCRYPTION_KEY:
file: '${PWD}/data/authelia/secrets/STORAGE_ENCRYPTION_KEY' file: ${PWD}/data/authelia/secrets/STORAGE_ENCRYPTION_KEY
services: services:
authelia: authelia:
container_name: 'authelia' container_name: authelia
image: 'docker.io/authelia/authelia:latest' image: docker.io/authelia/authelia:latest
restart: 'unless-stopped' restart: unless-stopped
networks: networks:
net: net:
aliases: [] aliases: []
expose: expose:
- 9091 - 9091
secrets: ['JWT_SECRET', 'SESSION_SECRET', 'STORAGE_PASSWORD', 'STORAGE_ENCRYPTION_KEY'] secrets: [JWT_SECRET, SESSION_SECRET, STORAGE_PASSWORD, STORAGE_ENCRYPTION_KEY]
environment: environment:
AUTHELIA_JWT_SECRET_FILE: '/run/secrets/JWT_SECRET' AUTHELIA_JWT_SECRET_FILE: /run/secrets/JWT_SECRET
AUTHELIA_SESSION_SECRET_FILE: '/run/secrets/SESSION_SECRET' AUTHELIA_SESSION_SECRET_FILE: /run/secrets/SESSION_SECRET
AUTHELIA_STORAGE_POSTGRES_PASSWORD_FILE: '/run/secrets/STORAGE_PASSWORD' AUTHELIA_STORAGE_POSTGRES_PASSWORD_FILE: /run/secrets/STORAGE_PASSWORD
AUTHELIA_STORAGE_ENCRYPTION_KEY_FILE: '/run/secrets/STORAGE_ENCRYPTION_KEY' AUTHELIA_STORAGE_ENCRYPTION_KEY_FILE: /run/secrets/STORAGE_ENCRYPTION_KEY
volumes: volumes:
- '${PWD}/data/authelia/config:/config' - ${PWD}/data/authelia/config:/config
networks: networks:
net: net:
external: true external: true
name: 'net' name: net
... ...
``` ```
{{< /details >}} {{< /details >}}
@ -136,26 +136,26 @@ Use this [Standalone Example](#standalone-example) if you want to use a standard
version: "3.8" version: "3.8"
services: services:
authelia: authelia:
container_name: 'authelia' container_name: authelia
image: 'docker.io/authelia/authelia:latest' image: docker.io/authelia/authelia:latest
restart: 'unless-stopped' restart: unless-stopped
networks: networks:
net: net:
aliases: [] aliases: []
expose: expose:
- 9091 - 9091
environment: environment:
AUTHELIA_JWT_SECRET_FILE: '/secrets/JWT_SECRET' AUTHELIA_JWT_SECRET_FILE: /secrets/JWT_SECRET
AUTHELIA_SESSION_SECRET_FILE: '/secrets/SESSION_SECRET' AUTHELIA_SESSION_SECRET_FILE: /secrets/SESSION_SECRET
AUTHELIA_STORAGE_POSTGRES_PASSWORD_FILE: '/secrets/STORAGE_PASSWORD' AUTHELIA_STORAGE_POSTGRES_PASSWORD_FILE: /secrets/STORAGE_PASSWORD
AUTHELIA_STORAGE_ENCRYPTION_KEY_FILE: '/secrets/STORAGE_ENCRYPTION_KEY' AUTHELIA_STORAGE_ENCRYPTION_KEY_FILE: /secrets/STORAGE_ENCRYPTION_KEY
volumes: volumes:
- '${PWD}/data/authelia/config:/config' - ${PWD}/data/authelia/config:/config
- '${PWD}/data/authelia/secrets:/secrets' - ${PWD}/data/authelia/secrets:/secrets
networks: networks:
net: net:
external: true external: true
name: 'net' name: net
``` ```
... ...
{{< /details >}} {{< /details >}}

View File

@ -55,16 +55,14 @@ In your Authelia configuration you will need to enter and update the following v
base_dn: DC=example,DC=com base_dn: DC=example,DC=com
additional_users_dn: OU=users additional_users_dn: OU=users
users_filter: (&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=person)) users_filter: (&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=person))
username_attribute: uid
mail_attribute: mail
display_name_attribute: displayName
additional_groups_dn: OU=groups additional_groups_dn: OU=groups
groups_filter: (&(member=UID={input},OU=users,DC=example,DC=com)(objectClass=groupOfNames)) groups_filter: (&(member=UID={input},OU=users,DC=example,DC=com)(objectClass=groupOfNames))
group_name_attribute: cn
user: UID=authelia,OU=service accounts,DC=example,DC=com user: UID=authelia,OU=service accounts,DC=example,DC=com
password: "SUPER_COMPLEX_PASSWORD" password: "SUPER_COMPLEX_PASSWORD"
attributes:
distinguished_name: 'distinguishedName'
username: 'uid'
mail: 'mail'
member_of: 'memberOf'
group_name: 'cn'
``` ```
Following this, restart Authelia, and you should be able to begin using LDAP integration for your user logins, with Following this, restart Authelia, and you should be able to begin using LDAP integration for your user logins, with
Authelia taking the email attribute for users straight from the 'mail' attribute within the LDAP object. Authelia taking the email attribute for users straight from the 'mail' attribute within the LDAP object.
@ -102,18 +100,16 @@ In your Authelia configuration you will need to enter and update the following v
skip_verify: true skip_verify: true
minimum_version: TLS1.2 minimum_version: TLS1.2
base_dn: dc=example,DC=com base_dn: dc=example,DC=com
username_attribute: uid
additional_users_dn: CN=users,CN=accounts additional_users_dn: CN=users,CN=accounts
users_filter: (&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=person)) users_filter: (&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=person))
additional_groups_dn: OU=groups additional_groups_dn: OU=groups
groups_filter: (&(member=UID={input},CN=users,CN=accounts,DC=example,DC=com)(objectClass=groupOfNames)) groups_filter: (&(member=UID={input},CN=users,CN=accounts,DC=example,DC=com)(objectClass=groupOfNames))
group_name_attribute: cn
mail_attribute: mail
display_name_attribute: displayName
user: UID=authelia,CN=users,CN=accounts,DC=example,DC=com user: UID=authelia,CN=users,CN=accounts,DC=example,DC=com
password: "SUPER_COMPLEX_PASSWORD" password: "SUPER_COMPLEX_PASSWORD"
attributes:
distinguished_name: 'distinguishedName'
username: 'uid'
mail: 'mail'
member_of: 'memberOf'
group_name: 'cn'
``` ```
Following this, restart Authelia, and you should be able to begin using LDAP integration for your user logins, with Following this, restart Authelia, and you should be able to begin using LDAP integration for your user logins, with
Authelia taking the email attribute for users straight from the 'mail' attribute within the LDAP object. Authelia taking the email attribute for users straight from the 'mail' attribute within the LDAP object.
@ -143,21 +139,19 @@ ldap:
timeout: 5s timeout: 5s
start_tls: false start_tls: false
base_dn: dc=example,DC=com base_dn: dc=example,DC=com
username_attribute: uid
additional_users_dn: OU=people additional_users_dn: OU=people
# To allow sign in both with username and email, one can use a filter like # To allow sign in both with username and email, one can use a filter like
# (&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=person)) # (&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=person))
users_filter: (&({username_attribute}={input})(objectClass=person)) users_filter: (&({username_attribute}={input})(objectClass=person))
additional_groups_dn: OU=groups additional_groups_dn: OU=groups
groups_filter: (member={dn}) groups_filter: (member={dn})
group_name_attribute: cn
mail_attribute: mail
display_name_attribute: displayName
# The username and password of the admin or service user. # The username and password of the admin or service user.
user: UID=authelia,OU=people,DC=example,DC=com user: UID=authelia,OU=people,DC=example,DC=com
password: "SUPER_COMPLEX_PASSWORD" password: "SUPER_COMPLEX_PASSWORD"
attributes:
distinguished_name: 'distinguishedName'
username: 'uid'
mail: 'mail'
member_of: 'memberOf'
group_name: 'cn'
``` ```
Following this, restart Authelia, and you should be able to begin using lldap integration for your user logins, with Following this, restart Authelia, and you should be able to begin using lldap integration for your user logins, with
Authelia taking the email attribute for users straight from the 'mail' attribute within the LDAP object. Authelia taking the email attribute for users straight from the 'mail' attribute within the LDAP object.

View File

@ -45,14 +45,14 @@ Easy, right?!
## Frequently Asked Questions ## Frequently Asked Questions
### Can I register multiple FIDO2 WebAuthn devices? ### Can I register multiple FIDO2 WebAuthn credentials?
At present this is not possible in the frontend. However the backend technically supports it. We plan to add this to the Yes, as of v4.38.0 and above Authelia supprots registering multiple WebAuthn credentials as per the
frontend in the near future. Subscribe to [this issue](https://github.com/authelia/authelia/issues/275) for updates. [roadmap](../../../roadmap/active/webauthn.md#multi-device-registration).
### Can I perform a passwordless login? ### Can I perform a passwordless login?
Not at this time. We will tackle this at a later date. Not at this time. We will tackle this at a later date as per the [roadmap](../../../roadmap/active/webauthn.md#passwordless-login).
### Why don't I have access to the *Security Key* option? ### Why don't I have access to the *Security Key* option?

View File

@ -75,32 +75,6 @@ The following implementations exist:
[GLAuth]: https://glauth.github.io/ [GLAuth]: https://glauth.github.io/
[RFC2307bis]: https://datatracker.ietf.org/doc/html/draft-howard-rfc2307bis-02 [RFC2307bis]: https://datatracker.ietf.org/doc/html/draft-howard-rfc2307bis-02
### Group Search Modes
There are currently two group search modes that exist.
#### Search Mode: filter
The `filter` search mode is the default search mode. Generally this is recommended.
#### Search Mode: memberof
The `memberof` search mode is a special search mode. Generally this is discouraged and is currently experimental.
Some systems provide a `memberOf` attribute which may include additional groups that the user is a member of. This
search mode allows using this attribute as a method to determine their groups. How it works is the search is performed
against the base with the subtree scope and the groups filter must include one of the `{memberof:*}` replacements, and
the distinguished names of the results from the search are compared (case-insensitive) against the users `memberOf`
attribute to determine if they are members.
This means:
1. The groups still must be in the search base that you have configured.
2. The `memberOf` attribute *__MUST__* include the distinguished name of the group.
3. If the `{memberof:dn}` replacement is used:
1. The distinguished name *__MUST__* be searchable by your directory server.
3. The first relative distinguished name of the distinguished name *__MUST__* be search
### Filter replacements ### Filter replacements
Various replacements occur in the user and groups filter. The replacements either occur at startup or upon an LDAP Various replacements occur in the user and groups filter. The replacements either occur at startup or upon an LDAP
@ -111,65 +85,25 @@ is ever established. In addition to this, during the startup phase we purposeful
phase replacements exist so we only have to check if the replacement is necessary once, and we don't needlessly perform phase replacements exist so we only have to check if the replacement is necessary once, and we don't needlessly perform
every possible replacement on every search regardless of if it's needed or not. every possible replacement on every search regardless of if it's needed or not.
#### General filter replacements
| Placeholder | Phase | Replacement |
|:------------------------------:|:-------:|:-------------------------------------------:|
| {distinguished_name_attribute} | startup | The configured distinguished name attribute |
| {username_attribute} | startup | The configured username attribute |
| {mail_attribute} | startup | The configured mail attribute |
| {display_name_attribute} | startup | The configured display name attribute |
| {member_of_attribute} | startup | The configured member of attribute |
| {input} | search | The input into the username field |
#### Users filter replacements #### Users filter replacements
| Placeholder | Phase | Replacement | | Placeholder | Phase | Replacement |
|:------------------------------:|:-------:|:----------------------------------------------------------------------------------------------------------------:| |:------------------------:|:-------:|:----------------------------------------------------------------------------------------------------------------:|
| {date-time:generalized} | search | The current UTC time formatted as a LDAP generalized time in the format of `20060102150405.0Z` | | {username_attribute} | startup | The configured username attribute |
| {date-time:unix} | search | The current time formatted as a Unix epoch | | {mail_attribute} | startup | The configured mail attribute |
| {date-time:microsoft-nt} | search | The current time formatted as a Microsoft NT epoch which is used by some Microsoft [Active Directory] attributes | | {display_name_attribute} | startup | The configured display name attribute |
| {input} | search | The input into the username field |
| {date-time:generalized} | search | The current UTC time formatted as a LDAP generalized time in the format of `20060102150405.0Z` |
| {date-time:unix} | search | The current time formatted as a Unix epoch |
| {date-time:microsoft-nt} | search | The current time formatted as a Microsoft NT epoch which is used by some Microsoft [Active Directory] attributes |
#### Groups filter replacements #### Groups filter replacements
| Placeholder | Phase | Replacement | | Placeholder | Phase | Replacement |
|:--------------:|:------:|:----------------------------------------------------------------------------------------------------------------------------------------------------:| |:-----------:|:------:|:-------------------------------------------------------------------------:|
| {username} | search | The username from the profile lookup obtained from the username attribute | | {input} | search | The input into the username field |
| {dn} | search | The distinguished name from the profile lookup | | {username} | search | The username from the profile lookup obtained from the username attribute |
| {memberof:dn} | search | See the detailed section below | | {dn} | search | The distinguished name from the profile lookup |
| {memberof:rdn} | search | Only allowed with the `memberof` search method and contains the first relative distinguished name of every `memberOf` entry a use has in parenthesis |
##### memberof:dn
Requirements:
1. Must be using the `memberof` search mode.
2. Must have the distinguished name attribute configured in Authelia.
3. Directory server must support searching by the distinguished name attribute (many directory services *__DO NOT__*
have a distinguished name attribute).
##### memberof:rdn
Requirements:
1. Must be using the `memberof` search mode.
2. Directory server must support searching by the first relative distinguished name as an attribute.
Splits every `memberOf` value to obtain th e first relative distinguished name and joins all of those after surrounding
them in parenthesis. This makes the general suggested filter pattern for this particular replacement
`(|{memberof:rdn})`. The format of this value is as follows:
```text
(<RDN>)
```
For example if the user has the following distinguished names in their object:
- CN=abc,OU=groups,DC=example,DC=com
- CN=xyz,OU=groups,DC=example,DC=com
The value will be replaced with `(CN=abc)(CN=xyz)` which using the suggested pattern for the filter becomes
`(|(CN=abc)(CN=xyz))` which will then return any user that as a `CN` of `abc` or `xyz`.
### Defaults ### Defaults
@ -188,14 +122,14 @@ The following set defaults for the `additional_users_dn` and `additional_groups_
This table describes the attribute defaults for each implementation. i.e. the username_attribute is described by the This table describes the attribute defaults for each implementation. i.e. the username_attribute is described by the
Username column. Username column.
| Implementation | Username | Display Name | Mail | Group Name | Distinguished Name | Member Of | | Implementation | Username | Display Name | Mail | Group Name |
|:---------------:|:--------------:|:------------:|:----:|:----------:|:------------------:|:---------:| |:---------------:|:--------------:|:------------:|:----:|:----------:|
| custom | N/A | displayName | mail | cn | N/A | N/A | | custom | N/A | displayName | mail | cn |
| activedirectory | sAMAccountName | displayName | mail | cn | distinguishedName | memberOf | | activedirectory | sAMAccountName | displayName | mail | cn |
| rfc2307bis | uid | displayName | mail | cn | N/A | memberOf | | rfc2307bis | uid | displayName | mail | cn |
| freeipa | uid | displayName | mail | cn | N/A | memberOf | | freeipa | uid | displayName | mail | cn |
| lldap | uid | cn | mail | cn | N/A | memberOf | | lldap | uid | cn | mail | cn |
| glauth | cn | description | mail | cn | N/A | memberOf | | glauth | cn | description | mail | cn |
#### Filter defaults #### Filter defaults
@ -212,8 +146,8 @@ the following conditions:
- Their password is expired: - Their password is expired:
- The [Active Directory] implementation achieves this via the `(!(pwdLastSet=0))` filter. - The [Active Directory] implementation achieves this via the `(!(pwdLastSet=0))` filter.
- The [FreeIPA] implementation achieves this via the `(krbPasswordExpiration>={date-time:generalized})` filter. - The [FreeIPA] implementation achieves this via the `(krbPasswordExpiration>={date-time:generalized})` filter.
- The [RFC2307bis] implementation achieves this via the `(!(pwdReset=TRUE))` filter.
- The following implementations have no suitable attribute for this as far as we're aware: - The following implementations have no suitable attribute for this as far as we're aware:
- [RFC2307bis]
- [GLAuth] - [GLAuth]
- [lldap] - [lldap]
- Their account is expired: - Their account is expired:
@ -228,7 +162,7 @@ the following conditions:
|:---------------:|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|:-----------------------------------------------------------------------------------------------------------------------------------------:| |:---------------:|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|:-----------------------------------------------------------------------------------------------------------------------------------------:|
| custom | N/A | N/A | | custom | N/A | N/A |
| activedirectory | (&(&#124;({username_attribute}={input})({mail_attribute}={input}))(sAMAccountType=805306368)(!(userAccountControl:1.2.840.113556.1.4.803:=2))(!(pwdLastSet=0))(&#124;(!(accountExpires=*))(accountExpires=0)(accountExpires>={date-time:microsoft-nt}))) | (&(member={dn})(&#124;(sAMAccountType=268435456)(sAMAccountType=536870912))) | | activedirectory | (&(&#124;({username_attribute}={input})({mail_attribute}={input}))(sAMAccountType=805306368)(!(userAccountControl:1.2.840.113556.1.4.803:=2))(!(pwdLastSet=0))(&#124;(!(accountExpires=*))(accountExpires=0)(accountExpires>={date-time:microsoft-nt}))) | (&(member={dn})(&#124;(sAMAccountType=268435456)(sAMAccountType=536870912))) |
| rfc2307bis | (&(&#124;({username_attribute}={input})({mail_attribute}={input}))(&#124;(objectClass=inetOrgPerson)(objectClass=organizationalPerson))(!(pwdReset=TRUE))) | (&(&#124;(member={dn})(uniqueMember={dn}))(&#124;(objectClass=groupOfNames)(objectClass=groupOfUniqueNames)(objectClass=groupOfMembers))) | | rfc2307bis | (&(&#124;({username_attribute}={input})({mail_attribute}={input}))(&#124;(objectClass=inetOrgPerson)(objectClass=organizationalPerson))) | (&(&#124;(member={dn})(uniqueMember={dn}))(&#124;(objectClass=groupOfNames)(objectClass=groupOfUniqueNames)(objectClass=groupOfMembers))) |
| freeipa | (&(&#124;({username_attribute}={input})({mail_attribute}={input}))(objectClass=person)(!(nsAccountLock=TRUE))(krbPasswordExpiration>={date-time:generalized})(&#124;(!(krbPrincipalExpiration=*))(krbPrincipalExpiration>={date-time:generalized}))) | (&(member={dn})(objectClass=groupOfNames)) | | freeipa | (&(&#124;({username_attribute}={input})({mail_attribute}={input}))(objectClass=person)(!(nsAccountLock=TRUE))(krbPasswordExpiration>={date-time:generalized})(&#124;(!(krbPrincipalExpiration=*))(krbPrincipalExpiration>={date-time:generalized}))) | (&(member={dn})(objectClass=groupOfNames)) |
| lldap | (&(&#124;({username_attribute}={input})({mail_attribute}={input}))(objectClass=person)) | (&(member={dn})(objectClass=groupOfNames)) | | lldap | (&(&#124;({username_attribute}={input})({mail_attribute}={input}))(objectClass=person)) | (&(member={dn})(objectClass=groupOfNames)) |
| glauth | (&(&#124;({username_attribute}={input})({mail_attribute}={input}))(objectClass=posixAccount)(!(accountStatus=inactive))) | (&(uniqueMember={dn})(objectClass=posixGroup)) | | glauth | (&(&#124;({username_attribute}={input})({mail_attribute}={input}))(objectClass=posixAccount)(!(accountStatus=inactive))) | (&(uniqueMember={dn})(objectClass=posixGroup)) |

View File

@ -2,7 +2,7 @@
title: "Database Integrations" title: "Database Integrations"
description: "A database integration reference guide" description: "A database integration reference guide"
lead: "This section contains a database integration reference guide for Authelia." lead: "This section contains a database integration reference guide for Authelia."
date: 2022-11-19T16:47:09+11:00 date: 2022-11-19T17:42:03+11:00
draft: false draft: false
images: [] images: []
menu: menu:

View File

@ -2,7 +2,7 @@
title: "Integrations" title: "Integrations"
description: "A collection of integration reference guides" description: "A collection of integration reference guides"
lead: "This section contains integration reference guides for Authelia." lead: "This section contains integration reference guides for Authelia."
date: 2022-11-19T16:47:09+11:00 date: 2022-11-19T17:42:03+11:00
draft: false draft: false
images: [] images: []
menu: menu:

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
{"defaults":{"language":{"display":"English","locale":"en"},"namespace":"portal"},"namespaces":["portal"],"languages":[{"display":"English","locale":"en","namespaces":["portal"],"fallbacks":["en"]},{"display":"Arabic","locale":"ar","namespaces":["portal"],"fallbacks":["en"]},{"display":"Arabic (Saudi Arabia)","locale":"ar-SA","namespaces":["portal"],"fallbacks":["ar","en"]},{"display":"Czech","locale":"cs","namespaces":["portal"],"fallbacks":["en"]},{"display":"Czech (Czechia)","locale":"cs-CZ","namespaces":["portal"],"fallbacks":["cs","en"]},{"display":"Danish","locale":"da","namespaces":["portal"],"fallbacks":["en"]},{"display":"Danish (Denmark)","locale":"da-DK","namespaces":["portal"],"fallbacks":["da","en"]},{"display":"German","locale":"de","namespaces":["portal"],"fallbacks":["en"]},{"display":"Greek","locale":"el","namespaces":["portal"],"fallbacks":["en"]},{"display":"Greek (Greece)","locale":"el-GR","namespaces":["portal"],"fallbacks":["el","en"]},{"display":"Spanish","locale":"es","namespaces":["portal"],"fallbacks":["en"]},{"display":"Finnish","locale":"fi","namespaces":["portal"],"fallbacks":["en"]},{"display":"French","locale":"fr","namespaces":["portal"],"fallbacks":["en"]},{"display":"Hungarian","locale":"hu","namespaces":["portal"],"fallbacks":["en"]},{"display":"Italian","locale":"it","namespaces":["portal"],"fallbacks":["en"]},{"display":"Japanese","locale":"ja","namespaces":["portal"],"fallbacks":["en"]},{"display":"Japanese (Japan)","locale":"ja-JP","namespaces":["portal"],"fallbacks":["ja","en"]},{"display":"Norwegian Bokmål","locale":"nb","namespaces":["portal"],"fallbacks":["en"]},{"display":"Norwegian Bokmål (Norway)","locale":"nb-NO","namespaces":["portal"],"fallbacks":["nb","en"]},{"display":"Dutch","locale":"nl","namespaces":["portal"],"fallbacks":["en"]},{"display":"Norwegian Bokmål","locale":"no","namespaces":["portal"],"fallbacks":["en"]},{"display":"Polish","locale":"pl","namespaces":["portal"],"fallbacks":["en"]},{"display":"Portuguese","locale":"pt","namespaces":["portal"],"fallbacks":["en"]},{"display":"Brazilian Portuguese","locale":"pt-BR","namespaces":["portal"],"fallbacks":["en"]},{"display":"Romanian","locale":"ro","namespaces":["portal"],"fallbacks":["en"]},{"display":"Russian","locale":"ru","namespaces":["portal"],"fallbacks":["en"]},{"display":"Slovenian","locale":"sl","namespaces":["portal"],"fallbacks":["en"]},{"display":"Slovenian (Slovenia)","locale":"sl-SI","namespaces":["portal"],"fallbacks":["sl","en"]},{"display":"Swedish","locale":"sv","namespaces":["portal"],"fallbacks":["en"]},{"display":"Swedish (Sweden)","locale":"sv-SE","namespaces":["portal"],"fallbacks":["sv","en"]},{"display":"Ukrainian","locale":"uk","namespaces":["portal"],"fallbacks":["en"]},{"display":"Ukrainian (Ukraine)","locale":"uk-UA","namespaces":["portal"],"fallbacks":["uk","en"]},{"display":"Chinese","locale":"zh","namespaces":["portal"],"fallbacks":["en"]},{"display":"Chinese (China)","locale":"zh-CN","namespaces":["portal"],"fallbacks":["zh","en"]},{"display":"Chinese (Taiwan)","locale":"zh-TW","namespaces":["portal"],"fallbacks":["en"]}]} {"defaults":{"language":{"display":"English","locale":"en"},"namespace":"portal"},"namespaces":["portal","settings"],"languages":[{"display":"English","locale":"en","namespaces":["portal","settings"],"fallbacks":["en"]},{"display":"Arabic","locale":"ar","namespaces":["portal"],"fallbacks":["en"]},{"display":"Arabic (Saudi Arabia)","locale":"ar-SA","namespaces":["portal"],"fallbacks":["ar","en"]},{"display":"Czech","locale":"cs","namespaces":["portal"],"fallbacks":["en"]},{"display":"Czech (Czechia)","locale":"cs-CZ","namespaces":["portal"],"fallbacks":["cs","en"]},{"display":"Danish","locale":"da","namespaces":["portal"],"fallbacks":["en"]},{"display":"Danish (Denmark)","locale":"da-DK","namespaces":["portal"],"fallbacks":["da","en"]},{"display":"German","locale":"de","namespaces":["portal"],"fallbacks":["en"]},{"display":"Greek","locale":"el","namespaces":["portal"],"fallbacks":["en"]},{"display":"Greek (Greece)","locale":"el-GR","namespaces":["portal"],"fallbacks":["el","en"]},{"display":"Spanish","locale":"es","namespaces":["portal"],"fallbacks":["en"]},{"display":"Finnish","locale":"fi","namespaces":["portal"],"fallbacks":["en"]},{"display":"French","locale":"fr","namespaces":["portal"],"fallbacks":["en"]},{"display":"Hungarian","locale":"hu","namespaces":["portal"],"fallbacks":["en"]},{"display":"Italian","locale":"it","namespaces":["portal"],"fallbacks":["en"]},{"display":"Japanese","locale":"ja","namespaces":["portal"],"fallbacks":["en"]},{"display":"Japanese (Japan)","locale":"ja-JP","namespaces":["portal"],"fallbacks":["ja","en"]},{"display":"Norwegian Bokmål","locale":"nb","namespaces":["portal"],"fallbacks":["en"]},{"display":"Norwegian Bokmål (Norway)","locale":"nb-NO","namespaces":["portal"],"fallbacks":["nb","en"]},{"display":"Dutch","locale":"nl","namespaces":["portal"],"fallbacks":["en"]},{"display":"Norwegian Bokmål","locale":"no","namespaces":["portal"],"fallbacks":["en"]},{"display":"Polish","locale":"pl","namespaces":["portal"],"fallbacks":["en"]},{"display":"Portuguese","locale":"pt","namespaces":["portal"],"fallbacks":["en"]},{"display":"Brazilian Portuguese","locale":"pt-BR","namespaces":["portal"],"fallbacks":["en"]},{"display":"Romanian","locale":"ro","namespaces":["portal"],"fallbacks":["en"]},{"display":"Russian","locale":"ru","namespaces":["portal"],"fallbacks":["en"]},{"display":"Slovenian","locale":"sl","namespaces":["portal"],"fallbacks":["en"]},{"display":"Slovenian (Slovenia)","locale":"sl-SI","namespaces":["portal"],"fallbacks":["sl","en"]},{"display":"Swedish","locale":"sv","namespaces":["portal"],"fallbacks":["en"]},{"display":"Swedish (Sweden)","locale":"sv-SE","namespaces":["portal"],"fallbacks":["sv","en"]},{"display":"Ukrainian","locale":"uk","namespaces":["portal"],"fallbacks":["en"]},{"display":"Ukrainian (Ukraine)","locale":"uk-UA","namespaces":["portal"],"fallbacks":["uk","en"]},{"display":"Chinese","locale":"zh","namespaces":["portal"],"fallbacks":["en"]},{"display":"Chinese (China)","locale":"zh-CN","namespaces":["portal"],"fallbacks":["zh","en"]},{"display":"Chinese (Taiwan)","locale":"zh-TW","namespaces":["portal"],"fallbacks":["en"]}]}

View File

@ -1,144 +0,0 @@
syntax = "proto3";
package envoy.service.auth.v3;
import "envoy/config/core/v3/base.proto";
import "envoy/service/auth/v3/attribute_context.proto";
import "envoy/type/v3/http_status.proto";
import "google/protobuf/struct.proto";
import "google/rpc/status.proto";
import "envoy/annotations/deprecation.proto";
import "udpa/annotations/status.proto";
import "udpa/annotations/versioning.proto";
option java_package = "io.envoyproxy.envoy.service.auth.v3";
option java_outer_classname = "ExternalAuthProto";
option java_multiple_files = true;
option go_package = "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3;authv3";
option (udpa.annotations.file_status).package_version_status = ACTIVE;
// [#protodoc-title: Authorization service]
// The authorization service request messages used by external authorization :ref:`network filter
// <config_network_filters_ext_authz>` and :ref:`HTTP filter <config_http_filters_ext_authz>`.
// A generic interface for performing authorization check on incoming
// requests to a networked service.
service Authorization {
// Performs authorization check based on the attributes associated with the
// incoming request, and returns status `OK` or not `OK`.
rpc Check(CheckRequest) returns (CheckResponse) {
}
}
message CheckRequest {
option (udpa.annotations.versioning).previous_message_type = "envoy.service.auth.v2.CheckRequest";
// The request attributes.
AttributeContext attributes = 1;
}
// HTTP attributes for a denied response.
message DeniedHttpResponse {
option (udpa.annotations.versioning).previous_message_type =
"envoy.service.auth.v2.DeniedHttpResponse";
// This field allows the authorization service to send an HTTP response status code to the
// downstream client. If not set, Envoy sends ``403 Forbidden`` HTTP status code by default.
type.v3.HttpStatus status = 1;
// This field allows the authorization service to send HTTP response headers
// to the downstream client. Note that the :ref:`append field in HeaderValueOption <envoy_v3_api_field_config.core.v3.HeaderValueOption.append>` defaults to
// false when used in this message.
repeated config.core.v3.HeaderValueOption headers = 2;
// This field allows the authorization service to send a response body data
// to the downstream client.
string body = 3;
}
// HTTP attributes for an OK response.
// [#next-free-field: 9]
message OkHttpResponse {
option (udpa.annotations.versioning).previous_message_type =
"envoy.service.auth.v2.OkHttpResponse";
// HTTP entity headers in addition to the original request headers. This allows the authorization
// service to append, to add or to override headers from the original request before
// dispatching it to the upstream. Note that the :ref:`append field in HeaderValueOption <envoy_v3_api_field_config.core.v3.HeaderValueOption.append>` defaults to
// false when used in this message. By setting the ``append`` field to ``true``,
// the filter will append the correspondent header value to the matched request header.
// By leaving ``append`` as false, the filter will either add a new header, or override an existing
// one if there is a match.
repeated config.core.v3.HeaderValueOption headers = 2;
// HTTP entity headers to remove from the original request before dispatching
// it to the upstream. This allows the authorization service to act on auth
// related headers (like ``Authorization``), process them, and consume them.
// Under this model, the upstream will either receive the request (if it's
// authorized) or not receive it (if it's not), but will not see headers
// containing authorization credentials.
//
// Pseudo headers (such as ``:authority``, ``:method``, ``:path`` etc), as well as
// the header ``Host``, may not be removed as that would make the request
// malformed. If mentioned in ``headers_to_remove`` these special headers will
// be ignored.
//
// When using the HTTP service this must instead be set by the HTTP
// authorization service as a comma separated list like so:
// ``x-envoy-auth-headers-to-remove: one-auth-header, another-auth-header``.
repeated string headers_to_remove = 5;
// This field has been deprecated in favor of :ref:`CheckResponse.dynamic_metadata
// <envoy_v3_api_field_service.auth.v3.CheckResponse.dynamic_metadata>`. Until it is removed,
// setting this field overrides :ref:`CheckResponse.dynamic_metadata
// <envoy_v3_api_field_service.auth.v3.CheckResponse.dynamic_metadata>`.
google.protobuf.Struct dynamic_metadata = 3
[deprecated = true, (envoy.annotations.deprecated_at_minor_version) = "3.0"];
// This field allows the authorization service to send HTTP response headers
// to the downstream client on success. Note that the :ref:`append field in HeaderValueOption <envoy_v3_api_field_config.core.v3.HeaderValueOption.append>`
// defaults to false when used in this message.
repeated config.core.v3.HeaderValueOption response_headers_to_add = 6;
// This field allows the authorization service to set (and overwrite) query
// string parameters on the original request before it is sent upstream.
repeated config.core.v3.QueryParameter query_parameters_to_set = 7;
// This field allows the authorization service to specify which query parameters
// should be removed from the original request before it is sent upstream. Each
// element in this list is a case-sensitive query parameter name to be removed.
repeated string query_parameters_to_remove = 8;
}
// Intended for gRPC and Network Authorization servers ``only``.
message CheckResponse {
option (udpa.annotations.versioning).previous_message_type =
"envoy.service.auth.v2.CheckResponse";
// Status ``OK`` allows the request. Any other status indicates the request should be denied, and
// for HTTP filter, if not overridden by :ref:`denied HTTP response status <envoy_v3_api_field_service.auth.v3.DeniedHttpResponse.status>`
// Envoy sends ``403 Forbidden`` HTTP status code by default.
google.rpc.Status status = 1;
// An message that contains HTTP response attributes. This message is
// used when the authorization service needs to send custom responses to the
// downstream client or, to modify/add request headers being dispatched to the upstream.
oneof http_response {
// Supplies http attributes for a denied response.
DeniedHttpResponse denied_response = 2;
// Supplies http attributes for an ok response.
OkHttpResponse ok_response = 3;
}
// Optional response metadata that will be emitted as dynamic metadata to be consumed by the next
// filter. This metadata lives in a namespace specified by the canonical name of extension filter
// that requires it:
//
// - :ref:`envoy.filters.http.ext_authz <config_http_filters_ext_authz_dynamic_metadata>` for HTTP filter.
// - :ref:`envoy.filters.network.ext_authz <config_network_filters_ext_authz_dynamic_metadata>` for network filter.
google.protobuf.Struct dynamic_metadata = 4;
}

View File

@ -45,7 +45,7 @@ services:
- TZ=Australia/Melbourne - TZ=Australia/Melbourne
traefik: traefik:
image: traefik:v2.10.3 image: traefik:v2.10.1
container_name: traefik container_name: traefik
volumes: volumes:
- ./traefik:/etc/traefik - ./traefik:/etc/traefik

View File

@ -32,7 +32,7 @@ services:
- TZ=Australia/Melbourne - TZ=Australia/Melbourne
traefik: traefik:
image: traefik:v2.10.3 image: traefik:v2.10.1
container_name: traefik container_name: traefik
volumes: volumes:
- ./traefik:/etc/traefik - ./traefik:/etc/traefik

56
go.mod
View File

@ -12,15 +12,15 @@ require (
github.com/fsnotify/fsnotify v1.6.0 github.com/fsnotify/fsnotify v1.6.0
github.com/go-asn1-ber/asn1-ber v1.5.4 github.com/go-asn1-ber/asn1-ber v1.5.4
github.com/go-crypt/crypt v0.2.9 github.com/go-crypt/crypt v0.2.9
github.com/go-ldap/ldap/v3 v3.4.5 github.com/go-ldap/ldap/v3 v3.4.5-0.20230521105649-cdb0754f6668
github.com/go-rod/rod v0.113.3 github.com/go-rod/rod v0.113.1
github.com/go-sql-driver/mysql v1.7.1 github.com/go-sql-driver/mysql v1.7.1
github.com/go-webauthn/webauthn v0.5.0 github.com/go-webauthn/webauthn v0.8.2
github.com/golang-jwt/jwt/v4 v4.5.0 github.com/golang-jwt/jwt/v4 v4.5.0
github.com/golang/mock v1.6.0 github.com/golang/mock v1.6.0
github.com/google/uuid v1.3.0 github.com/google/uuid v1.3.0
github.com/hashicorp/go-retryablehttp v0.7.4 github.com/hashicorp/go-retryablehttp v0.7.2
github.com/jackc/pgx/v5 v5.4.1 github.com/jackc/pgx/v5 v5.3.1
github.com/jmoiron/sqlx v1.3.5 github.com/jmoiron/sqlx v1.3.5
github.com/knadh/koanf/parsers/yaml v0.1.0 github.com/knadh/koanf/parsers/yaml v0.1.0
github.com/knadh/koanf/providers/confmap v0.1.0 github.com/knadh/koanf/providers/confmap v0.1.0
@ -28,27 +28,27 @@ require (
github.com/knadh/koanf/providers/posflag v0.1.0 github.com/knadh/koanf/providers/posflag v0.1.0
github.com/knadh/koanf/providers/rawbytes v0.1.0 github.com/knadh/koanf/providers/rawbytes v0.1.0
github.com/knadh/koanf/v2 v2.0.1 github.com/knadh/koanf/v2 v2.0.1
github.com/mattn/go-sqlite3 v1.14.17 github.com/mattn/go-sqlite3 v1.14.16
github.com/mitchellh/mapstructure v1.5.0 github.com/mitchellh/mapstructure v1.5.0
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826
github.com/ory/fosite v0.44.0 github.com/ory/fosite v0.44.0
github.com/ory/herodot v0.10.2 github.com/ory/herodot v0.10.2
github.com/ory/x v0.0.563 github.com/ory/x v0.0.555
github.com/otiai10/copy v1.12.0 github.com/otiai10/copy v1.11.0
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/pquerna/otp v1.4.0 github.com/pquerna/otp v1.4.0
github.com/prometheus/client_golang v1.16.0 github.com/prometheus/client_golang v1.15.1
github.com/sirupsen/logrus v1.9.3 github.com/sirupsen/logrus v1.9.2
github.com/spf13/cobra v1.7.0 github.com/spf13/cobra v1.7.0
github.com/spf13/pflag v1.0.5 github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.8.4 github.com/stretchr/testify v1.8.3
github.com/trustelem/zxcvbn v1.0.1 github.com/trustelem/zxcvbn v1.0.1
github.com/valyala/fasthttp v1.48.0 github.com/valyala/fasthttp v1.47.0
github.com/wneessen/go-mail v0.3.9 github.com/wneessen/go-mail v0.3.9
golang.org/x/net v0.11.0 golang.org/x/net v0.10.0
golang.org/x/sync v0.3.0 golang.org/x/sync v0.2.0
golang.org/x/term v0.9.0 golang.org/x/term v0.8.0
golang.org/x/text v0.10.0 golang.org/x/text v0.9.0
gopkg.in/square/go-jose.v2 v2.6.0 gopkg.in/square/go-jose.v2 v2.6.0
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
) )
@ -59,7 +59,6 @@ require (
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/cristalhq/jwt/v4 v4.0.2 // indirect github.com/cristalhq/jwt/v4 v4.0.2 // indirect
github.com/dave/jennifer v1.6.0 // indirect github.com/dave/jennifer v1.6.0 // indirect
@ -69,16 +68,12 @@ require (
github.com/dlclark/regexp2 v1.4.0 // indirect github.com/dlclark/regexp2 v1.4.0 // indirect
github.com/dustin/go-humanize v1.0.0 // indirect github.com/dustin/go-humanize v1.0.0 // indirect
github.com/ecordell/optgen v0.0.6 // indirect github.com/ecordell/optgen v0.0.6 // indirect
github.com/envoyproxy/go-control-plane v0.11.1 // indirect
github.com/envoyproxy/protoc-gen-validate v1.0.1 // indirect
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect
github.com/fxamacker/cbor/v2 v2.4.0 // indirect github.com/fxamacker/cbor/v2 v2.4.0 // indirect
github.com/go-crypt/x v0.2.1 // indirect github.com/go-crypt/x v0.2.1 // indirect
github.com/go-redis/redis/v8 v8.11.5 // indirect github.com/go-redis/redis/v8 v8.11.5 // indirect
github.com/go-webauthn/revoke v0.1.9 // indirect github.com/go-webauthn/revoke v0.1.9 // indirect
github.com/gogo/googleapis v1.4.1 // indirect github.com/golang/glog v1.0.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/glog v1.1.0 // indirect
github.com/golang/protobuf v1.5.3 // indirect github.com/golang/protobuf v1.5.3 // indirect
github.com/google/go-tpm v0.3.3 // indirect github.com/google/go-tpm v0.3.3 // indirect
github.com/gorilla/websocket v1.5.0 // indirect github.com/gorilla/websocket v1.5.0 // indirect
@ -102,9 +97,9 @@ require (
github.com/pelletier/go-toml/v2 v2.0.6 // indirect github.com/pelletier/go-toml/v2 v2.0.6 // indirect
github.com/philhofer/fwd v1.1.2 // indirect github.com/philhofer/fwd v1.1.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.4.0 // indirect github.com/prometheus/client_model v0.3.0 // indirect
github.com/prometheus/common v0.42.0 // indirect github.com/prometheus/common v0.42.0 // indirect
github.com/prometheus/procfs v0.10.1 // indirect github.com/prometheus/procfs v0.9.0 // indirect
github.com/redis/go-redis/v9 v9.0.4 // indirect github.com/redis/go-redis/v9 v9.0.4 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect
@ -122,15 +117,14 @@ require (
github.com/ysmood/got v0.34.1 // indirect github.com/ysmood/got v0.34.1 // indirect
github.com/ysmood/gson v0.7.3 // indirect github.com/ysmood/gson v0.7.3 // indirect
github.com/ysmood/leakless v0.8.0 // indirect github.com/ysmood/leakless v0.8.0 // indirect
golang.org/x/crypto v0.10.0 // indirect golang.org/x/crypto v0.7.0 // indirect
golang.org/x/mod v0.10.0 // indirect golang.org/x/mod v0.10.0 // indirect
golang.org/x/oauth2 v0.7.0 // indirect golang.org/x/oauth2 v0.5.0 // indirect
golang.org/x/sys v0.9.0 // indirect golang.org/x/sys v0.8.0 // indirect
golang.org/x/tools v0.8.0 // indirect golang.org/x/tools v0.7.0 // indirect
google.golang.org/appengine v1.6.7 // indirect google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20230526203410-71b5a4ffd15e // indirect google.golang.org/genproto v0.0.0-20230403163135-c38d8f061ccd // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230526203410-71b5a4ffd15e // indirect google.golang.org/grpc v1.54.0 // indirect
google.golang.org/grpc v1.56.0 // indirect
google.golang.org/protobuf v1.30.0 // indirect google.golang.org/protobuf v1.30.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect

105
go.sum
View File

@ -72,8 +72,6 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+gqO04wryn5h75LSazbRlnya1k=
github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
@ -115,11 +113,7 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.11.1 h1:wSUXTlLfiAQRWs2F+p+EKOY9rUyis1MyGqJ2DIk5HpM=
github.com/envoyproxy/go-control-plane v0.11.1/go.mod h1:uhMcXKCQMEJHiAb0w+YGefQLaTEw+YhGluxZkrTmD0g=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/envoyproxy/protoc-gen-validate v1.0.1 h1:kt9FtLiooDc0vbwTLhdg3dyNX1K9Qwa1EK9LcD4jVUQ=
github.com/envoyproxy/protoc-gen-validate v1.0.1/go.mod h1:0vj8bNkYbSTNS2PIyH87KZaeN4x9zpL9Qt8fQC7d+vs=
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A= github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A=
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg= github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg=
github.com/fasthttp/router v1.4.19 h1:RLE539IU/S4kfb4MP56zgP0TIBU9kEg0ID9GpWO0vqk= github.com/fasthttp/router v1.4.19 h1:RLE539IU/S4kfb4MP56zgP0TIBU9kEg0ID9GpWO0vqk=
@ -144,35 +138,29 @@ github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-ldap/ldap/v3 v3.4.5 h1:ekEKmaDrpvR2yf5Nc/DClsGG9lAmdDixe44mLzlW5r8= github.com/go-ldap/ldap/v3 v3.4.5-0.20230521105649-cdb0754f6668 h1:qbWHOCDBT8m2I1tDGP7S58dgi/xaDDCKuR5dbarLGOU=
github.com/go-ldap/ldap/v3 v3.4.5/go.mod h1:bMGIq3AGbytbaMwf8wdv5Phdxz0FWHTIYMSzyrYgnQs= github.com/go-ldap/ldap/v3 v3.4.5-0.20230521105649-cdb0754f6668/go.mod h1:bMGIq3AGbytbaMwf8wdv5Phdxz0FWHTIYMSzyrYgnQs=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
github.com/go-rod/rod v0.113.3 h1:oLiKZW721CCMwA5g7977cWfcAKQ+FuosP47Zf1QiDrA= github.com/go-rod/rod v0.113.1 h1:+Qb4K/vkR7BOhW6FhfhtLzUD3l11+0XlF4do+27sOQk=
github.com/go-rod/rod v0.113.3/go.mod h1:aiedSEFg5DwG/fnNbUOTPMTTWX3MRj6vIs/a684Mthw= github.com/go-rod/rod v0.113.1/go.mod h1:aiedSEFg5DwG/fnNbUOTPMTTWX3MRj6vIs/a684Mthw=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-webauthn/revoke v0.1.9 h1:gSJ1ckA9VaKA2GN4Ukp+kiGTk1/EXtaDb1YE8RknbS0= github.com/go-webauthn/revoke v0.1.9 h1:gSJ1ckA9VaKA2GN4Ukp+kiGTk1/EXtaDb1YE8RknbS0=
github.com/go-webauthn/revoke v0.1.9/go.mod h1:j6WKPnv0HovtEs++paan9g3ar46gm1NarktkXBaPR+w= github.com/go-webauthn/revoke v0.1.9/go.mod h1:j6WKPnv0HovtEs++paan9g3ar46gm1NarktkXBaPR+w=
github.com/go-webauthn/webauthn v0.5.0 h1:Tbmp37AGIhYbQmcy2hEffo3U3cgPClqvxJ7cLUnF7Rc= github.com/go-webauthn/webauthn v0.8.2 h1:8KLIbpldjz9KVGHfqEgJNbkhd7bbRXhNw4QWFJE15oA=
github.com/go-webauthn/webauthn v0.5.0/go.mod h1:0CBq/jNfPS9l033j4AxMk8K8MluiMsde9uGNSPFLEVE= github.com/go-webauthn/webauthn v0.8.2/go.mod h1:d+ezx/jMCNDiqSMzOchuynKb9CVU1NM9BumOnokfcVQ=
github.com/gogo/googleapis v1.4.1 h1:1Yx4Myt7BxzvUr5ldGSbwYiZG6t9wGBZ+8/fX3Wvtq0=
github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ= github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ=
github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4=
github.com/golang/glog v1.1.0 h1:/d3pCKDPWNnvIWe0vVUpNP32qc8U3PDVxySP/y360qE=
github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@ -253,8 +241,8 @@ github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9n
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
github.com/hashicorp/go-hclog v1.2.0 h1:La19f8d7WIlm4ogzNHB0JGqs5AUDAZ2UfCY4sJXcJdM= github.com/hashicorp/go-hclog v1.2.0 h1:La19f8d7WIlm4ogzNHB0JGqs5AUDAZ2UfCY4sJXcJdM=
github.com/hashicorp/go-retryablehttp v0.7.4 h1:ZQgVdpTdAL7WpMIwLzCfbalOcSUdkDZnpUv3/+BxzFA= github.com/hashicorp/go-retryablehttp v0.7.2 h1:AcYqCvkpalPnPF2pn0KamgwamS42TqUDDYFRKq/RAd0=
github.com/hashicorp/go-retryablehttp v0.7.4/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= github.com/hashicorp/go-retryablehttp v0.7.2/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
@ -268,8 +256,8 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.4.1 h1:oKfB/FhuVtit1bBM3zNRRsZ925ZkMN3HXL+LgLUM9lE= github.com/jackc/pgx/v5 v5.3.1 h1:Fcr8QJ1ZeLi5zsPZqQeUZhNhxfkkKBOgJuYkJHoBOtU=
github.com/jackc/pgx/v5 v5.4.1/go.mod h1:q6iHT8uDNXWiFNOlRqJzBTaSH3+2xCXkokxHZC5qWFY= github.com/jackc/pgx/v5 v5.3.1/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8=
github.com/jandelgado/gcov2lcov v1.0.5 h1:rkBt40h0CVK4oCb8Dps950gvfd1rYvQ8+cWa346lVU0= github.com/jandelgado/gcov2lcov v1.0.5 h1:rkBt40h0CVK4oCb8Dps950gvfd1rYvQ8+cWa346lVU0=
github.com/jandelgado/gcov2lcov v1.0.5/go.mod h1:NnSxK6TMlg1oGDBfGelGbjgorT5/L3cchlbtgFYZSss= github.com/jandelgado/gcov2lcov v1.0.5/go.mod h1:NnSxK6TMlg1oGDBfGelGbjgorT5/L3cchlbtgFYZSss=
github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
@ -279,7 +267,6 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI= github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI=
github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
@ -313,8 +300,8 @@ github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3v
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM= github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mattn/goveralls v0.0.11 h1:eJXea6R6IFlL1QMKNMzDvvHv/hwGrnvyig4N+0+XiMM= github.com/mattn/goveralls v0.0.11 h1:eJXea6R6IFlL1QMKNMzDvvHv/hwGrnvyig4N+0+XiMM=
github.com/mattn/goveralls v0.0.11/go.mod h1:gU8SyhNswsJKchEV93xRQxX6X3Ei4PJdQk/6ZHvrvRk= github.com/mattn/goveralls v0.0.11/go.mod h1:gU8SyhNswsJKchEV93xRQxX6X3Ei4PJdQk/6ZHvrvRk=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
@ -344,10 +331,10 @@ github.com/ory/go-convenience v0.1.0 h1:zouLKfF2GoSGnJwGq+PE/nJAE6dj2Zj5QlTgmMTs
github.com/ory/go-convenience v0.1.0/go.mod h1:uEY/a60PL5c12nYz4V5cHY03IBmwIAEm8TWB0yn9KNs= github.com/ory/go-convenience v0.1.0/go.mod h1:uEY/a60PL5c12nYz4V5cHY03IBmwIAEm8TWB0yn9KNs=
github.com/ory/herodot v0.10.2 h1:gGvNMHgAwWzdP/eo+roSiT5CGssygHSjDU7MSQNlJ4E= github.com/ory/herodot v0.10.2 h1:gGvNMHgAwWzdP/eo+roSiT5CGssygHSjDU7MSQNlJ4E=
github.com/ory/herodot v0.10.2/go.mod h1:MMNmY6MG1uB6fnXYFaHoqdV23DTWctlPsmRCeq/2+wc= github.com/ory/herodot v0.10.2/go.mod h1:MMNmY6MG1uB6fnXYFaHoqdV23DTWctlPsmRCeq/2+wc=
github.com/ory/x v0.0.563 h1:T77Bjt6ALMZmUJIsQ5UEkzDBCD+8vxfQlBCU1Y39uDk= github.com/ory/x v0.0.555 h1:Etg6yUnrXP5YVTlpgipRh+hDLMavldbTURj+Mb0kdIY=
github.com/ory/x v0.0.563/go.mod h1:kup4ebSC4SzwU6KPZJ4G60UR3EEsHxJ0apQVflVw5yQ= github.com/ory/x v0.0.555/go.mod h1:oRVemI3SQQOLvOCJWIRinHQKlgmay/NbwSyRUIsS/Yk=
github.com/otiai10/copy v1.12.0 h1:cLMgSQnXBs1eehF0Wy/FAGsgDTDmAqFR7rQylBb1nDY= github.com/otiai10/copy v1.11.0 h1:OKBD80J/mLBrwnzXqGtFCzprFSGioo30JcmR4APsNwc=
github.com/otiai10/copy v1.12.0/go.mod h1:rSaLseMUsZFFbsFGc7wCJnnkTAvdc5L6VWxPE4308Ww= github.com/otiai10/copy v1.11.0/go.mod h1:rSaLseMUsZFFbsFGc7wCJnnkTAvdc5L6VWxPE4308Ww=
github.com/otiai10/mint v1.5.1 h1:XaPLeE+9vGbuyEHem1JNk3bYc7KKqyI/na0/mLd/Kks= github.com/otiai10/mint v1.5.1 h1:XaPLeE+9vGbuyEHem1JNk3bYc7KKqyI/na0/mLd/Kks=
github.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw= github.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw=
github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
@ -369,23 +356,21 @@ github.com/pquerna/otp v1.4.0 h1:wZvl1TIVxKRThZIBiwOOHOGP/1+nZyWBil9Y2XNEDzg=
github.com/pquerna/otp v1.4.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg= github.com/pquerna/otp v1.4.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8= github.com/prometheus/client_golang v1.15.1 h1:8tXpTmJbyH5lydzFPoxSIJ0J46jdh3tylbvM1xCv0LI=
github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc= github.com/prometheus/client_golang v1.15.1/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4=
github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM= github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg= github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI=
github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM= github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/redis/go-redis/v9 v9.0.4 h1:FC82T+CHJ/Q/PdyLW++GeCO+Ol59Y4T7R4jbgjvktgc= github.com/redis/go-redis/v9 v9.0.4 h1:FC82T+CHJ/Q/PdyLW++GeCO+Ol59Y4T7R4jbgjvktgc=
github.com/redis/go-redis/v9 v9.0.4/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk= github.com/redis/go-redis/v9 v9.0.4/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk=
@ -400,8 +385,8 @@ github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1Avp
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g= github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.2 h1:oxx1eChJGI6Uks2ZC4W1zpLlVgqB8ner4EuQwV4Ik1Y=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.2/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
@ -436,8 +421,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8=
github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
github.com/test-go/testify v1.1.4 h1:Tf9lntrKUMHiXQ07qBScBTSA0dhYQlu83hswqelv1iE= github.com/test-go/testify v1.1.4 h1:Tf9lntrKUMHiXQ07qBScBTSA0dhYQlu83hswqelv1iE=
@ -451,8 +436,8 @@ github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGr
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.48.0 h1:oJWvHb9BIZToTQS3MuQ2R3bJZiNSa2KiNdeI8A+79Tc= github.com/valyala/fasthttp v1.47.0 h1:y7moDoxYzMooFpT5aHgNgVOQDrS3qlkfiP9mDtGGK9c=
github.com/valyala/fasthttp v1.48.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA= github.com/valyala/fasthttp v1.47.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA=
github.com/wneessen/go-mail v0.3.9 h1:Q4DbCk3htT5DtDWKeMgNXCiHc4bBY/vv/XQPT6XDXzc= github.com/wneessen/go-mail v0.3.9 h1:Q4DbCk3htT5DtDWKeMgNXCiHc4bBY/vv/XQPT6XDXzc=
github.com/wneessen/go-mail v0.3.9/go.mod h1:zxOlafWCP/r6FEhAaRgH4IC1vg2YXxO0Nar9u0IScZ8= github.com/wneessen/go-mail v0.3.9/go.mod h1:zxOlafWCP/r6FEhAaRgH4IC1vg2YXxO0Nar9u0IScZ8=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
@ -499,9 +484,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM=
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -581,8 +565,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU= golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -594,10 +578,6 @@ golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.5.0 h1:HuArIo48skDwlrvM3sEdHXElYslAMsf3KwRkkW4MC4s= golang.org/x/oauth2 v0.5.0 h1:HuArIo48skDwlrvM3sEdHXElYslAMsf3KwRkkW4MC4s=
golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I=
golang.org/x/oauth2 v0.6.0 h1:Lh8GPgSKBfWSwFvtuWOfeI3aAAnbXTSutYxJiOJFgIw=
golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw=
golang.org/x/oauth2 v0.7.0 h1:qe6s0zUXlPX80/dITx3440hWZ7GwMwgDDyrSGTPJG/g=
golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -611,8 +591,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -663,15 +643,15 @@ golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s= golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/term v0.9.0 h1:GRRCnKYhdQrD8kfRAdQ6Zcw1P0OcELxGLKJvtjVMZ28= golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols=
golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -683,8 +663,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58= golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@ -726,7 +706,6 @@ golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roY
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200717024301-6ddee64345a6/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200717024301-6ddee64345a6/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
@ -736,7 +715,6 @@ golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4f
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
@ -745,7 +723,6 @@ golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4=
golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -815,10 +792,6 @@ google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6D
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20230403163135-c38d8f061ccd h1:sLpv7bNL1AsX3fdnWh9WVh7ejIzXdOc1RRHGeAmeStU= google.golang.org/genproto v0.0.0-20230403163135-c38d8f061ccd h1:sLpv7bNL1AsX3fdnWh9WVh7ejIzXdOc1RRHGeAmeStU=
google.golang.org/genproto v0.0.0-20230403163135-c38d8f061ccd/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= google.golang.org/genproto v0.0.0-20230403163135-c38d8f061ccd/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak=
google.golang.org/genproto v0.0.0-20230526203410-71b5a4ffd15e h1:Ao9GzfUMPH3zjVfzXG5rlWlk+Q8MXWKwWpwVQE1MXfw=
google.golang.org/genproto v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:zqTuNwFlFRsw5zIts5VnzLQxSRqh+CGOTVMlYbY0Eyk=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230526203410-71b5a4ffd15e h1:NumxXLPfHSndr3wBBdeKiVHjGVFzi9RX2HwwQke94iY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
@ -838,10 +811,6 @@ google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA5
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag= google.golang.org/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag=
google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g=
google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag=
google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8=
google.golang.org/grpc v1.56.0 h1:+y7Bs8rtMd07LeXmL3NxcTLn7mUkbKZqEpPhMNkwJEE=
google.golang.org/grpc v1.56.0/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
google.golang.org/grpc/examples v0.0.0-20210304020650-930c79186c99 h1:qA8rMbz1wQ4DOFfM2ouD29DG9aHWBm6ZOy9BGxiUMmY= google.golang.org/grpc/examples v0.0.0-20210304020650-930c79186c99 h1:qA8rMbz1wQ4DOFfM2ouD29DG9aHWBm6ZOy9BGxiUMmY=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=

View File

@ -60,19 +60,12 @@ const (
) )
const ( const (
ldapPlaceholderInput = "{input}" ldapPlaceholderInput = "{input}"
ldapPlaceholderDistinguishedName = "{dn}" ldapPlaceholderDistinguishedName = "{dn}"
ldapPlaceholderMemberOfDistinguishedName = "{memberof:dn}" ldapPlaceholderUsername = "{username}"
ldapPlaceholderMemberOfRelativeDistinguishedName = "{memberof:rdn}" ldapPlaceholderDateTimeGeneralized = "{date-time:generalized}"
ldapPlaceholderUsername = "{username}" ldapPlaceholderDateTimeMicrosoftNTTimeEpoch = "{date-time:microsoft-nt}"
ldapPlaceholderDateTimeGeneralized = "{date-time:generalized}" ldapPlaceholderDateTimeUnixEpoch = "{date-time:unix}"
ldapPlaceholderDateTimeMicrosoftNTTimeEpoch = "{date-time:microsoft-nt}"
ldapPlaceholderDateTimeUnixEpoch = "{date-time:unix}"
ldapPlaceholderDistinguishedNameAttribute = "{distinguished_name_attribute}"
ldapPlaceholderUsernameAttribute = "{username_attribute}"
ldapPlaceholderDisplayNameAttribute = "{display_name_attribute}"
ldapPlaceholderMailAttribute = "{mail_attribute}"
ldapPlaceholderMemberOfAttribute = "{member_of_attribute}"
) )
const ( const (

View File

@ -3,7 +3,7 @@ package authentication
// This file is used to generate mocks. You can generate all mocks using the // This file is used to generate mocks. You can generate all mocks using the
// command `go generate github.com/authelia/authelia/v4/internal/authentication`. // command `go generate github.com/authelia/authelia/v4/internal/authentication`.
//go:generate mockgen -package authentication -destination ldap_client_mock_test.go -mock_names LDAPClient=MockLDAPClient github.com/authelia/authelia/v4/internal/authentication LDAPClient //go:generate mockgen -package authentication -destination ldap_client_mock.go -mock_names LDAPClient=MockLDAPClient github.com/authelia/authelia/v4/internal/authentication LDAPClient
//go:generate mockgen -package authentication -destination ldap_client_factory_mock_test.go -mock_names LDAPClientFactory=MockLDAPClientFactory github.com/authelia/authelia/v4/internal/authentication LDAPClientFactory //go:generate mockgen -package authentication -destination ldap_client_factory_mock.go -mock_names LDAPClientFactory=MockLDAPClientFactory github.com/authelia/authelia/v4/internal/authentication LDAPClientFactory
//go:generate mockgen -package authentication -destination file_user_provider_database_mock_test.go -mock_names FileUserDatabase=MockFileUserDatabase github.com/authelia/authelia/v4/internal/authentication FileUserDatabase //go:generate mockgen -package authentication -destination file_user_provider_database_mock_test.go -mock_names FileUserDatabase=MockFileUserDatabase github.com/authelia/authelia/v4/internal/authentication FileUserDatabase
//go:generate mockgen -package authentication -destination file_user_provider_hash_mock_test.go -mock_names Hash=MockHash github.com/go-crypt/crypt/algorithm Hash //go:generate mockgen -package authentication -destination file_user_provider_hash_mock_test.go -mock_names Hash=MockHash github.com/go-crypt/crypt/algorithm Hash

View File

@ -1,17 +0,0 @@
package authentication
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestControlMsftServerPolicyHints(t *testing.T) {
ct := &controlMsftServerPolicyHints{
oid: ldapOIDControlMsftServerPolicyHints,
}
assert.Equal(t, ldapOIDControlMsftServerPolicyHints, ct.GetControlType())
assert.Equal(t, "Enforce the password history length constraint (MS-SAMR section 3.1.1.7.1) during password set: 1.2.840.113556.1.4.2239", ct.String())
assert.NotNil(t, ct.Encode())
}

View File

@ -8,7 +8,7 @@ import (
"strconv" "strconv"
"strings" "strings"
ldap "github.com/go-ldap/ldap/v3" "github.com/go-ldap/ldap/v3"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/authelia/authelia/v4/internal/configuration/schema" "github.com/authelia/authelia/v4/internal/configuration/schema"
@ -40,13 +40,11 @@ type LDAPUserProvider struct {
usersFilterReplacementDateTimeMicrosoftNTTimeEpoch bool usersFilterReplacementDateTimeMicrosoftNTTimeEpoch bool
// Dynamically generated groups values. // Dynamically generated groups values.
groupsBaseDN string groupsBaseDN string
groupsAttributes []string groupsAttributes []string
groupsFilterReplacementInput bool groupsFilterReplacementInput bool
groupsFilterReplacementUsername bool groupsFilterReplacementUsername bool
groupsFilterReplacementDN bool groupsFilterReplacementDN bool
groupsFilterReplacementsMemberOfDN bool
groupsFilterReplacementsMemberOfRDN bool
} }
// NewLDAPUserProvider creates a new instance of LDAPUserProvider with the ProductionLDAPClientFactory. // NewLDAPUserProvider creates a new instance of LDAPUserProvider with the ProductionLDAPClientFactory.
@ -88,7 +86,6 @@ func NewLDAPUserProviderWithFactory(config schema.LDAPAuthenticationBackend, dis
provider.parseDynamicUsersConfiguration() provider.parseDynamicUsersConfiguration()
provider.parseDynamicGroupsConfiguration() provider.parseDynamicGroupsConfiguration()
provider.parseDynamicConfiguration()
return provider return provider
} }
@ -137,11 +134,38 @@ func (p *LDAPUserProvider) GetDetails(username string) (details *UserDetails, er
} }
var ( var (
groups []string request *ldap.SearchRequest
result *ldap.SearchResult
) )
if groups, err = p.getUserGroups(client, username, profile); err != nil { // Search for the users groups.
return nil, err request = ldap.NewSearchRequest(
p.groupsBaseDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases,
0, 0, false, p.resolveGroupsFilter(username, profile), p.groupsAttributes, nil,
)
p.log.
WithField("base_dn", request.BaseDN).
WithField("filter", request.Filter).
WithField("attr", request.Attributes).
WithField("scope", request.Scope).
WithField("deref", request.DerefAliases).
Trace("Performing group search")
if result, err = p.search(client, request); err != nil {
return nil, fmt.Errorf("unable to retrieve groups of user '%s'. Cause: %w", username, err)
}
groups := make([]string, 0)
for _, res := range result.Entries {
if len(res.Attributes) == 0 {
p.log.Warningf("No groups retrieved from LDAP for user %s", username)
break
}
// Append all values of the document. Normally there should be only one per document.
groups = append(groups, res.Attributes[0].Values...)
} }
return &UserDetails{ return &UserDetails{
@ -251,12 +275,14 @@ func (p *LDAPUserProvider) search(client LDAPClient, request *ldap.SearchRequest
} else { } else {
result.Referrals = append(result.Referrals, referral) result.Referrals = append(result.Referrals, referral)
} }
} else {
return nil, err
} }
} }
if !p.config.PermitReferrals || len(result.Referrals) == 0 { if !p.config.PermitReferrals || len(result.Referrals) == 0 {
if err != nil {
return nil, err
}
return result, nil return result, nil
} }
@ -331,11 +357,6 @@ func (p *LDAPUserProvider) getUserProfile(client LDAPClient, username string) (p
return nil, fmt.Errorf("there were %d users found when searching for '%s' but there should only be 1", len(result.Entries), username) return nil, fmt.Errorf("there were %d users found when searching for '%s' but there should only be 1", len(result.Entries), username)
} }
return p.getUserProfileResultToProfile(username, result)
}
//nolint:gocyclo // Not overly complex.
func (p *LDAPUserProvider) getUserProfileResultToProfile(username string, result *ldap.SearchResult) (profile *ldapUserProfile, err error) {
userProfile := ldapUserProfile{ userProfile := ldapUserProfile{
DN: result.Entries[0].DN, DN: result.Entries[0].DN,
} }
@ -343,50 +364,35 @@ func (p *LDAPUserProvider) getUserProfileResultToProfile(username string, result
for _, attr := range result.Entries[0].Attributes { for _, attr := range result.Entries[0].Attributes {
attrs := len(attr.Values) attrs := len(attr.Values)
switch attr.Name { if attr.Name == p.config.UsernameAttribute {
case p.config.Attributes.Username:
switch attrs { switch attrs {
case 1: case 1:
userProfile.Username = attr.Values[0] userProfile.Username = attr.Values[0]
if attr.Name == p.config.Attributes.DisplayName && userProfile.DisplayName == "" {
userProfile.DisplayName = attr.Values[0]
}
if attr.Name == p.config.Attributes.Mail && len(userProfile.Emails) == 0 {
userProfile.Emails = []string{attr.Values[0]}
}
case 0: case 0:
return nil, fmt.Errorf("user '%s' must have value for attribute '%s'", return nil, fmt.Errorf("user '%s' must have value for attribute '%s'",
username, p.config.Attributes.Username) username, p.config.UsernameAttribute)
default: default:
return nil, fmt.Errorf("user '%s' has %d values for for attribute '%s' but the attribute must be a single value attribute", return nil, fmt.Errorf("user '%s' has %d values for for attribute '%s' but the attribute must be a single value attribute",
username, attrs, p.config.Attributes.Username) username, attrs, p.config.UsernameAttribute)
}
case p.config.Attributes.Mail:
if attrs == 0 {
continue
} }
}
if attrs == 0 {
continue
}
if attr.Name == p.config.MailAttribute {
userProfile.Emails = attr.Values userProfile.Emails = attr.Values
case p.config.Attributes.DisplayName: }
if attrs == 0 {
continue
}
if attr.Name == p.config.DisplayNameAttribute {
userProfile.DisplayName = attr.Values[0] userProfile.DisplayName = attr.Values[0]
case p.config.Attributes.MemberOf:
if attrs == 0 {
continue
}
userProfile.MemberOf = attr.Values
} }
} }
if userProfile.Username == "" { if userProfile.Username == "" {
return nil, fmt.Errorf("user '%s' must have value for attribute '%s'", return nil, fmt.Errorf("user '%s' must have value for attribute '%s'",
username, p.config.Attributes.Username) username, p.config.UsernameAttribute)
} }
if userProfile.DN == "" { if userProfile.DN == "" {
@ -396,118 +402,6 @@ func (p *LDAPUserProvider) getUserProfileResultToProfile(username string, result
return &userProfile, nil return &userProfile, nil
} }
func (p *LDAPUserProvider) getUserGroups(client LDAPClient, username string, profile *ldapUserProfile) (groups []string, err error) {
request := ldap.NewSearchRequest(
p.groupsBaseDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases,
0, 0, false, p.resolveGroupsFilter(username, profile), p.groupsAttributes, nil,
)
p.log.
WithField("base_dn", request.BaseDN).
WithField("filter", request.Filter).
WithField("attributes", request.Attributes).
WithField("scope", request.Scope).
WithField("deref", request.DerefAliases).
WithField("mode", p.config.GroupSearchMode).
Trace("Performing group search")
switch p.config.GroupSearchMode {
case "", "filter":
return p.getUserGroupsRequestFilter(client, username, profile, request)
case "memberof":
return p.getUserGroupsRequestMemberOf(client, username, profile, request)
default:
return nil, fmt.Errorf("could not perform group search with mode '%s' as it's unknown", p.config.GroupSearchMode)
}
}
func (p *LDAPUserProvider) getUserGroupsRequestFilter(client LDAPClient, username string, _ *ldapUserProfile, request *ldap.SearchRequest) (groups []string, err error) {
var result *ldap.SearchResult
if result, err = p.search(client, request); err != nil {
return nil, fmt.Errorf("unable to retrieve groups of user '%s'. Cause: %w", username, err)
}
for _, entry := range result.Entries {
if group := p.getUserGroupFromEntry(entry); len(group) != 0 {
groups = append(groups, group)
}
}
return groups, nil
}
func (p *LDAPUserProvider) getUserGroupsRequestMemberOf(client LDAPClient, username string, profile *ldapUserProfile, request *ldap.SearchRequest) (groups []string, err error) {
var result *ldap.SearchResult
if result, err = p.search(client, request); err != nil {
return nil, fmt.Errorf("unable to retrieve groups of user '%s'. Cause: %w", username, err)
}
for _, entry := range result.Entries {
if len(entry.Attributes) == 0 {
p.log.
WithField("dn", entry.DN).
WithField("attributes", request.Attributes).
WithField("mode", "memberof").
Trace("Skipping Group as the server did not return any requested attributes")
continue
}
if !utils.IsStringInSliceFold(entry.DN, profile.MemberOf) {
p.log.
WithField("dn", entry.DN).
WithField("mode", "memberof").
Trace("Skipping Group as it doesn't match the users memberof entries")
continue
}
if group := p.getUserGroupFromEntry(entry); len(group) != 0 {
groups = append(groups, group)
}
}
return groups, nil
}
func (p *LDAPUserProvider) getUserGroupFromEntry(entry *ldap.Entry) string {
attributes:
for _, attr := range entry.Attributes {
switch attr.Name {
case p.config.Attributes.GroupName:
switch len(attr.Values) {
case 0:
p.log.
WithField("dn", entry.DN).
WithField("attribute", attr.Name).
Trace("Group skipped as the server returned a null attribute")
case 1:
switch len(attr.Values[0]) {
case 0:
p.log.
WithField("dn", entry.DN).
WithField("attribute", attr.Name).
Trace("Skipping group as the configured group name attribute had no value")
default:
return attr.Values[0]
}
default:
p.log.
WithField("dn", entry.DN).
WithField("attribute", attr.Name).
Trace("Group skipped as the server returned a multi-valued attribute but it should be a single-valued attribute")
}
break attributes
}
}
return ""
}
func (p *LDAPUserProvider) resolveUsersFilter(input string) (filter string) { func (p *LDAPUserProvider) resolveUsersFilter(input string) (filter string) {
filter = p.config.UsersFilter filter = p.config.UsersFilter
@ -551,27 +445,6 @@ func (p *LDAPUserProvider) resolveGroupsFilter(input string, profile *ldapUserPr
} }
} }
if p.groupsFilterReplacementsMemberOfDN {
sep := fmt.Sprintf(")(%s=", p.config.Attributes.DistinguishedName)
values := make([]string, len(profile.MemberOf))
for i, memberof := range profile.MemberOf {
values[i] = ldap.EscapeFilter(memberof)
}
filter = strings.ReplaceAll(filter, ldapPlaceholderMemberOfDistinguishedName, fmt.Sprintf("(%s=%s)", p.config.Attributes.DistinguishedName, strings.Join(values, sep)))
}
if p.groupsFilterReplacementsMemberOfRDN {
values := make([]string, len(profile.MemberOf))
for i, memberof := range profile.MemberOf {
values[i] = ldap.EscapeFilter(strings.SplitN(memberof, ",", 2)[0])
}
filter = strings.ReplaceAll(filter, ldapPlaceholderMemberOfRelativeDistinguishedName, fmt.Sprintf("(%s)", strings.Join(values, ")(")))
}
p.log.Tracef("Computed groups filter is %s", filter) p.log.Tracef("Computed groups filter is %s", filter)
return filter return filter

View File

@ -37,6 +37,11 @@ func (p *LDAPUserProvider) StartupCheck() (err error) {
"LDAP Server.") "LDAP Server.")
} }
if !p.features.Extensions.TLS && p.config.StartTLS {
p.log.Info("Your LDAP Server does not appear to support TLS but you enabled StartTLS which may result " +
"in an error.")
}
return nil return nil
} }
@ -85,24 +90,22 @@ func (p *LDAPUserProvider) getServerSupportedFeatures(client LDAPClient) (featur
} }
func (p *LDAPUserProvider) parseDynamicUsersConfiguration() { func (p *LDAPUserProvider) parseDynamicUsersConfiguration() {
p.config.UsersFilter = strings.ReplaceAll(p.config.UsersFilter, ldapPlaceholderDistinguishedNameAttribute, p.config.Attributes.DistinguishedName) p.config.UsersFilter = strings.ReplaceAll(p.config.UsersFilter, "{username_attribute}", p.config.UsernameAttribute)
p.config.UsersFilter = strings.ReplaceAll(p.config.UsersFilter, ldapPlaceholderUsernameAttribute, p.config.Attributes.Username) p.config.UsersFilter = strings.ReplaceAll(p.config.UsersFilter, "{mail_attribute}", p.config.MailAttribute)
p.config.UsersFilter = strings.ReplaceAll(p.config.UsersFilter, ldapPlaceholderDisplayNameAttribute, p.config.Attributes.DisplayName) p.config.UsersFilter = strings.ReplaceAll(p.config.UsersFilter, "{display_name_attribute}", p.config.DisplayNameAttribute)
p.config.UsersFilter = strings.ReplaceAll(p.config.UsersFilter, ldapPlaceholderMailAttribute, p.config.Attributes.Mail)
p.config.UsersFilter = strings.ReplaceAll(p.config.UsersFilter, ldapPlaceholderMemberOfAttribute, p.config.Attributes.MemberOf)
p.log.Tracef("Dynamically generated users filter is %s", p.config.UsersFilter) p.log.Tracef("Dynamically generated users filter is %s", p.config.UsersFilter)
if len(p.config.Attributes.Username) != 0 && !utils.IsStringInSlice(p.config.Attributes.Username, p.usersAttributes) { if !utils.IsStringInSlice(p.config.UsernameAttribute, p.usersAttributes) {
p.usersAttributes = append(p.usersAttributes, p.config.Attributes.Username) p.usersAttributes = append(p.usersAttributes, p.config.UsernameAttribute)
} }
if len(p.config.Attributes.Mail) != 0 && !utils.IsStringInSlice(p.config.Attributes.Mail, p.usersAttributes) { if !utils.IsStringInSlice(p.config.MailAttribute, p.usersAttributes) {
p.usersAttributes = append(p.usersAttributes, p.config.Attributes.Mail) p.usersAttributes = append(p.usersAttributes, p.config.MailAttribute)
} }
if len(p.config.Attributes.DisplayName) != 0 && !utils.IsStringInSlice(p.config.Attributes.DisplayName, p.usersAttributes) { if !utils.IsStringInSlice(p.config.DisplayNameAttribute, p.usersAttributes) {
p.usersAttributes = append(p.usersAttributes, p.config.Attributes.DisplayName) p.usersAttributes = append(p.usersAttributes, p.config.DisplayNameAttribute)
} }
if p.config.AdditionalUsersDN != "" { if p.config.AdditionalUsersDN != "" {
@ -134,14 +137,8 @@ func (p *LDAPUserProvider) parseDynamicUsersConfiguration() {
} }
func (p *LDAPUserProvider) parseDynamicGroupsConfiguration() { func (p *LDAPUserProvider) parseDynamicGroupsConfiguration() {
p.config.GroupsFilter = strings.ReplaceAll(p.config.GroupsFilter, ldapPlaceholderDistinguishedNameAttribute, p.config.Attributes.DistinguishedName) p.groupsAttributes = []string{
p.config.GroupsFilter = strings.ReplaceAll(p.config.GroupsFilter, ldapPlaceholderUsernameAttribute, p.config.Attributes.Username) p.config.GroupNameAttribute,
p.config.GroupsFilter = strings.ReplaceAll(p.config.GroupsFilter, ldapPlaceholderDisplayNameAttribute, p.config.Attributes.DisplayName)
p.config.GroupsFilter = strings.ReplaceAll(p.config.GroupsFilter, ldapPlaceholderMailAttribute, p.config.Attributes.Mail)
p.config.GroupsFilter = strings.ReplaceAll(p.config.GroupsFilter, ldapPlaceholderMemberOfAttribute, p.config.Attributes.MemberOf)
if len(p.config.Attributes.GroupName) != 0 && !utils.IsStringInSlice(p.config.Attributes.GroupName, p.groupsAttributes) {
p.groupsAttributes = append(p.groupsAttributes, p.config.Attributes.GroupName)
} }
if p.config.AdditionalGroupsDN != "" { if p.config.AdditionalGroupsDN != "" {
@ -164,25 +161,5 @@ func (p *LDAPUserProvider) parseDynamicGroupsConfiguration() {
p.groupsFilterReplacementDN = true p.groupsFilterReplacementDN = true
} }
if strings.Contains(p.config.GroupsFilter, ldapPlaceholderMemberOfDistinguishedName) {
p.groupsFilterReplacementsMemberOfDN = true
}
if strings.Contains(p.config.GroupsFilter, ldapPlaceholderMemberOfRelativeDistinguishedName) {
p.groupsFilterReplacementsMemberOfRDN = true
}
p.log.Tracef("Detected group filter replacements that need to be resolved per lookup are: input=%v, username=%v, dn=%v", p.groupsFilterReplacementInput, p.groupsFilterReplacementUsername, p.groupsFilterReplacementDN) p.log.Tracef("Detected group filter replacements that need to be resolved per lookup are: input=%v, username=%v, dn=%v", p.groupsFilterReplacementInput, p.groupsFilterReplacementUsername, p.groupsFilterReplacementDN)
} }
func (p *LDAPUserProvider) parseDynamicConfiguration() {
if len(p.config.Attributes.MemberOf) != 0 {
if !utils.IsStringInSlice(p.config.Attributes.MemberOf, p.usersAttributes) {
p.usersAttributes = append(p.usersAttributes, p.config.Attributes.MemberOf)
}
if !utils.IsStringInSlice(p.config.Attributes.MemberOf, p.groupsAttributes) {
p.groupsAttributes = append(p.groupsAttributes, p.config.Attributes.MemberOf)
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -5,7 +5,7 @@ import (
"strings" "strings"
ber "github.com/go-asn1-ber/asn1-ber" ber "github.com/go-asn1-ber/asn1-ber"
ldap "github.com/go-ldap/ldap/v3" "github.com/go-ldap/ldap/v3"
) )
func ldapEntriesContainsEntry(needle *ldap.Entry, haystack []*ldap.Entry) bool { func ldapEntriesContainsEntry(needle *ldap.Entry, haystack []*ldap.Entry) bool {
@ -67,12 +67,12 @@ func ldapEscape(inputUsername string) string {
} }
func ldapGetReferral(err error) (referral string, ok bool) { func ldapGetReferral(err error) (referral string, ok bool) {
if !ldap.IsErrorWithCode(err, ldap.LDAPResultReferral) {
return "", false
}
switch e := err.(type) { switch e := err.(type) {
case *ldap.Error: case *ldap.Error:
if e.ResultCode != ldap.LDAPResultReferral {
return "", false
}
if e.Packet == nil { if e.Packet == nil {
return "", false return "", false
} }

View File

@ -193,12 +193,6 @@ func TestLDAPGetReferral(t *testing.T) {
expectedReferral: "", expectedReferral: "",
expectedOK: false, expectedOK: false,
}, },
{
description: "ShouldNotGetInvalidErrType",
have: errors.New("not an err"),
expectedReferral: "",
expectedOK: false,
},
} }
for _, tc := range testCases { for _, tc := range testCases {

View File

@ -82,7 +82,6 @@ type ldapUserProfile struct {
Emails []string Emails []string
DisplayName string DisplayName string
Username string Username string
MemberOf []string
} }
// LDAPSupportedFeatures represents features which a server may support which are implemented in code. // LDAPSupportedFeatures represents features which a server may support which are implemented in code.

View File

@ -16,7 +16,6 @@ import (
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/valyala/fasthttp" "github.com/valyala/fasthttp"
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
"google.golang.org/grpc"
"github.com/authelia/authelia/v4/internal/authentication" "github.com/authelia/authelia/v4/internal/authentication"
"github.com/authelia/authelia/v4/internal/server" "github.com/authelia/authelia/v4/internal/server"
@ -34,17 +33,6 @@ func NewServerService(name string, server *fasthttp.Server, listener net.Listene
} }
} }
// NewGRCPServerService creates a new ServerService with the appropriate logger etc.
func NewGRCPServerService(name string, server *grpc.Server, listener net.Listener, isTLS bool, log *logrus.Logger) (service *GRCPServerService) {
return &GRCPServerService{
name: name,
server: server,
listener: listener,
isTLS: isTLS,
log: log.WithFields(map[string]any{logFieldService: serviceTypeServer, serviceTypeServer: name}),
}
}
// NewFileWatcherService creates a new FileWatcherService with the appropriate logger etc. // NewFileWatcherService creates a new FileWatcherService with the appropriate logger etc.
func NewFileWatcherService(name, path string, reload ProviderReload, log *logrus.Logger) (service *FileWatcherService, err error) { func NewFileWatcherService(name, path string, reload ProviderReload, log *logrus.Logger) (service *FileWatcherService, err error) {
if path == "" { if path == "" {
@ -173,54 +161,6 @@ func (service *ServerService) Log() *logrus.Entry {
return service.log return service.log
} }
// GRCPServerService is a Service which runs a gRCP server.
type GRCPServerService struct {
name string
server *grpc.Server
isTLS bool
listener net.Listener
log *logrus.Entry
}
// ServiceType returns the service type for this service, which is always 'server'.
func (service *GRCPServerService) ServiceType() string {
return serviceTypeServer
}
// ServiceName returns the individual name for this service.
func (service *GRCPServerService) ServiceName() string {
return service.name
}
// Run the ServerService.
func (service *GRCPServerService) Run() (err error) {
defer func() {
if r := recover(); r != nil {
service.log.WithError(recoverErr(r)).Error("Critical error caught (recovered)")
}
}()
service.log.Infof(fmtLogServerListening, connectionType(service.isTLS), service.listener.Addr().String())
if err = service.server.Serve(service.listener); err != nil {
service.log.WithError(err).Error("Error returned attempting to serve requests")
return err
}
return nil
}
// Shutdown the ServerService.
func (service *GRCPServerService) Shutdown() {
service.server.Stop()
}
// Log returns the *logrus.Entry of the ServerService.
func (service *GRCPServerService) Log() *logrus.Entry {
return service.log
}
// FileWatcherService is a Service that watches files for changes. // FileWatcherService is a Service that watches files for changes.
type FileWatcherService struct { type FileWatcherService struct {
name string name string
@ -332,19 +272,6 @@ func svcSvrMetricsFunc(ctx *CmdCtx) (service Service) {
return service return service
} }
func svcSvrGRPCFunc(ctx *CmdCtx) (service Service) {
switch svr, listener, isTLS, err := server.CreateGRPCServer(ctx.config, ctx.providers); {
case err != nil:
ctx.log.WithError(err).Fatal("Create Server Service (gRPC) returned error")
case svr != nil && listener != nil:
service = NewGRCPServerService("gRCP", svr, listener, isTLS, ctx.log)
default:
ctx.log.Debug("Create Server Service (gRPC) skipped")
}
return service
}
func svcWatcherUsersFunc(ctx *CmdCtx) (service Service) { func svcWatcherUsersFunc(ctx *CmdCtx) (service Service) {
var err error var err error
@ -385,7 +312,7 @@ func servicesRun(ctx *CmdCtx) {
) )
for _, serviceFunc := range []func(ctx *CmdCtx) Service{ for _, serviceFunc := range []func(ctx *CmdCtx) Service{
svcSvrMainFunc, svcSvrGRPCFunc, svcSvrMetricsFunc, svcSvrMainFunc, svcSvrMetricsFunc,
svcWatcherUsersFunc, svcWatcherUsersFunc,
} { } {
if service := serviceFunc(ctx); service != nil { if service := serviceFunc(ctx); service != nil {

View File

@ -550,7 +550,7 @@ func (ctx *CmdCtx) StorageUserWebAuthnListRunE(cmd *cobra.Command, args []string
user := args[0] user := args[0]
devices, err = ctx.providers.StorageProvider.LoadWebAuthnDevicesByUsername(ctx, user) devices, err = ctx.providers.StorageProvider.LoadWebAuthnDevicesByUsername(ctx, "", user)
switch { switch {
case len(devices) == 0 || (err != nil && errors.Is(err, storage.ErrNoWebAuthnDevice)): case len(devices) == 0 || (err != nil && errors.Is(err, storage.ErrNoWebAuthnDevice)):

View File

@ -39,7 +39,7 @@ default_redirection_url: 'https://home.example.com/'
## Set the default 2FA method for new users and for when a user has a preferred method configured that has been ## Set the default 2FA method for new users and for when a user has a preferred method configured that has been
## disabled. This setting must be a method that is enabled. ## disabled. This setting must be a method that is enabled.
## Options are totp, webauthn, mobile_push. ## Options are totp, webauthn, mobile_push.
# default_2fa_method: '' default_2fa_method: ''
## ##
## Server Configuration ## Server Configuration
@ -47,12 +47,15 @@ default_redirection_url: 'https://home.example.com/'
server: server:
## The address for the Main server to listen on in the address common syntax. ## The address for the Main server to listen on in the address common syntax.
## Formats: ## Formats:
## - [<scheme>://]<hostname>[:<port>][/<path>] ## - [<scheme>://]<hostname>[:<port>]
## - [<scheme>://][hostname]:<port>[/<path>] ## - [<scheme>://][hostname]:<port>
## Square brackets indicate optional portions of the format. Scheme must be 'tcp', 'tcp4', 'tcp6', or 'unix'. ## Square brackets indicate optional portions of the format. Scheme must be 'tcp', 'tcp4', 'tcp6', or 'unix'.
## The default scheme is 'unix' if the address is an absolute path otherwise it's 'tcp'. The default port is '9091'. ## The default scheme is 'unix' if the address is an absolute path otherwise it's 'tcp'. The default port is '9091'.
## If the path is specified this configures the router to handle both the `/` path and the configured path. address: 'tcp://:9091'
address: 'tcp://:9091/'
## Set the single level path Authelia listens on.
## Must be alphanumeric chars and should not contain any slashes.
path: ''
## Set the path on disk to Authelia assets. ## Set the path on disk to Authelia assets.
## Useful to allow overriding of specific static assets. ## Useful to allow overriding of specific static assets.
@ -159,12 +162,11 @@ telemetry:
## The address for the Metrics server to listen on in the address common syntax. ## The address for the Metrics server to listen on in the address common syntax.
## Formats: ## Formats:
## - [<scheme>://]<hostname>[:<port>][/<path>] ## - [<scheme>://]<hostname>[:<port>]
## - [<scheme>://][hostname]:<port>[/<path>] ## - [<scheme>://][hostname]:<port>
## Square brackets indicate optional portions of the format. Scheme must be 'tcp', 'tcp4', 'tcp6', or 'unix'. ## Square brackets indicate optional portions of the format. Scheme must be 'tcp', 'tcp4', 'tcp6', or 'unix'.
## The default scheme is 'unix' if the address is an absolute path otherwise it's 'tcp'. The default port is '9959'. ## The default scheme is 'unix' if the address is an absolute path otherwise it's 'tcp'. The default port is '9959'.
## If the path is not specified it defaults to `/metrics`. address: 'tcp://:9959'
address: 'tcp://:9959/metrics'
## Metrics Server Buffers configuration. ## Metrics Server Buffers configuration.
# buffers: # buffers:
@ -201,7 +203,7 @@ totp:
## The TOTP algorithm to use. ## The TOTP algorithm to use.
## It is CRITICAL you read the documentation before changing this option: ## It is CRITICAL you read the documentation before changing this option:
## https://www.authelia.com/c/totp#algorithm ## https://www.authelia.com/c/totp#algorithm
algorithm: 'SHA1' algorithm: 'sha1'
## The number of digits a user has to input. Must either be 6 or 8. ## The number of digits a user has to input. Must either be 6 or 8.
## Changing this option only affects newly generated TOTP configurations. ## Changing this option only affects newly generated TOTP configurations.
@ -298,7 +300,7 @@ authentication_backend:
## External reset password url that redirects the user to an external reset portal. This disables the internal reset ## External reset password url that redirects the user to an external reset portal. This disables the internal reset
## functionality. ## functionality.
# custom_url: '' custom_url: ''
## The amount of time to wait before we refresh data from the authentication backend in the duration common syntax. ## The amount of time to wait before we refresh data from the authentication backend in the duration common syntax.
## To disable this feature set it to 'disable', this will slightly reduce security because for Authelia, users will ## To disable this feature set it to 'disable', this will slightly reduce security because for Authelia, users will
@ -315,7 +317,7 @@ authentication_backend:
## because it allows Authelia to offload the stateful operations ## because it allows Authelia to offload the stateful operations
## onto the LDAP service. ## onto the LDAP service.
# ldap: # ldap:
## The address of the directory server to connect to in the address common syntax. ## The address of the LDAP server to connect to in the address common syntax.
## Format: [<scheme>://]<hostname>[:<port>]. ## Format: [<scheme>://]<hostname>[:<port>].
## Square brackets indicate optional portions of the format. Scheme must be 'ldap', 'ldaps', or 'ldapi`. ## Square brackets indicate optional portions of the format. Scheme must be 'ldap', 'ldaps', or 'ldapi`.
## The default scheme is 'ldapi' if the address is an absolute path otherwise it's 'ldaps'. ## The default scheme is 'ldapi' if the address is an absolute path otherwise it's 'ldaps'.
@ -394,13 +396,23 @@ authentication_backend:
# SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH # SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH
# ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg # ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg
# 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg # 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg
# HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRs_DO_NOT_USE= # HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRsDO_NOT_USE=
# -----END RSA PRIVATE KEY----- # -----END RSA PRIVATE KEY-----
## The distinguished name of the container searched for objects in the directory information tree. ## The distinguished name of the container searched for objects in the directory information tree.
## See also: additional_users_dn, additional_groups_dn. ## See also: additional_users_dn, additional_groups_dn.
# base_dn: 'dc=example,dc=com' # base_dn: 'dc=example,dc=com'
## The attribute holding the username of the user. This attribute is used to populate the username in the session
## information. For your information, Microsoft Active Directory usually uses 'sAMAccountName' and OpenLDAP usually
## uses 'uid'. Beware that this attribute holds the unique identifiers for the users binding the user and the
## configuration stored in database. Therefore only single value attributes are allowed and the value must never be
## changed once attributed to a user otherwise it would break the configuration for that user. Technically,
## non-unique attributes like 'mail' can also be used but we don't recommend using them, we instead advise to use
## a filter to perform alternative lookups and the attributes mentioned above (sAMAccountName and uid) to
## follow https://datatracker.ietf.org/doc/html/rfc2307.
# username_attribute: 'uid'
## The additional_users_dn is prefixed to base_dn and delimited by a comma when searching for users. ## The additional_users_dn is prefixed to base_dn and delimited by a comma when searching for users.
## i.e. with this set to OU=Users and base_dn set to DC=a,DC=com; OU=Users,DC=a,DC=com is searched for users. ## i.e. with this set to OU=Users and base_dn set to DC=a,DC=com; OU=Users,DC=a,DC=com is searched for users.
# additional_users_dn: 'ou=users' # additional_users_dn: 'ou=users'
@ -431,9 +443,15 @@ authentication_backend:
## (&(uniqueMember={dn})(objectClass=groupOfUniqueNames)) ## (&(uniqueMember={dn})(objectClass=groupOfUniqueNames))
# groups_filter: '(&(member={dn})(objectClass=groupOfNames))' # groups_filter: '(&(member={dn})(objectClass=groupOfNames))'
## The group search mode to use. Options are 'filter' or 'memberof'. It's essential to read the docs if you wish to ## The attribute holding the name of the group.
## use 'memberof'. Also 'filter' is the best choice for most use cases. # group_name_attribute: 'cn'
# group_search_mode: 'filter'
## The attribute holding the mail address of the user. If multiple email addresses are defined for a user, only the
## first one returned by the LDAP server is used.
# mail_attribute: 'mail'
## The attribute holding the display name of the user. This will be used to greet an authenticated user.
# display_name_attribute: 'displayName'
## Follow referrals returned by the server. ## Follow referrals returned by the server.
## This is especially useful for environments where read-only servers exist. Only implemented for write operations. ## This is especially useful for environments where read-only servers exist. Only implemented for write operations.
@ -444,37 +462,6 @@ authentication_backend:
## Password can also be set using a secret: https://www.authelia.com/c/secrets ## Password can also be set using a secret: https://www.authelia.com/c/secrets
# password: 'password' # password: 'password'
## The attributes for users and objects from the directory server.
# attributes:
## The distinguished name attribute if your directory server supports it. Users should read the docs before
## configuring. Only used for the 'memberof' group search mode.
# distinguished_name: ''
## The attribute holding the username of the user. This attribute is used to populate the username in the session
## information. For your information, Microsoft Active Directory usually uses 'sAMAccountName' and OpenLDAP
## usually uses 'uid'. Beware that this attribute holds the unique identifiers for the users binding the user and
## the configuration stored in database; therefore only single value attributes are allowed and the value must
## never be changed once attributed to a user otherwise it would break the configuration for that user.
## Technically non-unique attributes like 'mail' can also be used but we don't recommend using them, we instead
## advise to use a filter to perform alternative lookups and the attributes mentioned above
## (sAMAccountName and uid) to follow https://datatracker.ietf.org/doc/html/rfc2307.
# username: 'uid'
## The attribute holding the display name of the user. This will be used to greet an authenticated user.
# display_name: 'displayName'
## The attribute holding the mail address of the user. If multiple email addresses are defined for a user, only
## the first one returned by the directory server is used.
# mail: 'mail'
## The attribute which provides distinguished names of groups an object is a member of.
## Only used for the 'memberof' group search mode.
# member_of: 'memberOf'
## The attribute holding the name of the group.
# group_name: 'cn'
## ##
## File (Authentication Provider) ## File (Authentication Provider)
## ##
@ -833,7 +820,7 @@ session:
# SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH # SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH
# ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg # ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg
# 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg # 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg
# HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRs_DO_NOT_USE= # HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRsDO_NOT_USE=
# -----END RSA PRIVATE KEY----- # -----END RSA PRIVATE KEY-----
## The Redis HA configuration options. ## The Redis HA configuration options.
@ -978,7 +965,7 @@ regulation:
# SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH # SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH
# ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg # ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg
# 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg # 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg
# HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRs_DO_NOT_USE= # HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRsDO_NOT_USE=
# -----END RSA PRIVATE KEY----- # -----END RSA PRIVATE KEY-----
## ##
@ -1061,8 +1048,8 @@ regulation:
# SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH # SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH
# ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg # ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg
# 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg # 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg
# HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRs_DO_NOT_USE= # HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRsDO_NOT_USE=
# -----END RSA PRIVATE KEY----- # -----END RSA PRIVATE KEY-----
## ##
## Notification Provider ## Notification Provider
@ -1179,7 +1166,7 @@ notifier:
# SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH # SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH
# ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg # ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg
# 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg # 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg
# HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRs_DO_NOT_USE= # HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRsDO_NOT_USE=
# -----END RSA PRIVATE KEY----- # -----END RSA PRIVATE KEY-----
## ##
@ -1221,7 +1208,7 @@ notifier:
# SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH # SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH
# ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg # ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg
# 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg # 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg
# HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRs_DO_NOT_USE= # HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRsDO_NOT_USE=
# -----END RSA PRIVATE KEY----- # -----END RSA PRIVATE KEY-----
@ -1251,7 +1238,7 @@ notifier:
# SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH # SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH
# ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg # ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg
# 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg # 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg
# HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRs_DO_NOT_USE= # HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRsDO_NOT_USE=
# -----END RSA PRIVATE KEY----- # -----END RSA PRIVATE KEY-----
## Optional matching certificate chain in PEM DER form that matches the issuer_private_key. All certificates within ## Optional matching certificate chain in PEM DER form that matches the issuer_private_key. All certificates within
@ -1300,7 +1287,6 @@ notifier:
## List of endpoints in addition to the metadata endpoints to permit cross-origin requests on. ## List of endpoints in addition to the metadata endpoints to permit cross-origin requests on.
# endpoints: # endpoints:
# - 'authorization' # - 'authorization'
# - 'pushed-authorization-request'
# - 'token' # - 'token'
# - 'revocation' # - 'revocation'
# - 'introspection' # - 'introspection'
@ -1339,7 +1325,7 @@ notifier:
## Redirect URI's specifies a list of valid case-sensitive callbacks for this client. ## Redirect URI's specifies a list of valid case-sensitive callbacks for this client.
# redirect_uris: # redirect_uris:
# - 'https://oidc.example.com:8080/oauth2/callback' # - 'https://oidc.example.com:8080/oauth2/callback'
## Audience this client is allowed to request. ## Audience this client is allowed to request.
# audience: [] # audience: []
@ -1354,7 +1340,7 @@ notifier:
## Grant Types configures which grants this client can obtain. ## Grant Types configures which grants this client can obtain.
## It's not recommended to define this unless you know what you're doing. ## It's not recommended to define this unless you know what you're doing.
# grant_types: # grant_types:
# - 'authorization_code' # - 'authorization_code'
## Response Types configures which responses this client can be sent. ## Response Types configures which responses this client can be sent.
## It's not recommended to define this unless you know what you're doing. ## It's not recommended to define this unless you know what you're doing.
@ -1426,7 +1412,7 @@ notifier:
# key: | # key: |
# -----BEGIN RSA PUBLIC KEY----- # -----BEGIN RSA PUBLIC KEY-----
# MEgCQQDAwV26ZA1lodtOQxNrJ491gWT+VzFum9IeZ+WTmMypYWyW1CzXKwsvTHDz # MEgCQQDAwV26ZA1lodtOQxNrJ491gWT+VzFum9IeZ+WTmMypYWyW1CzXKwsvTHDz
# 9ec+jserR3EMQ0Rr24lj13FL1ib5AgMBAAE_DO_NOT_USE= # 9ec+jserR3EMQ0Rr24lj13FL1ib5AgMBAAE=
# -----END RSA PUBLIC KEY---- # -----END RSA PUBLIC KEY----
## The matching certificate chain in PEM DER form that matches the key if available. ## The matching certificate chain in PEM DER form that matches the key if available.

View File

@ -38,8 +38,6 @@ const (
errFmtDecodeHookCouldNotParse = "could not decode '%s' to a %s%s: %w" errFmtDecodeHookCouldNotParse = "could not decode '%s' to a %s%s: %w"
errFmtDecodeHookCouldNotParseBasic = "could not decode to a %s%s: %w" errFmtDecodeHookCouldNotParseBasic = "could not decode to a %s%s: %w"
errFmtDecodeHookCouldNotParseEmptyValue = "could not decode an empty value to a %s%s: %w" errFmtDecodeHookCouldNotParseEmptyValue = "could not decode an empty value to a %s%s: %w"
errFmtSpecialRemappedKey = "configuration key '%s' is deprecated in %s and has been replaced by '%s' when combined with the '%s' in the format of '%s': this should be automatically mapped for you but you will need to adjust your configuration to remove this message"
) )
const ( const (

View File

@ -634,11 +634,11 @@ func StringToPasswordDigestHookFunc() mapstructure.DecodeHookFuncType {
var result *schema.PasswordDigest var result *schema.PasswordDigest
if dataStr != "" { if !strings.HasPrefix(dataStr, "$") {
if !strings.HasPrefix(dataStr, "$") { dataStr = fmt.Sprintf(plaintext.EncodingFmt, plaintext.AlgIdentifierPlainText, dataStr)
dataStr = fmt.Sprintf(plaintext.EncodingFmt, plaintext.AlgIdentifierPlainText, dataStr) }
}
if dataStr != "" {
if result, err = schema.DecodePasswordDigest(dataStr); err != nil { if result, err = schema.DecodePasswordDigest(dataStr); err != nil {
return nil, fmt.Errorf(errFmtDecodeHookCouldNotParse, dataStr, prefixType, expectedType.String(), err) return nil, fmt.Errorf(errFmtDecodeHookCouldNotParse, dataStr, prefixType, expectedType.String(), err)
} }

View File

@ -4,10 +4,8 @@ import (
"bytes" "bytes"
"crypto/ecdsa" "crypto/ecdsa"
"crypto/rsa" "crypto/rsa"
"crypto/tls"
"crypto/x509" "crypto/x509"
"encoding/pem" "encoding/pem"
"fmt"
"math" "math"
"net/mail" "net/mail"
"net/url" "net/url"
@ -788,6 +786,24 @@ func TestStringToRegexpFuncPointers(t *testing.T) {
} }
func TestStringToAddressHookFunc(t *testing.T) { func TestStringToAddressHookFunc(t *testing.T) {
mustParseAddress := func(a string) (addr schema.Address) {
addrs, err := schema.NewAddress(a)
if err != nil {
panic(err)
}
return *addrs
}
mustParseAddressPtr := func(a string) (addr *schema.Address) {
addr, err := schema.NewAddress(a)
if err != nil {
panic(err)
}
return addr
}
testCases := []struct { testCases := []struct {
name string name string
have any have any
@ -798,13 +814,13 @@ func TestStringToAddressHookFunc(t *testing.T) {
{ {
name: "ShouldDecodeNonPtr", name: "ShouldDecodeNonPtr",
have: "tcp://0.0.0.0:2020", have: "tcp://0.0.0.0:2020",
expected: MustParseAddress("tcp://0.0.0.0:2020"), expected: mustParseAddress("tcp://0.0.0.0:2020"),
decode: true, decode: true,
}, },
{ {
name: "ShouldDecodePtr", name: "ShouldDecodePtr",
have: "tcp://0.0.0.0:2020", have: "tcp://0.0.0.0:2020",
expected: MustParseAddressPtr("tcp://0.0.0.0:2020"), expected: mustParseAddressPtr("tcp://0.0.0.0:2020"),
decode: true, decode: true,
}, },
{ {
@ -856,90 +872,6 @@ func TestStringToAddressHookFunc(t *testing.T) {
err: "could not decode 'tcp://&!@^#*&!@#&*@!:2020' to a schema.Address: could not parse string 'tcp://&!@^#*&!@#&*@!:2020' as address: expected format is [<scheme>://]<hostname>[:<port>]: parse \"tcp://&!@^\": invalid character \"^\" in host name", err: "could not decode 'tcp://&!@^#*&!@#&*@!:2020' to a schema.Address: could not parse string 'tcp://&!@^#*&!@#&*@!:2020' as address: expected format is [<scheme>://]<hostname>[:<port>]: parse \"tcp://&!@^\": invalid character \"^\" in host name",
decode: false, decode: false,
}, },
{
name: "ShouldDecodeTCP",
have: "tcp://127.0.0.1",
expected: schema.AddressTCP{Address: MustParseAddress("tcp://127.0.0.1")},
err: "",
decode: true,
},
{
name: "ShouldDecodeTCPPtr",
have: "tcp://127.0.0.1",
expected: &schema.AddressTCP{Address: MustParseAddress("tcp://127.0.0.1")},
err: "",
decode: true,
},
{
name: "ShouldDecodeUDP",
have: "udp://127.0.0.1",
expected: schema.AddressUDP{Address: MustParseAddress("udp://127.0.0.1")},
err: "",
decode: true,
},
{
name: "ShouldDecodeUDPPtr",
have: "udp://127.0.0.1",
expected: &schema.AddressUDP{Address: MustParseAddress("udp://127.0.0.1")},
err: "",
decode: true,
},
{
name: "ShouldDecodeLDAP",
have: "ldap://127.0.0.1",
expected: schema.AddressLDAP{Address: MustParseAddress("ldap://127.0.0.1")},
err: "",
decode: true,
},
{
name: "ShouldDecodeLDAPPtr",
have: "ldap://127.0.0.1",
expected: &schema.AddressLDAP{Address: MustParseAddress("ldap://127.0.0.1")},
err: "",
decode: true,
},
{
name: "ShouldDecodeSMTP",
have: "smtp://127.0.0.1",
expected: schema.AddressSMTP{Address: MustParseAddress("smtp://127.0.0.1")},
err: "",
decode: true,
},
{
name: "ShouldDecodeSMTPPtr",
have: "smtp://127.0.0.1",
expected: &schema.AddressSMTP{Address: MustParseAddress("smtp://127.0.0.1")},
err: "",
decode: true,
},
{
name: "ShouldFailDecodeTCP",
have: "@@@@@@@",
expected: schema.AddressTCP{Address: MustParseAddress("tcp://127.0.0.1")},
err: "could not decode '@@@@@@@' to a schema.AddressTCP: error validating the address: the url 'tcp://%40%40%40%40%40%40@' appears to have user info but this is not valid for addresses",
decode: false,
},
{
name: "ShouldFailDecodeUDP",
have: "@@@@@@@",
expected: schema.AddressUDP{Address: MustParseAddress("udp://127.0.0.1")},
err: "could not decode '@@@@@@@' to a schema.AddressUDP: error validating the address: the url 'udp://%40%40%40%40%40%40@' appears to have user info but this is not valid for addresses",
decode: false,
},
{
name: "ShouldFailDecodeLDAP",
have: "@@@@@@@",
expected: schema.AddressLDAP{Address: MustParseAddress("ldap://127.0.0.1")},
err: "could not decode '@@@@@@@' to a schema.AddressLDAP: error validating the address: the url 'ldaps://%40%40%40%40%40%40@' appears to have user info but this is not valid for addresses",
decode: false,
},
{
name: "ShouldFailDecodeSMTP",
have: "@@@@@@@",
expected: schema.AddressSMTP{Address: MustParseAddress("smtp://127.0.0.1")},
err: "could not decode '@@@@@@@' to a schema.AddressSMTP: error validating the address: the url 'smtp://%40%40%40%40%40%40@' appears to have user info but this is not valid for addresses",
decode: false,
},
} }
hook := configuration.StringToAddressHookFunc() hook := configuration.StringToAddressHookFunc()
@ -1163,160 +1095,6 @@ func TestStringToX509CertificateHookFunc(t *testing.T) {
} }
} }
func TestStringToPasswordDigestHookFunc(t *testing.T) {
var nilvalue *schema.PasswordDigest
testCases := []struct {
name string
have any
expected any
err string
decode bool
}{
{
"ShouldParse",
"$plaintext$example",
MustParsePasswordDigest("$plaintext$example"),
"",
true,
},
{
"ShouldParsePtr",
"$plaintext$example",
MustParsePasswordDigestPtr("$plaintext$example"),
"",
true,
},
{
"ShouldNotParseUnknown",
"$abc$example",
schema.PasswordDigest{},
"could not decode '$abc$example' to a schema.PasswordDigest: provided encoded hash has an invalid identifier: the identifier 'abc' is unknown to the decoder",
false,
},
{
"ShouldNotParseWrongType",
"$abc$example",
schema.TLSVersion{},
"",
false,
},
{
"ShouldNotParseWrongTypePtr",
"$abc$example",
&schema.TLSVersion{},
"",
false,
},
{
"ShouldNotParseEmptyString",
"",
schema.PasswordDigest{},
"could not decode an empty value to a schema.PasswordDigest: must have a non-empty value",
false,
},
{
"ShouldParseEmptyStringPtr",
"",
nilvalue,
"",
true,
},
}
hook := configuration.StringToPasswordDigestHookFunc()
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
actual, err := hook(reflect.TypeOf(tc.have), reflect.TypeOf(tc.expected), tc.have)
if tc.err != "" {
assert.EqualError(t, err, tc.err)
if !tc.decode {
assert.Nil(t, actual)
}
} else {
assert.NoError(t, err)
if tc.decode {
assert.Equal(t, tc.expected, actual)
} else {
assert.Equal(t, tc.have, actual)
}
}
})
}
}
func TestStringToTLSVersionHookFunc(t *testing.T) {
testCases := []struct {
name string
have any
expected any
err string
decode bool
}{
{
"ShouldParseTLS1.3",
"TLS1.3",
schema.TLSVersion{Value: tls.VersionTLS13},
"",
true,
},
{
"ShouldParseTLS1.3PTR",
"TLS1.3",
&schema.TLSVersion{Value: tls.VersionTLS13},
"",
true,
},
{
"ShouldParseTLS1.2",
"TLS1.2",
schema.TLSVersion{Value: tls.VersionTLS12},
"",
true,
},
{
"ShouldNotParseInt",
1,
&schema.TLSVersion{},
"",
false,
},
{
"ShouldNotParseNonVersion",
"1",
&schema.TLSVersion{},
"could not decode '1' to a *schema.TLSVersion: supplied tls version isn't supported",
false,
},
}
hook := configuration.StringToTLSVersionHookFunc()
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
actual, err := hook(reflect.TypeOf(tc.have), reflect.TypeOf(tc.expected), tc.have)
if tc.err != "" {
assert.EqualError(t, err, tc.err)
if !tc.decode {
assert.Nil(t, actual)
}
} else {
assert.NoError(t, err)
if tc.decode {
assert.Equal(t, tc.expected, actual)
} else {
assert.Equal(t, tc.have, actual)
}
}
})
}
}
func TestStringToX509CertificateChainHookFunc(t *testing.T) { func TestStringToX509CertificateChainHookFunc(t *testing.T) {
var nilkey *schema.X509CertificateChain var nilkey *schema.X509CertificateChain
@ -1633,42 +1411,3 @@ var (
testZero int32 testZero int32
testString = "" testString = ""
) )
func MustParseAddress(input string) schema.Address {
address, err := schema.NewAddress(input)
if err != nil {
panic(err)
}
fmt.Println(address.String())
addr := *address
return addr
}
func MustParseAddressPtr(input string) *schema.Address {
address, err := schema.NewAddress(input)
if err != nil {
panic(err)
}
return address
}
func MustParsePasswordDigest(input string) schema.PasswordDigest {
digest, err := schema.DecodePasswordDigest(input)
if err != nil {
panic(err)
}
return *digest
}
func MustParsePasswordDigestPtr(input string) *schema.PasswordDigest {
digest, err := schema.DecodePasswordDigest(input)
if err != nil {
panic(err)
}
return digest
}

View File

@ -187,7 +187,7 @@ var deprecations = map[string]Deprecation{
Keep: true, Keep: true,
MapFunc: nil, MapFunc: nil,
ErrFunc: func(d Deprecation, _ map[string]any, _ any, val *schema.StructValidator) { ErrFunc: func(d Deprecation, _ map[string]any, _ any, val *schema.StructValidator) {
val.PushWarning(fmt.Errorf("configuration key '%s' is deprecated in %s and has been replaced by '%s' when combined with the 'server.port' and 'server.path' in the format of %s: this should be automatically mapped for you but you will need to adjust your configuration to remove this message", d.Key, d.Version.String(), d.NewKey, "'[tcp[(4|6)]://]<hostname>[:<port>][/<path>]' or 'tcp[(4|6)://][hostname]:<port>[/<path>]'")) val.PushWarning(fmt.Errorf("configuration key 'server.host' is deprecated in %s and has been replaced by 'server.address' when combined with the 'server.port' in the format of '[tcp://]<hostname>[:<port>]': this should be automatically mapped for you but you will need to adjust your configuration to remove this message", d.Version.String()))
}, },
}, },
"server.port": { "server.port": {
@ -198,18 +198,7 @@ var deprecations = map[string]Deprecation{
Keep: true, Keep: true,
MapFunc: nil, MapFunc: nil,
ErrFunc: func(d Deprecation, _ map[string]any, _ any, val *schema.StructValidator) { ErrFunc: func(d Deprecation, _ map[string]any, _ any, val *schema.StructValidator) {
val.PushWarning(fmt.Errorf("configuration key '%s' is deprecated in %s and has been replaced by '%s' when combined with the 'server.host' and 'server.path' in the format of %s: this should be automatically mapped for you but you will need to adjust your configuration to remove this message", d.Key, d.Version.String(), d.NewKey, "'[tcp[(4|6)]://]<hostname>[:<port>][/<path>]' or 'tcp[(4|6)://][hostname]:<port>[/<path>]'")) val.PushWarning(fmt.Errorf("configuration key 'server.port' is deprecated in %s and has been replaced by 'server.address' when combined with the 'server.host' in the format of '[tcp://]<hostname>[:<port>]': this should be automatically mapped for you but you will need to adjust your configuration to remove this message", d.Version.String()))
},
},
"server.path": {
Version: model.SemanticVersion{Major: 4, Minor: 38},
Key: "server.path",
NewKey: "server.address",
AutoMap: false,
Keep: true,
MapFunc: nil,
ErrFunc: func(d Deprecation, _ map[string]any, _ any, val *schema.StructValidator) {
val.PushWarning(fmt.Errorf("configuration key '%s' is deprecated in %s and has been replaced by '%s' when combined with the 'server.host' and 'server.port' in the format of %s: this should be automatically mapped for you but you will need to adjust your configuration to remove this message", d.Key, d.Version.String(), d.NewKey, "'[tcp[(4|6)]://]<hostname>[:<port>][/<path>]' or 'tcp[(4|6)://][hostname]:<port>[/<path>]'"))
}, },
}, },
"storage.mysql.host": { "storage.mysql.host": {
@ -220,7 +209,7 @@ var deprecations = map[string]Deprecation{
Keep: true, Keep: true,
MapFunc: nil, MapFunc: nil,
ErrFunc: func(d Deprecation, _ map[string]any, _ any, val *schema.StructValidator) { ErrFunc: func(d Deprecation, _ map[string]any, _ any, val *schema.StructValidator) {
val.PushWarning(fmt.Errorf(errFmtSpecialRemappedKey, d.Key, d.Version.String(), d.NewKey, "storage.mysql.port", "[tcp://]<hostname>[:<port>]")) val.PushWarning(fmt.Errorf("configuration key 'storage.mysql.host' is deprecated in %s and has been replaced by 'storage.mysql.address' when combined with the 'storage.mysql.port' in the format of '[tcp://]<hostname>[:<port>]': this should be automatically mapped for you but you will need to adjust your configuration to remove this message", d.Version.String()))
}, },
}, },
"storage.mysql.port": { "storage.mysql.port": {
@ -231,7 +220,7 @@ var deprecations = map[string]Deprecation{
Keep: true, Keep: true,
MapFunc: nil, MapFunc: nil,
ErrFunc: func(d Deprecation, _ map[string]any, _ any, val *schema.StructValidator) { ErrFunc: func(d Deprecation, _ map[string]any, _ any, val *schema.StructValidator) {
val.PushWarning(fmt.Errorf(errFmtSpecialRemappedKey, d.Key, d.Version.String(), d.NewKey, "storage.mysql.host", "[tcp://]<hostname>[:<port>]")) val.PushWarning(fmt.Errorf("configuration key 'storage.mysql.port' is deprecated in %s and has been replaced by 'storage.mysql.address' when combined with the 'storage.mysql.host' in the format of '[tcp://]<hostname>[:<port>]': this should be automatically mapped for you but you will need to adjust your configuration to remove this message", d.Version.String()))
}, },
}, },
"storage.postgres.host": { "storage.postgres.host": {
@ -242,7 +231,7 @@ var deprecations = map[string]Deprecation{
Keep: true, Keep: true,
MapFunc: nil, MapFunc: nil,
ErrFunc: func(d Deprecation, _ map[string]any, _ any, val *schema.StructValidator) { ErrFunc: func(d Deprecation, _ map[string]any, _ any, val *schema.StructValidator) {
val.PushWarning(fmt.Errorf(errFmtSpecialRemappedKey, d.Key, d.Version.String(), d.NewKey, "storage.postgres.port", "[tcp://]<hostname>[:<port>]")) val.PushWarning(fmt.Errorf("configuration key 'storage.postgres.host' is deprecated in %s and has been replaced by 'storage.postgres.address' when combined with the 'storage.postgres.port' in the format of '[tcp://]<hostname>[:<port>]': this should be automatically mapped for you but you will need to adjust your configuration to remove this message", d.Version.String()))
}, },
}, },
"storage.postgres.port": { "storage.postgres.port": {
@ -253,29 +242,7 @@ var deprecations = map[string]Deprecation{
Keep: true, Keep: true,
MapFunc: nil, MapFunc: nil,
ErrFunc: func(d Deprecation, _ map[string]any, _ any, val *schema.StructValidator) { ErrFunc: func(d Deprecation, _ map[string]any, _ any, val *schema.StructValidator) {
val.PushWarning(fmt.Errorf(errFmtSpecialRemappedKey, d.Key, d.Version.String(), d.NewKey, "storage.postgres.host", "[tcp://]<hostname>[:<port>]")) val.PushWarning(fmt.Errorf("configuration key 'storage.postgres.port' is deprecated in %s and has been replaced by 'storage.postgres.address' when combined with the 'storage.postgres.host' in the format of '[tcp://]<hostname>[:<port>]': this should be automatically mapped for you but you will need to adjust your configuration to remove this message", d.Version.String()))
},
},
"notifier.smtp.host": {
Version: model.SemanticVersion{Major: 4, Minor: 38},
Key: "notifier.smtp.host",
NewKey: "notifier.smtp.address",
AutoMap: false,
Keep: true,
MapFunc: nil,
ErrFunc: func(d Deprecation, _ map[string]any, _ any, val *schema.StructValidator) {
val.PushWarning(fmt.Errorf(errFmtSpecialRemappedKey, d.Key, d.Version.String(), d.NewKey, "notifier.smtp.port", "[tcp://]<hostname>[:<port>]"))
},
},
"notifier.smtp.port": {
Version: model.SemanticVersion{Major: 4, Minor: 38},
Key: "notifier.smtp.port",
NewKey: "notifier.smtp.address",
AutoMap: false,
Keep: true,
MapFunc: nil,
ErrFunc: func(d Deprecation, _ map[string]any, _ any, val *schema.StructValidator) {
val.PushWarning(fmt.Errorf(errFmtSpecialRemappedKey, d.Key, d.Version.String(), d.NewKey, "notifier.smtp.host", "[tcp://]<hostname>[:<port>]"))
}, },
}, },
"authentication_backend.ldap.url": { "authentication_backend.ldap.url": {
@ -294,36 +261,4 @@ var deprecations = map[string]Deprecation{
MapFunc: nil, MapFunc: nil,
ErrFunc: nil, ErrFunc: nil,
}, },
"authentication_backend.ldap.username_attribute": {
Version: model.SemanticVersion{Major: 4, Minor: 38},
Key: "authentication_backend.ldap.username_attribute",
NewKey: "authentication_backend.ldap.attributes.username",
AutoMap: true,
MapFunc: nil,
ErrFunc: nil,
},
"authentication_backend.ldap.mail_attribute": {
Version: model.SemanticVersion{Major: 4, Minor: 38},
Key: "authentication_backend.ldap.mail_attribute",
NewKey: "authentication_backend.ldap.attributes.mail",
AutoMap: true,
MapFunc: nil,
ErrFunc: nil,
},
"authentication_backend.ldap.display_name_attribute": {
Version: model.SemanticVersion{Major: 4, Minor: 38},
Key: "authentication_backend.ldap.display_name_attribute",
NewKey: "authentication_backend.ldap.attributes.display_name",
AutoMap: true,
MapFunc: nil,
ErrFunc: nil,
},
"authentication_backend.ldap.group_name_attribute": {
Version: model.SemanticVersion{Major: 4, Minor: 38},
Key: "authentication_backend.ldap.group_name_attribute",
NewKey: "authentication_backend.ldap.attributes.group_name",
AutoMap: true,
MapFunc: nil,
ErrFunc: nil,
},
} }

View File

@ -6,7 +6,6 @@ import (
"runtime" "runtime"
"testing" "testing"
"github.com/spf13/pflag"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -126,85 +125,3 @@ func TestKoanfSecretCallbackShouldErrorOnFSError(t *testing.T) {
assert.Len(t, val.Warnings(), 0) assert.Len(t, val.Warnings(), 0)
assert.EqualError(t, val.Errors()[0], fmt.Sprintf("secrets: error loading secret path %s into key 'theme': file permission error occurred: open %s: permission denied", secret, secret)) assert.EqualError(t, val.Errors()[0], fmt.Sprintf("secrets: error loading secret path %s into key 'theme': file permission error occurred: open %s: permission denied", secret, secret))
} }
func TestKoanfCommandLineWithMappingCallback(t *testing.T) {
testCases := []struct {
name string
have []string
flagName string
flagValue string
mapped string
valid bool
unchanged bool
expectedName string
expectedValue any
}{
{
"ShouldDecodeStandard",
[]string{"--commands", "abc"},
"commands",
"",
"command.another",
false,
false,
"command.another",
"abc",
},
{
"ShouldSkipUnchangedKey",
[]string{},
"commands",
"abc",
"command.another",
false,
false,
"",
nil,
},
{
"ShouldLookupNormalizedKey",
[]string{"--log.file-path", "abc"},
"log.file-path",
"",
"",
true,
false,
"log.file_path",
"abc",
},
{
"ShouldReturnUnmodified",
[]string{"--commands", "abc"},
"commands",
"",
"",
false,
false,
"",
nil,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
flagset := pflag.NewFlagSet("test", pflag.ContinueOnError)
flagset.String(tc.flagName, tc.flagValue, "")
assert.NoError(t, flagset.Parse(tc.have))
mapper := map[string]string{}
if tc.mapped != "" {
mapper[tc.flagName] = tc.mapped
}
callback := koanfCommandLineWithMappingCallback(mapper, tc.valid, tc.unchanged)
actualName, actualValue := callback(flagset.Lookup(tc.flagName))
assert.Equal(t, tc.expectedName, actualName)
assert.Equal(t, tc.expectedValue, actualValue)
})
}
}

View File

@ -245,28 +245,6 @@ func TestShouldLoadURLList(t *testing.T) {
assert.Equal(t, "https://example.com", config.IdentityProviders.OIDC.CORS.AllowedOrigins[1].String()) assert.Equal(t, "https://example.com", config.IdentityProviders.OIDC.CORS.AllowedOrigins[1].String())
} }
func TestShouldDisableOIDCEntropy(t *testing.T) {
val := schema.NewStructValidator()
keys, config, err := Load(val, NewDefaultSources([]string{"./test_resources/config_oidc_disable_entropy.yml"}, DefaultEnvPrefix, DefaultEnvDelimiter)...)
assert.NoError(t, err)
validator.ValidateKeys(keys, DefaultEnvPrefix, val)
assert.Len(t, val.Errors(), 0)
assert.Len(t, val.Warnings(), 0)
assert.Equal(t, -1, config.IdentityProviders.OIDC.MinimumParameterEntropy)
validator.ValidateIdentityProviders(&config.IdentityProviders, val)
assert.Len(t, val.Errors(), 1)
require.Len(t, val.Warnings(), 2)
assert.EqualError(t, val.Warnings()[0], "identity_providers: oidc: option 'minimum_parameter_entropy' is disabled which is considered unsafe and insecure")
assert.Equal(t, -1, config.IdentityProviders.OIDC.MinimumParameterEntropy)
}
func TestShouldConfigureConsent(t *testing.T) { func TestShouldConfigureConsent(t *testing.T) {
val := schema.NewStructValidator() val := schema.NewStructValidator()
keys, config, err := Load(val, NewDefaultSources([]string{"./test_resources/config_oidc.yml"}, DefaultEnvPrefix, DefaultEnvDelimiter)...) keys, config, err := Load(val, NewDefaultSources([]string{"./test_resources/config_oidc.yml"}, DefaultEnvPrefix, DefaultEnvDelimiter)...)
@ -336,24 +314,44 @@ func TestShouldValidateDeprecatedEnvNamesWithDeprecatedKeys(t *testing.T) {
assert.Len(t, val.Errors(), 0) assert.Len(t, val.Errors(), 0)
warnings := val.Warnings() warnings := val.Warnings()
require.Len(t, warnings, 10) require.Len(t, warnings, 3)
sort.Sort(utils.ErrSliceSortAlphabetical(warnings)) sort.Sort(utils.ErrSliceSortAlphabetical(warnings))
assert.EqualError(t, warnings[0], "configuration key 'authentication_backend.ldap.url' is deprecated in 4.38.0 and has been replaced by 'authentication_backend.ldap.address': this has been automatically mapped for you but you will need to adjust your configuration to remove this message") assert.EqualError(t, warnings[0], "configuration key 'authentication_backend.ldap.url' is deprecated in 4.38.0 and has been replaced by 'authentication_backend.ldap.address': this has been automatically mapped for you but you will need to adjust your configuration to remove this message")
assert.EqualError(t, warnings[1], "configuration key 'notifier.smtp.host' is deprecated in 4.38.0 and has been replaced by 'notifier.smtp.address' when combined with the 'notifier.smtp.port' in the format of '[tcp://]<hostname>[:<port>]': this should be automatically mapped for you but you will need to adjust your configuration to remove this message") assert.EqualError(t, warnings[1], "configuration key 'storage.mysql.host' is deprecated in 4.38.0 and has been replaced by 'storage.mysql.address' when combined with the 'storage.mysql.port' in the format of '[tcp://]<hostname>[:<port>]': this should be automatically mapped for you but you will need to adjust your configuration to remove this message")
assert.EqualError(t, warnings[2], "configuration key 'notifier.smtp.port' is deprecated in 4.38.0 and has been replaced by 'notifier.smtp.address' when combined with the 'notifier.smtp.host' in the format of '[tcp://]<hostname>[:<port>]': this should be automatically mapped for you but you will need to adjust your configuration to remove this message") assert.EqualError(t, warnings[2], "configuration key 'storage.mysql.port' is deprecated in 4.38.0 and has been replaced by 'storage.mysql.address' when combined with the 'storage.mysql.host' in the format of '[tcp://]<hostname>[:<port>]': this should be automatically mapped for you but you will need to adjust your configuration to remove this message")
assert.EqualError(t, warnings[3], "configuration key 'server.host' is deprecated in 4.38.0 and has been replaced by 'server.address' when combined with the 'server.port' and 'server.path' in the format of '[tcp[(4|6)]://]<hostname>[:<port>][/<path>]' or 'tcp[(4|6)://][hostname]:<port>[/<path>]': this should be automatically mapped for you but you will need to adjust your configuration to remove this message")
assert.EqualError(t, warnings[4], "configuration key 'server.path' is deprecated in 4.38.0 and has been replaced by 'server.address' when combined with the 'server.host' and 'server.port' in the format of '[tcp[(4|6)]://]<hostname>[:<port>][/<path>]' or 'tcp[(4|6)://][hostname]:<port>[/<path>]': this should be automatically mapped for you but you will need to adjust your configuration to remove this message")
assert.EqualError(t, warnings[5], "configuration key 'server.port' is deprecated in 4.38.0 and has been replaced by 'server.address' when combined with the 'server.host' and 'server.path' in the format of '[tcp[(4|6)]://]<hostname>[:<port>][/<path>]' or 'tcp[(4|6)://][hostname]:<port>[/<path>]': this should be automatically mapped for you but you will need to adjust your configuration to remove this message")
assert.EqualError(t, warnings[6], "configuration key 'storage.mysql.host' is deprecated in 4.38.0 and has been replaced by 'storage.mysql.address' when combined with the 'storage.mysql.port' in the format of '[tcp://]<hostname>[:<port>]': this should be automatically mapped for you but you will need to adjust your configuration to remove this message")
assert.EqualError(t, warnings[7], "configuration key 'storage.mysql.port' is deprecated in 4.38.0 and has been replaced by 'storage.mysql.address' when combined with the 'storage.mysql.host' in the format of '[tcp://]<hostname>[:<port>]': this should be automatically mapped for you but you will need to adjust your configuration to remove this message")
assert.EqualError(t, warnings[8], "configuration key 'storage.postgres.host' is deprecated in 4.38.0 and has been replaced by 'storage.postgres.address' when combined with the 'storage.postgres.port' in the format of '[tcp://]<hostname>[:<port>]': this should be automatically mapped for you but you will need to adjust your configuration to remove this message")
assert.EqualError(t, warnings[9], "configuration key 'storage.postgres.port' is deprecated in 4.38.0 and has been replaced by 'storage.postgres.address' when combined with the 'storage.postgres.host' in the format of '[tcp://]<hostname>[:<port>]': this should be automatically mapped for you but you will need to adjust your configuration to remove this message")
assert.Equal(t, "ldap://from-env:389", c.AuthenticationBackend.LDAP.Address.String()) assert.Equal(t, "ldap://from-env:389", c.AuthenticationBackend.LDAP.Address.String())
} }
func TestShouldValidateDeprecatedEnvNamesWithDeprecatedKeysAlt(t *testing.T) {
val := schema.NewStructValidator()
keys, c, err := Load(val, NewDefaultSources([]string{"./test_resources/config.deprecated.alt.yml"}, DefaultEnvPrefix, DefaultEnvDelimiter)...)
assert.NoError(t, err)
validator.ValidateKeys(keys, DefaultEnvPrefix, val)
assert.Len(t, val.Errors(), 0)
warnings := val.Warnings()
require.Len(t, warnings, 3)
sort.Sort(utils.ErrSliceSortAlphabetical(warnings))
assert.EqualError(t, warnings[0], "configuration key 'authentication_backend.ldap.url' is deprecated in 4.38.0 and has been replaced by 'authentication_backend.ldap.address': this has been automatically mapped for you but you will need to adjust your configuration to remove this message")
assert.EqualError(t, warnings[1], "configuration key 'storage.postgres.host' is deprecated in 4.38.0 and has been replaced by 'storage.postgres.address' when combined with the 'storage.postgres.port' in the format of '[tcp://]<hostname>[:<port>]': this should be automatically mapped for you but you will need to adjust your configuration to remove this message")
assert.EqualError(t, warnings[2], "configuration key 'storage.postgres.port' is deprecated in 4.38.0 and has been replaced by 'storage.postgres.address' when combined with the 'storage.postgres.host' in the format of '[tcp://]<hostname>[:<port>]': this should be automatically mapped for you but you will need to adjust your configuration to remove this message")
val.Clear()
validator.ValidateConfiguration(c, val)
require.NotNil(t, c.Storage.PostgreSQL.Address)
assert.Equal(t, "tcp://127.0.0.1:5432", c.Storage.PostgreSQL.Address.String())
}
func TestShouldRaiseErrOnInvalidNotifierSMTPSender(t *testing.T) { func TestShouldRaiseErrOnInvalidNotifierSMTPSender(t *testing.T) {
val := schema.NewStructValidator() val := schema.NewStructValidator()
keys, _, err := Load(val, NewDefaultSources([]string{"./test_resources/config_smtp_sender_invalid.yml"}, DefaultEnvPrefix, DefaultEnvDelimiter)...) keys, _, err := Load(val, NewDefaultSources([]string{"./test_resources/config_smtp_sender_invalid.yml"}, DefaultEnvPrefix, DefaultEnvDelimiter)...)
@ -497,7 +495,7 @@ func TestShouldLoadDirectoryConfiguration(t *testing.T) {
assert.Len(t, val.Errors(), 0) assert.Len(t, val.Errors(), 0)
require.Len(t, val.Warnings(), 1) require.Len(t, val.Warnings(), 1)
assert.EqualError(t, val.Warnings()[0], "configuration key 'server.port' is deprecated in 4.38.0 and has been replaced by 'server.address' when combined with the 'server.host' and 'server.path' in the format of '[tcp[(4|6)]://]<hostname>[:<port>][/<path>]' or 'tcp[(4|6)://][hostname]:<port>[/<path>]': this should be automatically mapped for you but you will need to adjust your configuration to remove this message") assert.EqualError(t, val.Warnings()[0], "configuration key 'server.port' is deprecated in 4.38.0 and has been replaced by 'server.address' when combined with the 'server.host' in the format of '[tcp://]<hostname>[:<port>]': this should be automatically mapped for you but you will need to adjust your configuration to remove this message")
} }
func testSetEnv(t *testing.T, key, value string) { func testSetEnv(t *testing.T, key, value string) {

View File

@ -108,9 +108,11 @@ type LDAPAuthenticationBackend struct {
AdditionalGroupsDN string `koanf:"additional_groups_dn"` AdditionalGroupsDN string `koanf:"additional_groups_dn"`
GroupsFilter string `koanf:"groups_filter"` GroupsFilter string `koanf:"groups_filter"`
GroupSearchMode string `koanf:"group_search_mode"`
Attributes LDAPAuthenticationAttributes `koanf:"attributes"` GroupNameAttribute string `koanf:"group_name_attribute"`
UsernameAttribute string `koanf:"username_attribute"`
MailAttribute string `koanf:"mail_attribute"`
DisplayNameAttribute string `koanf:"display_name_attribute"`
PermitReferrals bool `koanf:"permit_referrals"` PermitReferrals bool `koanf:"permit_referrals"`
PermitUnauthenticatedBind bool `koanf:"permit_unauthenticated_bind"` PermitUnauthenticatedBind bool `koanf:"permit_unauthenticated_bind"`
@ -120,16 +122,6 @@ type LDAPAuthenticationBackend struct {
Password string `koanf:"password"` Password string `koanf:"password"`
} }
// LDAPAuthenticationAttributes represents the configuration related to LDAP server attributes.
type LDAPAuthenticationAttributes struct {
DistinguishedName string `koanf:"distinguished_name"`
Username string `koanf:"username"`
DisplayName string `koanf:"display_name"`
Mail string `koanf:"mail"`
MemberOf string `koanf:"member_of"`
GroupName string `koanf:"group_name"`
}
// DefaultPasswordConfig represents the default configuration related to Argon2id hashing. // DefaultPasswordConfig represents the default configuration related to Argon2id hashing.
var DefaultPasswordConfig = Password{ var DefaultPasswordConfig = Password{
Algorithm: argon2, Algorithm: argon2,
@ -183,14 +175,11 @@ var DefaultCIPasswordConfig = Password{
// DefaultLDAPAuthenticationBackendConfigurationImplementationCustom represents the default LDAP config. // DefaultLDAPAuthenticationBackendConfigurationImplementationCustom represents the default LDAP config.
var DefaultLDAPAuthenticationBackendConfigurationImplementationCustom = LDAPAuthenticationBackend{ var DefaultLDAPAuthenticationBackendConfigurationImplementationCustom = LDAPAuthenticationBackend{
GroupSearchMode: ldapGroupSearchModeFilter, UsernameAttribute: ldapAttrUserID,
Attributes: LDAPAuthenticationAttributes{ MailAttribute: ldapAttrMail,
Username: ldapAttrUserID, DisplayNameAttribute: ldapAttrDisplayName,
DisplayName: ldapAttrDisplayName, GroupNameAttribute: ldapAttrCommonName,
Mail: ldapAttrMail, Timeout: time.Second * 5,
GroupName: ldapAttrCommonName,
},
Timeout: time.Second * 5,
TLS: &TLSConfig{ TLS: &TLSConfig{
MinimumVersion: TLSVersion{tls.VersionTLS12}, MinimumVersion: TLSVersion{tls.VersionTLS12},
}, },
@ -198,18 +187,13 @@ var DefaultLDAPAuthenticationBackendConfigurationImplementationCustom = LDAPAuth
// DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory represents the default LDAP config for the LDAPImplementationActiveDirectory Implementation. // DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory represents the default LDAP config for the LDAPImplementationActiveDirectory Implementation.
var DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory = LDAPAuthenticationBackend{ var DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory = LDAPAuthenticationBackend{
UsersFilter: "(&(|({username_attribute}={input})({mail_attribute}={input}))(sAMAccountType=805306368)(!(userAccountControl:1.2.840.113556.1.4.803:=2))(!(pwdLastSet=0))(|(!(accountExpires=*))(accountExpires=0)(accountExpires>={date-time:microsoft-nt})))", UsersFilter: "(&(|({username_attribute}={input})({mail_attribute}={input}))(sAMAccountType=805306368)(!(userAccountControl:1.2.840.113556.1.4.803:=2))(!(pwdLastSet=0))(|(!(accountExpires=*))(accountExpires=0)(accountExpires>={date-time:microsoft-nt})))",
GroupsFilter: "(&(member={dn})(|(sAMAccountType=268435456)(sAMAccountType=536870912)))", UsernameAttribute: "sAMAccountName",
GroupSearchMode: ldapGroupSearchModeFilter, MailAttribute: ldapAttrMail,
Attributes: LDAPAuthenticationAttributes{ DisplayNameAttribute: ldapAttrDisplayName,
DistinguishedName: ldapAttrDistinguishedName, GroupsFilter: "(&(member={dn})(|(sAMAccountType=268435456)(sAMAccountType=536870912)))",
Username: ldapAttrSAMAccountName, GroupNameAttribute: ldapAttrCommonName,
DisplayName: ldapAttrDisplayName, Timeout: time.Second * 5,
Mail: ldapAttrMail,
MemberOf: ldapAttrMemberOf,
GroupName: ldapAttrCommonName,
},
Timeout: time.Second * 5,
TLS: &TLSConfig{ TLS: &TLSConfig{
MinimumVersion: TLSVersion{tls.VersionTLS12}, MinimumVersion: TLSVersion{tls.VersionTLS12},
}, },
@ -217,17 +201,13 @@ var DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory =
// DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis represents the default LDAP config for the LDAPImplementationRFC2307bis Implementation. // DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis represents the default LDAP config for the LDAPImplementationRFC2307bis Implementation.
var DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis = LDAPAuthenticationBackend{ var DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis = LDAPAuthenticationBackend{
UsersFilter: "(&(|({username_attribute}={input})({mail_attribute}={input}))(|(objectClass=inetOrgPerson)(objectClass=organizationalPerson)))", UsersFilter: "(&(|({username_attribute}={input})({mail_attribute}={input}))(|(objectClass=inetOrgPerson)(objectClass=organizationalPerson)))",
GroupsFilter: "(&(|(member={dn})(uniqueMember={dn}))(|(objectClass=groupOfNames)(objectClass=groupOfUniqueNames)(objectClass=groupOfMembers))(!(pwdReset=TRUE)))", UsernameAttribute: ldapAttrUserID,
GroupSearchMode: ldapGroupSearchModeFilter, MailAttribute: ldapAttrMail,
Attributes: LDAPAuthenticationAttributes{ DisplayNameAttribute: ldapAttrDisplayName,
Username: ldapAttrUserID, GroupsFilter: "(&(|(member={dn})(uniqueMember={dn}))(|(objectClass=groupOfNames)(objectClass=groupOfUniqueNames)(objectClass=groupOfMembers)))",
DisplayName: ldapAttrDisplayName, GroupNameAttribute: ldapAttrCommonName,
Mail: ldapAttrMail, Timeout: time.Second * 5,
MemberOf: ldapAttrMemberOf,
GroupName: ldapAttrCommonName,
},
Timeout: time.Second * 5,
TLS: &TLSConfig{ TLS: &TLSConfig{
MinimumVersion: TLSVersion{tls.VersionTLS12}, MinimumVersion: TLSVersion{tls.VersionTLS12},
}, },
@ -235,17 +215,13 @@ var DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis = LDAP
// DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA represents the default LDAP config for the LDAPImplementationFreeIPA Implementation. // DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA represents the default LDAP config for the LDAPImplementationFreeIPA Implementation.
var DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA = LDAPAuthenticationBackend{ var DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA = LDAPAuthenticationBackend{
UsersFilter: "(&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=person)(!(nsAccountLock=TRUE))(krbPasswordExpiration>={date-time:generalized})(|(!(krbPrincipalExpiration=*))(krbPrincipalExpiration>={date-time:generalized})))", UsersFilter: "(&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=person)(!(nsAccountLock=TRUE))(krbPasswordExpiration>={date-time:generalized})(|(!(krbPrincipalExpiration=*))(krbPrincipalExpiration>={date-time:generalized})))",
GroupsFilter: "(&(member={dn})(objectClass=groupOfNames))", UsernameAttribute: ldapAttrUserID,
GroupSearchMode: ldapGroupSearchModeFilter, MailAttribute: ldapAttrMail,
Attributes: LDAPAuthenticationAttributes{ DisplayNameAttribute: ldapAttrDisplayName,
Username: ldapAttrUserID, GroupsFilter: "(&(member={dn})(objectClass=groupOfNames))",
DisplayName: ldapAttrDisplayName, GroupNameAttribute: ldapAttrCommonName,
Mail: ldapAttrMail, Timeout: time.Second * 5,
MemberOf: ldapAttrMemberOf,
GroupName: ldapAttrCommonName,
},
Timeout: time.Second * 5,
TLS: &TLSConfig{ TLS: &TLSConfig{
MinimumVersion: TLSVersion{tls.VersionTLS12}, MinimumVersion: TLSVersion{tls.VersionTLS12},
}, },
@ -253,19 +229,15 @@ var DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA = LDAPAut
// DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP represents the default LDAP config for the LDAPImplementationLLDAP Implementation. // DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP represents the default LDAP config for the LDAPImplementationLLDAP Implementation.
var DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP = LDAPAuthenticationBackend{ var DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP = LDAPAuthenticationBackend{
AdditionalUsersDN: "OU=people", AdditionalUsersDN: "OU=people",
AdditionalGroupsDN: "OU=groups", AdditionalGroupsDN: "OU=groups",
UsersFilter: "(&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=person))", UsersFilter: "(&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=person))",
GroupsFilter: "(&(member={dn})(objectClass=groupOfUniqueNames))", UsernameAttribute: ldapAttrUserID,
GroupSearchMode: ldapGroupSearchModeFilter, MailAttribute: ldapAttrMail,
Attributes: LDAPAuthenticationAttributes{ DisplayNameAttribute: ldapAttrCommonName,
Username: ldapAttrUserID, GroupsFilter: "(&(member={dn})(objectClass=groupOfUniqueNames))",
DisplayName: ldapAttrCommonName, GroupNameAttribute: ldapAttrCommonName,
Mail: ldapAttrMail, Timeout: time.Second * 5,
MemberOf: ldapAttrMemberOf,
GroupName: ldapAttrCommonName,
},
Timeout: time.Second * 5,
TLS: &TLSConfig{ TLS: &TLSConfig{
MinimumVersion: TLSVersion{tls.VersionTLS12}, MinimumVersion: TLSVersion{tls.VersionTLS12},
}, },
@ -273,17 +245,13 @@ var DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP = LDAPAuthe
// DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth represents the default LDAP config for the LDAPImplementationGLAuth Implementation. // DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth represents the default LDAP config for the LDAPImplementationGLAuth Implementation.
var DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth = LDAPAuthenticationBackend{ var DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth = LDAPAuthenticationBackend{
UsersFilter: "(&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=posixAccount)(!(accountStatus=inactive)))", UsersFilter: "(&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=posixAccount)(!(accountStatus=inactive)))",
GroupsFilter: "(&(uniqueMember={dn})(objectClass=posixGroup))", UsernameAttribute: ldapAttrCommonName,
GroupSearchMode: ldapGroupSearchModeFilter, MailAttribute: ldapAttrMail,
Attributes: LDAPAuthenticationAttributes{ DisplayNameAttribute: ldapAttrDescription,
Username: ldapAttrCommonName, GroupsFilter: "(&(uniqueMember={dn})(objectClass=posixGroup))",
DisplayName: ldapAttrDescription, GroupNameAttribute: ldapAttrCommonName,
Mail: ldapAttrMail, Timeout: time.Second * 5,
MemberOf: ldapAttrMemberOf,
GroupName: ldapAttrCommonName,
},
Timeout: time.Second * 5,
TLS: &TLSConfig{ TLS: &TLSConfig{
MinimumVersion: TLSVersion{tls.VersionTLS12}, MinimumVersion: TLSVersion{tls.VersionTLS12},
}, },

View File

@ -78,14 +78,6 @@ const (
LDAPImplementationGLAuth = "glauth" LDAPImplementationGLAuth = "glauth"
) )
const (
// LDAPGroupSearchModeFilter is the string for the filter group search mode.
LDAPGroupSearchModeFilter = "filter"
// LDAPGroupSearchModeMemberOf is the string for the memberOf group search mode.
LDAPGroupSearchModeMemberOf = "memberof"
)
// TOTP Algorithm. // TOTP Algorithm.
const ( const (
TOTPAlgorithmSHA1 = "SHA1" TOTPAlgorithmSHA1 = "SHA1"
@ -129,18 +121,11 @@ const (
) )
const ( const (
ldapGroupSearchModeFilter = "filter" ldapAttrMail = "mail"
) ldapAttrUserID = "uid"
ldapAttrDisplayName = "displayName"
const ( ldapAttrDescription = "description"
ldapAttrDistinguishedName = "distinguishedName" ldapAttrCommonName = "cn"
ldapAttrMail = "mail"
ldapAttrUserID = "uid"
ldapAttrSAMAccountName = "sAMAccountName"
ldapAttrDisplayName = "displayName"
ldapAttrDescription = "description"
ldapAttrCommonName = "cn"
ldapAttrMemberOf = "memberOf"
) )
// Address Schemes. // Address Schemes.

View File

@ -120,13 +120,10 @@ var Keys = []string{
"authentication_backend.ldap.users_filter", "authentication_backend.ldap.users_filter",
"authentication_backend.ldap.additional_groups_dn", "authentication_backend.ldap.additional_groups_dn",
"authentication_backend.ldap.groups_filter", "authentication_backend.ldap.groups_filter",
"authentication_backend.ldap.group_search_mode", "authentication_backend.ldap.group_name_attribute",
"authentication_backend.ldap.attributes.distinguished_name", "authentication_backend.ldap.username_attribute",
"authentication_backend.ldap.attributes.username", "authentication_backend.ldap.mail_attribute",
"authentication_backend.ldap.attributes.display_name", "authentication_backend.ldap.display_name_attribute",
"authentication_backend.ldap.attributes.mail",
"authentication_backend.ldap.attributes.member_of",
"authentication_backend.ldap.attributes.group_name",
"authentication_backend.ldap.permit_referrals", "authentication_backend.ldap.permit_referrals",
"authentication_backend.ldap.permit_unauthenticated_bind", "authentication_backend.ldap.permit_unauthenticated_bind",
"authentication_backend.ldap.permit_feature_detection_failure", "authentication_backend.ldap.permit_feature_detection_failure",
@ -262,10 +259,9 @@ var Keys = []string{
"notifier.smtp.port", "notifier.smtp.port",
"notifier.template_path", "notifier.template_path",
"server.address", "server.address",
"server.path",
"server.asset_path", "server.asset_path",
"server.disable_healthcheck", "server.disable_healthcheck",
"server.disable_autho_https_redirect",
"server.use_ip_for_ban",
"server.tls.certificate", "server.tls.certificate",
"server.tls.key", "server.tls.key",
"server.tls.client_certificates", "server.tls.client_certificates",
@ -276,8 +272,6 @@ var Keys = []string{
"server.endpoints.authz.*.implementation", "server.endpoints.authz.*.implementation",
"server.endpoints.authz.*.authn_strategies", "server.endpoints.authz.*.authn_strategies",
"server.endpoints.authz.*.authn_strategies[].name", "server.endpoints.authz.*.authn_strategies[].name",
"server.grpc.address",
"server.grpc.disableTLS",
"server.buffers.read", "server.buffers.read",
"server.buffers.write", "server.buffers.write",
"server.timeouts.read", "server.timeouts.read",
@ -285,7 +279,6 @@ var Keys = []string{
"server.timeouts.idle", "server.timeouts.idle",
"server.host", "server.host",
"server.port", "server.port",
"server.path",
"telemetry.metrics.enabled", "telemetry.metrics.enabled",
"telemetry.metrics.address", "telemetry.metrics.address",
"telemetry.metrics.buffers.read", "telemetry.metrics.buffers.read",

View File

@ -7,16 +7,14 @@ import (
// ServerConfiguration represents the configuration of the http server. // ServerConfiguration represents the configuration of the http server.
type ServerConfiguration struct { type ServerConfiguration struct {
Address *AddressTCP `koanf:"address"` Address *AddressTCP `koanf:"address"`
AssetPath string `koanf:"asset_path"` Path string `koanf:"path"`
DisableHealthcheck bool `koanf:"disable_healthcheck"` AssetPath string `koanf:"asset_path"`
DisableAutoHttpsRedirect bool `koanf:"disable_autho_https_redirect"` DisableHealthcheck bool `koanf:"disable_healthcheck"`
UseIPInsteadOfUserForBan bool `koanf:"use_ip_for_ban"`
TLS ServerTLS `koanf:"tls"` TLS ServerTLS `koanf:"tls"`
Headers ServerHeaders `koanf:"headers"` Headers ServerHeaders `koanf:"headers"`
Endpoints ServerEndpoints `koanf:"endpoints"` Endpoints ServerEndpoints `koanf:"endpoints"`
GRPC ServerGRPC `koanf:"grpc"`
Buffers ServerBuffers `koanf:"buffers"` Buffers ServerBuffers `koanf:"buffers"`
Timeouts ServerTimeouts `koanf:"timeouts"` Timeouts ServerTimeouts `koanf:"timeouts"`
@ -26,9 +24,6 @@ type ServerConfiguration struct {
// Deprecated: use address instead. // Deprecated: use address instead.
Port int `koanf:"port"` Port int `koanf:"port"`
// Deprecated: use address instead.
Path string `koanf:"path"`
} }
// ServerEndpoints is the endpoints configuration for the HTTP server. // ServerEndpoints is the endpoints configuration for the HTTP server.
@ -63,18 +58,9 @@ type ServerHeaders struct {
CSPTemplate string `koanf:"csp_template"` CSPTemplate string `koanf:"csp_template"`
} }
// ServerGRCP contains configuration options for the gRCP server.
type ServerGRPC struct {
// Address with port to listen on. If this field is empty, no grcp server
// will be spawned.
Address *AddressTCP `koanf:"address"`
DisableTLS bool `koanf:"disableTLS"`
}
// DefaultServerConfiguration represents the default values of the ServerConfiguration. // DefaultServerConfiguration represents the default values of the ServerConfiguration.
var DefaultServerConfiguration = ServerConfiguration{ var DefaultServerConfiguration = ServerConfiguration{
Address: &AddressTCP{Address{true, false, -1, 9091, &url.URL{Scheme: AddressSchemeTCP, Host: ":9091", Path: "/"}}}, Address: &AddressTCP{Address{true, false, -1, 9091, &url.URL{Scheme: AddressSchemeTCP, Host: ":9091"}}},
Buffers: ServerBuffers{ Buffers: ServerBuffers{
Read: 4096, Read: 4096,
Write: 4096, Write: 4096,

View File

@ -21,7 +21,7 @@ type TelemetryMetricsConfig struct {
// DefaultTelemetryConfig is the default telemetry configuration. // DefaultTelemetryConfig is the default telemetry configuration.
var DefaultTelemetryConfig = TelemetryConfig{ var DefaultTelemetryConfig = TelemetryConfig{
Metrics: TelemetryMetricsConfig{ Metrics: TelemetryMetricsConfig{
Address: &AddressTCP{Address{true, false, -1, 9959, &url.URL{Scheme: AddressSchemeTCP, Host: ":9959", Path: "/metrics"}}}, Address: &AddressTCP{Address{true, false, -1, 9959, &url.URL{Scheme: AddressSchemeTCP, Host: ":9959"}}},
Buffers: ServerBuffers{ Buffers: ServerBuffers{
Read: 4096, Read: 4096,
Write: 4096, Write: 4096,

View File

@ -6,6 +6,7 @@ import (
"net/url" "net/url"
"strconv" "strconv"
"strings" "strings"
"syscall"
) )
// NewAddress returns an *Address and error depending on the ability to parse the string as an Address. // NewAddress returns an *Address and error depending on the ability to parse the string as an Address.
@ -335,15 +336,6 @@ func (a *Address) Path() string {
return a.url.Path return a.url.Path
} }
// SetPath sets the path.
func (a *Address) SetPath(path string) {
if !a.valid || a.url == nil {
return
}
a.url.Path = path
}
// SocketHostname returns the correct hostname for a socket connection. // SocketHostname returns the correct hostname for a socket connection.
func (a *Address) SocketHostname() string { func (a *Address) SocketHostname() string {
if !a.valid || a.url == nil { if !a.valid || a.url == nil {
@ -380,6 +372,25 @@ func (a *Address) NetworkAddress() string {
return a.url.Host return a.url.Host
} }
// Listener creates and returns a net.Listener.
func (a *Address) Listener() (ln net.Listener, err error) {
if a.url == nil {
return nil, fmt.Errorf("address url is nil")
}
if a.socket && a.umask != -1 {
umask := syscall.Umask(a.umask)
ln, err = net.Listen(a.Network(), a.NetworkAddress())
_ = syscall.Umask(umask)
return ln, err
}
return net.Listen(a.Network(), a.NetworkAddress())
}
// Dial creates and returns a dialed net.Conn. // Dial creates and returns a dialed net.Conn.
func (a *Address) Dial() (net.Conn, error) { func (a *Address) Dial() (net.Conn, error) {
if !a.valid || a.url == nil { if !a.valid || a.url == nil {

View File

@ -1,28 +0,0 @@
//go:build linux || freebsd || darwin || netbsd || solaris
package schema
import (
"fmt"
"net"
"syscall"
)
// Listener creates and returns a net.Listener.
func (a *Address) Listener() (ln net.Listener, err error) {
if a.url == nil {
return nil, fmt.Errorf("address url is nil")
}
if a.socket && a.umask != -1 {
umask := syscall.Umask(a.umask)
ln, err = net.Listen(a.Network(), a.NetworkAddress())
_ = syscall.Umask(umask)
return ln, err
}
return net.Listen(a.Network(), a.NetworkAddress())
}

View File

@ -1,17 +0,0 @@
//go:build !linux && !freebsd && !darwin && !netbsd && !solaris
package schema
import (
"fmt"
"net"
)
// Listener creates and returns a net.Listener.
func (a *Address) Listener() (ln net.Listener, err error) {
if a.url == nil {
return nil, fmt.Errorf("address url is nil")
}
return net.Listen(a.Network(), a.NetworkAddress())
}

View File

@ -268,21 +268,6 @@ func TestX509CertificateChain(t *testing.T) {
assert.Regexp(t, regexp.MustCompile(`^certificate #1 in chain is invalid before 13569465600 but the time is \d+$`), err.Error()) assert.Regexp(t, regexp.MustCompile(`^certificate #1 in chain is invalid before 13569465600 but the time is \d+$`), err.Error())
} }
func TestPasswordDigest_IsPlainText(t *testing.T) {
digest, err := DecodePasswordDigest("$plaintext$exam")
assert.NoError(t, err)
assert.True(t, digest.IsPlainText())
digest = &PasswordDigest{}
assert.False(t, digest.IsPlainText())
digest, err = DecodePasswordDigest("$pbkdf2-sha512$310000$c8p78n7pUMln0jzvd4aK4Q$JNRBzwAo0ek5qKn50cFzzvE9RXV88h1wJn5KGiHrD0YKtZaR/nCb2CJPOsKaPK0hjf.9yHxzQGZziziccp6Yng")
assert.NoError(t, err)
assert.False(t, digest.IsPlainText())
}
func MustParseX509CertificateChain(data string) *X509CertificateChain { func MustParseX509CertificateChain(data string) *X509CertificateChain {
chain, err := NewX509CertificateChain(data) chain, err := NewX509CertificateChain(data)
if err != nil { if err != nil {

View File

@ -0,0 +1,173 @@
---
default_redirection_url: https://home.example.com:8080/
server:
address: "tcp://127.0.0.1:9091"
endpoints:
authz:
forward-auth:
implementation: ForwardAuth
authn_strategies:
- name: HeaderProxyAuthorization
- name: CookieSession
ext-authz:
implementation: ExtAuthz
authn_strategies:
- name: HeaderProxyAuthorization
- name: CookieSession
auth-request:
implementation: AuthRequest
authn_strategies:
- name: HeaderAuthRequestProxyAuthorization
- name: CookieSession
legacy:
implementation: Legacy
log:
level: debug
totp:
issuer: authelia.com
duo_api:
hostname: api-123456789.example.com
integration_key: ABCDEF
authentication_backend:
ldap:
url: 'ldap://127.0.0.1'
tls:
private_key: |
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA6z1LOg1ZCqb0lytXWZ+MRBpMHEXOoTOLYgfZXt1IYyE3Z758
cyalk0NYQhY5cZDsXPYWPvAHiPMUxutWkoxFwby56S+AbIMa3/Is+ILrHRJs8Exn
ZkpyrYFxPX12app2kErdmAkHSx0Z5/kuXiz96PHs8S8/ZbyZolLHzdfLtSzjvRm5
Zue5iFzsf19NJz5CIBfv8g5lRwtE8wNJoRSpn1xq7fqfuA0weDNFPzjlNWRLy6aa
rK7qJexRkmkCs4sLgyl+9NODYJpvmN8E1yhyC27E0joI6rBFVW7Ihv+cSPCdDzGp
EWe81x3AeqAa3mjVqkiq4u4Z2i8JDgBaPboqJwIDAQABAoIBAAFdLZ58jVOefDSU
L8F5R1rtvBs93GDa56f926jNJ6pLewLC+/2+757W+SAI+PRLntM7Kg3bXm/Q2QH+
Q1Y+MflZmspbWCdI61L5GIGoYKyeers59i+FpvySj5GHtLQRiTZ0+Kv1AXHSDWBm
9XneUOqU3IbZe0ifu1RRno72/VtjkGXbW8Mkkw+ohyGbIeTx/0/JQ6sSNZTT3Vk7
8i4IXptq3HSF0/vqZuah8rShoeNq72pD1YLM9YPdL5by1QkDLnqATDiCpLBTCaNV
I8sqYEun+HYbQzBj8ZACG2JVZpEEidONWQHw5BPWO95DSZYrVnEkuCqeH+u5vYt7
CHuJ3AECgYEA+W3v5z+j91w1VPHS0VB3SCDMouycAMIUnJPAbt+0LPP0scUFsBGE
hPAKddC54pmMZRQ2KIwBKiyWfCrJ8Xz8Yogn7fJgmwTHidJBr2WQpIEkNGlK3Dzi
jXL2sh0yC7sHvn0DqiQ79l/e7yRbSnv2wrTJEczOOH2haD7/tBRyCYECgYEA8W+q
E9YyGvEltnPFaOxofNZ8LHVcZSsQI5b6fc0iE7fjxFqeXPXEwGSOTwqQLQRiHn9b
CfPmIG4Vhyq0otVmlPvUnfBZ2OK+tl5X2/mQFO3ROMdvpi0KYa994uqfJdSTaqLn
jjoKFB906UFHnDQDLZUNiV1WwnkTglgLc+xrd6cCgYEAqqthyv6NyBTM3Tm2gcio
Ra9Dtntl51LlXZnvwy3IkDXBCd6BHM9vuLKyxZiziGx+Vy90O1xI872cnot8sINQ
Am+dur/tAEVN72zxyv0Y8qb2yfH96iKy9gxi5s75TnOEQgAygLnYWaWR2lorKRUX
bHTdXBOiS58S0UzCFEslGIECgYBqkO4SKWYeTDhoKvuEj2yjRYyzlu28XeCWxOo1
otiauX0YSyNBRt2cSgYiTzhKFng0m+QUJYp63/wymB/5C5Zmxi0XtWIDADpLhqLj
HmmBQ2Mo26alQ5YkffBju0mZyhVzaQop1eZi8WuKFV1FThPlB7hc3E0SM5zv2Grd
tQnOWwKBgQC40yZY0PcjuILhy+sIc0Wvh7LUA7taSdTye149kRvbvsCDN7Jh75lM
USjhLXY0Nld2zBm9r8wMb81mXH29uvD+tDqqsICvyuKlA/tyzXR+QTr7dCVKVwu0
1YjCJ36UpTsLre2f8nOSLtNmRfDPtbOE2mkOoO9dD9UU0XZwnvn9xw==
-----END RSA PRIVATE KEY-----
base_dn: dc=example,dc=com
username_attribute: uid
additional_users_dn: ou=users
users_filter: (&({username_attribute}={input})(objectCategory=person)(objectClass=user))
additional_groups_dn: ou=groups
groups_filter: (&(member={dn})(objectClass=groupOfNames))
group_name_attribute: cn
mail_attribute: mail
user: cn=admin,dc=example,dc=com
access_control:
default_policy: deny
rules:
# Rules applied to everyone
- domain: public.example.com
policy: bypass
- domain: secure.example.com
policy: one_factor
# Network based rule, if not provided any network matches.
networks:
- 192.168.1.0/24
- domain: secure.example.com
policy: two_factor
- domain: [singlefactor.example.com, onefactor.example.com]
policy: one_factor
# Rules applied to 'admins' group
- domain: "mx2.mail.example.com"
subject: "group:admins"
policy: deny
- domain: "*.example.com"
subject: "group:admins"
policy: two_factor
# Rules applied to 'dev' group
- domain: dev.example.com
resources:
- "^/groups/dev/.*$"
subject: "group:dev"
policy: two_factor
# Rules applied to user 'john'
- domain: dev.example.com
resources:
- "^/users/john/.*$"
subject: "user:john"
policy: two_factor
# Rules applied to 'dev' group and user 'john'
- domain: dev.example.com
resources:
- "^/deny-all.*$"
subject: ["group:dev", "user:john"]
policy: deny
# Rules applied to user 'harry'
- domain: dev.example.com
resources:
- "^/users/harry/.*$"
subject: "user:harry"
policy: two_factor
# Rules applied to user 'bob'
- domain: "*.mail.example.com"
subject: "user:bob"
policy: two_factor
- domain: "dev.example.com"
resources:
- "^/users/bob/.*$"
subject: "user:bob"
policy: two_factor
session:
name: authelia_session
expiration: 3600000 # 1 hour
inactivity: 300000 # 5 minutes
domain: example.com
redis:
host: 127.0.0.1
port: 6379
high_availability:
sentinel_name: test
regulation:
max_retries: 3
find_time: 120
ban_time: 300
storage:
postgres:
host: 127.0.0.1
port: 5432
database: authelia
username: authelia
notifier:
smtp:
username: test
host: 127.0.0.1
port: 1025
sender: admin@example.com
disable_require_tls: true
...

View File

@ -1,23 +1,173 @@
--- ---
default_redirection_url: https://home.example.com:8080/
server: server:
host: '0.0.0.0' address: "tcp://127.0.0.1:9091"
port: 9091 endpoints:
path: "auth" authz:
forward-auth:
implementation: ForwardAuth
authn_strategies:
- name: HeaderProxyAuthorization
- name: CookieSession
ext-authz:
implementation: ExtAuthz
authn_strategies:
- name: HeaderProxyAuthorization
- name: CookieSession
auth-request:
implementation: AuthRequest
authn_strategies:
- name: HeaderAuthRequestProxyAuthorization
- name: CookieSession
legacy:
implementation: Legacy
log:
level: debug
totp:
issuer: authelia.com
duo_api:
hostname: api-123456789.example.com
integration_key: ABCDEF
authentication_backend: authentication_backend:
ldap: ldap:
url: 'ldap://127.0.0.1' url: 'ldap://127.0.0.1'
tls:
private_key: |
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA6z1LOg1ZCqb0lytXWZ+MRBpMHEXOoTOLYgfZXt1IYyE3Z758
cyalk0NYQhY5cZDsXPYWPvAHiPMUxutWkoxFwby56S+AbIMa3/Is+ILrHRJs8Exn
ZkpyrYFxPX12app2kErdmAkHSx0Z5/kuXiz96PHs8S8/ZbyZolLHzdfLtSzjvRm5
Zue5iFzsf19NJz5CIBfv8g5lRwtE8wNJoRSpn1xq7fqfuA0weDNFPzjlNWRLy6aa
rK7qJexRkmkCs4sLgyl+9NODYJpvmN8E1yhyC27E0joI6rBFVW7Ihv+cSPCdDzGp
EWe81x3AeqAa3mjVqkiq4u4Z2i8JDgBaPboqJwIDAQABAoIBAAFdLZ58jVOefDSU
L8F5R1rtvBs93GDa56f926jNJ6pLewLC+/2+757W+SAI+PRLntM7Kg3bXm/Q2QH+
Q1Y+MflZmspbWCdI61L5GIGoYKyeers59i+FpvySj5GHtLQRiTZ0+Kv1AXHSDWBm
9XneUOqU3IbZe0ifu1RRno72/VtjkGXbW8Mkkw+ohyGbIeTx/0/JQ6sSNZTT3Vk7
8i4IXptq3HSF0/vqZuah8rShoeNq72pD1YLM9YPdL5by1QkDLnqATDiCpLBTCaNV
I8sqYEun+HYbQzBj8ZACG2JVZpEEidONWQHw5BPWO95DSZYrVnEkuCqeH+u5vYt7
CHuJ3AECgYEA+W3v5z+j91w1VPHS0VB3SCDMouycAMIUnJPAbt+0LPP0scUFsBGE
hPAKddC54pmMZRQ2KIwBKiyWfCrJ8Xz8Yogn7fJgmwTHidJBr2WQpIEkNGlK3Dzi
jXL2sh0yC7sHvn0DqiQ79l/e7yRbSnv2wrTJEczOOH2haD7/tBRyCYECgYEA8W+q
E9YyGvEltnPFaOxofNZ8LHVcZSsQI5b6fc0iE7fjxFqeXPXEwGSOTwqQLQRiHn9b
CfPmIG4Vhyq0otVmlPvUnfBZ2OK+tl5X2/mQFO3ROMdvpi0KYa994uqfJdSTaqLn
jjoKFB906UFHnDQDLZUNiV1WwnkTglgLc+xrd6cCgYEAqqthyv6NyBTM3Tm2gcio
Ra9Dtntl51LlXZnvwy3IkDXBCd6BHM9vuLKyxZiziGx+Vy90O1xI872cnot8sINQ
Am+dur/tAEVN72zxyv0Y8qb2yfH96iKy9gxi5s75TnOEQgAygLnYWaWR2lorKRUX
bHTdXBOiS58S0UzCFEslGIECgYBqkO4SKWYeTDhoKvuEj2yjRYyzlu28XeCWxOo1
otiauX0YSyNBRt2cSgYiTzhKFng0m+QUJYp63/wymB/5C5Zmxi0XtWIDADpLhqLj
HmmBQ2Mo26alQ5YkffBju0mZyhVzaQop1eZi8WuKFV1FThPlB7hc3E0SM5zv2Grd
tQnOWwKBgQC40yZY0PcjuILhy+sIc0Wvh7LUA7taSdTye149kRvbvsCDN7Jh75lM
USjhLXY0Nld2zBm9r8wMb81mXH29uvD+tDqqsICvyuKlA/tyzXR+QTr7dCVKVwu0
1YjCJ36UpTsLre2f8nOSLtNmRfDPtbOE2mkOoO9dD9UU0XZwnvn9xw==
-----END RSA PRIVATE KEY-----
base_dn: dc=example,dc=com
username_attribute: uid
additional_users_dn: ou=users
users_filter: (&({username_attribute}={input})(objectCategory=person)(objectClass=user))
additional_groups_dn: ou=groups
groups_filter: (&(member={dn})(objectClass=groupOfNames))
group_name_attribute: cn
mail_attribute: mail
user: cn=admin,dc=example,dc=com
access_control:
default_policy: deny
rules:
# Rules applied to everyone
- domain: public.example.com
policy: bypass
- domain: secure.example.com
policy: one_factor
# Network based rule, if not provided any network matches.
networks:
- 192.168.1.0/24
- domain: secure.example.com
policy: two_factor
- domain: [singlefactor.example.com, onefactor.example.com]
policy: one_factor
# Rules applied to 'admins' group
- domain: "mx2.mail.example.com"
subject: "group:admins"
policy: deny
- domain: "*.example.com"
subject: "group:admins"
policy: two_factor
# Rules applied to 'dev' group
- domain: dev.example.com
resources:
- "^/groups/dev/.*$"
subject: "group:dev"
policy: two_factor
# Rules applied to user 'john'
- domain: dev.example.com
resources:
- "^/users/john/.*$"
subject: "user:john"
policy: two_factor
# Rules applied to 'dev' group and user 'john'
- domain: dev.example.com
resources:
- "^/deny-all.*$"
subject: ["group:dev", "user:john"]
policy: deny
# Rules applied to user 'harry'
- domain: dev.example.com
resources:
- "^/users/harry/.*$"
subject: "user:harry"
policy: two_factor
# Rules applied to user 'bob'
- domain: "*.mail.example.com"
subject: "user:bob"
policy: two_factor
- domain: "dev.example.com"
resources:
- "^/users/bob/.*$"
subject: "user:bob"
policy: two_factor
session:
name: authelia_session
expiration: 3600000 # 1 hour
inactivity: 300000 # 5 minutes
domain: example.com
redis:
host: 127.0.0.1
port: 6379
high_availability:
sentinel_name: test
regulation:
max_retries: 3
find_time: 120
ban_time: 300
storage: storage:
mysql: mysql:
host: 127.0.0.1 host: 127.0.0.1
port: 3306 port: 3306
postgres: database: authelia
host: 127.0.0.1 username: authelia
port: 5432
notifier: notifier:
smtp: smtp:
username: test
host: 127.0.0.1 host: 127.0.0.1
port: 1025 port: 1025
sender: admin@example.com
disable_require_tls: true
... ...

View File

@ -66,15 +66,14 @@ authentication_backend:
1YjCJ36UpTsLre2f8nOSLtNmRfDPtbOE2mkOoO9dD9UU0XZwnvn9xw== 1YjCJ36UpTsLre2f8nOSLtNmRfDPtbOE2mkOoO9dD9UU0XZwnvn9xw==
-----END RSA PRIVATE KEY----- -----END RSA PRIVATE KEY-----
base_dn: 'dc=example,dc=com' base_dn: 'dc=example,dc=com'
username_attribute: 'uid'
additional_users_dn: 'ou=users' additional_users_dn: 'ou=users'
users_filter: '(&({username_attribute}={input})(objectCategory=person)(objectClass=user))' users_filter: '(&({username_attribute}={input})(objectCategory=person)(objectClass=user))'
additional_groups_dn: 'ou=groups' additional_groups_dn: 'ou=groups'
groups_filter: '(&(member={dn})(objectClass=groupOfNames))' groups_filter: '(&(member={dn})(objectClass=groupOfNames))'
group_name_attribute: 'cn'
mail_attribute: 'mail'
user: 'cn=admin,dc=example,dc=com' user: 'cn=admin,dc=example,dc=com'
attributes:
username: 'uid'
group_name: 'cn'
mail: 'mail'
access_control: access_control:
default_policy: 'deny' default_policy: 'deny'

View File

@ -47,15 +47,14 @@ authentication_backend:
1YjCJ36UpTsLre2f8nOSLtNmRfDPtbOE2mkOoO9dD9UU0XZwnvn9xw== 1YjCJ36UpTsLre2f8nOSLtNmRfDPtbOE2mkOoO9dD9UU0XZwnvn9xw==
-----END RSA PRIVATE KEY----- -----END RSA PRIVATE KEY-----
base_dn: dc=example,dc=com base_dn: dc=example,dc=com
username_attribute: uid
additional_users_dn: ou=users additional_users_dn: ou=users
users_filter: (&({username_attribute}={input})(objectCategory=person)(objectClass=user)) users_filter: (&({username_attribute}={input})(objectCategory=person)(objectClass=user))
additional_groups_dn: ou=groups additional_groups_dn: ou=groups
groups_filter: (&(member={dn})(objectClass=groupOfNames)) groups_filter: (&(member={dn})(objectClass=groupOfNames))
group_name_attribute: cn
mail_attribute: mail
user: cn=admin,dc=example,dc=com user: cn=admin,dc=example,dc=com
attributes:
username: 'uid'
group_name: 'cn'
mail: 'mail'
access_control: access_control:
default_policy: deny default_policy: deny

View File

@ -66,15 +66,14 @@ authentication_backend:
1YjCJ36UpTsLre2f8nOSLtNmRfDPtbOE2mkOoO9dD9UU0XZwnvn9xw== 1YjCJ36UpTsLre2f8nOSLtNmRfDPtbOE2mkOoO9dD9UU0XZwnvn9xw==
-----END RSA PRIVATE KEY----- -----END RSA PRIVATE KEY-----
base_dn: 'dc=example,dc=com' base_dn: 'dc=example,dc=com'
username_attribute: 'uid'
additional_users_dn: 'ou=users' additional_users_dn: 'ou=users'
users_filter: '(&({username_attribute}={input})(objectCategory=person)(objectClass=user))' users_filter: '(&({username_attribute}={input})(objectCategory=person)(objectClass=user))'
additional_groups_dn: 'ou=groups' additional_groups_dn: 'ou=groups'
groups_filter: '(&(member={dn})(objectClass=groupOfNames))' groups_filter: '(&(member={dn})(objectClass=groupOfNames))'
group_name_attribute: 'cn'
mail_attribute: 'mail'
user: 'cn=admin,dc=example,dc=com' user: 'cn=admin,dc=example,dc=com'
attributes:
username: 'uid'
group_name: 'cn'
mail: 'mail'
access_control: access_control:
default_policy: 'deny' default_policy: 'deny'

View File

@ -18,16 +18,14 @@ authentication_backend:
ldap: ldap:
address: 'ldap://127.0.0.1' address: 'ldap://127.0.0.1'
base_dn: 'dc=example,dc=com' base_dn: 'dc=example,dc=com'
username_attribute: 'uid'
additional_users_dn: 'ou=users' additional_users_dn: 'ou=users'
users_filter: '(&({username_attribute}={input})(objectCategory=person)(objectClass=user))' users_filter: '(&({username_attribute}={input})(objectCategory=person)(objectClass=user))'
additional_groups_dn: 'ou=groups' additional_groups_dn: 'ou=groups'
groups_filter: '(&(member={dn})(objectClass=groupOfNames))' groups_filter: '(&(member={dn})(objectClass=groupOfNames))'
group_name_attribute: 'cn'
mail_attribute: 'mail'
user: 'cn=admin,dc=example,dc=com' user: 'cn=admin,dc=example,dc=com'
attributes:
mail: 'mail'
group_name: 'cn'
username: 'uid'
access_control: access_control:
default_policy: 'deny' default_policy: 'deny'

View File

@ -19,15 +19,14 @@ authentication_backend:
ldap: ldap:
address: 'ldap://127.0.0.1' address: 'ldap://127.0.0.1'
base_dn: 'dc=example,dc=com' base_dn: 'dc=example,dc=com'
username_attribute: 'uid'
additional_users_dn: 'ou=users' additional_users_dn: 'ou=users'
users_filter: '(&({username_attribute}={input})(objectCategory=person)(objectClass=user))' users_filter: '(&({username_attribute}={input})(objectCategory=person)(objectClass=user))'
additional_groups_dn: 'ou=groups' additional_groups_dn: 'ou=groups'
groups_filter: '(&(member={dn})(objectClass=groupOfNames))' groups_filter: '(&(member={dn})(objectClass=groupOfNames))'
group_name_attribute: 'cn'
mail_attribute: 'mail'
user: 'cn=admin,dc=example,dc=com' user: 'cn=admin,dc=example,dc=com'
attributes:
username: 'uid'
group_name: 'cn'
mail: 'mail'
access_control: access_control:
default_policy: 'deny' default_policy: 'deny'

View File

@ -18,15 +18,14 @@ authentication_backend:
ldap: ldap:
address: 'ldap://127.0.0.1' address: 'ldap://127.0.0.1'
base_dn: 'dc=example,dc=com' base_dn: 'dc=example,dc=com'
username_attribute: 'uid'
additional_users_dn: 'ou=users' additional_users_dn: 'ou=users'
users_filter: '(&({username_attribute}={input})(objectCategory=person)(objectClass=user))' users_filter: '(&({username_attribute}={input})(objectCategory=person)(objectClass=user))'
additional_groups_dn: 'ou=groups' additional_groups_dn: 'ou=groups'
groups_filter: '(&(member={dn})(objectClass=groupOfNames))' groups_filter: '(&(member={dn})(objectClass=groupOfNames))'
group_name_attribute: 'cn'
mail_attribute: 'mail'
user: 'cn=admin,dc=example,dc=com' user: 'cn=admin,dc=example,dc=com'
attributes:
username: 'uid'
group_name: 'cn'
mail: 'mail'
access_control: access_control:
default_policy: 'deny' default_policy: 'deny'

View File

@ -18,15 +18,13 @@ authentication_backend:
ldap: ldap:
address: 'ldap://127.0.0.1' address: 'ldap://127.0.0.1'
base_dn: 'dc=example,dc=com' base_dn: 'dc=example,dc=com'
username_attribute: 'uid'
additional_users_dn: 'ou=users' additional_users_dn: 'ou=users'
users_filter: '(&({username_attribute}={input})(objectCategory=person)(objectClass=user))' users_filter: '(&({username_attribute}={input})(objectCategory=person)(objectClass=user))'
additional_groups_dn: 'ou=groups' additional_groups_dn: 'ou=groups'
groups_filter: '(&(member={dn})(objectClass=groupOfNames))' groups_filter: '(&(member={dn})(objectClass=groupOfNames))'
attributes: group_name_attribute: 'cn'
group_name: 'cn' mail_attribute: 'mail'
mail: 'mail'
username: 'uid'
user: 'cn=admin,dc=example,dc=com' user: 'cn=admin,dc=example,dc=com'
access_control: access_control:

View File

@ -18,15 +18,14 @@ authentication_backend:
ldap: ldap:
address: 'ldap://127.0.0.1' address: 'ldap://127.0.0.1'
base_dn: 'dc=example,dc=com' base_dn: 'dc=example,dc=com'
username_attribute: 'uid'
additional_users_dn: 'ou=users' additional_users_dn: 'ou=users'
users_filter: '(&({username_attribute}={input})(objectCategory=person)(objectClass=user))' users_filter: '(&({username_attribute}={input})(objectCategory=person)(objectClass=user))'
additional_groups_dn: 'ou=groups' additional_groups_dn: 'ou=groups'
groups_filter: '(&(member={dn})(objectClass=groupOfNames))' groups_filter: '(&(member={dn})(objectClass=groupOfNames))'
group_name_attribute: 'cn'
mail_attribute: 'mail'
user: 'cn=admin,dc=example,dc=com' user: 'cn=admin,dc=example,dc=com'
attributes:
username: 'uid'
group_name: 'cn'
mail: 'mail'
access_control: access_control:
default_policy: 'deny' default_policy: 'deny'

View File

@ -1,137 +0,0 @@
---
default_redirection_url: 'https://home.example.com:8080/'
server:
address: 'tcp://127.0.0.1:9091'
log:
level: 'debug'
totp:
issuer: 'authelia.com'
duo_api:
hostname: 'api-123456789.example.com'
integration_key: 'ABCDEF'
authentication_backend:
ldap:
address: 'ldap://127.0.0.1'
base_dn: 'dc=example,dc=com'
additional_users_dn: 'ou=users'
users_filter: '(&({username_attribute}={input})(objectCategory=person)(objectClass=user))'
additional_groups_dn: 'ou=groups'
groups_filter: '(&(member={dn})(objectClass=groupOfNames))'
user: 'cn=admin,dc=example,dc=com'
attributes:
username: 'uid'
group_name: 'cn'
mail: 'mail'
access_control:
default_policy: 'deny'
rules:
# Rules applied to everyone
- domain: 'public.example.com'
policy: 'bypass'
- domain: 'secure.example.com'
policy: 'one_factor'
# Network based rule, if not provided any network matches.
networks:
- '192.168.1.0/24'
- domain: 'secure.example.com'
policy: 'two_factor'
- domain: ['singlefactor.example.com', 'onefactor.example.com']
policy: 'one_factor'
# Rules applied to 'admins' group
- domain: 'mx2.mail.example.com'
subject: 'group:admins'
policy: 'deny'
- domain: '*.example.com'
subject: 'group:admins'
policy: 'two_factor'
# Rules applied to 'dev' group
- domain: 'dev.example.com'
resources:
- '^/groups/dev/.*$'
subject: 'group:dev'
policy: 'two_factor'
# Rules applied to user 'john'
- domain: 'dev.example.com'
resources:
- '^/users/john/.*$'
subject: 'user:john'
policy: 'two_factor'
# Rules applied to 'dev' group and user 'john'
- domain: 'dev.example.com'
resources:
- '^/deny-all.*$'
subject: ['group:dev', 'user:john']
policy: 'deny'
# Rules applied to user 'harry'
- domain: 'dev.example.com'
resources:
- '^/users/harry/.*$'
subject: 'user:harry'
policy: 'two_factor'
# Rules applied to user 'bob'
- domain: '*.mail.example.com'
subject: 'user:bob'
policy: 'two_factor'
- domain: 'dev.example.com'
resources:
- '^/users/bob/.*$'
subject: 'user:bob'
policy: 'two_factor'
session:
name: 'authelia_session'
expiration: '1h' # 1 hour
inactivity: '5m' # 5 minutes
domain: 'example.com'
redis:
host: '127.0.0.1'
port: 6379
high_availability:
sentinel_name: 'test'
regulation:
max_retries: 3
find_time: '2m'
ban_time: '5m'
storage:
mysql:
address: 'tcp://127.0.0.1:3306'
database: 'authelia'
username: 'authelia'
notifier:
smtp:
address: 'smtp://127.0.0.1:1025'
username: 'test'
sender: 'admin@example.com'
disable_require_tls: true
identity_providers:
oidc:
cors:
allowed_origins:
- 'https://google.com'
- 'https://example.com'
minimum_parameter_entropy: -1
clients:
- id: 'abc'
secret: '123'
consent_mode: 'explicit'
userinfo_signing_alg: 'none'
...

View File

@ -1,111 +1,108 @@
--- ---
default_redirection_url: 'https://home.example.com:8080/' default_redirection_url: https://home.example.com:8080/
server: server:
host: 127.0.0.1 host: 127.0.0.1
port: 9091 port: 9091
log: log:
level: 'debug' level: debug
totp: totp:
issuer: 'authelia.com' issuer: authelia.com
duo_api: duo_api:
hostname: 'api-123456789.example.com' hostname: api-123456789.example.com
integration_key: 'ABCDEF' integration_key: ABCDEF
authentication_backend: authentication_backend:
ldap: ldap:
url: 'ldap://127.0.0.1' url: ldap://127.0.0.1
base_dn: 'dc=example,dc=com' base_dn: dc=example,dc=com
additional_users_dn: 'ou=users' username_attribute: uid
users_filter: '(&({username_attribute}={input})(objectCategory=person)(objectClass=user))' additional_users_dn: ou=users
additional_groups_dn: 'ou=groups' users_filter: (&({username_attribute}={input})(objectCategory=person)(objectClass=user))
groups_filter: '(&(member={dn})(objectClass=groupOfNames))' additional_groups_dn: ou=groups
user: 'cn=admin,dc=example,dc=com' groups_filter: (&(member={dn})(objectClass=groupOfNames))
attributes: group_name_attribute: cn
mail: 'mail' mail_attribute: mail
username: 'uid' user: cn=admin,dc=example,dc=com
group_name: 'cn'
access_control: access_control:
default_policy: 'deny' default_policy: deny
rules: rules:
# Rules applied to everyone # Rules applied to everyone
- domain: 'public.example.com' - domain: public.example.com
policy: 'bypass' policy: bypass
- domain: 'secure.example.com' - domain: secure.example.com
policy: 'one_factor' policy: one_factor
# Network based rule, if not provided any network matches. # Network based rule, if not provided any network matches.
networks: networks:
- '192.168.1.0/24' - 192.168.1.0/24
- domain: 'secure.example.com' - domain: secure.example.com
policy: 'two_factor' policy: two_factor
- domain: - domain: [singlefactor.example.com, onefactor.example.com]
- 'singlefactor.example.com' policy: one_factor
- 'onefactor.example.com'
policy: 'one_factor'
# Rules applied to 'admins' group # Rules applied to 'admins' group
- domain: 'mx2.mail.example.com' - domain: "mx2.mail.example.com"
subject: 'group:admins' subject: "group:admins"
policy: 'deny' policy: deny
- domain: '*.example.com' - domain: "*.example.com"
subject: 'group:admins' subject: "group:admins"
policy: 'two_factor' policy: two_factor
# Rules applied to 'dev' group # Rules applied to 'dev' group
- domain: 'dev.example.com' - domain: dev.example.com
resources: resources:
- '^/groups/dev/.*$' - "^/groups/dev/.*$"
subject: 'group:dev' subject: "group:dev"
policy: 'two_factor' policy: two_factor
# Rules applied to user 'john' # Rules applied to user 'john'
- domain: 'dev.example.com' - domain: dev.example.com
resources: resources:
- '^/users/john/.*$' - "^/users/john/.*$"
subject: 'user:john' subject: "user:john"
policy: 'two_factor' policy: two_factor
# Rules applied to 'dev' group and user 'john' # Rules applied to 'dev' group and user 'john'
- domain: 'dev.example.com' - domain: dev.example.com
resources: resources:
- '^/deny-all.*$' - "^/deny-all.*$"
subject: ['group:dev', 'user:john'] subject: ["group:dev", "user:john"]
policy: 'deny' policy: deny
# Rules applied to user 'harry' # Rules applied to user 'harry'
- domain: 'dev.example.com' - domain: dev.example.com
resources: resources:
- '^/users/harry/.*$' - "^/users/harry/.*$"
subject: 'user:harry' subject: "user:harry"
policy: 'two_factor' policy: two_factor
# Rules applied to user 'bob' # Rules applied to user 'bob'
- domain: '*.mail.example.com' - domain: "*.mail.example.com"
subject: 'user:bob' subject: "user:bob"
policy: 'two_factor' policy: two_factor
- domain: 'dev.example.com' - domain: "dev.example.com"
resources: resources:
- '^/users/bob/.*$' - "^/users/bob/.*$"
subject: 'user:bob' subject: "user:bob"
policy: 'two_factor' policy: two_factor
session: session:
name: 'authelia_session' name: authelia_session
expiration: 3600000 # 1 hour expiration: 3600000 # 1 hour
inactivity: 300000 # 5 minutes inactivity: 300000 # 5 minutes
domain: 'example.com' domain: example.com
redis: redis:
host: 127.0.0.1 host: 127.0.0.1
port: 6379 port: 6379
high_availability: high_availability:
sentinel_name: 'test' sentinel_name: test
regulation: regulation:
max_retries: 3 max_retries: 3
@ -116,12 +113,12 @@ storage:
mysql: mysql:
host: 127.0.0.1 host: 127.0.0.1
port: 3306 port: 3306
database: 'authelia' database: authelia
username: 'authelia' username: authelia
notifier: notifier:
smtp: smtp:
username: 'test' username: test
host: 127.0.0.1 host: 127.0.0.1
port: 1025 port: 1025
sender: admin@example.com sender: admin@example.com
@ -129,7 +126,7 @@ notifier:
identity_providers: identity_providers:
oidc: oidc:
hmac_secret: '1nb2j3kh1b23kjh1b23jh1b23j1h2b3' hmac_secret: 1nb2j3kh1b23kjh1b23jh1b23j1h2b3
issuer_private_keys: issuer_private_keys:
keys: keys:
keya: keya:
@ -205,10 +202,10 @@ identity_providers:
-----END CERTIFICATE----- -----END CERTIFICATE-----
cors: cors:
allowed_origins: allowed_origins:
- 'https://google.com' - https://google.com
- 'https://example.com' - https://example.com
clients: clients:
- id: 'abc' - id: abc
secret: '123' secret: '123'
consent_mode: 'explicit' consent_mode: explicit
... ...

View File

@ -18,15 +18,14 @@ authentication_backend:
ldap: ldap:
address: 'ldap://127.0.0.1' address: 'ldap://127.0.0.1'
base_dn: 'dc=example,dc=com' base_dn: 'dc=example,dc=com'
username_attribute: 'uid'
additional_users_dn: 'ou=users' additional_users_dn: 'ou=users'
users_filter: '(&({username_attribute}={input})(objectCategory=person)(objectClass=user))' users_filter: '(&({username_attribute}={input})(objectCategory=person)(objectClass=user))'
additional_groups_dn: 'ou=groups' additional_groups_dn: 'ou=groups'
groups_filter: '(&(member={dn})(objectClass=groupOfNames))' groups_filter: '(&(member={dn})(objectClass=groupOfNames))'
group_name_attribute: 'cn'
mail_attribute: 'mail'
user: 'cn=admin,dc=example,dc=com' user: 'cn=admin,dc=example,dc=com'
attributes:
username: 'uid'
group_name: 'cn'
mail: 'mail'
access_control: access_control:
default_policy: 'deny' default_policy: 'deny'

View File

@ -18,15 +18,14 @@ authentication_backend:
ldap: ldap:
address: 'ldap://127.0.0.1' address: 'ldap://127.0.0.1'
base_dn: 'dc=example,dc=com' base_dn: 'dc=example,dc=com'
username_attribute: 'uid'
additional_users_dn: 'ou=users' additional_users_dn: 'ou=users'
users_filter: '(&({username_attribute}={input})(objectCategory=person)(objectClass=user))' users_filter: '(&({username_attribute}={input})(objectCategory=person)(objectClass=user))'
additional_groups_dn: 'ou=groups' additional_groups_dn: 'ou=groups'
groups_filter: '(&(member={dn})(objectClass=groupOfNames))' groups_filter: '(&(member={dn})(objectClass=groupOfNames))'
group_name_attribute: 'cn'
mail_attribute: 'mail'
user: 'cn=admin,dc=example,dc=com' user: 'cn=admin,dc=example,dc=com'
attributes:
username: 'uid'
group_name: 'cn'
mail: 'mail'
access_control: access_control:
default_policy: 'deny' default_policy: 'deny'

View File

@ -20,16 +20,14 @@ authentication_backend:
ldap: ldap:
address: 'ldap://127.0.0.1' address: 'ldap://127.0.0.1'
base_dn: 'dc=example,dc=com' base_dn: 'dc=example,dc=com'
username_attribute: 'uid'
additional_users_dn: 'ou=users' additional_users_dn: 'ou=users'
users_filter: '(&({username_attribute}={input})(objectCategory=person)(objectClass=user))' users_filter: '(&({username_attribute}={input})(objectCategory=person)(objectClass=user))'
additional_groups_dn: 'ou=groups' additional_groups_dn: 'ou=groups'
groups_filter: '(&(member={dn})(objectClass=groupOfNames))' groups_filter: '(&(member={dn})(objectClass=groupOfNames))'
group_name_attribute: 'cn'
mail_attribute: 'mail'
user: 'cn=admin,dc=example,dc=com' user: 'cn=admin,dc=example,dc=com'
attributes:
username: 'uid'
group_name: 'cn'
mail: 'mail'
access_control: access_control:
default_policy: 'deny' default_policy: 'deny'

View File

@ -21,7 +21,7 @@ type FileSource struct {
filters []FileFilter filters []FileFilter
} }
// EnvironmentSource is a configuration.Source which loads values from the environment. // EnvironmentSource is a configuration configuration.Source which loads values from the environment.
type EnvironmentSource struct { type EnvironmentSource struct {
koanf *koanf.Koanf koanf *koanf.Koanf
prefix string prefix string

View File

@ -364,7 +364,7 @@ func validateLDAPAuthenticationBackendImplementation(config *schema.Authenticati
case schema.LDAPImplementationGLAuth: case schema.LDAPImplementationGLAuth:
implementation = &schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth implementation = &schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth
default: default:
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendOptionMustBeOneOf, "implementation", strJoinOr(validLDAPImplementations), config.LDAP.Implementation)) validator.Push(fmt.Errorf(errFmtLDAPAuthBackendImplementation, strJoinOr(validLDAPImplementations), config.LDAP.Implementation))
} }
tlsconfig := &schema.TLSConfig{} tlsconfig := &schema.TLSConfig{}
@ -394,44 +394,32 @@ func setDefaultImplementationLDAPAuthenticationBackendProfileAttributes(config *
config.AdditionalUsersDN = implementation.AdditionalUsersDN config.AdditionalUsersDN = implementation.AdditionalUsersDN
} }
if ldapImplementationShouldSetStr(config.AdditionalGroupsDN, implementation.AdditionalGroupsDN) {
config.AdditionalGroupsDN = implementation.AdditionalGroupsDN
}
if ldapImplementationShouldSetStr(config.UsersFilter, implementation.UsersFilter) { if ldapImplementationShouldSetStr(config.UsersFilter, implementation.UsersFilter) {
config.UsersFilter = implementation.UsersFilter config.UsersFilter = implementation.UsersFilter
} }
if ldapImplementationShouldSetStr(config.AdditionalGroupsDN, implementation.AdditionalGroupsDN) { if ldapImplementationShouldSetStr(config.UsernameAttribute, implementation.UsernameAttribute) {
config.AdditionalGroupsDN = implementation.AdditionalGroupsDN config.UsernameAttribute = implementation.UsernameAttribute
}
if ldapImplementationShouldSetStr(config.DisplayNameAttribute, implementation.DisplayNameAttribute) {
config.DisplayNameAttribute = implementation.DisplayNameAttribute
}
if ldapImplementationShouldSetStr(config.MailAttribute, implementation.MailAttribute) {
config.MailAttribute = implementation.MailAttribute
} }
if ldapImplementationShouldSetStr(config.GroupsFilter, implementation.GroupsFilter) { if ldapImplementationShouldSetStr(config.GroupsFilter, implementation.GroupsFilter) {
config.GroupsFilter = implementation.GroupsFilter config.GroupsFilter = implementation.GroupsFilter
} }
if ldapImplementationShouldSetStr(config.GroupSearchMode, implementation.GroupSearchMode) { if ldapImplementationShouldSetStr(config.GroupNameAttribute, implementation.GroupNameAttribute) {
config.GroupSearchMode = implementation.GroupSearchMode config.GroupNameAttribute = implementation.GroupNameAttribute
}
if ldapImplementationShouldSetStr(config.Attributes.DistinguishedName, implementation.Attributes.DistinguishedName) {
config.Attributes.DistinguishedName = implementation.Attributes.DistinguishedName
}
if ldapImplementationShouldSetStr(config.Attributes.Username, implementation.Attributes.Username) {
config.Attributes.Username = implementation.Attributes.Username
}
if ldapImplementationShouldSetStr(config.Attributes.DisplayName, implementation.Attributes.DisplayName) {
config.Attributes.DisplayName = implementation.Attributes.DisplayName
}
if ldapImplementationShouldSetStr(config.Attributes.Mail, implementation.Attributes.Mail) {
config.Attributes.Mail = implementation.Attributes.Mail
}
if ldapImplementationShouldSetStr(config.Attributes.MemberOf, implementation.Attributes.MemberOf) {
config.Attributes.MemberOf = implementation.Attributes.MemberOf
}
if ldapImplementationShouldSetStr(config.Attributes.GroupName, implementation.Attributes.GroupName) {
config.Attributes.GroupName = implementation.Attributes.GroupName
} }
} }
@ -498,32 +486,4 @@ func validateLDAPRequiredParameters(config *schema.AuthenticationBackend, valida
} else if !strings.HasPrefix(config.LDAP.GroupsFilter, "(") || !strings.HasSuffix(config.LDAP.GroupsFilter, ")") { } else if !strings.HasPrefix(config.LDAP.GroupsFilter, "(") || !strings.HasSuffix(config.LDAP.GroupsFilter, ")") {
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendFilterEnclosingParenthesis, "groups_filter", config.LDAP.GroupsFilter, config.LDAP.GroupsFilter)) validator.Push(fmt.Errorf(errFmtLDAPAuthBackendFilterEnclosingParenthesis, "groups_filter", config.LDAP.GroupsFilter, config.LDAP.GroupsFilter))
} }
validateLDAPGroupFilter(config, validator)
}
func validateLDAPGroupFilter(config *schema.AuthenticationBackend, validator *schema.StructValidator) {
if config.LDAP.GroupSearchMode == "" {
config.LDAP.GroupSearchMode = schema.LDAPGroupSearchModeFilter
}
if !utils.IsStringInSlice(config.LDAP.GroupSearchMode, validLDAPGroupSearchModes) {
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendOptionMustBeOneOf, "group_search_mode", strJoinOr(validLDAPGroupSearchModes), config.LDAP.GroupSearchMode))
}
pMemberOfDN, pMemberOfRDN := strings.Contains(config.LDAP.GroupsFilter, "{memberof:dn}"), strings.Contains(config.LDAP.GroupsFilter, "{memberof:rdn}")
if config.LDAP.GroupSearchMode == schema.LDAPGroupSearchModeMemberOf {
if !pMemberOfDN && !pMemberOfRDN {
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendFilterMissingPlaceholderGroupSearchMode, "groups_filter", strJoinOr([]string{"{memberof:rdn}", "{memberof:dn}"}), config.LDAP.GroupSearchMode))
}
}
if pMemberOfDN && config.LDAP.Attributes.DistinguishedName == "" {
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendFilterMissingAttribute, "distinguished_name", strJoinOr([]string{"{memberof:dn}"})))
}
if (pMemberOfDN || pMemberOfRDN) && config.LDAP.Attributes.MemberOf == "" {
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendFilterMissingAttribute, "member_of", strJoinOr([]string{"{memberof:rdn}", "{memberof:dn}"})))
}
} }

View File

@ -577,7 +577,7 @@ func (suite *LDAPAuthenticationBackendSuite) SetupTest() {
suite.config.LDAP.User = testLDAPUser suite.config.LDAP.User = testLDAPUser
suite.config.LDAP.Password = testLDAPPassword suite.config.LDAP.Password = testLDAPPassword
suite.config.LDAP.BaseDN = testLDAPBaseDN suite.config.LDAP.BaseDN = testLDAPBaseDN
suite.config.LDAP.Attributes.Username = "uid" suite.config.LDAP.UsernameAttribute = "uid"
suite.config.LDAP.UsersFilter = "({username_attribute}={input})" suite.config.LDAP.UsersFilter = "({username_attribute}={input})"
suite.config.LDAP.GroupsFilter = "(cn={input})" suite.config.LDAP.GroupsFilter = "(cn={input})"
} }
@ -591,12 +591,12 @@ func (suite *LDAPAuthenticationBackendSuite) TestShouldValidateCompleteConfigura
func (suite *LDAPAuthenticationBackendSuite) TestShouldValidateDefaultImplementationAndUsernameAttribute() { func (suite *LDAPAuthenticationBackendSuite) TestShouldValidateDefaultImplementationAndUsernameAttribute() {
suite.config.LDAP.Implementation = "" suite.config.LDAP.Implementation = ""
suite.config.LDAP.Attributes.Username = "" suite.config.LDAP.UsernameAttribute = ""
ValidateAuthenticationBackend(&suite.config, suite.validator) ValidateAuthenticationBackend(&suite.config, suite.validator)
suite.Equal(schema.LDAPImplementationCustom, suite.config.LDAP.Implementation) suite.Equal(schema.LDAPImplementationCustom, suite.config.LDAP.Implementation)
suite.Equal(suite.config.LDAP.Attributes.Username, schema.DefaultLDAPAuthenticationBackendConfigurationImplementationCustom.Attributes.Username) suite.Equal(suite.config.LDAP.UsernameAttribute, schema.DefaultLDAPAuthenticationBackendConfigurationImplementationCustom.UsernameAttribute)
suite.Len(suite.validator.Warnings(), 0) suite.Len(suite.validator.Warnings(), 0)
suite.Len(suite.validator.Errors(), 0) suite.Len(suite.validator.Errors(), 0)
} }
@ -743,7 +743,7 @@ func (suite *LDAPAuthenticationBackendSuite) TestShouldRaiseOnEmptyUsersFilter()
} }
func (suite *LDAPAuthenticationBackendSuite) TestShouldNotRaiseOnEmptyUsernameAttribute() { func (suite *LDAPAuthenticationBackendSuite) TestShouldNotRaiseOnEmptyUsernameAttribute() {
suite.config.LDAP.Attributes.Username = "" suite.config.LDAP.UsernameAttribute = ""
ValidateAuthenticationBackend(&suite.config, suite.validator) ValidateAuthenticationBackend(&suite.config, suite.validator)
@ -793,7 +793,7 @@ func (suite *LDAPAuthenticationBackendSuite) TestShouldSetDefaultGroupNameAttrib
suite.Len(suite.validator.Warnings(), 0) suite.Len(suite.validator.Warnings(), 0)
suite.Len(suite.validator.Errors(), 0) suite.Len(suite.validator.Errors(), 0)
suite.Equal("cn", suite.config.LDAP.Attributes.GroupName) suite.Equal("cn", suite.config.LDAP.GroupNameAttribute)
} }
func (suite *LDAPAuthenticationBackendSuite) TestShouldSetDefaultMailAttribute() { func (suite *LDAPAuthenticationBackendSuite) TestShouldSetDefaultMailAttribute() {
@ -802,7 +802,7 @@ func (suite *LDAPAuthenticationBackendSuite) TestShouldSetDefaultMailAttribute()
suite.Len(suite.validator.Warnings(), 0) suite.Len(suite.validator.Warnings(), 0)
suite.Len(suite.validator.Errors(), 0) suite.Len(suite.validator.Errors(), 0)
suite.Equal("mail", suite.config.LDAP.Attributes.Mail) suite.Equal("mail", suite.config.LDAP.MailAttribute)
} }
func (suite *LDAPAuthenticationBackendSuite) TestShouldSetDefaultDisplayNameAttribute() { func (suite *LDAPAuthenticationBackendSuite) TestShouldSetDefaultDisplayNameAttribute() {
@ -811,7 +811,7 @@ func (suite *LDAPAuthenticationBackendSuite) TestShouldSetDefaultDisplayNameAttr
suite.Len(suite.validator.Warnings(), 0) suite.Len(suite.validator.Warnings(), 0)
suite.Len(suite.validator.Errors(), 0) suite.Len(suite.validator.Errors(), 0)
suite.Equal("displayName", suite.config.LDAP.Attributes.DisplayName) suite.Equal("displayName", suite.config.LDAP.DisplayNameAttribute)
} }
func (suite *LDAPAuthenticationBackendSuite) TestShouldSetDefaultRefreshInterval() { func (suite *LDAPAuthenticationBackendSuite) TestShouldSetDefaultRefreshInterval() {
@ -890,64 +890,6 @@ func (suite *LDAPAuthenticationBackendSuite) TestShouldNotAllowSSL30() {
suite.EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: tls: option 'minimum_version' is invalid: minimum version is TLS1.0 but SSL3.0 was configured") suite.EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: tls: option 'minimum_version' is invalid: minimum version is TLS1.0 but SSL3.0 was configured")
} }
func (suite *LDAPAuthenticationBackendSuite) TestShouldErrorOnBadSearchMode() {
suite.config.LDAP.GroupSearchMode = "memberOF"
ValidateAuthenticationBackend(&suite.config, suite.validator)
suite.Len(suite.validator.Warnings(), 0)
suite.Require().Len(suite.validator.Errors(), 1)
suite.EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: option 'group_search_mode' must be one of 'filter' or 'memberof' but it's configured as 'memberOF'")
}
func (suite *LDAPAuthenticationBackendSuite) TestShouldNoErrorOnPlaceholderSearchMode() {
suite.config.LDAP.GroupSearchMode = memberof
suite.config.LDAP.GroupsFilter = filterMemberOfRDN
suite.config.LDAP.Attributes.MemberOf = memberOf
ValidateAuthenticationBackend(&suite.config, suite.validator)
suite.Len(suite.validator.Warnings(), 0)
suite.Len(suite.validator.Errors(), 0)
}
func (suite *LDAPAuthenticationBackendSuite) TestShouldErrorOnMissingPlaceholderSearchMode() {
suite.config.LDAP.GroupSearchMode = memberof
ValidateAuthenticationBackend(&suite.config, suite.validator)
suite.Len(suite.validator.Warnings(), 0)
suite.Require().Len(suite.validator.Errors(), 1)
suite.EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: option 'groups_filter' must contain one of the '{memberof:rdn}' or '{memberof:dn}' placeholders when using a group_search_mode of 'memberof' but they're absent")
}
func (suite *LDAPAuthenticationBackendSuite) TestShouldErrorOnMissingDistinguishedNameDN() {
suite.config.LDAP.Attributes.DistinguishedName = ""
suite.config.LDAP.GroupsFilter = "(|({memberof:dn}))"
ValidateAuthenticationBackend(&suite.config, suite.validator)
suite.Len(suite.validator.Warnings(), 0)
suite.Require().Len(suite.validator.Errors(), 2)
suite.EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: attributes: option 'distinguished_name' must be provided when using the '{memberof:dn}' placeholder but it's absent")
suite.EqualError(suite.validator.Errors()[1], "authentication_backend: ldap: attributes: option 'member_of' must be provided when using the '{memberof:rdn}' or '{memberof:dn}' placeholder but it's absent")
}
func (suite *LDAPAuthenticationBackendSuite) TestShouldErrorOnMissingMemberOfRDN() {
suite.config.LDAP.Attributes.DistinguishedName = ""
suite.config.LDAP.GroupsFilter = filterMemberOfRDN
ValidateAuthenticationBackend(&suite.config, suite.validator)
suite.Len(suite.validator.Warnings(), 0)
suite.Require().Len(suite.validator.Errors(), 1)
suite.EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: attributes: option 'member_of' must be provided when using the '{memberof:rdn}' or '{memberof:dn}' placeholder but it's absent")
}
func (suite *LDAPAuthenticationBackendSuite) TestShouldNotAllowTLSVerMinGreaterThanVerMax() { func (suite *LDAPAuthenticationBackendSuite) TestShouldNotAllowTLSVerMinGreaterThanVerMax() {
suite.config.LDAP.TLS = &schema.TLSConfig{ suite.config.LDAP.TLS = &schema.TLSConfig{
MinimumVersion: schema.TLSVersion{Value: tls.VersionTLS13}, MinimumVersion: schema.TLSVersion{Value: tls.VersionTLS13},
@ -967,7 +909,9 @@ func TestLDAPAuthenticationBackend(t *testing.T) {
} }
type ActiveDirectoryAuthenticationBackendSuite struct { type ActiveDirectoryAuthenticationBackendSuite struct {
LDAPImplementationSuite suite.Suite
config schema.AuthenticationBackend
validator *schema.StructValidator
} }
func (suite *ActiveDirectoryAuthenticationBackendSuite) SetupTest() { func (suite *ActiveDirectoryAuthenticationBackendSuite) SetupTest() {
@ -988,30 +932,81 @@ func (suite *ActiveDirectoryAuthenticationBackendSuite) TestShouldSetActiveDirec
suite.Len(suite.validator.Warnings(), 0) suite.Len(suite.validator.Warnings(), 0)
suite.Len(suite.validator.Errors(), 0) suite.Len(suite.validator.Errors(), 0)
suite.EqualImplementationDefaults(schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory) suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.Timeout,
suite.config.LDAP.Timeout)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.AdditionalUsersDN,
suite.config.LDAP.AdditionalUsersDN)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.AdditionalGroupsDN,
suite.config.LDAP.AdditionalGroupsDN)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.AdditionalUsersDN,
suite.config.LDAP.AdditionalUsersDN)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.AdditionalGroupsDN,
suite.config.LDAP.AdditionalGroupsDN)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.UsersFilter,
suite.config.LDAP.UsersFilter)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.UsernameAttribute,
suite.config.LDAP.UsernameAttribute)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.DisplayNameAttribute,
suite.config.LDAP.DisplayNameAttribute)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.MailAttribute,
suite.config.LDAP.MailAttribute)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.GroupsFilter,
suite.config.LDAP.GroupsFilter)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.GroupNameAttribute,
suite.config.LDAP.GroupNameAttribute)
} }
func (suite *ActiveDirectoryAuthenticationBackendSuite) TestShouldOnlySetDefaultsIfNotManuallyConfigured() { func (suite *ActiveDirectoryAuthenticationBackendSuite) TestShouldOnlySetDefaultsIfNotManuallyConfigured() {
suite.config.LDAP.Timeout = time.Second * 2 suite.config.LDAP.Timeout = time.Second * 2
suite.config.LDAP.UsersFilter = "(&({username_attribute}={input})(objectCategory=person)(objectClass=user)(!userAccountControl:1.2.840.113556.1.4.803:=2))" suite.config.LDAP.UsersFilter = "(&({username_attribute}={input})(objectCategory=person)(objectClass=user)(!userAccountControl:1.2.840.113556.1.4.803:=2))"
suite.config.LDAP.Attributes.Username = "cn" suite.config.LDAP.UsernameAttribute = "cn"
suite.config.LDAP.Attributes.Mail = "userPrincipalName" suite.config.LDAP.MailAttribute = "userPrincipalName"
suite.config.LDAP.Attributes.DisplayName = "name" suite.config.LDAP.DisplayNameAttribute = "name"
suite.config.LDAP.GroupsFilter = "(&(member={dn})(objectClass=group)(objectCategory=group))" suite.config.LDAP.GroupsFilter = "(&(member={dn})(objectClass=group)(objectCategory=group))"
suite.config.LDAP.Attributes.GroupName = "distinguishedName" suite.config.LDAP.GroupNameAttribute = "distinguishedName"
suite.config.LDAP.AdditionalUsersDN = "OU=test" suite.config.LDAP.AdditionalUsersDN = "OU=test"
suite.config.LDAP.AdditionalGroupsDN = "OU=grps" suite.config.LDAP.AdditionalGroupsDN = "OU=grps"
suite.config.LDAP.Attributes.MemberOf = member
suite.config.LDAP.GroupSearchMode = memberof
suite.config.LDAP.Attributes.DistinguishedName = "objectGUID"
ValidateAuthenticationBackend(&suite.config, suite.validator) ValidateAuthenticationBackend(&suite.config, suite.validator)
suite.NotEqualImplementationDefaults(schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory) suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.Timeout,
suite.Equal(member, suite.config.LDAP.Attributes.MemberOf) suite.config.LDAP.Timeout)
suite.Equal("objectGUID", suite.config.LDAP.Attributes.DistinguishedName) suite.NotEqual(
suite.Equal(memberof, suite.config.LDAP.GroupSearchMode) schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.AdditionalUsersDN,
suite.config.LDAP.AdditionalUsersDN)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.AdditionalGroupsDN,
suite.config.LDAP.AdditionalGroupsDN)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.UsersFilter,
suite.config.LDAP.UsersFilter)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.UsernameAttribute,
suite.config.LDAP.UsernameAttribute)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.DisplayNameAttribute,
suite.config.LDAP.DisplayNameAttribute)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.MailAttribute,
suite.config.LDAP.MailAttribute)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.GroupsFilter,
suite.config.LDAP.GroupsFilter)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.GroupNameAttribute,
suite.config.LDAP.GroupNameAttribute)
} }
func (suite *ActiveDirectoryAuthenticationBackendSuite) TestShouldRaiseErrorOnInvalidURLWithHTTP() { func (suite *ActiveDirectoryAuthenticationBackendSuite) TestShouldRaiseErrorOnInvalidURLWithHTTP() {
@ -1028,7 +1023,9 @@ func TestActiveDirectoryAuthenticationBackend(t *testing.T) {
} }
type RFC2307bisAuthenticationBackendSuite struct { type RFC2307bisAuthenticationBackendSuite struct {
LDAPImplementationSuite suite.Suite
config schema.AuthenticationBackend
validator *schema.StructValidator
} }
func (suite *RFC2307bisAuthenticationBackendSuite) SetupTest() { func (suite *RFC2307bisAuthenticationBackendSuite) SetupTest() {
@ -1049,29 +1046,78 @@ func (suite *RFC2307bisAuthenticationBackendSuite) TestShouldSetDefaults() {
suite.Len(suite.validator.Warnings(), 0) suite.Len(suite.validator.Warnings(), 0)
suite.Len(suite.validator.Errors(), 0) suite.Len(suite.validator.Errors(), 0)
suite.EqualImplementationDefaults(schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis) suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.Timeout,
suite.config.LDAP.Timeout)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.AdditionalUsersDN,
suite.config.LDAP.AdditionalUsersDN)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.AdditionalGroupsDN,
suite.config.LDAP.AdditionalGroupsDN)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.UsersFilter,
suite.config.LDAP.UsersFilter)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.UsernameAttribute,
suite.config.LDAP.UsernameAttribute)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.DisplayNameAttribute,
suite.config.LDAP.DisplayNameAttribute)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.MailAttribute,
suite.config.LDAP.MailAttribute)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.GroupsFilter,
suite.config.LDAP.GroupsFilter)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.GroupNameAttribute,
suite.config.LDAP.GroupNameAttribute)
} }
func (suite *RFC2307bisAuthenticationBackendSuite) TestShouldOnlySetDefaultsIfNotManuallyConfigured() { func (suite *RFC2307bisAuthenticationBackendSuite) TestShouldOnlySetDefaultsIfNotManuallyConfigured() {
suite.config.LDAP.Timeout = time.Second * 2 suite.config.LDAP.Timeout = time.Second * 2
suite.config.LDAP.UsersFilter = "(&({username_attribute}={input})(objectClass=Person))" suite.config.LDAP.UsersFilter = "(&({username_attribute}={input})(objectClass=Person))"
suite.config.LDAP.Attributes.Username = "o" suite.config.LDAP.UsernameAttribute = "o"
suite.config.LDAP.Attributes.Mail = "Email" suite.config.LDAP.MailAttribute = "Email"
suite.config.LDAP.Attributes.DisplayName = "Given" suite.config.LDAP.DisplayNameAttribute = "Given"
suite.config.LDAP.GroupsFilter = "(&(member={dn})(objectClass=posixGroup)(objectClass=top))" suite.config.LDAP.GroupsFilter = "(&(member={dn})(objectClass=posixGroup)(objectClass=top))"
suite.config.LDAP.Attributes.GroupName = "gid" suite.config.LDAP.GroupNameAttribute = "gid"
suite.config.LDAP.Attributes.MemberOf = member
suite.config.LDAP.AdditionalUsersDN = "OU=users,OU=OpenLDAP" suite.config.LDAP.AdditionalUsersDN = "OU=users,OU=OpenLDAP"
suite.config.LDAP.AdditionalGroupsDN = "OU=groups,OU=OpenLDAP" suite.config.LDAP.AdditionalGroupsDN = "OU=groups,OU=OpenLDAP"
suite.config.LDAP.GroupSearchMode = memberof
ValidateAuthenticationBackend(&suite.config, suite.validator) ValidateAuthenticationBackend(&suite.config, suite.validator)
suite.NotEqualImplementationDefaults(schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis) suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.Timeout,
suite.Equal(member, suite.config.LDAP.Attributes.MemberOf) suite.config.LDAP.Timeout)
suite.Equal("", suite.config.LDAP.Attributes.DistinguishedName) suite.NotEqual(
suite.Equal(schema.LDAPGroupSearchModeMemberOf, suite.config.LDAP.GroupSearchMode) schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.AdditionalUsersDN,
suite.config.LDAP.AdditionalUsersDN)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.AdditionalGroupsDN,
suite.config.LDAP.AdditionalGroupsDN)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.Timeout,
suite.config.LDAP.Timeout)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.UsersFilter,
suite.config.LDAP.UsersFilter)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.UsernameAttribute,
suite.config.LDAP.UsernameAttribute)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.DisplayNameAttribute,
suite.config.LDAP.DisplayNameAttribute)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.MailAttribute,
suite.config.LDAP.MailAttribute)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.GroupsFilter,
suite.config.LDAP.GroupsFilter)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.GroupNameAttribute,
suite.config.LDAP.GroupNameAttribute)
} }
func TestRFC2307bisAuthenticationBackend(t *testing.T) { func TestRFC2307bisAuthenticationBackend(t *testing.T) {
@ -1079,7 +1125,9 @@ func TestRFC2307bisAuthenticationBackend(t *testing.T) {
} }
type FreeIPAAuthenticationBackendSuite struct { type FreeIPAAuthenticationBackendSuite struct {
LDAPImplementationSuite suite.Suite
config schema.AuthenticationBackend
validator *schema.StructValidator
} }
func (suite *FreeIPAAuthenticationBackendSuite) SetupTest() { func (suite *FreeIPAAuthenticationBackendSuite) SetupTest() {
@ -1100,29 +1148,75 @@ func (suite *FreeIPAAuthenticationBackendSuite) TestShouldSetDefaults() {
suite.Len(suite.validator.Warnings(), 0) suite.Len(suite.validator.Warnings(), 0)
suite.Len(suite.validator.Errors(), 0) suite.Len(suite.validator.Errors(), 0)
suite.EqualImplementationDefaults(schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA) suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA.Timeout,
suite.config.LDAP.Timeout)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA.AdditionalUsersDN,
suite.config.LDAP.AdditionalUsersDN)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA.AdditionalGroupsDN,
suite.config.LDAP.AdditionalGroupsDN)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA.UsersFilter,
suite.config.LDAP.UsersFilter)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA.UsernameAttribute,
suite.config.LDAP.UsernameAttribute)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA.DisplayNameAttribute,
suite.config.LDAP.DisplayNameAttribute)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA.MailAttribute,
suite.config.LDAP.MailAttribute)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA.GroupsFilter,
suite.config.LDAP.GroupsFilter)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA.GroupNameAttribute,
suite.config.LDAP.GroupNameAttribute)
} }
func (suite *FreeIPAAuthenticationBackendSuite) TestShouldOnlySetDefaultsIfNotManuallyConfigured() { func (suite *FreeIPAAuthenticationBackendSuite) TestShouldOnlySetDefaultsIfNotManuallyConfigured() {
suite.config.LDAP.Timeout = time.Second * 2 suite.config.LDAP.Timeout = time.Second * 2
suite.config.LDAP.UsersFilter = "(&({username_attribute}={input})(objectClass=person)(!(nsAccountLock=TRUE)))" suite.config.LDAP.UsersFilter = "(&({username_attribute}={input})(objectClass=person)(!(nsAccountLock=TRUE)))"
suite.config.LDAP.Attributes.Username = "dn" suite.config.LDAP.UsernameAttribute = "dn"
suite.config.LDAP.Attributes.Mail = "email" suite.config.LDAP.MailAttribute = "email"
suite.config.LDAP.Attributes.DisplayName = "gecos" suite.config.LDAP.DisplayNameAttribute = "gecos"
suite.config.LDAP.GroupsFilter = "(&(member={dn})(objectClass=posixgroup))" suite.config.LDAP.GroupsFilter = "(&(member={dn})(objectClass=posixgroup))"
suite.config.LDAP.GroupSearchMode = schema.LDAPGroupSearchModeMemberOf suite.config.LDAP.GroupNameAttribute = "groupName"
suite.config.LDAP.Attributes.GroupName = "groupName"
suite.config.LDAP.Attributes.MemberOf = member
suite.config.LDAP.AdditionalUsersDN = "OU=people" suite.config.LDAP.AdditionalUsersDN = "OU=people"
suite.config.LDAP.AdditionalGroupsDN = "OU=grp" suite.config.LDAP.AdditionalGroupsDN = "OU=grp"
ValidateAuthenticationBackend(&suite.config, suite.validator) ValidateAuthenticationBackend(&suite.config, suite.validator)
suite.NotEqualImplementationDefaults(schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA) suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA.Timeout,
suite.Equal(member, suite.config.LDAP.Attributes.MemberOf) suite.config.LDAP.Timeout)
suite.Equal("", suite.config.LDAP.Attributes.DistinguishedName) suite.NotEqual(
suite.Equal(schema.LDAPGroupSearchModeMemberOf, suite.config.LDAP.GroupSearchMode) schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA.AdditionalUsersDN,
suite.config.LDAP.AdditionalUsersDN)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA.AdditionalGroupsDN,
suite.config.LDAP.AdditionalGroupsDN)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA.UsersFilter,
suite.config.LDAP.UsersFilter)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA.UsernameAttribute,
suite.config.LDAP.UsernameAttribute)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA.DisplayNameAttribute,
suite.config.LDAP.DisplayNameAttribute)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA.MailAttribute,
suite.config.LDAP.MailAttribute)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA.GroupsFilter,
suite.config.LDAP.GroupsFilter)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA.GroupNameAttribute,
suite.config.LDAP.GroupNameAttribute)
} }
func TestFreeIPAAuthenticationBackend(t *testing.T) { func TestFreeIPAAuthenticationBackend(t *testing.T) {
@ -1130,7 +1224,9 @@ func TestFreeIPAAuthenticationBackend(t *testing.T) {
} }
type LLDAPAuthenticationBackendSuite struct { type LLDAPAuthenticationBackendSuite struct {
LDAPImplementationSuite suite.Suite
config schema.AuthenticationBackend
validator *schema.StructValidator
} }
func (suite *LLDAPAuthenticationBackendSuite) SetupTest() { func (suite *LLDAPAuthenticationBackendSuite) SetupTest() {
@ -1151,29 +1247,78 @@ func (suite *LLDAPAuthenticationBackendSuite) TestShouldSetDefaults() {
suite.Len(suite.validator.Warnings(), 0) suite.Len(suite.validator.Warnings(), 0)
suite.Len(suite.validator.Errors(), 0) suite.Len(suite.validator.Errors(), 0)
suite.EqualImplementationDefaults(schema.DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP) suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP.Timeout,
suite.config.LDAP.Timeout)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP.AdditionalUsersDN,
suite.config.LDAP.AdditionalUsersDN)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP.AdditionalGroupsDN,
suite.config.LDAP.AdditionalGroupsDN)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP.UsersFilter,
suite.config.LDAP.UsersFilter)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP.UsernameAttribute,
suite.config.LDAP.UsernameAttribute)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP.DisplayNameAttribute,
suite.config.LDAP.DisplayNameAttribute)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP.MailAttribute,
suite.config.LDAP.MailAttribute)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP.GroupsFilter,
suite.config.LDAP.GroupsFilter)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP.GroupNameAttribute,
suite.config.LDAP.GroupNameAttribute)
} }
func (suite *LLDAPAuthenticationBackendSuite) TestShouldOnlySetDefaultsIfNotManuallyConfigured() { func (suite *LLDAPAuthenticationBackendSuite) TestShouldOnlySetDefaultsIfNotManuallyConfigured() {
suite.config.LDAP.Timeout = time.Second * 2 suite.config.LDAP.Timeout = time.Second * 2
suite.config.LDAP.UsersFilter = "(&({username_attribute}={input})(objectClass=Person)(!(nsAccountLock=TRUE)))" suite.config.LDAP.UsersFilter = "(&({username_attribute}={input})(objectClass=Person)(!(nsAccountLock=TRUE)))"
suite.config.LDAP.Attributes.Username = "username" suite.config.LDAP.UsernameAttribute = "username"
suite.config.LDAP.Attributes.Mail = "m" suite.config.LDAP.MailAttribute = "m"
suite.config.LDAP.Attributes.DisplayName = "fn" suite.config.LDAP.DisplayNameAttribute = "fn"
suite.config.LDAP.Attributes.MemberOf = member
suite.config.LDAP.GroupsFilter = "(&(member={dn})(!(objectClass=posixGroup)))" suite.config.LDAP.GroupsFilter = "(&(member={dn})(!(objectClass=posixGroup)))"
suite.config.LDAP.Attributes.GroupName = "grpz" suite.config.LDAP.GroupNameAttribute = "grpz"
suite.config.LDAP.AdditionalUsersDN = "OU=no" suite.config.LDAP.AdditionalUsersDN = "OU=no"
suite.config.LDAP.AdditionalGroupsDN = "OU=yes" suite.config.LDAP.AdditionalGroupsDN = "OU=yes"
suite.config.LDAP.GroupSearchMode = memberof
ValidateAuthenticationBackend(&suite.config, suite.validator) ValidateAuthenticationBackend(&suite.config, suite.validator)
suite.NotEqualImplementationDefaults(schema.DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP) suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP.Timeout,
suite.Equal(member, suite.config.LDAP.Attributes.MemberOf) suite.config.LDAP.Timeout)
suite.Equal("", suite.config.LDAP.Attributes.DistinguishedName) suite.NotEqual(
suite.Equal(schema.LDAPGroupSearchModeMemberOf, suite.config.LDAP.GroupSearchMode) schema.DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP.AdditionalUsersDN,
suite.config.LDAP.AdditionalUsersDN)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP.AdditionalGroupsDN,
suite.config.LDAP.AdditionalGroupsDN)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP.Timeout,
suite.config.LDAP.Timeout)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP.UsersFilter,
suite.config.LDAP.UsersFilter)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP.UsernameAttribute,
suite.config.LDAP.UsernameAttribute)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP.DisplayNameAttribute,
suite.config.LDAP.DisplayNameAttribute)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP.MailAttribute,
suite.config.LDAP.MailAttribute)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP.GroupsFilter,
suite.config.LDAP.GroupsFilter)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP.GroupNameAttribute,
suite.config.LDAP.GroupNameAttribute)
} }
func TestLLDAPAuthenticationBackend(t *testing.T) { func TestLLDAPAuthenticationBackend(t *testing.T) {
@ -1181,7 +1326,9 @@ func TestLLDAPAuthenticationBackend(t *testing.T) {
} }
type GLAuthAuthenticationBackendSuite struct { type GLAuthAuthenticationBackendSuite struct {
LDAPImplementationSuite suite.Suite
config schema.AuthenticationBackend
validator *schema.StructValidator
} }
func (suite *GLAuthAuthenticationBackendSuite) SetupTest() { func (suite *GLAuthAuthenticationBackendSuite) SetupTest() {
@ -1202,80 +1349,80 @@ func (suite *GLAuthAuthenticationBackendSuite) TestShouldSetDefaults() {
suite.Len(suite.validator.Warnings(), 0) suite.Len(suite.validator.Warnings(), 0)
suite.Len(suite.validator.Errors(), 0) suite.Len(suite.validator.Errors(), 0)
suite.EqualImplementationDefaults(schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth) suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth.Timeout,
suite.config.LDAP.Timeout)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth.AdditionalUsersDN,
suite.config.LDAP.AdditionalUsersDN)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth.AdditionalGroupsDN,
suite.config.LDAP.AdditionalGroupsDN)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth.UsersFilter,
suite.config.LDAP.UsersFilter)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth.UsernameAttribute,
suite.config.LDAP.UsernameAttribute)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth.DisplayNameAttribute,
suite.config.LDAP.DisplayNameAttribute)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth.MailAttribute,
suite.config.LDAP.MailAttribute)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth.GroupsFilter,
suite.config.LDAP.GroupsFilter)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth.GroupNameAttribute,
suite.config.LDAP.GroupNameAttribute)
} }
func (suite *GLAuthAuthenticationBackendSuite) TestShouldOnlySetDefaultsIfNotManuallyConfigured() { func (suite *GLAuthAuthenticationBackendSuite) TestShouldOnlySetDefaultsIfNotManuallyConfigured() {
suite.config.LDAP.Timeout = time.Second * 2 suite.config.LDAP.Timeout = time.Second * 2
suite.config.LDAP.UsersFilter = "(&({username_attribute}={input})(objectClass=Person)(!(accountStatus=inactive)))" suite.config.LDAP.UsersFilter = "(&({username_attribute}={input})(objectClass=Person)(!(accountStatus=inactive)))"
suite.config.LDAP.Attributes.Username = "description" suite.config.LDAP.UsernameAttribute = "description"
suite.config.LDAP.Attributes.Mail = "sender" suite.config.LDAP.MailAttribute = "sender"
suite.config.LDAP.Attributes.DisplayName = "given" suite.config.LDAP.DisplayNameAttribute = "given"
suite.config.LDAP.GroupsFilter = "(&(member={dn})(objectClass=posixGroup))" suite.config.LDAP.GroupsFilter = "(&(member={dn})(objectClass=posixGroup))"
suite.config.LDAP.Attributes.GroupName = "grp" suite.config.LDAP.GroupNameAttribute = "grp"
suite.config.LDAP.AdditionalUsersDN = "OU=users,OU=GlAuth" suite.config.LDAP.AdditionalUsersDN = "OU=users,OU=GlAuth"
suite.config.LDAP.AdditionalGroupsDN = "OU=groups,OU=GLAuth" suite.config.LDAP.AdditionalGroupsDN = "OU=groups,OU=GLAuth"
suite.config.LDAP.Attributes.MemberOf = member
suite.config.LDAP.GroupSearchMode = memberof
ValidateAuthenticationBackend(&suite.config, suite.validator) ValidateAuthenticationBackend(&suite.config, suite.validator)
suite.NotEqualImplementationDefaults(schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth) suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth.Timeout,
suite.Equal(member, suite.config.LDAP.Attributes.MemberOf) suite.config.LDAP.Timeout)
suite.Equal("", suite.config.LDAP.Attributes.DistinguishedName) suite.NotEqual(
suite.Equal(schema.LDAPGroupSearchModeMemberOf, suite.config.LDAP.GroupSearchMode) schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth.AdditionalUsersDN,
suite.config.LDAP.AdditionalUsersDN)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth.AdditionalGroupsDN,
suite.config.LDAP.AdditionalGroupsDN)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth.Timeout,
suite.config.LDAP.Timeout)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth.UsersFilter,
suite.config.LDAP.UsersFilter)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth.UsernameAttribute,
suite.config.LDAP.UsernameAttribute)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth.DisplayNameAttribute,
suite.config.LDAP.DisplayNameAttribute)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth.MailAttribute,
suite.config.LDAP.MailAttribute)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth.GroupsFilter,
suite.config.LDAP.GroupsFilter)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth.GroupNameAttribute,
suite.config.LDAP.GroupNameAttribute)
} }
func TestGLAuthAuthenticationBackend(t *testing.T) { func TestGLAuthAuthenticationBackend(t *testing.T) {
suite.Run(t, new(GLAuthAuthenticationBackendSuite)) suite.Run(t, new(GLAuthAuthenticationBackendSuite))
} }
type LDAPImplementationSuite struct {
suite.Suite
config schema.AuthenticationBackend
validator *schema.StructValidator
}
func (suite *LDAPImplementationSuite) EqualImplementationDefaults(expected schema.LDAPAuthenticationBackend) {
suite.Equal(expected.Timeout, suite.config.LDAP.Timeout)
suite.Equal(expected.AdditionalUsersDN, suite.config.LDAP.AdditionalUsersDN)
suite.Equal(expected.AdditionalGroupsDN, suite.config.LDAP.AdditionalGroupsDN)
suite.Equal(expected.UsersFilter, suite.config.LDAP.UsersFilter)
suite.Equal(expected.GroupsFilter, suite.config.LDAP.GroupsFilter)
suite.Equal(expected.GroupSearchMode, suite.config.LDAP.GroupSearchMode)
suite.Equal(expected.Attributes.DistinguishedName, suite.config.LDAP.Attributes.DistinguishedName)
suite.Equal(expected.Attributes.Username, suite.config.LDAP.Attributes.Username)
suite.Equal(expected.Attributes.DisplayName, suite.config.LDAP.Attributes.DisplayName)
suite.Equal(expected.Attributes.Mail, suite.config.LDAP.Attributes.Mail)
suite.Equal(expected.Attributes.MemberOf, suite.config.LDAP.Attributes.MemberOf)
suite.Equal(expected.Attributes.GroupName, suite.config.LDAP.Attributes.GroupName)
}
func (suite *LDAPImplementationSuite) NotEqualImplementationDefaults(expected schema.LDAPAuthenticationBackend) {
suite.NotEqual(expected.Timeout, suite.config.LDAP.Timeout)
suite.NotEqual(expected.UsersFilter, suite.config.LDAP.UsersFilter)
suite.NotEqual(expected.GroupsFilter, suite.config.LDAP.GroupsFilter)
suite.NotEqual(expected.GroupSearchMode, suite.config.LDAP.GroupSearchMode)
suite.NotEqual(expected.Attributes.Username, suite.config.LDAP.Attributes.Username)
suite.NotEqual(expected.Attributes.DisplayName, suite.config.LDAP.Attributes.DisplayName)
suite.NotEqual(expected.Attributes.Mail, suite.config.LDAP.Attributes.Mail)
suite.NotEqual(expected.Attributes.GroupName, suite.config.LDAP.Attributes.GroupName)
if expected.Attributes.DistinguishedName != "" {
suite.NotEqual(expected.Attributes.DistinguishedName, suite.config.LDAP.Attributes.DistinguishedName)
}
if expected.AdditionalUsersDN != "" {
suite.NotEqual(expected.AdditionalUsersDN, suite.config.LDAP.AdditionalUsersDN)
}
if expected.AdditionalGroupsDN != "" {
suite.NotEqual(expected.AdditionalGroupsDN, suite.config.LDAP.AdditionalGroupsDN)
}
if expected.Attributes.MemberOf != "" {
suite.NotEqual(expected.Attributes.MemberOf, suite.config.LDAP.Attributes.MemberOf)
}
}

View File

@ -98,9 +98,9 @@ const (
errFmtLDAPAuthBackendUnauthenticatedBindWithPassword = "authentication_backend: ldap: option 'permit_unauthenticated_bind' can't be enabled when a password is specified" errFmtLDAPAuthBackendUnauthenticatedBindWithPassword = "authentication_backend: ldap: option 'permit_unauthenticated_bind' can't be enabled when a password is specified"
errFmtLDAPAuthBackendUnauthenticatedBindWithResetEnabled = "authentication_backend: ldap: option 'permit_unauthenticated_bind' can't be enabled when password reset is enabled" errFmtLDAPAuthBackendUnauthenticatedBindWithResetEnabled = "authentication_backend: ldap: option 'permit_unauthenticated_bind' can't be enabled when password reset is enabled"
errFmtLDAPAuthBackendMissingOption = "authentication_backend: ldap: option '%s' is required" errFmtLDAPAuthBackendMissingOption = "authentication_backend: ldap: option '%s' is required"
errFmtLDAPAuthBackendTLSConfigInvalid = "authentication_backend: ldap: tls: %w" errFmtLDAPAuthBackendTLSConfigInvalid = "authentication_backend: ldap: tls: %w"
errFmtLDAPAuthBackendOptionMustBeOneOf = "authentication_backend: ldap: option '%s' " + errFmtLDAPAuthBackendImplementation = "authentication_backend: ldap: option 'implementation' " +
errSuffixMustBeOneOf errSuffixMustBeOneOf
errFmtLDAPAuthBackendFilterReplacedPlaceholders = "authentication_backend: ldap: option " + errFmtLDAPAuthBackendFilterReplacedPlaceholders = "authentication_backend: ldap: option " +
"'%s' has an invalid placeholder: '%s' has been removed, please use '%s' instead" "'%s' has an invalid placeholder: '%s' has been removed, please use '%s' instead"
@ -109,10 +109,6 @@ const (
"'%s' must contain enclosing parenthesis: '%s' should probably be '(%s)'" "'%s' must contain enclosing parenthesis: '%s' should probably be '(%s)'"
errFmtLDAPAuthBackendFilterMissingPlaceholder = "authentication_backend: ldap: option " + errFmtLDAPAuthBackendFilterMissingPlaceholder = "authentication_backend: ldap: option " +
"'%s' must contain the placeholder '{%s}' but it's absent" "'%s' must contain the placeholder '{%s}' but it's absent"
errFmtLDAPAuthBackendFilterMissingPlaceholderGroupSearchMode = "authentication_backend: ldap: option " +
"'%s' must contain one of the %s placeholders when using a group_search_mode of '%s' but they're absent"
errFmtLDAPAuthBackendFilterMissingAttribute = "authentication_backend: ldap: attributes: option " +
"'%s' must be provided when using the %s placeholder but it's absent"
) )
// TOTP Error constants. // TOTP Error constants.
@ -327,9 +323,8 @@ const (
errFmtServerAddressLegacyAndModern = "server: option 'host' and 'port' can't be configured at the same time as 'address'" errFmtServerAddressLegacyAndModern = "server: option 'host' and 'port' can't be configured at the same time as 'address'"
errFmtServerAddress = "server: option 'address' with value '%s' is invalid: %w" errFmtServerAddress = "server: option 'address' with value '%s' is invalid: %w"
errFmtServerPathNoForwardSlashes = "server: option 'path' must not contain any forward slashes" errFmtServerPathNoForwardSlashes = "server: option 'path' must not contain any forward slashes"
errFmtServerPathNotEndForwardSlash = "server: option 'address' must not and with a forward slash but it's configured as '%s'" errFmtServerPathAlphaNum = "server: option 'path' must only contain alpha numeric characters"
errFmtServerPathAlphaNum = "server: option 'path' must only contain alpha numeric characters"
errFmtServerEndpointsAuthzImplementation = "server: endpoints: authz: %s: option 'implementation' must be one of %s but it's configured as '%s'" errFmtServerEndpointsAuthzImplementation = "server: endpoints: authz: %s: option 'implementation' must be one of %s but it's configured as '%s'"
errFmtServerEndpointsAuthzStrategy = "server: endpoints: authz: %s: authn_strategies: option 'name' must be one of %s but it's configured as '%s'" errFmtServerEndpointsAuthzStrategy = "server: endpoints: authz: %s: authn_strategies: option 'name' must be one of %s but it's configured as '%s'"
@ -378,6 +373,17 @@ const (
operatorNotPattern = "not pattern" operatorNotPattern = "not pattern"
) )
var (
validLDAPImplementations = []string{
schema.LDAPImplementationCustom,
schema.LDAPImplementationActiveDirectory,
schema.LDAPImplementationRFC2307bis,
schema.LDAPImplementationFreeIPA,
schema.LDAPImplementationLLDAP,
schema.LDAPImplementationGLAuth,
}
)
const ( const (
legacy = "legacy" legacy = "legacy"
authzImplementationLegacy = "Legacy" authzImplementationLegacy = "Legacy"
@ -393,22 +399,6 @@ var (
validAuthzAuthnStrategies = []string{"CookieSession", "HeaderAuthorization", "HeaderProxyAuthorization", "HeaderAuthRequestProxyAuthorization", "HeaderLegacy"} validAuthzAuthnStrategies = []string{"CookieSession", "HeaderAuthorization", "HeaderProxyAuthorization", "HeaderAuthRequestProxyAuthorization", "HeaderLegacy"}
) )
var (
validLDAPImplementations = []string{
schema.LDAPImplementationCustom,
schema.LDAPImplementationActiveDirectory,
schema.LDAPImplementationRFC2307bis,
schema.LDAPImplementationFreeIPA,
schema.LDAPImplementationLLDAP,
schema.LDAPImplementationGLAuth,
}
validLDAPGroupSearchModes = []string{
schema.LDAPGroupSearchModeFilter,
schema.LDAPGroupSearchModeMemberOf,
}
)
var ( var (
validArgon2Variants = []string{"argon2id", "id", "argon2i", "i", "argon2d", "d"} validArgon2Variants = []string{"argon2id", "id", "argon2i", "i", "argon2d", "d"}
validSHA2CryptVariants = []string{digestSHA256, digestSHA512} validSHA2CryptVariants = []string{digestSHA256, digestSHA512}

View File

@ -13,11 +13,6 @@ const (
testLDAPURL = "ldap://ldap" testLDAPURL = "ldap://ldap"
testLDAPUser = "user" testLDAPUser = "user"
testEncryptionKey = "a_not_so_secure_encryption_key" testEncryptionKey = "a_not_so_secure_encryption_key"
member = "member"
memberof = "memberof"
memberOf = "memberOf"
filterMemberOfRDN = "(|({memberof:rdn}))"
) )
const ( const (

View File

@ -59,6 +59,17 @@ func ValidateServer(config *schema.Configuration, validator *schema.StructValida
ValidateServerAddress(config, validator) ValidateServerAddress(config, validator)
ValidateServerTLS(config, validator) ValidateServerTLS(config, validator)
switch {
case strings.Contains(config.Server.Path, "/"):
validator.Push(fmt.Errorf(errFmtServerPathNoForwardSlashes))
case !utils.IsStringAlphaNumeric(config.Server.Path):
validator.Push(fmt.Errorf(errFmtServerPathAlphaNum))
case config.Server.Path == "": // Don't do anything if it's blank.
break
default:
config.Server.Path = path.Clean("/" + config.Server.Path)
}
if config.Server.Buffers.Read <= 0 { if config.Server.Buffers.Read <= 0 {
config.Server.Buffers.Read = schema.DefaultServerConfiguration.Buffers.Read config.Server.Buffers.Read = schema.DefaultServerConfiguration.Buffers.Read
} }
@ -85,12 +96,11 @@ func ValidateServer(config *schema.Configuration, validator *schema.StructValida
// ValidateServerAddress checks the configured server address is correct. // ValidateServerAddress checks the configured server address is correct.
func ValidateServerAddress(config *schema.Configuration, validator *schema.StructValidator) { func ValidateServerAddress(config *schema.Configuration, validator *schema.StructValidator) {
if config.Server.Address == nil { if config.Server.Address == nil {
if config.Server.Host == "" && config.Server.Port == 0 && config.Server.Path == "" { //nolint:staticcheck if config.Server.Host == "" && config.Server.Port == 0 { //nolint:staticcheck
config.Server.Address = schema.DefaultServerConfiguration.Address config.Server.Address = schema.DefaultServerConfiguration.Address
} else { } else {
host := config.Server.Host //nolint:staticcheck host := config.Server.Host //nolint:staticcheck
port := config.Server.Port //nolint:staticcheck port := config.Server.Port //nolint:staticcheck
subpath := config.Server.Path //nolint:staticcheck
if host == "" { if host == "" {
host = schema.DefaultServerConfiguration.Address.Hostname() host = schema.DefaultServerConfiguration.Address.Hostname()
@ -100,20 +110,7 @@ func ValidateServerAddress(config *schema.Configuration, validator *schema.Struc
port = schema.DefaultServerConfiguration.Address.Port() port = schema.DefaultServerConfiguration.Address.Port()
} }
switch {
case strings.Contains(subpath, "/"):
validator.Push(fmt.Errorf(errFmtServerPathNoForwardSlashes))
case !utils.IsStringAlphaNumeric(config.Server.Path): //nolint:staticcheck
validator.Push(fmt.Errorf(errFmtServerPathAlphaNum))
case subpath == "":
subpath = schema.DefaultServerConfiguration.Address.Path()
default:
subpath = path.Clean("/" + config.Server.Path) //nolint:staticcheck
}
config.Server.Address = &schema.AddressTCP{Address: schema.NewAddressFromNetworkValues(schema.AddressSchemeTCP, host, port)} config.Server.Address = &schema.AddressTCP{Address: schema.NewAddressFromNetworkValues(schema.AddressSchemeTCP, host, port)}
config.Server.Address.SetPath(subpath)
} }
} else { } else {
if config.Server.Host != "" || config.Server.Port != 0 { //nolint:staticcheck if config.Server.Host != "" || config.Server.Port != 0 { //nolint:staticcheck
@ -126,10 +123,6 @@ func ValidateServerAddress(config *schema.Configuration, validator *schema.Struc
validator.Push(fmt.Errorf(errFmtServerAddress, config.Server.Address.String(), err)) validator.Push(fmt.Errorf(errFmtServerAddress, config.Server.Address.String(), err))
} }
} }
if path := config.Server.Address.Path(); path != "/" && strings.HasSuffix(path, "/") {
validator.Push(fmt.Errorf(errFmtServerPathNotEndForwardSlash, path))
}
} }
// ValidateServerEndpoints configures the default endpoints and checks the configuration of custom endpoints. // ValidateServerEndpoints configures the default endpoints and checks the configuration of custom endpoints.

View File

@ -23,97 +23,66 @@ func TestShouldSetDefaultServerValues(t *testing.T) {
assert.Len(t, validator.Errors(), 0) assert.Len(t, validator.Errors(), 0)
assert.Len(t, validator.Warnings(), 0) assert.Len(t, validator.Warnings(), 0)
assert.Equal(t, "", config.Server.Host) //nolint:staticcheck
assert.Equal(t, 0, config.Server.Port) //nolint:staticcheck
assert.Equal(t, schema.DefaultServerConfiguration.Address, config.Server.Address) assert.Equal(t, schema.DefaultServerConfiguration.Address, config.Server.Address)
assert.Equal(t, schema.DefaultServerConfiguration.Buffers.Read, config.Server.Buffers.Read) assert.Equal(t, schema.DefaultServerConfiguration.Buffers.Read, config.Server.Buffers.Read)
assert.Equal(t, schema.DefaultServerConfiguration.Buffers.Write, config.Server.Buffers.Write) assert.Equal(t, schema.DefaultServerConfiguration.Buffers.Write, config.Server.Buffers.Write)
assert.Equal(t, schema.DefaultServerConfiguration.TLS.Key, config.Server.TLS.Key) assert.Equal(t, schema.DefaultServerConfiguration.TLS.Key, config.Server.TLS.Key)
assert.Equal(t, schema.DefaultServerConfiguration.TLS.Certificate, config.Server.TLS.Certificate) assert.Equal(t, schema.DefaultServerConfiguration.TLS.Certificate, config.Server.TLS.Certificate)
assert.Equal(t, schema.DefaultServerConfiguration.Path, config.Server.Path)
assert.Equal(t, schema.DefaultServerConfiguration.Endpoints.EnableExpvars, config.Server.Endpoints.EnableExpvars) assert.Equal(t, schema.DefaultServerConfiguration.Endpoints.EnableExpvars, config.Server.Endpoints.EnableExpvars)
assert.Equal(t, schema.DefaultServerConfiguration.Endpoints.EnablePprof, config.Server.Endpoints.EnablePprof) assert.Equal(t, schema.DefaultServerConfiguration.Endpoints.EnablePprof, config.Server.Endpoints.EnablePprof)
assert.Equal(t, schema.DefaultServerConfiguration.Endpoints.Authz, config.Server.Endpoints.Authz) assert.Equal(t, schema.DefaultServerConfiguration.Endpoints.Authz, config.Server.Endpoints.Authz)
assert.Equal(t, "", config.Server.Host) //nolint:staticcheck
assert.Equal(t, 0, config.Server.Port) //nolint:staticcheck
assert.Equal(t, "", config.Server.Path) //nolint:staticcheck
} }
func TestShouldSetDefaultServerValuesWithLegacyAddress(t *testing.T) { func TestShouldSetDefaultServerValuesWithLegacyAddress(t *testing.T) {
testCases := []struct { validator := schema.NewStructValidator()
name string config := &schema.Configuration{
have schema.ServerConfiguration Server: schema.ServerConfiguration{
expected schema.Address Host: "abc",
}{ Port: 123,
{
"ShouldParseAll",
schema.ServerConfiguration{
Host: "abc",
Port: 123,
Path: "subpath",
},
MustParseAddress("tcp://abc:123/subpath"),
},
{
"ShouldParseHostAndPort",
schema.ServerConfiguration{
Host: "abc",
Port: 123,
},
MustParseAddress("tcp://abc:123/"),
},
{
"ShouldParseHostAndPath",
schema.ServerConfiguration{
Host: "abc",
Path: "subpath",
},
MustParseAddress("tcp://abc:9091/subpath"),
},
{
"ShouldParsePortAndPath",
schema.ServerConfiguration{
Port: 123,
Path: "subpath",
},
MustParseAddress("tcp://:123/subpath"),
},
{
"ShouldParseHost",
schema.ServerConfiguration{
Host: "abc",
},
MustParseAddress("tcp://abc:9091/"),
},
{
"ShouldParsePort",
schema.ServerConfiguration{
Port: 123,
},
MustParseAddress("tcp://:123/"),
},
{
"ShouldParsePath",
schema.ServerConfiguration{
Path: "subpath",
},
MustParseAddress("tcp://:9091/subpath"),
}, },
} }
for _, tc := range testCases { ValidateServer(config, validator)
t.Run(tc.name, func(t *testing.T) {
validator := schema.NewStructValidator()
config := &schema.Configuration{
Server: tc.have,
}
ValidateServer(config, validator) assert.Len(t, validator.Errors(), 0)
assert.Len(t, validator.Warnings(), 0)
assert.Len(t, validator.Errors(), 0) assert.Equal(t, "abc", config.Server.Host) //nolint:staticcheck
assert.Len(t, validator.Warnings(), 0) assert.Equal(t, 123, config.Server.Port) //nolint:staticcheck
assert.Equal(t, &schema.AddressTCP{Address: MustParseAddress("tcp://abc:123")}, config.Server.Address)
assert.Equal(t, &schema.AddressTCP{Address: tc.expected}, config.Server.Address) config = &schema.Configuration{
}) Server: schema.ServerConfiguration{
Host: "abc",
},
} }
ValidateServer(config, validator)
assert.Len(t, validator.Errors(), 0)
assert.Len(t, validator.Warnings(), 0)
assert.Equal(t, "abc", config.Server.Host) //nolint:staticcheck
assert.Equal(t, 0, config.Server.Port) //nolint:staticcheck
assert.Equal(t, &schema.AddressTCP{Address: MustParseAddress("tcp://abc:9091")}, config.Server.Address)
config = &schema.Configuration{
Server: schema.ServerConfiguration{
Port: 123,
},
}
ValidateServer(config, validator)
assert.Len(t, validator.Errors(), 0)
assert.Len(t, validator.Warnings(), 0)
assert.Equal(t, "", config.Server.Host) //nolint:staticcheck
assert.Equal(t, 123, config.Server.Port) //nolint:staticcheck
assert.Equal(t, &schema.AddressTCP{Address: MustParseAddress("tcp://:123")}, config.Server.Address)
} }
func TestShouldSetDefaultConfig(t *testing.T) { func TestShouldSetDefaultConfig(t *testing.T) {
@ -129,20 +98,20 @@ func TestShouldSetDefaultConfig(t *testing.T) {
assert.Equal(t, schema.DefaultServerConfiguration.Buffers.Write, config.Server.Buffers.Write) assert.Equal(t, schema.DefaultServerConfiguration.Buffers.Write, config.Server.Buffers.Write)
} }
func TestValidateSeverAddress(t *testing.T) { func TestShouldParsePathCorrectly(t *testing.T) {
validator := schema.NewStructValidator()
config := &schema.Configuration{ config := &schema.Configuration{
Server: schema.ServerConfiguration{ Server: schema.ServerConfiguration{
Address: &schema.AddressTCP{Address: MustParseAddress("tcp://:9091/path/")}, Path: "apple",
}, },
} }
validator := schema.NewStructValidator()
ValidateServer(config, validator) ValidateServer(config, validator)
require.Len(t, validator.Errors(), 1) assert.Len(t, validator.Errors(), 0)
assert.Len(t, validator.Warnings(), 0) assert.Len(t, validator.Warnings(), 0)
assert.EqualError(t, validator.Errors()[0], "server: option 'address' must not and with a forward slash but it's configured as '/path/'") assert.Equal(t, "/apple", config.Server.Path)
} }
func TestValidateServerShouldCorrectlyIdentifyValidAddressSchemes(t *testing.T) { func TestValidateServerShouldCorrectlyIdentifyValidAddressSchemes(t *testing.T) {
@ -267,7 +236,7 @@ func TestShouldValidateAndUpdateAddress(t *testing.T) {
ValidateServer(&config, validator) ValidateServer(&config, validator)
require.Len(t, validator.Errors(), 0) require.Len(t, validator.Errors(), 0)
assert.Equal(t, "tcp://:9091/", config.Server.Address.String()) assert.Equal(t, "tcp://:9091", config.Server.Address.String())
} }
func TestShouldRaiseErrorOnLegacyAndModernValues(t *testing.T) { func TestShouldRaiseErrorOnLegacyAndModernValues(t *testing.T) {
@ -282,6 +251,22 @@ func TestShouldRaiseErrorOnLegacyAndModernValues(t *testing.T) {
assert.EqualError(t, validator.Errors()[0], "server: option 'host' and 'port' can't be configured at the same time as 'address'") assert.EqualError(t, validator.Errors()[0], "server: option 'host' and 'port' can't be configured at the same time as 'address'")
} }
func TestShouldValidateAndUpdateAddressWithOldValues(t *testing.T) {
validator := schema.NewStructValidator()
config := newDefaultConfig()
config.Server.Address = nil
config.Server.Host = local25 //nolint:staticcheck
config.Server.Port = 9999 //nolint:staticcheck
ValidateServer(&config, validator)
assert.Len(t, validator.Errors(), 0)
assert.Len(t, validator.Warnings(), 0)
require.NotNil(t, config.Server.Address)
assert.Equal(t, "tcp://127.0.0.25:9999", config.Server.Address.String())
}
func TestShouldRaiseErrorWhenTLSCertWithoutKeyIsProvided(t *testing.T) { func TestShouldRaiseErrorWhenTLSCertWithoutKeyIsProvided(t *testing.T) {
validator := schema.NewStructValidator() validator := schema.NewStructValidator()
config := newDefaultConfig() config := newDefaultConfig()

View File

@ -20,10 +20,6 @@ func ValidateTelemetry(config *schema.Configuration, validator *schema.StructVal
config.Telemetry.Metrics.Address.SetPort(schema.DefaultTelemetryConfig.Metrics.Address.Port()) config.Telemetry.Metrics.Address.SetPort(schema.DefaultTelemetryConfig.Metrics.Address.Port())
} }
if config.Telemetry.Metrics.Address.Path() == "" {
config.Telemetry.Metrics.Address.SetPath(schema.DefaultTelemetryConfig.Metrics.Address.Path())
}
if config.Telemetry.Metrics.Buffers.Read <= 0 { if config.Telemetry.Metrics.Buffers.Read <= 0 {
config.Telemetry.Metrics.Buffers.Read = schema.DefaultTelemetryConfig.Metrics.Buffers.Read config.Telemetry.Metrics.Buffers.Read = schema.DefaultTelemetryConfig.Metrics.Buffers.Read
} }

View File

@ -38,7 +38,7 @@ func TestValidateTelemetry(t *testing.T) {
&schema.Configuration{Telemetry: schema.TelemetryConfig{Metrics: schema.TelemetryMetricsConfig{Address: mustParseAddress("tcp://0.0.0.0")}}}, &schema.Configuration{Telemetry: schema.TelemetryConfig{Metrics: schema.TelemetryMetricsConfig{Address: mustParseAddress("tcp://0.0.0.0")}}},
&schema.Configuration{Telemetry: schema.TelemetryConfig{ &schema.Configuration{Telemetry: schema.TelemetryConfig{
Metrics: schema.TelemetryMetricsConfig{ Metrics: schema.TelemetryMetricsConfig{
Address: mustParseAddress("tcp://0.0.0.0:9959/metrics"), Address: &schema.AddressTCP{Address: schema.NewAddressFromNetworkValues(schema.AddressSchemeTCP, "0.0.0.0", schema.DefaultTelemetryConfig.Metrics.Address.Port())},
Buffers: schema.ServerBuffers{ Buffers: schema.ServerBuffers{
Read: 4096, Read: 4096,
Write: 4096, Write: 4096,
@ -55,7 +55,7 @@ func TestValidateTelemetry(t *testing.T) {
}, },
{ {
"ShouldSetDefaultPortAlt", "ShouldSetDefaultPortAlt",
&schema.Configuration{Telemetry: schema.TelemetryConfig{Metrics: schema.TelemetryMetricsConfig{Address: mustParseAddress("tcp://:0/metrics")}}}, &schema.Configuration{Telemetry: schema.TelemetryConfig{Metrics: schema.TelemetryMetricsConfig{Address: mustParseAddress("tcp://:0")}}},
&schema.Configuration{Telemetry: schema.DefaultTelemetryConfig}, &schema.Configuration{Telemetry: schema.DefaultTelemetryConfig},
nil, nil,
nil, nil,
@ -63,14 +63,14 @@ func TestValidateTelemetry(t *testing.T) {
{ {
"ShouldSetDefaultPortWithCustomIP", "ShouldSetDefaultPortWithCustomIP",
&schema.Configuration{Telemetry: schema.TelemetryConfig{Metrics: schema.TelemetryMetricsConfig{Address: mustParseAddress("tcp://127.0.0.1")}}}, &schema.Configuration{Telemetry: schema.TelemetryConfig{Metrics: schema.TelemetryMetricsConfig{Address: mustParseAddress("tcp://127.0.0.1")}}},
&schema.Configuration{Telemetry: schema.TelemetryConfig{Metrics: schema.TelemetryMetricsConfig{Address: mustParseAddress("tcp://127.0.0.1:9959/metrics")}}}, &schema.Configuration{Telemetry: schema.TelemetryConfig{Metrics: schema.TelemetryMetricsConfig{Address: mustParseAddress("tcp://127.0.0.1:9959")}}},
nil, nil,
nil, nil,
}, },
{ {
"ShouldNotValidateUDP", "ShouldNotValidateUDP",
&schema.Configuration{Telemetry: schema.TelemetryConfig{Metrics: schema.TelemetryMetricsConfig{Address: mustParseAddress("udp://0.0.0.0")}}}, &schema.Configuration{Telemetry: schema.TelemetryConfig{Metrics: schema.TelemetryMetricsConfig{Address: mustParseAddress("udp://0.0.0.0")}}},
&schema.Configuration{Telemetry: schema.TelemetryConfig{Metrics: schema.TelemetryMetricsConfig{Address: mustParseAddress("udp://0.0.0.0:9959/metrics")}}}, &schema.Configuration{Telemetry: schema.TelemetryConfig{Metrics: schema.TelemetryMetricsConfig{Address: mustParseAddress("udp://0.0.0.0:9959")}}},
nil, nil,
[]string{"telemetry: metrics: option 'address' with value 'udp://0.0.0.0:0' is invalid: scheme must be one of 'tcp', 'tcp4', 'tcp6', or 'unix' but is configured as 'udp'"}, []string{"telemetry: metrics: option 'address' with value 'udp://0.0.0.0:0' is invalid: scheme must be one of 'tcp', 'tcp4', 'tcp6', or 'unix' but is configured as 'udp'"},
}, },

View File

@ -68,6 +68,7 @@ const (
messageAuthenticationFailed = "Authentication failed. Check your credentials." messageAuthenticationFailed = "Authentication failed. Check your credentials."
messageUnableToRegisterOneTimePassword = "Unable to set up one-time passwords." //nolint:gosec messageUnableToRegisterOneTimePassword = "Unable to set up one-time passwords." //nolint:gosec
messageUnableToRegisterSecurityKey = "Unable to register your security key." messageUnableToRegisterSecurityKey = "Unable to register your security key."
messageSecurityKeyDuplicateName = "Another one of your security keys is already registered with that display name."
messageUnableToResetPassword = "Unable to reset your password." messageUnableToResetPassword = "Unable to reset your password."
messageMFAValidationFailed = "Authentication failed, please retry later." messageMFAValidationFailed = "Authentication failed, please retry later."
messagePasswordWeak = "Your supplied password does not meet the password policy requirements" messagePasswordWeak = "Your supplied password does not meet the password policy requirements"

View File

@ -1,410 +0,0 @@
package handlers
import (
"encoding/json"
"fmt"
"net"
"net/url"
"strings"
"github.com/authelia/authelia/v4/internal/authorization"
"github.com/authelia/authelia/v4/internal/configuration/schema"
"github.com/authelia/authelia/v4/internal/middlewares"
"github.com/authelia/authelia/v4/internal/session"
"github.com/authelia/authelia/v4/internal/utils"
core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
autha "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3"
envoy_type "github.com/envoyproxy/go-control-plane/envoy/type/v3"
"github.com/gogo/googleapis/google/rpc"
"github.com/valyala/fasthttp"
rpcstatus "google.golang.org/genproto/googleapis/rpc/status"
"google.golang.org/grpc/codes"
healthpb "google.golang.org/grpc/health/grpc_health_v1"
"google.golang.org/grpc/status"
"context"
)
type AuthzGRCP struct {
Config *schema.Configuration
Providers middlewares.Providers
AuthStrategies []AuthnStrategy
Authz Authz
}
func NewAuthzGRCP(config *schema.Configuration, providers middlewares.Providers) *AuthzGRCP {
// Determine the refresh interval
authBuilder := NewAuthzBuilder().WithConfig(config)
// Only the following strategies are supported. These are hardcoded at the moment and won't be taken from the configuration
strategies := []AuthnStrategy{NewHeaderProxyAuthorizationAuthnStrategy() /* NewHeaderAuthorizationAuthnStrategy(), */, NewCookieSessionAuthnStrategy(authBuilder.config.RefreshInterval)}
return &AuthzGRCP{
Config: config,
Providers: providers,
AuthStrategies: strategies,
Authz: Authz{strategies: strategies},
}
}
type GRCPRequestData struct {
RemoteHost string
Domain string
Method string
Protocol string
Path string
AutheliaURL string
}
func (data *GRCPRequestData) String() string {
return fmt.Sprintf(
`Extracted data from headers:
Remote Host = %s
Domain = %s
Method = %s
Protocol = %s
Path = %s
AutheliaURL = %s
`, data.RemoteHost, data.Domain, data.Method, data.Protocol, data.Path, data.AutheliaURL)
}
// GetRequestURI returns a request URI from the provided Data inside this struct.
// It returns an error if values are missing
func (data *GRCPRequestData) GetRequestURI() (*url.URL, error) {
if len(data.Protocol) == 0 || len(data.Domain) == 0 || len(data.Path) == 0 || len(data.Method) == 0 {
return nil, fmt.Errorf("missing required values for URI. One of 'Protocol', 'Domain', 'Path' or 'Method' was not found")
}
return url.ParseRequestURI(fmt.Sprintf("%s://%s%s", data.Protocol, data.Domain, data.Path))
}
// GetAutheliaURI returns a URI for the authelia protal from the provided Data inside this struct.
func (data *GRCPRequestData) GetAutheliaURI(provider *session.Session) (*url.URL, error) {
if len(data.AutheliaURL) == 0 {
return nil, fmt.Errorf("missing required 'AutheliaURL' for request")
}
// Parse the URL
uri, err := url.ParseRequestURI(data.AutheliaURL)
if err != nil {
return nil, err
}
// Validate URL
if !utils.HasURIDomainSuffix(uri, provider.Config.Domain) {
return nil, fmt.Errorf("authelia url '%s' is not valid for detected domain '%s' as the url does not have the domain as a suffix", data.AutheliaURL, provider.Config.Domain)
}
return uri, nil
}
// HandleGRPC is the authentication handler for envoy proxies via the gRPC protocoll.
// It is oriented on handler_authz.go
func (authz *AuthzGRCP) Check(goCtx context.Context, req *autha.CheckRequest) (*autha.CheckResponse, error) {
request, data := authz.GetHttpCtxFromGRPC(req)
ctx := middlewares.NewAutheliaCtx(request, *authz.Config, authz.Providers)
// Log the parsed data and the inbound headers
ctx.Logger.Debug(data)
b, err := json.MarshalIndent(req.Attributes.Request.Http.Headers, "", " ")
if err == nil {
ctx.Logger.Trace("Inbound Headers: ")
ctx.Logger.Trace((string(b)))
}
// Get request URI
uri, err := data.GetRequestURI()
if err != nil {
ctx.Logger.WithError(err).Error("Error getting Target URL and Request Method")
return authz.ErrAuthResponse(envoy_type.StatusCode_BadRequest, "Bad authentication request"), nil
}
// Get authentication object
object := authorization.NewObject(uri, data.Method)
if !utils.IsURISecure(object.URL) {
// Check for redirect
if !ctx.Configuration.Server.DisableAutoHttpsRedirect {
// Redirect to https
return authz.AuthResponseHeader(envoy_type.StatusCode_PermanentRedirect, "", []*core.HeaderValueOption{
{
Header: &core.HeaderValue{
Key: "Location",
Value: fmt.Sprintf("https://%s%s", data.Domain, data.Path),
},
},
}), nil
}
ctx.Logger.Errorf("Target URL '%s' has an insecure scheme '%s', only the 'https' and 'wss' schemes are supported so session cookies can be transmitted securely", object.URL.String(), object.URL.Scheme)
return authz.ErrAuthResponse(envoy_type.StatusCode_BadRequest, "Bad protocol for authentication request"), nil
}
// Get provider
provider, err := ctx.GetSessionProviderByTargetURL(object.URL)
if err != nil {
ctx.Logger.WithError(err).WithField("target_url", object.URL.String()).Error("Target URL does not appear to have a relevant session cookies configuration")
return authz.ErrAuthResponse(envoy_type.StatusCode_BadRequest, "Bad domain for authentication request"), nil
}
// Get authelia url
if len(data.AutheliaURL) == 0 {
ctx.Logger.Info("Received no authelia URL from headers. Using the default of https://auth.rpjosh.de")
data.AutheliaURL = "https://auth.rpjosh.de"
}
autheliaURI, err := data.GetAutheliaURI(provider)
if err != nil {
ctx.Logger.WithError(err).WithField("target_url", object.URL.String()).Error("Error occurred trying to determine the external Authelia URL for Target URL")
return authz.ErrAuthResponse(envoy_type.StatusCode_BadRequest, "Bad authentication request"), nil
}
ctx.Logger.Trace("Using authelia URL " + autheliaURI.String())
var (
authn Authn
strategy AuthnStrategy
)
// Get strategie
if authn, strategy, err = authz.Authz.authn(ctx, provider); err != nil {
authn.Object = object
ctx.Logger.WithError(err).Error("Error occurred while attempting to authenticate a request")
switch strategy {
case nil:
ctx.Logger.Trace("Received no strategy to use")
return authz.ErrAuthResponse(envoy_type.StatusCode_Unauthorized, "Unauthorized"), nil
default:
// Because the response is modified directly, we have to catch this and rewrite this function
if s, ok := strategy.(*HeaderAuthnStrategy); ok {
ctx.Logger.Trace("Rewriting HeaderAuthnStrategy")
ctx.Logger.Debugf("Responding %d %s", s.authn, autheliaURI)
headers := make([]*core.HeaderValueOption, 0)
if s.headerAuthenticate != nil {
headers = append(headers, &core.HeaderValueOption{
Header: &core.HeaderValue{
Key: string(s.headerAuthenticate),
Value: string(headerValueAuthenticateBasic),
},
})
}
return authz.AuthResponseHeader(envoy_type.StatusCode(s.statusAuthenticate), "", headers), nil
} else if _, ok := strategy.(*CookieSessionAuthnStrategy); ok {
ctx.Logger.Trace("Rewriting CookieSessionAuthnStrategy")
}
ctx.Logger.Error("Received unsupported auth strategy for gRPC server")
// strategy.HandleUnauthorized(ctx, &authn, authz.Authz.getRedirectionURL(&object, autheliaURI))
}
return authz.ErrAuthResponse(envoy_type.StatusCode_Unauthorized, "Unauthorized"), nil
}
authn.Object = object
authn.Method = friendlyMethod(authn.Object.Method)
ruleHasSubject, required := ctx.Providers.Authorizer.GetRequiredLevel(
authorization.Subject{
Username: authn.Details.Username,
Groups: authn.Details.Groups,
IP: net.ParseIP(data.RemoteHost),
},
object,
)
switch isAuthzResult(authn.Level, required, ruleHasSubject) {
case AuthzResultForbidden:
ctx.Logger.Infof("Access to '%s' is forbidden to user '%s'", object.URL.String(), authn.Username)
return authz.ErrAuthResponse(envoy_type.StatusCode_Forbidden, "Forbidden"), nil
case AuthzResultUnauthorized:
if strategy != nil {
ctx.Logger.Error("Handling not supported handler")
//strategy.HandleUnauthorized(ctx, &authn, authz.Authz.getRedirectionURL(&object, autheliaURI))
} else {
ctx.Logger.Debugf("Redirecting user")
return authz.HandleUnauthorizedRedirect(ctx, &authn, authz.Authz.getRedirectionURL(&object, autheliaURI)), nil
}
case AuthzResultAuthorized:
ctx.Logger.Debugf("Authorized request")
return authz.HandleAuthorized(ctx, &authn), nil
//authz.Authz.handleAuthorized(ctx, &authn)
}
ctx.Logger.Info("Handling default redirection")
return authz.HandleUnauthorizedRedirect(ctx, &authn, authz.Authz.getRedirectionURL(&object, autheliaURI)), nil
}
// GetHttpCtxFromGRPC is an Adapter that converts common fields between a grpc request
// a "normal" http request.
// It also returns the
func (authz *AuthzGRCP) GetHttpCtxFromGRPC(req *autha.CheckRequest) (*fasthttp.RequestCtx, *GRCPRequestData) {
// Extract headers
headers := req.Attributes.Request.Http.Headers
// Parse the header data
data := &GRCPRequestData{
RemoteHost: headers["x-forwarded-for"],
Domain: headers[":authority"],
Method: headers[":method"],
Protocol: headers["x-forwarded-proto"],
Path: headers[":path"],
AutheliaURL: headers[""],
}
// Build fasthttp request with common types
rtc := &fasthttp.RequestCtx{}
// General headers
rtc.Request.Header.Set(fasthttp.HeaderXForwardedFor, data.RemoteHost)
// Needed for NewHeaderProxyAuthorizationAuthnStrategy and NewHeaderAuthorizationAuthnStrategy
authz.setHeaderIfSet(fasthttp.HeaderAuthorization, rtc, &headers)
authz.setHeaderIfSet(fasthttp.HeaderProxyAuthorization, rtc, &headers)
authz.setHeaderIfSet(fasthttp.HeaderWWWAuthenticate, rtc, &headers)
authz.setHeaderIfSet(fasthttp.HeaderProxyAuthenticate, rtc, &headers)
// Needed for CookieSesseionauthnStrategy
rtc.Request.Header.Set("cookie", headers["cookie"])
return rtc, data
}
// setHeaderIfSet sets the header in the given fastHttp request if the header from the envoy authentication
// request was also set
func (authz *AuthzGRCP) setHeaderIfSet(headerKeyFast string, rtc *fasthttp.RequestCtx, envoyHeaders *map[string]string) {
// Envoys provided header keys are always lower case
envoyHeaderKey := strings.ToLower(headerKeyFast)
if val, isSet := (*envoyHeaders)[envoyHeaderKey]; isSet {
rtc.Request.Header.Set(headerKeyFast, val)
}
}
// ErrAuthResponse returns an authentication error for envoy with the given status code
// and the given text body
func (authz *AuthzGRCP) ErrAuthResponse(statuscode envoy_type.StatusCode, body string) *autha.CheckResponse {
return &autha.CheckResponse{
Status: &rpcstatus.Status{
Code: int32(rpc.UNAUTHENTICATED),
},
HttpResponse: &autha.CheckResponse_DeniedResponse{
DeniedResponse: &autha.DeniedHttpResponse{
Status: &envoy_type.HttpStatus{
Code: statuscode,
},
Body: body,
},
},
}
}
// ErrAuthResponse returns an authentication error for envoy with the given status code
// and the given text body
func (authz *AuthzGRCP) AuthResponseHeader(statuscode envoy_type.StatusCode, body string, headers []*core.HeaderValueOption) *autha.CheckResponse {
return &autha.CheckResponse{
Status: &rpcstatus.Status{
Code: int32(rpc.UNAUTHENTICATED),
},
HttpResponse: &autha.CheckResponse_DeniedResponse{
DeniedResponse: &autha.DeniedHttpResponse{
Status: &envoy_type.HttpStatus{
Code: statuscode,
},
Body: body,
Headers: headers,
},
},
}
}
func (authz *AuthzGRCP) HandleAuthorized(ctx *middlewares.AutheliaCtx, authn *Authn) *autha.CheckResponse {
return &autha.CheckResponse{
Status: &rpcstatus.Status{
Code: int32(rpc.OK),
},
HttpResponse: &autha.CheckResponse_OkResponse{
OkResponse: &autha.OkHttpResponse{
Headers: []*core.HeaderValueOption{
{
Header: &core.HeaderValue{
Key: "Auth-Handler",
Value: getAuthType(authn.Type),
},
},
{
Header: &core.HeaderValue{
Key: "Auth-Username",
Value: authn.Username,
},
},
{
Header: &core.HeaderValue{
Key: "Auth-Username-Display",
Value: authn.Details.DisplayName,
},
},
{
Header: &core.HeaderValue{
Key: "Auth-Realm",
Value: authn.Object.Domain,
},
},
},
},
},
}
}
func (authz *AuthzGRCP) HandleUnauthorizedRedirect(ctx *middlewares.AutheliaCtx, authn *Authn, redirectionURL *url.URL) *autha.CheckResponse {
return &autha.CheckResponse{
Status: &rpcstatus.Status{
Code: int32(rpc.UNAUTHENTICATED),
},
HttpResponse: &autha.CheckResponse_DeniedResponse{
DeniedResponse: &autha.DeniedHttpResponse{
Status: &envoy_type.HttpStatus{
Code: 302,
},
Headers: []*core.HeaderValueOption{
{
Header: &core.HeaderValue{
Key: "Location",
Value: redirectionURL.String(),
},
},
},
},
},
}
}
func getAuthType(t AuthnType) string {
switch t {
case AuthnTypeNone:
return "none"
case AuthnTypeCookie:
return "cookie"
case AuthnTypeProxyAuthorization:
return "proxy"
case AuthnTypeAuthorization:
return "header"
default:
return "unknown"
}
}
// HealthEndpoint implements a health function to check if the appliction is
// still alive
type HealthEndpoint struct{}
func (s *HealthEndpoint) Check(ctx context.Context, in *healthpb.HealthCheckRequest) (*healthpb.HealthCheckResponse, error) {
return &healthpb.HealthCheckResponse{Status: healthpb.HealthCheckResponse_SERVING}, nil
}
func (s *HealthEndpoint) Watch(in *healthpb.HealthCheckRequest, srv healthpb.Health_WatchServer) error {
return status.Error(codes.Unimplemented, "Watch is not implemented")
}

View File

@ -33,7 +33,7 @@ func FirstFactorPOST(delayFunc middlewares.TimingAttackDelayFunc) middlewares.Re
return return
} }
if bannedUntil, err := ctx.Providers.Regulator.Regulate(ctx, bodyJSON.Username, ctx.RemoteIP().String()); err != nil { if bannedUntil, err := ctx.Providers.Regulator.Regulate(ctx, bodyJSON.Username); err != nil {
if errors.Is(err, regulation.ErrUserIsBanned) { if errors.Is(err, regulation.ErrUserIsBanned) {
_ = markAuthenticationAttempt(ctx, false, &bannedUntil, bodyJSON.Username, regulation.AuthType1FA, nil) _ = markAuthenticationAttempt(ctx, false, &bannedUntil, bodyJSON.Username, regulation.AuthType1FA, nil)

View File

@ -2,6 +2,8 @@ package handlers
import ( import (
"bytes" "bytes"
"encoding/json"
"strings"
"github.com/go-webauthn/webauthn/protocol" "github.com/go-webauthn/webauthn/protocol"
"github.com/go-webauthn/webauthn/webauthn" "github.com/go-webauthn/webauthn/webauthn"
@ -11,35 +13,21 @@ import (
"github.com/authelia/authelia/v4/internal/model" "github.com/authelia/authelia/v4/internal/model"
"github.com/authelia/authelia/v4/internal/regulation" "github.com/authelia/authelia/v4/internal/regulation"
"github.com/authelia/authelia/v4/internal/session" "github.com/authelia/authelia/v4/internal/session"
"github.com/authelia/authelia/v4/internal/storage"
) )
// WebauthnIdentityStart the handler for initiating the identity validation. // WebAuthnRegistrationPUT returns the attestation challenge from the server.
var WebauthnIdentityStart = middlewares.IdentityVerificationStart(middlewares.IdentityVerificationStartArgs{ func WebAuthnRegistrationPUT(ctx *middlewares.AutheliaCtx) {
MailTitle: "Register your key",
MailButtonContent: "Register",
TargetEndpoint: "/webauthn/register",
ActionClaim: ActionWebAuthnRegistration,
IdentityRetrieverFunc: identityRetrieverFromSession,
}, nil)
// WebauthnIdentityFinish the handler for finishing the identity validation.
var WebauthnIdentityFinish = middlewares.IdentityVerificationFinish(
middlewares.IdentityVerificationFinishArgs{
ActionClaim: ActionWebAuthnRegistration,
IsTokenUserValidFunc: isTokenUserValidFor2FARegistration,
}, SecondFactorWebAuthnAttestationGET)
// SecondFactorWebAuthnAttestationGET returns the attestation challenge from the server.
func SecondFactorWebAuthnAttestationGET(ctx *middlewares.AutheliaCtx, _ string) {
var ( var (
w *webauthn.WebAuthn w *webauthn.WebAuthn
user *model.WebAuthnUser user *model.WebAuthnUser
userSession session.UserSession userSession session.UserSession
bodyJSON bodyRegisterWebAuthnPUTRequest
err error err error
) )
if userSession, err = ctx.GetSession(); err != nil { if userSession, err = ctx.GetSession(); err != nil {
ctx.Logger.WithError(err).Errorf("Error occurred retrieving session for %s attestation challenge", regulation.AuthTypeWebAuthn) ctx.Logger.WithError(err).Errorf("Error occurred retrieving session for %s registration challenge", regulation.AuthTypeWebAuthn)
respondUnauthorized(ctx, messageUnableToRegisterSecurityKey) respondUnauthorized(ctx, messageUnableToRegisterSecurityKey)
@ -47,40 +35,90 @@ func SecondFactorWebAuthnAttestationGET(ctx *middlewares.AutheliaCtx, _ string)
} }
if w, err = newWebAuthn(ctx); err != nil { if w, err = newWebAuthn(ctx); err != nil {
ctx.Logger.Errorf("Unable to create %s attestation challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err) ctx.Logger.Errorf("Unable to create provider to generate %s registration challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
respondUnauthorized(ctx, messageUnableToRegisterSecurityKey) respondUnauthorized(ctx, messageUnableToRegisterSecurityKey)
return return
} }
if user, err = getWebAuthnUser(ctx, userSession); err != nil { if err = json.Unmarshal(ctx.PostBody(), &bodyJSON); err != nil {
ctx.Logger.Errorf("Unable to load %s devices for assertion challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err) ctx.Logger.Errorf("Unable to parse %s registration request PUT data for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
respondUnauthorized(ctx, messageMFAValidationFailed)
return
}
var credentialCreation *protocol.CredentialCreation
if credentialCreation, userSession.WebAuthn, err = w.BeginRegistration(user); err != nil {
ctx.Logger.Errorf("Unable to create %s attestation challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
respondUnauthorized(ctx, messageUnableToRegisterSecurityKey) respondUnauthorized(ctx, messageUnableToRegisterSecurityKey)
return return
} }
if length := len(bodyJSON.Description); length == 0 || length > 64 {
ctx.Logger.Errorf("Failed to validate the user chosen display name for during %s registration for user '%s': the value has a length of %d but must be between 1 and 64", regulation.AuthTypeWebAuthn, userSession.Username, length)
respondUnauthorized(ctx, messageUnableToRegisterSecurityKey)
return
}
devices, err := ctx.Providers.StorageProvider.LoadWebAuthnDevicesByUsername(ctx, w.Config.RPID, userSession.Username)
if err != nil && err != storage.ErrNoWebAuthnDevice {
ctx.Logger.Errorf("Unable to load existing %s devices for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
respondUnauthorized(ctx, messageUnableToRegisterSecurityKey)
return
}
for _, device := range devices {
if strings.EqualFold(device.Description, bodyJSON.Description) {
ctx.Logger.Errorf("Unable to generate %s registration challenge: device for for user '%s' with display name '%s' already exists", regulation.AuthTypeWebAuthn, userSession.Username, bodyJSON.Description)
ctx.SetStatusCode(fasthttp.StatusConflict)
ctx.SetJSONError(messageSecurityKeyDuplicateName)
return
}
}
if user, err = getWebAuthnUserByRPID(ctx, userSession.Username, userSession.DisplayName, w.Config.RPID); err != nil {
ctx.Logger.Errorf("Unable to load %s devices for registration challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
respondUnauthorized(ctx, messageUnableToRegisterSecurityKey)
return
}
var (
creation *protocol.CredentialCreation
)
opts := []webauthn.RegistrationOption{
webauthn.WithExclusions(user.WebAuthnCredentialDescriptors()),
webauthn.WithExtensions(map[string]any{"credProps": true}),
webauthn.WithResidentKeyRequirement(protocol.ResidentKeyRequirementDiscouraged),
}
data := session.WebAuthn{
Description: bodyJSON.Description,
}
if creation, data.SessionData, err = w.BeginRegistration(user, opts...); err != nil {
ctx.Logger.Errorf("Unable to create %s registration challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
respondUnauthorized(ctx, messageUnableToRegisterSecurityKey)
return
}
userSession.WebAuthn = &data
if err = ctx.SaveSession(userSession); err != nil { if err = ctx.SaveSession(userSession); err != nil {
ctx.Logger.Errorf(logFmtErrSessionSave, "attestation challenge", regulation.AuthTypeWebAuthn, userSession.Username, err) ctx.Logger.Errorf(logFmtErrSessionSave, "registration challenge", regulation.AuthTypeWebAuthn, userSession.Username, err)
respondUnauthorized(ctx, messageUnableToRegisterSecurityKey) respondUnauthorized(ctx, messageUnableToRegisterSecurityKey)
return return
} }
if err = ctx.SetJSONBody(credentialCreation); err != nil { if err = ctx.SetJSONBody(creation); err != nil {
ctx.Logger.Errorf(logFmtErrWriteResponseBody, regulation.AuthTypeWebAuthn, userSession.Username, err) ctx.Logger.Errorf(logFmtErrWriteResponseBody, regulation.AuthTypeWebAuthn, userSession.Username, err)
respondUnauthorized(ctx, messageUnableToRegisterSecurityKey) respondUnauthorized(ctx, messageUnableToRegisterSecurityKey)
@ -89,8 +127,8 @@ func SecondFactorWebAuthnAttestationGET(ctx *middlewares.AutheliaCtx, _ string)
} }
} }
// WebAuthnAttestationPOST processes the attestation challenge response from the client. // WebAuthnRegistrationPOST processes the attestation challenge response from the client.
func WebAuthnAttestationPOST(ctx *middlewares.AutheliaCtx) { func WebAuthnRegistrationPOST(ctx *middlewares.AutheliaCtx) {
var ( var (
err error err error
w *webauthn.WebAuthn w *webauthn.WebAuthn
@ -98,75 +136,89 @@ func WebAuthnAttestationPOST(ctx *middlewares.AutheliaCtx) {
userSession session.UserSession userSession session.UserSession
attestationResponse *protocol.ParsedCredentialCreationData response *protocol.ParsedCredentialCreationData
credential *webauthn.Credential
credential *webauthn.Credential
) )
if userSession, err = ctx.GetSession(); err != nil { if userSession, err = ctx.GetSession(); err != nil {
ctx.Logger.WithError(err).Errorf("Error occurred retrieving session for %s attestation response", regulation.AuthTypeWebAuthn) ctx.Logger.WithError(err).Errorf("Error occurred retrieving session for %s registration response", regulation.AuthTypeWebAuthn)
respondUnauthorized(ctx, messageUnableToRegisterSecurityKey) respondUnauthorized(ctx, messageUnableToRegisterSecurityKey)
return return
} }
if userSession.WebAuthn == nil { if userSession.WebAuthn == nil || userSession.WebAuthn.SessionData == nil {
ctx.Logger.Errorf("WebAuthn session data is not present in order to handle attestation for user '%s'. This could indicate a user trying to POST to the wrong endpoint, or the session data is not present for the browser they used.", userSession.Username) ctx.Logger.Errorf("WebAuthn session data is not present in order to handle %s registration for user '%s'. This could indicate a user trying to POST to the wrong endpoint, or the session data is not present for the browser they used.", regulation.AuthTypeWebAuthn, userSession.Username)
respondUnauthorized(ctx, messageMFAValidationFailed) respondUnauthorized(ctx, messageUnableToRegisterSecurityKey)
return return
} }
if w, err = newWebAuthn(ctx); err != nil { if w, err = newWebAuthn(ctx); err != nil {
ctx.Logger.Errorf("Unable to configure %s during assertion challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err) ctx.Logger.Errorf("Unable to configure %s during registration for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
respondUnauthorized(ctx, messageUnableToRegisterSecurityKey) respondUnauthorized(ctx, messageUnableToRegisterSecurityKey)
return return
} }
if attestationResponse, err = protocol.ParseCredentialCreationResponseBody(bytes.NewReader(ctx.PostBody())); err != nil { if response, err = protocol.ParseCredentialCreationResponseBody(bytes.NewReader(ctx.PostBody())); err != nil {
ctx.Logger.Errorf("Unable to parse %s assertionfor user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err) switch e := err.(type) {
case *protocol.Error:
ctx.Logger.Errorf("Unable to parse %s registration for user '%s': %+v (%s)", regulation.AuthTypeWebAuthn, userSession.Username, err, e.DevInfo)
default:
ctx.Logger.Errorf("Unable to parse %s registration for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
}
respondUnauthorized(ctx, messageMFAValidationFailed) respondUnauthorized(ctx, messageUnableToRegisterSecurityKey)
return return
} }
if user, err = getWebAuthnUser(ctx, userSession); err != nil { if user, err = getWebAuthnUserByRPID(ctx, userSession.Username, userSession.DisplayName, w.Config.RPID); err != nil {
ctx.Logger.Errorf("Unable to load %s devices for assertion challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err) ctx.Logger.Errorf("Unable to load %s user details for registration for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
respondUnauthorized(ctx, messageMFAValidationFailed) respondUnauthorized(ctx, messageUnableToRegisterSecurityKey)
return return
} }
if credential, err = w.CreateCredential(user, *userSession.WebAuthn, attestationResponse); err != nil { if credential, err = w.CreateCredential(user, *userSession.WebAuthn.SessionData, response); err != nil {
ctx.Logger.Errorf("Unable to load %s devices for assertion challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err) switch e := err.(type) {
case *protocol.Error:
ctx.Logger.Errorf("Unable to create %s credential for user '%s': %+v (%s)", regulation.AuthTypeWebAuthn, userSession.Username, err, e.DevInfo)
default:
ctx.Logger.Errorf("Unable to create %s credential for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
}
respondUnauthorized(ctx, messageMFAValidationFailed) respondUnauthorized(ctx, messageUnableToRegisterSecurityKey)
return return
} }
device := model.NewWebAuthnDeviceFromCredential(w.Config.RPID, userSession.Username, "Primary", credential) device := model.NewWebAuthnDeviceFromCredential(w.Config.RPID, userSession.Username, userSession.WebAuthn.Description, credential)
device.Discoverable = webauthnCredentialCreationIsDiscoverable(ctx, response)
if err = ctx.Providers.StorageProvider.SaveWebAuthnDevice(ctx, device); err != nil { if err = ctx.Providers.StorageProvider.SaveWebAuthnDevice(ctx, device); err != nil {
ctx.Logger.Errorf("Unable to load %s devices for assertion challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err) ctx.Logger.Errorf("Unable to save %s device registration for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
respondUnauthorized(ctx, messageMFAValidationFailed) respondUnauthorized(ctx, messageUnableToRegisterSecurityKey)
return return
} }
userSession.WebAuthn = nil userSession.WebAuthn = nil
if err = ctx.SaveSession(userSession); err != nil { if err = ctx.SaveSession(userSession); err != nil {
ctx.Logger.Errorf(logFmtErrSessionSave, "removal of the attestation challenge", regulation.AuthTypeWebAuthn, userSession.Username, err) ctx.Logger.Errorf(logFmtErrSessionSave, "removal of the registration challenge", regulation.AuthTypeWebAuthn, userSession.Username, err)
} }
ctx.ReplyOK() ctx.ReplyOK()
ctx.SetStatusCode(fasthttp.StatusCreated) ctx.SetStatusCode(fasthttp.StatusCreated)
ctxLogEvent(ctx, userSession.Username, "Second Factor Method Added", map[string]any{"Action": "Second Factor Method Added", "Category": "WebAuthn Credential", "Device Name": "Primary"}) ctxLogEvent(ctx, userSession.Username, "Second Factor Method Added", map[string]any{"Action": "Second Factor Method Added", "Category": "WebAuthn Credential", "Credential Description": device.Description})
} }

View File

@ -30,45 +30,50 @@ func WebAuthnAssertionGET(ctx *middlewares.AutheliaCtx) {
} }
if w, err = newWebAuthn(ctx); err != nil { if w, err = newWebAuthn(ctx); err != nil {
ctx.Logger.Errorf("Unable to configure %s during assertion challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err) ctx.Logger.Errorf("Unable to configure %s during authentication challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
respondUnauthorized(ctx, messageMFAValidationFailed) respondUnauthorized(ctx, messageMFAValidationFailed)
return return
} }
if user, err = getWebAuthnUser(ctx, userSession); err != nil { if user, err = getWebAuthnUserByRPID(ctx, userSession.Username, userSession.DisplayName, w.Config.RPID); err != nil {
ctx.Logger.Errorf("Unable to create %s assertion challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err) ctx.Logger.Errorf("Unable to load %s user details during authentication challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
respondUnauthorized(ctx, messageMFAValidationFailed) respondUnauthorized(ctx, messageMFAValidationFailed)
return return
} }
extensions := map[string]any{}
if user.HasFIDOU2F() {
extensions["appid"] = w.Config.RPOrigins[0]
}
var opts = []webauthn.LoginOption{ var opts = []webauthn.LoginOption{
webauthn.WithAllowedCredentials(user.WebAuthnCredentialDescriptors()), webauthn.WithAllowedCredentials(user.WebAuthnCredentialDescriptors()),
} }
extensions := map[string]any{}
if user.HasFIDOU2F() {
extensions["appid"] = w.Config.RPOrigin
}
if len(extensions) != 0 { if len(extensions) != 0 {
opts = append(opts, webauthn.WithAssertionExtensions(extensions)) opts = append(opts, webauthn.WithAssertionExtensions(extensions))
} }
var assertion *protocol.CredentialAssertion var (
assertion *protocol.CredentialAssertion
data session.WebAuthn
)
if assertion, userSession.WebAuthn, err = w.BeginLogin(user, opts...); err != nil { if assertion, data.SessionData, err = w.BeginLogin(user, opts...); err != nil {
ctx.Logger.Errorf("Unable to create %s assertion challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err) ctx.Logger.Errorf("Unable to create %s authentication challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
respondUnauthorized(ctx, messageMFAValidationFailed) respondUnauthorized(ctx, messageMFAValidationFailed)
return return
} }
userSession.WebAuthn = &data
if err = ctx.SaveSession(userSession); err != nil { if err = ctx.SaveSession(userSession); err != nil {
ctx.Logger.Errorf(logFmtErrSessionSave, "assertion challenge", regulation.AuthTypeWebAuthn, userSession.Username, err) ctx.Logger.Errorf(logFmtErrSessionSave, "assertion challenge", regulation.AuthTypeWebAuthn, userSession.Username, err)
@ -115,8 +120,8 @@ func WebAuthnAssertionPOST(ctx *middlewares.AutheliaCtx) {
return return
} }
if userSession.WebAuthn == nil { if userSession.WebAuthn == nil || userSession.WebAuthn.SessionData == nil {
ctx.Logger.Errorf("WebAuthn session data is not present in order to handle assertion for user '%s'. This could indicate a user trying to POST to the wrong endpoint, or the session data is not present for the browser they used.", userSession.Username) ctx.Logger.Errorf("WebAuthn session data is not present in order to handle authentication challenge for user '%s'. This could indicate a user trying to POST to the wrong endpoint, or the session data is not present for the browser they used.", userSession.Username)
respondUnauthorized(ctx, messageMFAValidationFailed) respondUnauthorized(ctx, messageMFAValidationFailed)
@ -124,7 +129,7 @@ func WebAuthnAssertionPOST(ctx *middlewares.AutheliaCtx) {
} }
if w, err = newWebAuthn(ctx); err != nil { if w, err = newWebAuthn(ctx); err != nil {
ctx.Logger.Errorf("Unable to configure %s during assertion challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err) ctx.Logger.Errorf("Unable to configure %s during authentication challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
respondUnauthorized(ctx, messageMFAValidationFailed) respondUnauthorized(ctx, messageMFAValidationFailed)
@ -137,23 +142,23 @@ func WebAuthnAssertionPOST(ctx *middlewares.AutheliaCtx) {
user *model.WebAuthnUser user *model.WebAuthnUser
) )
if assertionResponse, err = protocol.ParseCredentialRequestResponseBody(bytes.NewReader(ctx.PostBody())); err != nil { if assertionResponse, err = protocol.ParseCredentialRequestResponseBody(bytes.NewReader(bodyJSON.Response)); err != nil {
ctx.Logger.Errorf("Unable to parse %s assertionfor user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err) ctx.Logger.Errorf("Unable to parse %s authentication challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
respondUnauthorized(ctx, messageMFAValidationFailed) respondUnauthorized(ctx, messageMFAValidationFailed)
return return
} }
if user, err = getWebAuthnUser(ctx, userSession); err != nil { if user, err = getWebAuthnUserByRPID(ctx, userSession.Username, userSession.DisplayName, w.Config.RPID); err != nil {
ctx.Logger.Errorf("Unable to load %s devices for assertion challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err) ctx.Logger.Errorf("Unable to load %s credentials for authentication challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
respondUnauthorized(ctx, messageMFAValidationFailed) respondUnauthorized(ctx, messageMFAValidationFailed)
return return
} }
if credential, err = w.ValidateLogin(user, *userSession.WebAuthn, assertionResponse); err != nil { if credential, err = w.ValidateLogin(user, *userSession.WebAuthn.SessionData, assertionResponse); err != nil {
_ = markAuthenticationAttempt(ctx, false, nil, userSession.Username, regulation.AuthTypeWebAuthn, err) _ = markAuthenticationAttempt(ctx, false, nil, userSession.Username, regulation.AuthTypeWebAuthn, err)
respondUnauthorized(ctx, messageMFAValidationFailed) respondUnauthorized(ctx, messageMFAValidationFailed)
@ -169,8 +174,8 @@ func WebAuthnAssertionPOST(ctx *middlewares.AutheliaCtx) {
found = true found = true
if err = ctx.Providers.StorageProvider.UpdateWebAuthnDeviceSignIn(ctx, device.ID, device.RPID, device.LastUsedAt, device.SignCount, device.CloneWarning); err != nil { if err = ctx.Providers.StorageProvider.UpdateWebAuthnDeviceSignIn(ctx, device); err != nil {
ctx.Logger.Errorf("Unable to save %s device signin count for assertion challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err) ctx.Logger.Errorf("Unable to save %s device signin count for authentication challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
respondUnauthorized(ctx, messageMFAValidationFailed) respondUnauthorized(ctx, messageMFAValidationFailed)
@ -182,7 +187,7 @@ func WebAuthnAssertionPOST(ctx *middlewares.AutheliaCtx) {
} }
if !found { if !found {
ctx.Logger.Errorf("Unable to save %s device signin count for assertion challenge for user '%s' device '%x' count '%d': unable to find device", regulation.AuthTypeWebAuthn, userSession.Username, credential.ID, credential.Authenticator.SignCount) ctx.Logger.Errorf("Unable to save %s device signin count for authentication challenge for user '%s' device '%x' count '%d': unable to find device", regulation.AuthTypeWebAuthn, userSession.Username, credential.ID, credential.Authenticator.SignCount)
respondUnauthorized(ctx, messageMFAValidationFailed) respondUnauthorized(ctx, messageMFAValidationFailed)
@ -204,11 +209,11 @@ func WebAuthnAssertionPOST(ctx *middlewares.AutheliaCtx) {
} }
userSession.SetTwoFactorWebAuthn(ctx.Clock.Now(), userSession.SetTwoFactorWebAuthn(ctx.Clock.Now(),
assertionResponse.Response.AuthenticatorData.Flags.UserPresent(), assertionResponse.Response.AuthenticatorData.Flags.HasUserPresent(),
assertionResponse.Response.AuthenticatorData.Flags.UserVerified()) assertionResponse.Response.AuthenticatorData.Flags.HasUserVerified())
if err = ctx.SaveSession(userSession); err != nil { if err = ctx.SaveSession(userSession); err != nil {
ctx.Logger.Errorf(logFmtErrSessionSave, "removal of the assertion challenge and authentication time", regulation.AuthTypeWebAuthn, userSession.Username, err) ctx.Logger.Errorf(logFmtErrSessionSave, "removal of the authentiation challenge and authentication time", regulation.AuthTypeWebAuthn, userSession.Username, err)
respondUnauthorized(ctx, messageMFAValidationFailed) respondUnauthorized(ctx, messageMFAValidationFailed)

View File

@ -0,0 +1,160 @@
package handlers
import (
"encoding/json"
"errors"
"fmt"
"net/url"
"strconv"
"github.com/valyala/fasthttp"
"github.com/authelia/authelia/v4/internal/middlewares"
"github.com/authelia/authelia/v4/internal/model"
"github.com/authelia/authelia/v4/internal/regulation"
"github.com/authelia/authelia/v4/internal/session"
"github.com/authelia/authelia/v4/internal/storage"
)
func getWebAuthnDeviceIDFromContext(ctx *middlewares.AutheliaCtx) (int, error) {
deviceIDStr, ok := ctx.UserValue("deviceID").(string)
if !ok {
ctx.SetStatusCode(fasthttp.StatusBadRequest)
return 0, errors.New("Invalid device ID type")
}
deviceID, err := strconv.Atoi(deviceIDStr)
if err != nil {
ctx.Error(err, messageOperationFailed)
ctx.SetStatusCode(fasthttp.StatusBadRequest)
return 0, err
}
return deviceID, nil
}
// WebAuthnDevicesGET returns all devices registered for the current user.
func WebAuthnDevicesGET(ctx *middlewares.AutheliaCtx) {
var (
userSession session.UserSession
origin *url.URL
err error
)
if userSession, err = ctx.GetSession(); err != nil {
ctx.Logger.WithError(err).Error("Error occurred retrieving user session")
ctx.ReplyForbidden()
return
}
if origin, err = ctx.GetOrigin(); err != nil {
ctx.Logger.WithError(err).Error("Error occurred retrieving origin")
ctx.ReplyForbidden()
return
}
devices, err := ctx.Providers.StorageProvider.LoadWebAuthnDevicesByUsername(ctx, origin.Hostname(), userSession.Username)
if err != nil && err != storage.ErrNoWebAuthnDevice {
ctx.Error(err, messageOperationFailed)
return
}
if err = ctx.SetJSONBody(devices); err != nil {
ctx.Error(err, messageOperationFailed)
return
}
}
// WebAuthnDevicePUT updates the description for a specific device for the current user.
func WebAuthnDevicePUT(ctx *middlewares.AutheliaCtx) {
var (
bodyJSON bodyEditWebAuthnDeviceRequest
id int
device *model.WebAuthnDevice
userSession session.UserSession
err error
)
if userSession, err = ctx.GetSession(); err != nil {
ctx.Logger.WithError(err).Error("Error occurred retrieving user session")
ctx.ReplyForbidden()
return
}
if err = json.Unmarshal(ctx.PostBody(), &bodyJSON); err != nil {
ctx.Logger.Errorf("Unable to parse %s update request data for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
ctx.SetStatusCode(fasthttp.StatusBadRequest)
ctx.Error(err, messageOperationFailed)
return
}
if id, err = getWebAuthnDeviceIDFromContext(ctx); err != nil {
return
}
if device, err = ctx.Providers.StorageProvider.LoadWebAuthnDeviceByID(ctx, id); err != nil {
ctx.Error(err, messageOperationFailed)
return
}
if device.Username != userSession.Username {
ctx.Error(fmt.Errorf("user '%s' tried to delete device with id '%d' which belongs to '%s", userSession.Username, device.ID, device.Username), messageOperationFailed)
return
}
if err = ctx.Providers.StorageProvider.UpdateWebAuthnDeviceDescription(ctx, userSession.Username, id, bodyJSON.Description); err != nil {
ctx.Error(err, messageOperationFailed)
return
}
}
// WebAuthnDeviceDELETE deletes a specific device for the current user.
func WebAuthnDeviceDELETE(ctx *middlewares.AutheliaCtx) {
var (
id int
device *model.WebAuthnDevice
userSession session.UserSession
err error
)
if id, err = getWebAuthnDeviceIDFromContext(ctx); err != nil {
return
}
if device, err = ctx.Providers.StorageProvider.LoadWebAuthnDeviceByID(ctx, id); err != nil {
ctx.Error(err, messageOperationFailed)
return
}
if userSession, err = ctx.GetSession(); err != nil {
ctx.Logger.WithError(err).Error("Error occurred retrieving user session")
ctx.ReplyForbidden()
return
}
if device.Username != userSession.Username {
ctx.Error(fmt.Errorf("user '%s' tried to delete device with id '%d' which belongs to '%s", userSession.Username, device.ID, device.Username), messageOperationFailed)
return
}
if err = ctx.Providers.StorageProvider.DeleteWebAuthnDevice(ctx, device.KID.String()); err != nil {
ctx.Error(err, messageOperationFailed)
return
}
ctx.ReplyOK()
}

View File

@ -277,18 +277,13 @@ func markAuthenticationAttempt(ctx *middlewares.AutheliaCtx, successful bool, ba
if successful { if successful {
ctx.Logger.Debugf("Successful %s authentication attempt made by user '%s'", authType, username) ctx.Logger.Debugf("Successful %s authentication attempt made by user '%s'", authType, username)
} else { } else {
reasonPhrase := "by user '" + username + "'"
if ctx.Configuration.Server.UseIPInsteadOfUserForBan {
reasonPhrase = fmt.Sprintf("by ip %q (user %q)", ctx.RemoteIP().String(), username)
}
switch { switch {
case errAuth != nil: case errAuth != nil:
ctx.Logger.Errorf("Unsuccessful %s authentication attempt %s: %+v", authType, reasonPhrase, errAuth) ctx.Logger.Errorf("Unsuccessful %s authentication attempt by user '%s': %+v", authType, username, errAuth)
case bannedUntil != nil: case bannedUntil != nil:
ctx.Logger.Errorf("Unsuccessful %s authentication attempt %s and they are banned until %s", authType, reasonPhrase, bannedUntil) ctx.Logger.Errorf("Unsuccessful %s authentication attempt by user '%s' and they are banned until %s", authType, username, bannedUntil)
default: default:
ctx.Logger.Errorf("Unsuccessful %s authentication attempt %s", authType, reasonPhrase) ctx.Logger.Errorf("Unsuccessful %s authentication attempt by user '%s'", authType, username)
} }
} }

View File

@ -1,6 +1,7 @@
package handlers package handlers
import ( import (
"encoding/json"
"net/http" "net/http"
"net/url" "net/url"
@ -35,6 +36,16 @@ type bodySignWebAuthnRequest struct {
TargetURL string `json:"targetURL"` TargetURL string `json:"targetURL"`
Workflow string `json:"workflow"` Workflow string `json:"workflow"`
WorkflowID string `json:"workflowID"` WorkflowID string `json:"workflowID"`
Response json.RawMessage `json:"response"`
}
type bodyRegisterWebAuthnPUTRequest struct {
Description string `json:"description"`
}
type bodyEditWebAuthnDeviceRequest struct {
Description string `json:"description"`
} }
// bodySignDuoRequest is the model of the request body of Duo 2FA authentication endpoint. // bodySignDuoRequest is the model of the request body of Duo 2FA authentication endpoint.

View File

@ -1,28 +1,42 @@
package handlers package handlers
import ( import (
"fmt"
"net/url" "net/url"
"strings"
"github.com/go-webauthn/webauthn/protocol" "github.com/go-webauthn/webauthn/protocol"
"github.com/go-webauthn/webauthn/webauthn" "github.com/go-webauthn/webauthn/webauthn"
"github.com/authelia/authelia/v4/internal/middlewares" "github.com/authelia/authelia/v4/internal/middlewares"
"github.com/authelia/authelia/v4/internal/model" "github.com/authelia/authelia/v4/internal/model"
"github.com/authelia/authelia/v4/internal/session" "github.com/authelia/authelia/v4/internal/random"
) )
func getWebAuthnUser(ctx *middlewares.AutheliaCtx, userSession session.UserSession) (user *model.WebAuthnUser, err error) { func getWebAuthnUserByRPID(ctx *middlewares.AutheliaCtx, username, displayname string, rpid string) (user *model.WebAuthnUser, err error) {
user = &model.WebAuthnUser{ if user, err = ctx.Providers.StorageProvider.LoadWebAuthnUser(ctx, rpid, username); err != nil {
Username: userSession.Username, return nil, err
DisplayName: userSession.DisplayName, }
if user == nil {
user = &model.WebAuthnUser{
RPID: rpid,
Username: username,
UserID: ctx.Providers.Random.StringCustom(64, random.CharSetASCII),
DisplayName: displayname,
}
if err = ctx.Providers.StorageProvider.SaveWebAuthnUser(ctx, *user); err != nil {
return nil, err
}
} else {
user.DisplayName = displayname
} }
if user.DisplayName == "" { if user.DisplayName == "" {
user.DisplayName = user.Username user.DisplayName = user.Username
} }
if user.Devices, err = ctx.Providers.StorageProvider.LoadWebAuthnDevicesByUsername(ctx, userSession.Username); err != nil { if user.Devices, err = ctx.Providers.StorageProvider.LoadWebAuthnDevicesByUsername(ctx, rpid, user.Username); err != nil {
return nil, err return nil, err
} }
@ -31,33 +45,72 @@ func getWebAuthnUser(ctx *middlewares.AutheliaCtx, userSession session.UserSessi
func newWebAuthn(ctx *middlewares.AutheliaCtx) (w *webauthn.WebAuthn, err error) { func newWebAuthn(ctx *middlewares.AutheliaCtx) (w *webauthn.WebAuthn, err error) {
var ( var (
u *url.URL origin *url.URL
) )
if u, err = ctx.GetXOriginalURLOrXForwardedURL(); err != nil { if origin, err = ctx.GetOrigin(); err != nil {
return nil, err return nil, err
} }
rpID := u.Hostname()
origin := fmt.Sprintf("%s://%s", u.Scheme, u.Host)
config := &webauthn.Config{ config := &webauthn.Config{
RPDisplayName: ctx.Configuration.WebAuthn.DisplayName, RPID: origin.Hostname(),
RPID: rpID, RPDisplayName: ctx.Configuration.WebAuthn.DisplayName,
RPOrigin: origin, RPOrigins: []string{origin.String()},
RPIcon: "",
AttestationPreference: ctx.Configuration.WebAuthn.ConveyancePreference, AttestationPreference: ctx.Configuration.WebAuthn.ConveyancePreference,
AuthenticatorSelection: protocol.AuthenticatorSelection{ AuthenticatorSelection: protocol.AuthenticatorSelection{
AuthenticatorAttachment: protocol.CrossPlatform, AuthenticatorAttachment: protocol.CrossPlatform,
UserVerification: ctx.Configuration.WebAuthn.UserVerification,
RequireResidentKey: protocol.ResidentKeyNotRequired(), RequireResidentKey: protocol.ResidentKeyNotRequired(),
ResidentKey: protocol.ResidentKeyRequirementDiscouraged,
UserVerification: ctx.Configuration.WebAuthn.UserVerification,
},
Debug: false,
EncodeUserIDAsString: true,
Timeouts: webauthn.TimeoutsConfig{
Login: webauthn.TimeoutConfig{
Enforce: true,
Timeout: ctx.Configuration.WebAuthn.Timeout,
TimeoutUVD: ctx.Configuration.WebAuthn.Timeout,
},
Registration: webauthn.TimeoutConfig{
Enforce: true,
Timeout: ctx.Configuration.WebAuthn.Timeout,
TimeoutUVD: ctx.Configuration.WebAuthn.Timeout,
},
}, },
Timeout: int(ctx.Configuration.WebAuthn.Timeout.Milliseconds()),
} }
ctx.Logger.Tracef("Creating new WebAuthn RP instance with ID %s and Origins %s", config.RPID, config.RPOrigin) ctx.Logger.Tracef("Creating new WebAuthn RP instance with ID %s and Origins %s", config.RPID, strings.Join(config.RPOrigins, ", "))
return webauthn.New(config) return webauthn.New(config)
} }
func webauthnCredentialCreationIsDiscoverable(ctx *middlewares.AutheliaCtx, response *protocol.ParsedCredentialCreationData) (discoverable bool) {
if value, ok := response.ClientExtensionResults["credProps"]; ok {
switch credentialProperties := value.(type) {
case map[string]any:
var v any
if v, ok = credentialProperties["rk"]; ok {
if discoverable, ok = v.(bool); ok {
ctx.Logger.WithFields(map[string]any{"discoverable": discoverable}).Trace("Determined Credential Discoverability via Client Extension Results")
return discoverable
} else {
ctx.Logger.WithFields(map[string]any{"discoverable": false}).Trace("Assuming Credential Discoverability is false as the 'rk' field for the 'credProps' extension in the Client Extension Results was not a boolean")
}
} else {
ctx.Logger.WithFields(map[string]any{"discoverable": false}).Trace("Assuming Credential Discoverability is false as the 'rk' field for the 'credProps' extension was missing from the Client Extension Results")
}
return false
default:
ctx.Logger.WithFields(map[string]any{"discoverable": false}).Trace("Assuming Credential Discoverability is false as the 'credProps' extension in the Client Extension Results does not appear to be a dictionary")
return false
}
}
ctx.Logger.WithFields(map[string]any{"discoverable": false}).Trace("Assuming Credential Discoverability is false as the 'credProps' extension is missing from the Client Extension Results")
return false
}

View File

@ -5,12 +5,14 @@ import (
"testing" "testing"
"github.com/go-webauthn/webauthn/protocol" "github.com/go-webauthn/webauthn/protocol"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/valyala/fasthttp" "github.com/valyala/fasthttp"
"github.com/authelia/authelia/v4/internal/mocks" "github.com/authelia/authelia/v4/internal/mocks"
"github.com/authelia/authelia/v4/internal/model" "github.com/authelia/authelia/v4/internal/model"
"github.com/authelia/authelia/v4/internal/random"
"github.com/authelia/authelia/v4/internal/session" "github.com/authelia/authelia/v4/internal/session"
) )
@ -22,10 +24,14 @@ func TestWebAuthnGetUser(t *testing.T) {
DisplayName: "John Smith", DisplayName: "John Smith",
} }
ctx.StorageMock.EXPECT().LoadWebAuthnDevicesByUsername(ctx.Ctx, "john").Return([]model.WebAuthnDevice{ ctx.StorageMock.EXPECT().
LoadWebAuthnUser(ctx.Ctx, "example.com", "john").
Return(&model.WebAuthnUser{ID: 1, RPID: "example.com", Username: "john", UserID: "john123"}, nil)
ctx.StorageMock.EXPECT().LoadWebAuthnDevicesByUsername(ctx.Ctx, "example.com", "john").Return([]model.WebAuthnDevice{
{ {
ID: 1, ID: 1,
RPID: "https://example.com", RPID: "example.com",
Username: "john", Username: "john",
Description: "Primary", Description: "Primary",
KID: model.NewBase64([]byte("abc123")), KID: model.NewBase64([]byte("abc123")),
@ -48,12 +54,12 @@ func TestWebAuthnGetUser(t *testing.T) {
}, },
}, nil) }, nil)
user, err := getWebAuthnUser(ctx.Ctx, userSession) user, err := getWebAuthnUserByRPID(ctx.Ctx, userSession.Username, userSession.DisplayName, "example.com")
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, user) require.NotNil(t, user)
assert.Equal(t, []byte{}, user.WebAuthnID()) assert.Equal(t, []byte("john123"), user.WebAuthnID())
assert.Equal(t, "john", user.WebAuthnName()) assert.Equal(t, "john", user.WebAuthnName())
assert.Equal(t, "john", user.Username) assert.Equal(t, "john", user.Username)
@ -65,7 +71,107 @@ func TestWebAuthnGetUser(t *testing.T) {
require.Len(t, user.Devices, 2) require.Len(t, user.Devices, 2)
assert.Equal(t, 1, user.Devices[0].ID) assert.Equal(t, 1, user.Devices[0].ID)
assert.Equal(t, "https://example.com", user.Devices[0].RPID) assert.Equal(t, "example.com", user.Devices[0].RPID)
assert.Equal(t, "john", user.Devices[0].Username)
assert.Equal(t, "Primary", user.Devices[0].Description)
assert.Equal(t, "", user.Devices[0].Transport)
assert.Equal(t, "fido-u2f", user.Devices[0].AttestationType)
assert.Equal(t, []byte("data"), user.Devices[0].PublicKey)
assert.Equal(t, uint32(0), user.Devices[0].SignCount)
assert.False(t, user.Devices[0].CloneWarning)
descriptors := user.WebAuthnCredentialDescriptors()
assert.Equal(t, "fido-u2f", descriptors[0].AttestationType)
assert.Equal(t, "abc123", string(descriptors[0].CredentialID))
assert.Equal(t, protocol.PublicKeyCredentialType, descriptors[0].Type)
assert.Len(t, descriptors[0].Transport, 0)
assert.Equal(t, 2, user.Devices[1].ID)
assert.Equal(t, "example.com", user.Devices[1].RPID)
assert.Equal(t, "john", user.Devices[1].Username)
assert.Equal(t, "Secondary", user.Devices[1].Description)
assert.Equal(t, "usb,nfc", user.Devices[1].Transport)
assert.Equal(t, "packed", user.Devices[1].AttestationType)
assert.Equal(t, []byte("data"), user.Devices[1].PublicKey)
assert.Equal(t, uint32(100), user.Devices[1].SignCount)
assert.False(t, user.Devices[1].CloneWarning)
assert.Equal(t, "packed", descriptors[1].AttestationType)
assert.Equal(t, "123abc", string(descriptors[1].CredentialID))
assert.Equal(t, protocol.PublicKeyCredentialType, descriptors[1].Type)
assert.Len(t, descriptors[1].Transport, 2)
assert.Equal(t, protocol.AuthenticatorTransport("usb"), descriptors[1].Transport[0])
assert.Equal(t, protocol.AuthenticatorTransport("nfc"), descriptors[1].Transport[1])
}
func TestWebAuthnGetNewUser(t *testing.T) {
ctx := mocks.NewMockAutheliaCtx(t)
// Use the random mock.
ctx.Ctx.Providers.Random = ctx.RandomMock
userSession := session.UserSession{
Username: "john",
DisplayName: "John Smith",
}
gomock.InOrder(
ctx.StorageMock.EXPECT().
LoadWebAuthnUser(ctx.Ctx, "example.com", "john").
Return(nil, nil),
ctx.RandomMock.EXPECT().
StringCustom(64, random.CharSetASCII).
Return("=ckBRe.%fp{w#K[qw4)AWMZrAP)(z3NUt5n3g?;>'^Rp>+eE4z>[^.<3?&n;LM#w"),
ctx.StorageMock.EXPECT().
SaveWebAuthnUser(ctx.Ctx, model.WebAuthnUser{RPID: "example.com", Username: "john", DisplayName: "John Smith", UserID: "=ckBRe.%fp{w#K[qw4)AWMZrAP)(z3NUt5n3g?;>'^Rp>+eE4z>[^.<3?&n;LM#w"}).
Return(nil),
ctx.StorageMock.EXPECT().LoadWebAuthnDevicesByUsername(ctx.Ctx, "example.com", "john").Return([]model.WebAuthnDevice{
{
ID: 1,
RPID: "example.com",
Username: "john",
Description: "Primary",
KID: model.NewBase64([]byte("abc123")),
AttestationType: "fido-u2f",
PublicKey: []byte("data"),
SignCount: 0,
CloneWarning: false,
},
{
ID: 2,
RPID: "example.com",
Username: "john",
Description: "Secondary",
KID: model.NewBase64([]byte("123abc")),
AttestationType: "packed",
Transport: "usb,nfc",
PublicKey: []byte("data"),
SignCount: 100,
CloneWarning: false,
},
}, nil),
)
user, err := getWebAuthnUserByRPID(ctx.Ctx, userSession.Username, userSession.DisplayName, "example.com")
require.NoError(t, err)
require.NotNil(t, user)
assert.Equal(t, []byte("=ckBRe.%fp{w#K[qw4)AWMZrAP)(z3NUt5n3g?;>'^Rp>+eE4z>[^.<3?&n;LM#w"), user.WebAuthnID())
assert.Equal(t, "john", user.WebAuthnName())
assert.Equal(t, "john", user.Username)
assert.Equal(t, "", user.WebAuthnIcon())
assert.Equal(t, "John Smith", user.WebAuthnDisplayName())
assert.Equal(t, "John Smith", user.DisplayName)
require.Len(t, user.Devices, 2)
assert.Equal(t, 1, user.Devices[0].ID)
assert.Equal(t, "example.com", user.Devices[0].RPID)
assert.Equal(t, "john", user.Devices[0].Username) assert.Equal(t, "john", user.Devices[0].Username)
assert.Equal(t, "Primary", user.Devices[0].Description) assert.Equal(t, "Primary", user.Devices[0].Description)
assert.Equal(t, "", user.Devices[0].Transport) assert.Equal(t, "", user.Devices[0].Transport)
@ -107,7 +213,11 @@ func TestWebAuthnGetUserWithoutDisplayName(t *testing.T) {
Username: "john", Username: "john",
} }
ctx.StorageMock.EXPECT().LoadWebAuthnDevicesByUsername(ctx.Ctx, "john").Return([]model.WebAuthnDevice{ ctx.StorageMock.EXPECT().
LoadWebAuthnUser(ctx.Ctx, "example.com", "john").
Return(&model.WebAuthnUser{ID: 1, RPID: "example.com", Username: "john", UserID: "john123"}, nil)
ctx.StorageMock.EXPECT().LoadWebAuthnDevicesByUsername(ctx.Ctx, "example.com", "john").Return([]model.WebAuthnDevice{
{ {
ID: 1, ID: 1,
RPID: "example.com", RPID: "example.com",
@ -121,7 +231,7 @@ func TestWebAuthnGetUserWithoutDisplayName(t *testing.T) {
}, },
}, nil) }, nil)
user, err := getWebAuthnUser(ctx.Ctx, userSession) user, err := getWebAuthnUserByRPID(ctx.Ctx, userSession.Username, userSession.DisplayName, "example.com")
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, user) require.NotNil(t, user)
@ -137,9 +247,15 @@ func TestWebAuthnGetUserWithErr(t *testing.T) {
Username: "john", Username: "john",
} }
ctx.StorageMock.EXPECT().LoadWebAuthnDevicesByUsername(ctx.Ctx, "john").Return(nil, errors.New("not found")) ctx.StorageMock.EXPECT().
LoadWebAuthnUser(ctx.Ctx, "example.com", "john").
Return(&model.WebAuthnUser{ID: 1, RPID: "example.com", Username: "john", UserID: "john123"}, nil)
user, err := getWebAuthnUser(ctx.Ctx, userSession) ctx.StorageMock.EXPECT().
LoadWebAuthnDevicesByUsername(ctx.Ctx, "example.com", "john").
Return(nil, errors.New("not found"))
user, err := getWebAuthnUserByRPID(ctx.Ctx, userSession.Username, userSession.DisplayName, "example.com")
assert.EqualError(t, err, "not found") assert.EqualError(t, err, "not found")
assert.Nil(t, user) assert.Nil(t, user)
@ -165,5 +281,5 @@ func TestWebAuthnNewWebAuthnShouldReturnErrWhenWebAuthnNotConfigured(t *testing.
w, err := newWebAuthn(ctx.Ctx) w, err := newWebAuthn(ctx.Ctx)
assert.Nil(t, w) assert.Nil(t, w)
assert.EqualError(t, err, "Configuration error: Missing RPDisplayName") assert.EqualError(t, err, "error occurred validating the configuration: the field 'RPDisplayName' must be configured but it is empty")
} }

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