From edda5e63201c19d360886f5601e8cea7de98fdf4 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 13 Apr 2023 01:46:54 +1000 Subject: [PATCH 01/15] build(deps): update dependency @types/react to v18.0.35 (#5223) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- web/package.json | 2 +- web/pnpm-lock.yaml | 94 +++++++++++++++++++++++----------------------- 2 files changed, 48 insertions(+), 48 deletions(-) diff --git a/web/package.json b/web/package.json index 42a3cfa0f..5466dbcea 100644 --- a/web/package.json +++ b/web/package.json @@ -73,7 +73,7 @@ "@testing-library/react": "14.0.0", "@types/node": "18.15.11", "@types/qrcode.react": "1.0.2", - "@types/react": "18.0.34", + "@types/react": "18.0.35", "@types/react-dom": "18.0.11", "@types/testing-library__jest-dom": "5.14.5", "@types/zxcvbn": "4.4.1", diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index f80e1f523..f39bb9673 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -6,10 +6,10 @@ dependencies: version: 11.10.7 '@emotion/react': specifier: 11.10.6 - version: 11.10.6(@types/react@18.0.34)(react@18.2.0) + version: 11.10.6(@types/react@18.0.35)(react@18.2.0) '@emotion/styled': specifier: 11.10.6 - version: 11.10.6(@emotion/react@11.10.6)(@types/react@18.0.34)(react@18.2.0) + version: 11.10.6(@emotion/react@11.10.6)(@types/react@18.0.35)(react@18.2.0) '@fortawesome/fontawesome-svg-core': specifier: 6.4.0 version: 6.4.0 @@ -24,13 +24,13 @@ dependencies: version: 0.2.0(@fortawesome/fontawesome-svg-core@6.4.0)(react@18.2.0) '@mui/icons-material': specifier: 5.11.16 - version: 5.11.16(@mui/material@5.12.0)(@types/react@18.0.34)(react@18.2.0) + version: 5.11.16(@mui/material@5.12.0)(@types/react@18.0.35)(react@18.2.0) '@mui/material': specifier: 5.12.0 - version: 5.12.0(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(@types/react@18.0.34)(react-dom@18.2.0)(react@18.2.0) + version: 5.12.0(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(@types/react@18.0.35)(react-dom@18.2.0)(react@18.2.0) '@mui/styles': specifier: 5.12.0 - version: 5.12.0(@types/react@18.0.34)(react@18.2.0) + version: 5.12.0(@types/react@18.0.35)(react@18.2.0) axios: specifier: 1.3.5 version: 1.3.5 @@ -97,8 +97,8 @@ devDependencies: specifier: 1.0.2 version: 1.0.2 '@types/react': - specifier: 18.0.34 - version: 18.0.34 + specifier: 18.0.35 + version: 18.0.35 '@types/react-dom': specifier: 18.0.11 version: 18.0.11 @@ -1767,7 +1767,7 @@ packages: resolution: {integrity: sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA==} dev: false - /@emotion/react@11.10.6(@types/react@18.0.34)(react@18.2.0): + /@emotion/react@11.10.6(@types/react@18.0.35)(react@18.2.0): resolution: {integrity: sha512-6HT8jBmcSkfzO7mc+N1L9uwvOnlcGoix8Zn7srt+9ga0MjREo6lRpuVX0kzo6Jp6oTqDhREOFsygN6Ew4fEQbw==} peerDependencies: '@types/react': '*' @@ -1783,7 +1783,7 @@ packages: '@emotion/use-insertion-effect-with-fallbacks': 1.0.0(react@18.2.0) '@emotion/utils': 1.2.0 '@emotion/weak-memoize': 0.3.0 - '@types/react': 18.0.34 + '@types/react': 18.0.35 hoist-non-react-statics: 3.3.2 react: 18.2.0 dev: false @@ -1802,7 +1802,7 @@ packages: resolution: {integrity: sha512-zxRBwl93sHMsOj4zs+OslQKg/uhF38MB+OMKoCrVuS0nyTkqnau+BM3WGEoOptg9Oz45T/aIGs1qbVAsEFo3nA==} dev: false - /@emotion/styled@11.10.6(@emotion/react@11.10.6)(@types/react@18.0.34)(react@18.2.0): + /@emotion/styled@11.10.6(@emotion/react@11.10.6)(@types/react@18.0.35)(react@18.2.0): resolution: {integrity: sha512-OXtBzOmDSJo5Q0AFemHCfl+bUueT8BIcPSxu0EGTpGk6DmI5dnhSzQANm1e1ze0YZL7TDyAyy6s/b/zmGOS3Og==} peerDependencies: '@emotion/react': ^11.0.0-rc.0 @@ -1815,11 +1815,11 @@ packages: '@babel/runtime': 7.21.0 '@emotion/babel-plugin': 11.10.6 '@emotion/is-prop-valid': 1.2.0 - '@emotion/react': 11.10.6(@types/react@18.0.34)(react@18.2.0) + '@emotion/react': 11.10.6(@types/react@18.0.35)(react@18.2.0) '@emotion/serialize': 1.1.1 '@emotion/use-insertion-effect-with-fallbacks': 1.0.0(react@18.2.0) '@emotion/utils': 1.2.0 - '@types/react': 18.0.34 + '@types/react': 18.0.35 react: 18.2.0 dev: false @@ -2258,7 +2258,7 @@ packages: tsconfig-paths: 3.14.2 dev: true - /@mui/base@5.0.0-alpha.125(@types/react@18.0.34)(react-dom@18.2.0)(react@18.2.0): + /@mui/base@5.0.0-alpha.125(@types/react@18.0.35)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-hAHJJ97SATu6SrkLH/HsAayK1zMZt89lrWyKuAInBKVyn363H78d1MnwyZwre9vDK5MrPoDL/NnZxtAXhwTnBA==} engines: {node: '>=12.0.0'} peerDependencies: @@ -2271,10 +2271,10 @@ packages: dependencies: '@babel/runtime': 7.21.0 '@emotion/is-prop-valid': 1.2.0 - '@mui/types': 7.2.4(@types/react@18.0.34) + '@mui/types': 7.2.4(@types/react@18.0.35) '@mui/utils': 5.12.0(react@18.2.0) '@popperjs/core': 2.11.7 - '@types/react': 18.0.34 + '@types/react': 18.0.35 clsx: 1.2.1 prop-types: 15.8.1 react: 18.2.0 @@ -2286,7 +2286,7 @@ packages: resolution: {integrity: sha512-1hoFIdlLI0sG+mkJgm70FjgIVpfLcE1vxPtNolg1tLFXrvbXGUYp9NHy3d6c41nDkg2OajuVS+Mn6A8UirFuMw==} dev: false - /@mui/icons-material@5.11.16(@mui/material@5.12.0)(@types/react@18.0.34)(react@18.2.0): + /@mui/icons-material@5.11.16(@mui/material@5.12.0)(@types/react@18.0.35)(react@18.2.0): resolution: {integrity: sha512-oKkx9z9Kwg40NtcIajF9uOXhxiyTZrrm9nmIJ4UjkU2IdHpd4QVLbCc/5hZN/y0C6qzi2Zlxyr9TGddQx2vx2A==} engines: {node: '>=12.0.0'} peerDependencies: @@ -2298,12 +2298,12 @@ packages: optional: true dependencies: '@babel/runtime': 7.21.0 - '@mui/material': 5.12.0(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(@types/react@18.0.34)(react-dom@18.2.0)(react@18.2.0) - '@types/react': 18.0.34 + '@mui/material': 5.12.0(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(@types/react@18.0.35)(react-dom@18.2.0)(react@18.2.0) + '@types/react': 18.0.35 react: 18.2.0 dev: false - /@mui/material@5.12.0(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(@types/react@18.0.34)(react-dom@18.2.0)(react@18.2.0): + /@mui/material@5.12.0(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(@types/react@18.0.35)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-IMellv153zJ6+xfhLWgXpAm/9hsX8qE6gP66xWcW/Pf2B8ubyVhmkTXsp8pAJxk81D6p/EyYcnAjo5DiDVkj9g==} engines: {node: '>=12.0.0'} peerDependencies: @@ -2321,14 +2321,14 @@ packages: optional: true dependencies: '@babel/runtime': 7.21.0 - '@emotion/react': 11.10.6(@types/react@18.0.34)(react@18.2.0) - '@emotion/styled': 11.10.6(@emotion/react@11.10.6)(@types/react@18.0.34)(react@18.2.0) - '@mui/base': 5.0.0-alpha.125(@types/react@18.0.34)(react-dom@18.2.0)(react@18.2.0) + '@emotion/react': 11.10.6(@types/react@18.0.35)(react@18.2.0) + '@emotion/styled': 11.10.6(@emotion/react@11.10.6)(@types/react@18.0.35)(react@18.2.0) + '@mui/base': 5.0.0-alpha.125(@types/react@18.0.35)(react-dom@18.2.0)(react@18.2.0) '@mui/core-downloads-tracker': 5.12.0 - '@mui/system': 5.12.0(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(@types/react@18.0.34)(react@18.2.0) - '@mui/types': 7.2.4(@types/react@18.0.34) + '@mui/system': 5.12.0(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(@types/react@18.0.35)(react@18.2.0) + '@mui/types': 7.2.4(@types/react@18.0.35) '@mui/utils': 5.12.0(react@18.2.0) - '@types/react': 18.0.34 + '@types/react': 18.0.35 '@types/react-transition-group': 4.4.5 clsx: 1.2.1 csstype: 3.1.2 @@ -2339,7 +2339,7 @@ packages: react-transition-group: 4.4.5(react-dom@18.2.0)(react@18.2.0) dev: false - /@mui/private-theming@5.12.0(@types/react@18.0.34)(react@18.2.0): + /@mui/private-theming@5.12.0(@types/react@18.0.35)(react@18.2.0): resolution: {integrity: sha512-w5dwMen1CUm1puAtubqxY9BIzrBxbOThsg2iWMvRJmWyJAPdf3Z583fPXpqeA2lhTW79uH2jajk5Ka4FuGlTPg==} engines: {node: '>=12.0.0'} peerDependencies: @@ -2351,7 +2351,7 @@ packages: dependencies: '@babel/runtime': 7.21.0 '@mui/utils': 5.12.0(react@18.2.0) - '@types/react': 18.0.34 + '@types/react': 18.0.35 prop-types: 15.8.1 react: 18.2.0 dev: false @@ -2371,14 +2371,14 @@ packages: dependencies: '@babel/runtime': 7.21.0 '@emotion/cache': 11.10.7 - '@emotion/react': 11.10.6(@types/react@18.0.34)(react@18.2.0) - '@emotion/styled': 11.10.6(@emotion/react@11.10.6)(@types/react@18.0.34)(react@18.2.0) + '@emotion/react': 11.10.6(@types/react@18.0.35)(react@18.2.0) + '@emotion/styled': 11.10.6(@emotion/react@11.10.6)(@types/react@18.0.35)(react@18.2.0) csstype: 3.1.2 prop-types: 15.8.1 react: 18.2.0 dev: false - /@mui/styles@5.12.0(@types/react@18.0.34)(react@18.2.0): + /@mui/styles@5.12.0(@types/react@18.0.35)(react@18.2.0): resolution: {integrity: sha512-X7obkgZTd9X+7igqwKKe8pEncyXYdUCNmyJfHruV9TSc6LThoI29OYs6hkN6n+7ueNli+YDKdZ+TCoC1GpJuOw==} engines: {node: '>=12.0.0'} peerDependencies: @@ -2390,10 +2390,10 @@ packages: dependencies: '@babel/runtime': 7.21.0 '@emotion/hash': 0.9.0 - '@mui/private-theming': 5.12.0(@types/react@18.0.34)(react@18.2.0) - '@mui/types': 7.2.4(@types/react@18.0.34) + '@mui/private-theming': 5.12.0(@types/react@18.0.35)(react@18.2.0) + '@mui/types': 7.2.4(@types/react@18.0.35) '@mui/utils': 5.12.0(react@18.2.0) - '@types/react': 18.0.34 + '@types/react': 18.0.35 clsx: 1.2.1 csstype: 3.1.2 hoist-non-react-statics: 3.3.2 @@ -2409,7 +2409,7 @@ packages: react: 18.2.0 dev: false - /@mui/system@5.12.0(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(@types/react@18.0.34)(react@18.2.0): + /@mui/system@5.12.0(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(@types/react@18.0.35)(react@18.2.0): resolution: {integrity: sha512-Zi+WHuiJfK1ya+9+oeJQ1rLIBdY8CGDYT5oVlQg/6kIuyiCaE6SnN9PVzxBxfY77wHuOPwz4kxcPe9srdZc12Q==} engines: {node: '>=12.0.0'} peerDependencies: @@ -2426,20 +2426,20 @@ packages: optional: true dependencies: '@babel/runtime': 7.21.0 - '@emotion/react': 11.10.6(@types/react@18.0.34)(react@18.2.0) - '@emotion/styled': 11.10.6(@emotion/react@11.10.6)(@types/react@18.0.34)(react@18.2.0) - '@mui/private-theming': 5.12.0(@types/react@18.0.34)(react@18.2.0) + '@emotion/react': 11.10.6(@types/react@18.0.35)(react@18.2.0) + '@emotion/styled': 11.10.6(@emotion/react@11.10.6)(@types/react@18.0.35)(react@18.2.0) + '@mui/private-theming': 5.12.0(@types/react@18.0.35)(react@18.2.0) '@mui/styled-engine': 5.12.0(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(react@18.2.0) - '@mui/types': 7.2.4(@types/react@18.0.34) + '@mui/types': 7.2.4(@types/react@18.0.35) '@mui/utils': 5.12.0(react@18.2.0) - '@types/react': 18.0.34 + '@types/react': 18.0.35 clsx: 1.2.1 csstype: 3.1.2 prop-types: 15.8.1 react: 18.2.0 dev: false - /@mui/types@7.2.4(@types/react@18.0.34): + /@mui/types@7.2.4(@types/react@18.0.35): resolution: {integrity: sha512-LBcwa8rN84bKF+f5sDyku42w1NTxaPgPyYKODsh01U1fVstTClbUoSA96oyRBnSNyEiAVjKm6Gwx9vjR+xyqHA==} peerDependencies: '@types/react': '*' @@ -2447,7 +2447,7 @@ packages: '@types/react': optional: true dependencies: - '@types/react': 18.0.34 + '@types/react': 18.0.35 dev: false /@mui/utils@5.12.0(react@18.2.0): @@ -2837,7 +2837,7 @@ packages: /@types/qrcode.react@1.0.2: resolution: {integrity: sha512-I9Oq5Cjlkgy3Tw7krCnCXLw2/zMhizkTere49OOcta23tkvH0xBTP0yInimTh0gstLRtb8Ki9NZVujE5UI6ffQ==} dependencies: - '@types/react': 18.0.34 + '@types/react': 18.0.35 dev: true /@types/qs@6.9.7: @@ -2851,23 +2851,23 @@ packages: /@types/react-dom@18.0.11: resolution: {integrity: sha512-O38bPbI2CWtgw/OoQoY+BRelw7uysmXbWvw3nLWO21H1HSh+GOlqPuXshJfjmpNlKiiSDG9cc1JZAaMmVdcTlw==} dependencies: - '@types/react': 18.0.34 + '@types/react': 18.0.35 dev: true /@types/react-is@17.0.3: resolution: {integrity: sha512-aBTIWg1emtu95bLTLx0cpkxwGW3ueZv71nE2YFBpL8k/z5czEW8yYpOo8Dp+UUAFAtKwNaOsh/ioSeQnWlZcfw==} dependencies: - '@types/react': 18.0.34 + '@types/react': 18.0.35 dev: false /@types/react-transition-group@4.4.5: resolution: {integrity: sha512-juKD/eiSM3/xZYzjuzH6ZwpP+/lejltmiS3QEzV/vmb/Q8+HfDmxu+Baga8UEMGBqV88Nbg4l2hY/K2DkyaLLA==} dependencies: - '@types/react': 18.0.34 + '@types/react': 18.0.35 dev: false - /@types/react@18.0.34: - resolution: {integrity: sha512-NO1UO8941541CJl1BeOXi8a9dNKFK09Gnru5ZJqkm4Q3/WoQJtHvmwt0VX0SB9YCEwe7TfSSxDuaNmx6H2BAIQ==} + /@types/react@18.0.35: + resolution: {integrity: sha512-6Laome31HpetaIUGFWl1VQ3mdSImwxtFZ39rh059a1MNnKGqBpC88J6NJ8n/Is3Qx7CefDGLgf/KhN/sYCf7ag==} dependencies: '@types/prop-types': 15.7.5 '@types/scheduler': 0.16.3 From 4232f1b997377d6ddaef62c6509c26f3ee81c4c6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 13 Apr 2023 01:58:27 +1000 Subject: [PATCH 02/15] build(deps): update module github.com/ory/herodot to v0.10.2 (#5224) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index ded9a0169..7751c737c 100644 --- a/go.mod +++ b/go.mod @@ -32,7 +32,7 @@ require ( github.com/mitchellh/mapstructure v1.5.0 github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 github.com/ory/fosite v0.44.0 - github.com/ory/herodot v0.10.1 + github.com/ory/herodot v0.10.2 github.com/ory/x v0.0.550 github.com/otiai10/copy v1.10.0 github.com/pkg/errors v0.9.1 diff --git a/go.sum b/go.sum index 388bbdfc1..2a8f1c7a9 100644 --- a/go.sum +++ b/go.sum @@ -349,8 +349,8 @@ github.com/ory/go-acc v0.2.9-0.20230103102148-6b1c9a70dbbe h1:rvu4obdvqR0fkSIJ8I github.com/ory/go-acc v0.2.9-0.20230103102148-6b1c9a70dbbe/go.mod h1:z4n3u6as84LbV4YmgjHhnwtccQqzf4cZlSk9f1FhygI= github.com/ory/go-convenience v0.1.0 h1:zouLKfF2GoSGnJwGq+PE/nJAE6dj2Zj5QlTgmMTsTS8= github.com/ory/go-convenience v0.1.0/go.mod h1:uEY/a60PL5c12nYz4V5cHY03IBmwIAEm8TWB0yn9KNs= -github.com/ory/herodot v0.10.1 h1:espcYXC2VJrgYXAhKCOX+3pvD9eD7cT2Ceqad9yWEXc= -github.com/ory/herodot v0.10.1/go.mod h1:MMNmY6MG1uB6fnXYFaHoqdV23DTWctlPsmRCeq/2+wc= +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/x v0.0.550 h1:bcaOdUW/jHByoT8U6cfraBpRCtNtUB8lNBD97LTrB+Y= github.com/ory/x v0.0.550/go.mod h1:oRVemI3SQQOLvOCJWIRinHQKlgmay/NbwSyRUIsS/Yk= github.com/otiai10/copy v1.10.0 h1:znyI7l134wNg/wDktoVQPxPkgvhDfGCYUasey+h0rDQ= From 538f8530c554829cca09d45856909b189babf5d9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 13 Apr 2023 07:22:38 +1000 Subject: [PATCH 03/15] build(deps): update module github.com/ory/x to v0.0.551 (#5226) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 7751c737c..54a357baa 100644 --- a/go.mod +++ b/go.mod @@ -33,7 +33,7 @@ require ( github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 github.com/ory/fosite v0.44.0 github.com/ory/herodot v0.10.2 - github.com/ory/x v0.0.550 + github.com/ory/x v0.0.551 github.com/otiai10/copy v1.10.0 github.com/pkg/errors v0.9.1 github.com/pquerna/otp v1.4.0 diff --git a/go.sum b/go.sum index 2a8f1c7a9..4636cd17c 100644 --- a/go.sum +++ b/go.sum @@ -351,8 +351,8 @@ 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/herodot v0.10.2 h1:gGvNMHgAwWzdP/eo+roSiT5CGssygHSjDU7MSQNlJ4E= github.com/ory/herodot v0.10.2/go.mod h1:MMNmY6MG1uB6fnXYFaHoqdV23DTWctlPsmRCeq/2+wc= -github.com/ory/x v0.0.550 h1:bcaOdUW/jHByoT8U6cfraBpRCtNtUB8lNBD97LTrB+Y= -github.com/ory/x v0.0.550/go.mod h1:oRVemI3SQQOLvOCJWIRinHQKlgmay/NbwSyRUIsS/Yk= +github.com/ory/x v0.0.551 h1:U3z2bvSzAwDP0SWmbAdjzfvWPu4k+oWrPctoCdalGk0= +github.com/ory/x v0.0.551/go.mod h1:oRVemI3SQQOLvOCJWIRinHQKlgmay/NbwSyRUIsS/Yk= github.com/otiai10/copy v1.10.0 h1:znyI7l134wNg/wDktoVQPxPkgvhDfGCYUasey+h0rDQ= github.com/otiai10/copy v1.10.0/go.mod h1:rSaLseMUsZFFbsFGc7wCJnnkTAvdc5L6VWxPE4308Ww= github.com/otiai10/mint v1.5.1 h1:XaPLeE+9vGbuyEHem1JNk3bYc7KKqyI/na0/mLd/Kks= From 9d8c49620691a14f232cfdb2b8d856b42341b0c6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 13 Apr 2023 11:12:19 +1000 Subject: [PATCH 04/15] build(deps): update dependency vite-tsconfig-paths to v4.2.0 (#5228) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- web/package.json | 2 +- web/pnpm-lock.yaml | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/web/package.json b/web/package.json index 5466dbcea..9e784d7da 100644 --- a/web/package.json +++ b/web/package.json @@ -101,7 +101,7 @@ "vite-plugin-eslint": "1.8.1", "vite-plugin-istanbul": "4.0.1", "vite-plugin-svgr": "2.4.0", - "vite-tsconfig-paths": "4.1.0", + "vite-tsconfig-paths": "4.2.0", "vitest": "0.30.1", "vitest-preview": "0.0.1" } diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index f39bb9673..143b3940a 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -181,8 +181,8 @@ devDependencies: specifier: 2.4.0 version: 2.4.0(vite@4.2.1) vite-tsconfig-paths: - specifier: 4.1.0 - version: 4.1.0(typescript@5.0.4)(vite@4.2.1) + specifier: 4.2.0 + version: 4.2.0(typescript@5.0.4)(vite@4.2.1) vitest: specifier: 0.30.1 version: 0.30.1(happy-dom@9.2.1) @@ -7416,8 +7416,8 @@ packages: - supports-color dev: true - /vite-tsconfig-paths@4.1.0(typescript@5.0.4)(vite@4.2.1): - resolution: {integrity: sha512-Ps275He1fF6Wpm/3tvyokIfXd3lcmk4KsdCG4yduSTRVt+htaxIoEor88M1h3soOeXg5fn77ZiupVRwGeB/XNQ==} + /vite-tsconfig-paths@4.2.0(typescript@5.0.4)(vite@4.2.1): + resolution: {integrity: sha512-jGpus0eUy5qbbMVGiTxCL1iB9ZGN6Bd37VGLJU39kTDD6ZfULTTb1bcc5IeTWqWJKiWV5YihCaibeASPiGi8kw==} peerDependencies: vite: '*' peerDependenciesMeta: From 50b4039fba0a611561f107a40153922ea55766a4 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 13 Apr 2023 11:24:28 +1000 Subject: [PATCH 05/15] build(deps): update dependency happy-dom to v9.3.2 (#5227) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- web/package.json | 2 +- web/pnpm-lock.yaml | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/web/package.json b/web/package.json index 9e784d7da..c1b155a1c 100644 --- a/web/package.json +++ b/web/package.json @@ -92,7 +92,7 @@ "eslint-plugin-prettier": "4.2.1", "eslint-plugin-react": "7.32.2", "eslint-plugin-react-hooks": "4.6.0", - "happy-dom": "9.2.1", + "happy-dom": "9.3.2", "husky": "8.0.3", "prettier": "2.8.7", "react-test-renderer": "18.2.0", diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index 143b3940a..e3fc85956 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -154,8 +154,8 @@ devDependencies: specifier: 4.6.0 version: 4.6.0(eslint@8.38.0) happy-dom: - specifier: 9.2.1 - version: 9.2.1 + specifier: 9.3.2 + version: 9.3.2 husky: specifier: 8.0.3 version: 8.0.3 @@ -185,7 +185,7 @@ devDependencies: version: 4.2.0(typescript@5.0.4)(vite@4.2.1) vitest: specifier: 0.30.1 - version: 0.30.1(happy-dom@9.2.1) + version: 0.30.1(happy-dom@9.3.2) vitest-preview: specifier: 0.0.1 version: 0.0.1 @@ -3087,7 +3087,7 @@ packages: istanbul-lib-source-maps: 4.0.1 istanbul-reports: 3.1.5 test-exclude: 6.0.0 - vitest: 0.30.1(happy-dom@9.2.1) + vitest: 0.30.1(happy-dom@9.3.2) transitivePeerDependencies: - supports-color dev: true @@ -5100,8 +5100,8 @@ packages: resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==} dev: true - /happy-dom@9.2.1: - resolution: {integrity: sha512-6L1p1XalEVw/yzb+HVpCpfR0R3HDTAbTaTIDquJCzFPVVUC7q8+jHENH1W+4P2HsI9klCsPyeKm2/Ttfz/B5ag==} + /happy-dom@9.3.2: + resolution: {integrity: sha512-WTsnOmYXCiHs4REtjWSpi4lWJfhFj3ag9NMRrl0a1gwmAXwjRGm7fppdf6e3v38HtOins1Lgv0WKY8+ZlL0kDQ==} dependencies: css.escape: 1.5.1 he: 1.2.0 @@ -7519,7 +7519,7 @@ packages: - terser dev: true - /vitest@0.30.1(happy-dom@9.2.1): + /vitest@0.30.1(happy-dom@9.3.2): resolution: {integrity: sha512-y35WTrSTlTxfMLttgQk4rHcaDkbHQwDP++SNwPb+7H8yb13Q3cu2EixrtHzF27iZ8v0XCciSsLg00RkPAzB/aA==} engines: {node: '>=v14.18.0'} hasBin: true @@ -7564,7 +7564,7 @@ packages: chai: 4.3.7 concordance: 5.0.4 debug: 4.3.4 - happy-dom: 9.2.1 + happy-dom: 9.3.2 local-pkg: 0.4.3 magic-string: 0.30.0 pathe: 1.1.0 From 1847a506b75124d2d79488b77c65fcc48c6599e3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 13 Apr 2023 11:56:05 +1000 Subject: [PATCH 06/15] build(deps): update module github.com/knadh/koanf/v2 to v2.0.1 (#5225) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 54a357baa..aa25792a0 100644 --- a/go.mod +++ b/go.mod @@ -27,7 +27,7 @@ require ( github.com/knadh/koanf/providers/env 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/v2 v2.0.0 + github.com/knadh/koanf/v2 v2.0.1 github.com/mattn/go-sqlite3 v1.14.16 github.com/mitchellh/mapstructure v1.5.0 github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 diff --git a/go.sum b/go.sum index 4636cd17c..8756d6778 100644 --- a/go.sum +++ b/go.sum @@ -295,8 +295,8 @@ github.com/knadh/koanf/providers/posflag v0.1.0 h1:mKJlLrKPcAP7Ootf4pBZWJ6J+4wHY github.com/knadh/koanf/providers/posflag v0.1.0/go.mod h1:SYg03v/t8ISBNrMBRMlojH8OsKowbkXV7giIbBVgbz0= github.com/knadh/koanf/providers/rawbytes v0.1.0 h1:dpzgu2KO6uf6oCb4aP05KDmKmAmI51k5pe8RYKQ0qME= github.com/knadh/koanf/providers/rawbytes v0.1.0/go.mod h1:mMTB1/IcJ/yE++A2iEZbY1MLygX7vttU+C+S/YmPu9c= -github.com/knadh/koanf/v2 v2.0.0 h1:XPQ5ilNnwnNaHrfQ1YpTVhUAjcGHnEKA+lRpipQv02Y= -github.com/knadh/koanf/v2 v2.0.0/go.mod h1:ZeiIlIDXTE7w1lMT6UVcNiRAS2/rCeLn/GdLNvY1Dus= +github.com/knadh/koanf/v2 v2.0.1 h1:1dYGITt1I23x8cfx8ZnldtezdyaZtfAuRtIFOiRzK7g= +github.com/knadh/koanf/v2 v2.0.1/go.mod h1:ZeiIlIDXTE7w1lMT6UVcNiRAS2/rCeLn/GdLNvY1Dus= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= From 85e9792cf3af6f35ebb4bf9cffe9befaa8b5c32d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 13 Apr 2023 15:17:54 +1000 Subject: [PATCH 07/15] build(deps): update envoyproxy/envoy docker tag to v1.25.5 (#5229) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- internal/suites/example/compose/envoy/docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/suites/example/compose/envoy/docker-compose.yml b/internal/suites/example/compose/envoy/docker-compose.yml index c17497ace..231a2cbe6 100644 --- a/internal/suites/example/compose/envoy/docker-compose.yml +++ b/internal/suites/example/compose/envoy/docker-compose.yml @@ -2,7 +2,7 @@ version: '3' services: envoy: - image: envoyproxy/envoy:v1.25.4 + image: envoyproxy/envoy:v1.25.5 volumes: - ./example/compose/envoy/envoy.yaml:/etc/envoy/envoy.yaml - ./common/pki:/pki From db130dad483dfdbc36d0f781713d01d6fd1b960c Mon Sep 17 00:00:00 2001 From: James Elliott Date: Thu, 13 Apr 2023 20:10:12 +1000 Subject: [PATCH 08/15] docs: github links (#5230) Signed-off-by: James Elliott --- docs/content/en/configuration/methods/files.md | 2 +- .../en/configuration/prologue/introduction.md | 5 ++--- .../en/contributing/development/environment.md | 17 +++++++++++++++++ .../en/integration/deployment/bare-metal.md | 4 ++-- .../en/integration/prologue/get-started.md | 17 ++++++++++++----- .../authentication/push-notification/index.md | 2 +- docs/layouts/shortcodes/github-link.html | 17 +++++++++++++++++ 7 files changed, 52 insertions(+), 12 deletions(-) create mode 100644 docs/layouts/shortcodes/github-link.html diff --git a/docs/content/en/configuration/methods/files.md b/docs/content/en/configuration/methods/files.md index 667d38df3..8793cf889 100644 --- a/docs/content/en/configuration/methods/files.md +++ b/docs/content/en/configuration/methods/files.md @@ -61,7 +61,7 @@ authelia --config configuration.yml,config-acl.yml,config-other.yml ``` Authelia's configuration files use the YAML format. A template with all possible options can be found at the root of the -repository [here](https://github.com/authelia/authelia/blob/master/config.template.yml). +repository {{< github-link name="here" path="config.template.yml" >}}. *__Important Note:__ You should not have configuration sections such as Access Control Rules or OpenID Connect clients configured in multiple files. If you wish to split these into their own files that is fine, but if you have two files that diff --git a/docs/content/en/configuration/prologue/introduction.md b/docs/content/en/configuration/prologue/introduction.md index 76763c33a..586d61935 100644 --- a/docs/content/en/configuration/prologue/introduction.md +++ b/docs/content/en/configuration/prologue/introduction.md @@ -16,9 +16,8 @@ toc: true We document the configuration in two ways: -1. The [YAML] configuration template - [config.template.yml](https://github.com/authelia/authelia/blob/master/config.template.yml) has comments with very - limited documentation on the effective use of a particular option. All documentation lines start with `##`. Lines +1. The [YAML] configuration template {{< github-link path="config.template.yml" >}} has comments with very limited + documentation on the effective use of a particular option. All documentation lines start with `##`. Lines starting with a single `#` are [YAML] configuration options which are commented to disable them or as examples. 2. This documentation site. Generally each section of the configuration is in its own section of the documentation site. Each configuration option is listed in its relevant section as a heading, under that heading generally are two diff --git a/docs/content/en/contributing/development/environment.md b/docs/content/en/contributing/development/environment.md index 96fd84e29..008b87fc5 100644 --- a/docs/content/en/contributing/development/environment.md +++ b/docs/content/en/contributing/development/environment.md @@ -38,6 +38,23 @@ The additional tools are recommended: * [yamllint] * [VSCodium] or [GoLand] +## Certificate + +Authelia utilizes a self-signed Root CA certificate for the development environment. This allows us to sign elements of +the CI process uniformly and only trust a single additional Root CA Certificate. The private key for this certificate is +maintained by the [Core Team] so if you need an additional certificate signed for this purpose please reach out to them. + +While developing for Authelia you may also want to trust this Root CA. It is critical that you are aware of what this +means if you decide to do so. + +1. It will allow us to generate trusted certificates for machines this is installed on. +2. If compromised there is no formal revocation process at this time as we are not a certified CA. +3. Trusting Root CA's is not necessary for the development process it only makes it smoother. +4. Trusting additional Root CA's for prolonged periods is not generally a good idea. + +If you'd still like to trust the Root CA Certificate it's located (encoded as a PEM) in the main git repository at + [/internal/suites/common/pki/ca/ca.public.crt](https://github.com/authelia/authelia/blob/master/internal/suites/common/pki/ca/ca.public.crt). + ## Scripts There is a scripting context provided with __Authelia__ which can easily be configured. It allows running integration diff --git a/docs/content/en/integration/deployment/bare-metal.md b/docs/content/en/integration/deployment/bare-metal.md index 0e8126be3..8e03e00fb 100644 --- a/docs/content/en/integration/deployment/bare-metal.md +++ b/docs/content/en/integration/deployment/bare-metal.md @@ -25,8 +25,8 @@ bootstrapping *Authelia*. We publish two example [systemd] unit files: -* [authelia.service](https://github.com/authelia/authelia/blob/master/authelia.service) -* [authelia@.service](https://github.com/authelia/authelia/blob/master/authelia%40.service) +* {{< github-link path="authelia.service" >}} +* {{< github-link path="authelia@.service" >}} ## Arch Linux diff --git a/docs/content/en/integration/prologue/get-started.md b/docs/content/en/integration/prologue/get-started.md index 78e9d754a..3b5fe67b3 100644 --- a/docs/content/en/integration/prologue/get-started.md +++ b/docs/content/en/integration/prologue/get-started.md @@ -23,24 +23,31 @@ common scenarios however those using more advanced architectures are likely goin help with answering less specific questions about this and it may be possible if provided adequate information more specific questions may be answered. +1. Authelia *__MUST__* be served via the `https` scheme. This is not optional even for testing. This is a deliberate + design decision to improve security directly (by using encrypted communication) and indirectly by reducing complexity. + ### Forwarded Authentication Forwarded Authentication is a simple per-request authorization flow that checks the metadata of a request and a session cookie to determine if a user must be forwarded to the authentication portal. -Due to the fact a cookie is used, it's an intentional design decision that *__ALL__* applications/domains protected via +In addition to the `https` scheme requirement for Authelia itself: + +1. Due to the fact a cookie is used, it's an intentional design decision that *__ALL__* applications/domains protected via this method *__MUST__* use secure schemes (`https` and `wss`) for all of their communication. ### OpenID Connect -Only requires Authelia to be accessible via a secure scheme (`https`). +No additional requirements other than the use of the `https` scheme for Authelia itself exist excluding those mandated +by the relevant specifications. ## Configuration It's important to customize the configuration for *Authelia* in advance of deploying it. The configuration is static and -not configured via web GUI. You can find a -[configuration template](https://github.com/authelia/authelia/blob/master/config.template.yml) on GitHub which can be -used as a basis for configuration. +not configured via web GUI. You can find a configuration template named {{< github-link path="config.template.yml" >}} +on GitHub which can be used as a basis for configuration, alternatively *Authelia* will write this template relevant for +your version the first time it is started. Users should expect that they have to configure elements of this file as part +of initial setup. The important sections to consider in initial configuration are as follows: diff --git a/docs/content/en/overview/authentication/push-notification/index.md b/docs/content/en/overview/authentication/push-notification/index.md index c7e5ba725..bc6013c45 100644 --- a/docs/content/en/overview/authentication/push-notification/index.md +++ b/docs/content/en/overview/authentication/push-notification/index.md @@ -44,7 +44,7 @@ case you have multiple devices available, you will be asked to select your prefe ### Why don't I have access to the *Push Notification* option? It's likely that you have not configured __Authelia__ correctly. Please read this documentation again and be sure you -had a look at [config.template.yml](https://github.com/authelia/authelia/blob/master/config.template.yml) and +had a look at {{< github-link path="config.template.yml" >}} and [configuration documentation](../../../configuration/second-factor/duo.md). [Duo]: https://duo.com/ diff --git a/docs/layouts/shortcodes/github-link.html b/docs/layouts/shortcodes/github-link.html new file mode 100644 index 000000000..2f399ac4a --- /dev/null +++ b/docs/layouts/shortcodes/github-link.html @@ -0,0 +1,17 @@ +{{- $repo := "authelia/authelia" }}{{ with .Get "repo" }}{{ $repo = . }}{{ end }} +{{- $branch := printf "v%s" .Site.Data.misc.latest }}{{ with .Get "branch" }}{{ $branch = . }}{{ end }} +{{- $path := "" }}{{ with .Get "path" }}{{ $path = . }}{{ end }} +{{- $link := printf "https://github.com/%s/blob/%s/%s" $repo $branch (urlquery $path) }} +{{- $name := "" }} +{{- with .Get "name" }} +{{- $name = . }} +{{- else }} +{{- if (eq $repo "authelia/authelia") }} +{{- $name = $path }} +{{- else }} +{{- $name = printf "https://github.com/%s/blob/%s/%s" $repo $branch $path }} +{{- end }} +{{- end }} +{{- "" -}} +{{ $name }} +{{- "" -}} From 3d2da0b070d097129cc71b5e170692c3a6380b8f Mon Sep 17 00:00:00 2001 From: James Elliott Date: Thu, 13 Apr 2023 20:58:18 +1000 Subject: [PATCH 09/15] feat(oidc): client authentication modes (#5150) This adds a feature to OpenID Connect 1.0 where clients can be restricted to a specific client authentication mode, as well as implements some backend requirements for the private_key_jwt client authentication mode (and potentially the tls_client_auth / self_signed_tls_client_auth client authentication modes). It also adds some improvements to configuration defaults and validations which will for now be warnings but likely be made into errors. Signed-off-by: James Elliott --- config.template.yml | 15 +- .../identity-providers/open-id-connect.md | 68 +- .../en/integration/kubernetes/istio.md | 2 +- .../openid-connect/introduction.md | 111 +- docs/content/en/integration/proxies/envoy.md | 2 +- .../content/en/integration/proxies/support.md | 2 +- internal/configuration/config.template.yml | 15 +- .../schema/identity_providers.go | 5 +- internal/configuration/schema/keys.go | 1 + .../configuration/validator/access_control.go | 25 +- .../validator/access_control_test.go | 47 +- .../configuration/validator/authentication.go | 12 +- .../validator/authentication_test.go | 20 +- .../configuration/validator/configuration.go | 4 +- .../validator/configuration_test.go | 8 +- internal/configuration/validator/const.go | 156 +- internal/configuration/validator/duo_test.go | 11 +- .../validator/identity_providers.go | 420 +++-- .../validator/identity_providers_test.go | 1377 ++++++++++++++--- internal/configuration/validator/keys.go | 2 +- internal/configuration/validator/keys_test.go | 14 + internal/configuration/validator/log.go | 3 +- internal/configuration/validator/log_test.go | 2 +- .../configuration/validator/notifier_test.go | 28 + internal/configuration/validator/ntp_test.go | 2 +- .../validator/password_policy_test.go | 2 +- internal/configuration/validator/server.go | 6 +- .../configuration/validator/server_test.go | 12 +- internal/configuration/validator/session.go | 6 +- .../configuration/validator/session_test.go | 28 +- internal/configuration/validator/storage.go | 3 +- .../configuration/validator/storage_test.go | 2 +- .../configuration/validator/telemetry_test.go | 2 +- internal/configuration/validator/theme.go | 3 +- .../configuration/validator/theme_test.go | 2 +- internal/configuration/validator/totp.go | 2 +- internal/configuration/validator/totp_test.go | 12 +- internal/configuration/validator/util.go | 94 ++ internal/configuration/validator/webauthn.go | 5 +- .../configuration/validator/webauthn_test.go | 4 +- .../handlers/handler_oidc_authorization.go | 6 +- .../handler_oidc_authorization_consent.go | 30 +- ...ler_oidc_authorization_consent_explicit.go | 16 +- ...ler_oidc_authorization_consent_implicit.go | 26 +- ...dc_authorization_consent_pre_configured.go | 42 +- internal/handlers/handler_oidc_consent.go | 12 +- internal/handlers/handler_oidc_userinfo.go | 6 +- internal/handlers/response.go | 8 +- internal/handlers/types.go | 2 +- internal/oidc/client.go | 288 ++-- internal/oidc/client_test.go | 219 ++- internal/oidc/config.go | 6 - internal/oidc/const.go | 10 +- internal/oidc/const_test.go | 12 + internal/oidc/discovery.go | 243 ++- internal/oidc/discovery_test.go | 24 +- internal/oidc/provider.go | 15 +- internal/oidc/provider_test.go | 52 +- internal/oidc/store.go | 6 +- internal/oidc/store_test.go | 63 +- internal/oidc/types.go | 347 ++++- internal/oidc/types_test.go | 4 +- internal/utils/strings.go | 4 +- internal/utils/strings_test.go | 2 +- 64 files changed, 3007 insertions(+), 971 deletions(-) create mode 100644 internal/oidc/const_test.go diff --git a/config.template.yml b/config.template.yml index 1058fecc1..077237358 100644 --- a/config.template.yml +++ b/config.template.yml @@ -1480,12 +1480,6 @@ notifier: # - email # - profile - ## Grant Types configures which grants this client can obtain. - ## It's not recommended to define this unless you know what you're doing. - # grant_types: - # - refresh_token - # - authorization_code - ## Response Types configures which responses this client can be sent. ## It's not recommended to define this unless you know what you're doing. # response_types: @@ -1495,7 +1489,14 @@ notifier: # response_modes: # - form_post # - query - # - fragment + + ## Grant Types configures which grants this client can obtain. + ## It's not recommended to define this unless you know what you're doing. + # grant_types: + # - authorization_code + + ## The permitted client authentication method for the Token Endpoint for this client. + # token_endpoint_auth_method: client_secret_basic ## The policy to require for this client; one_factor or two_factor. # authorization_policy: two_factor diff --git a/docs/content/en/configuration/identity-providers/open-id-connect.md b/docs/content/en/configuration/identity-providers/open-id-connect.md index 89d35e63a..40b3bd695 100644 --- a/docs/content/en/configuration/identity-providers/open-id-connect.md +++ b/docs/content/en/configuration/identity-providers/open-id-connect.md @@ -451,9 +451,40 @@ A list of scopes to allow this client to consume. See documentation for the application you are trying to configure [OpenID Connect 1.0] for will likely have a list of scopes or claims required which can be matched with the above guide. +#### response_types + +{{< confkey type="list(string)" default="code" required="no" >}} + +*__Security Note:__ It is recommended that only the `code` response type (i.e. the default) is used. The other response +types are not as secure as this response type.* + +A list of response types this client supports. If a response type not in this list is requested by a client then an +error will be returned to the client. The response type indicates the types of values that are returned to the client. + +See the [Response Types](../../integration/openid-connect/introduction.md#response-types) section of the +[OpenID Connect 1.0 Integration Guide](../../integration/openid-connect/introduction.md#response-types) for more information. + +#### response_modes + +{{< confkey type="list(string)" default="form_post, query" required="no" >}} + +*__Important Note:__ It is recommended that this isn't configured at this time unless you know what you're doing.* + +A list of response modes this client supports. If a response mode not in this list is requested by a client then an +error will be returned to the client. The response mode controls how the response type is returned to the client. + +See the [Response Modes](../../integration/openid-connect/introduction.md#response-modes) section of the +[OpenID Connect 1.0 Integration Guide](../../integration/openid-connect/introduction.md#response-modes) for more +information. + +The default values are based on the [response_types](#responsetypes) values. When the [response_types](#responsetypes) +values include the `code` type then the `query` response mode will be included. When any other type is included the +`fragment` response mode will be included. It's important to note at this time we do not support the `none` response +type, but when it is supported it will include the `query` response mode. + #### grant_types -{{< confkey type="list(string)" default="refresh_token, authorization_code" required="no" >}} +{{< confkey type="list(string)" default="authorization_code" required="no" >}} *__Important Note:__ It is recommended that this isn't configured at this time unless you know what you're doing.* @@ -462,28 +493,6 @@ The list of grant types this client is permitted to use in order to obtain acces See the [Grant Types](../../integration/openid-connect/introduction.md#grant-types) section of the [OpenID Connect 1.0 Integration Guide](../../integration/openid-connect/introduction.md#grant-types) for more information. -#### response_types - -{{< confkey type="list(string)" default="code" required="no" >}} - -*__Important Note:__ It is recommended that this isn't configured at this time unless you know what you're doing.* - -A list of response types this client supports. - -See the [Response Types](../../integration/openid-connect/introduction.md#response-types) section of the -[OpenID Connect 1.0 Integration Guide](../../integration/openid-connect/introduction.md#response-types) for more information. - -#### response_modes - -{{< confkey type="list(string)" default="form_post, query, fragment" required="no" >}} - -*__Important Note:__ It is recommended that this isn't configured at this time unless you know what you're doing.* - -A list of response modes this client supports. - -See the [Response Modes](../../integration/openid-connect/introduction.md#response-modes) section of the -[OpenID Connect 1.0 Integration Guide](../../integration/openid-connect/introduction.md#response-modes) for more information. - #### authorization_policy {{< confkey type="string" default="two_factor" required="no" >}} @@ -522,6 +531,18 @@ The algorithm used to sign the userinfo endpoint responses. This can either be ` See the [integration guide](../../integration/openid-connect/introduction.md#user-information-signing-algorithm) for more information. +#### token_endpoint_auth_method + +{{< confkey type="string" default="" required="no" >}} + +The registered client authentication mechanism used by this client for the [Token Endpoint]. If no method is defined +the confidential client type will accept any supported method. The public client type defaults to `none` as this +is required by the specification. This may be required as a breaking change in future versions. +Supported values are `client_secret_basic`, `client_secret_post`, and `none`. + +See the [integration guide](../../integration/openid-connect/introduction.md#client-authentication-method) for +more information. + #### consent_mode {{< confkey type="string" default="auto" required="no" >}} @@ -565,6 +586,7 @@ To integrate Authelia's [OpenID Connect 1.0] implementation with a relying party [token lifespan]: https://docs.apigee.com/api-platform/antipatterns/oauth-long-expiration [OpenID Connect 1.0]: https://openid.net/connect/ +[Token Endpoint]: https://openid.net/specs/openid-connect-core-1_0.html#TokenEndpoint [JWT]: https://datatracker.ietf.org/doc/html/rfc7519 [RFC6234]: https://datatracker.ietf.org/doc/html/rfc6234 [RFC4648]: https://datatracker.ietf.org/doc/html/rfc4648 diff --git a/docs/content/en/integration/kubernetes/istio.md b/docs/content/en/integration/kubernetes/istio.md index c1c4927e3..60360c6b0 100644 --- a/docs/content/en/integration/kubernetes/istio.md +++ b/docs/content/en/integration/kubernetes/istio.md @@ -13,7 +13,7 @@ toc: true --- Istio uses [Envoy](../proxies/envoy.md) as an Ingress. This means it has a relatively comprehensive integration option. -Istio is supported with Authelia v4.37.0 and higher via [Envoy]'s [external authorization] filter. +Istio is supported with Authelia v4.37.0 and higher via the [Envoy] proxy [external authorization] filter. [external authorization]: https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/http/ext_authz/v3/ext_authz.proto.html#extensions-filters-http-ext-authz-v3-extauthz diff --git a/docs/content/en/integration/openid-connect/introduction.md b/docs/content/en/integration/openid-connect/introduction.md index 6ab7ba46c..37cca969d 100644 --- a/docs/content/en/integration/openid-connect/introduction.md +++ b/docs/content/en/integration/openid-connect/introduction.md @@ -21,8 +21,15 @@ documentation for some [OpenID Connect 1.0] Relying Party implementations. See the [configuration documentation](../../configuration/identity-providers/open-id-connect.md) for information on how to configure the Authelia [OpenID Connect 1.0] Provider. +This page is intended as an integration reference point for any implementers who wish to integrate an +[OpenID Connect 1.0] Relying Party (client application) either as a developer or user of the third party Reyling Party. + ## Scope Definitions +The following scope definitions describe each scope supported and the associated effects including the individual claims +returned by granting this scope. By default we do not issue any claims which reveal the users identity which allows +administrators semi-granular control over which claims the client is entitled to. + ### openid This is the default scope for [OpenID Connect 1.0]. This field is forced on every client by the configuration validation @@ -54,9 +61,16 @@ This scope is a special scope designed to allow applications to obtain a [Refres an application on behalf of a user. A [Refresh Token] is a special [Access Token] that allows refreshing previously issued token credentials, effectively it allows the relying party to obtain new tokens periodically. +As per [OpenID Connect 1.0] Section 11 [Offline Access] can only be granted during the [Authorization Code Flow] or a +[Hybrid Flow]. The [Refresh Token] will only ever be returned at the [Token Endpoint] when the client is exchanging +their [OAuth 2.0 Authorization Code]. + Generally unless an application supports this and actively requests this scope they should not be granted this scope via the client configuration. +It is also important to note that we treat a [Refresh Token] as single use and reissue a new [Refresh Token] during the +refresh flow. + ### groups This scope includes the groups the authentication backend reports the user is a member of in the [Claims] of the @@ -92,43 +106,21 @@ This scope includes the profile information the authentication backend reports a The following section describes advanced parameters which can be used in various endpoints as well as their related configuration options. -### Grant Types - -The following describes the various [OAuth 2.0] and [OpenID Connect 1.0] grant types and their support level. The value -field is both the required value for the `grant_type` parameter in the authorization request and the `grant_types` -configuration option. - -| Grant Type | Supported | Value | Notes | -|:-----------------------------------------------:|:---------:|:----------------------------------------------:|:-------------------------------------------------------------------:| -| [OAuth 2.0 Authorization Code] | Yes | `authorization_code` | | -| [OAuth 2.0 Resource Owner Password Credentials] | No | `password` | This Grant Type has been deprecated and should not normally be used | -| [OAuth 2.0 Client Credentials] | Yes | `client_credentials` | | -| [OAuth 2.0 Implicit] | Yes | `implicit` | This Grant Type has been deprecated and should not normally be used | -| [OAuth 2.0 Refresh Token] | Yes | `refresh_token` | | -| [OAuth 2.0 Device Code] | No | `urn:ietf:params:oauth:grant-type:device_code` | | -| - -[OAuth 2.0 Authorization Code]: https://datatracker.ietf.org/doc/html/rfc6749#section-1.3.1 -[OAuth 2.0 Implicit]: https://datatracker.ietf.org/doc/html/rfc6749#section-1.3.2 -[OAuth 2.0 Resource Owner Password Credentials]: https://datatracker.ietf.org/doc/html/rfc6749#section-1.3.3 -[OAuth 2.0 Client Credentials]: https://datatracker.ietf.org/doc/html/rfc6749#section-1.3.4 -[OAuth 2.0 Refresh Token]: https://datatracker.ietf.org/doc/html/rfc6749#section-1.5 -[OAuth 2.0 Device Code]: https://datatracker.ietf.org/doc/html/rfc8628#section-3.4 - ### Response Types The following describes the supported response types. See the [OAuth 2.0 Multiple Response Type Encoding Practices] for -more technical information. +more technical information. The default response modes column indicates which response modes are allowed by default on +clients configured with this flow type value. If more than a single response type is configured -| Flow Type | Values | -|:-------------------------:|:---------------------:| -| [Authorization Code Flow] | `code` | -| [Implicit Flow] | `token id_token` | -| [Implicit Flow] | `id_token` | -| [Implicit Flow] | `token` | -| [Hybrid Flow] | `code token` | -| [Hybrid Flow] | `code id_token` | -| [Hybrid Flow] | `code token id_token` | +| Flow Type | Value | Default [Response Modes](#response-modes) Values | +|:-------------------------:|:---------------------:|:------------------------------------------------:| +| [Authorization Code Flow] | `code` | `form_post`, `query` | +| [Implicit Flow] | `id_token token` | `form_post`, `fragment` | +| [Implicit Flow] | `id_token` | `form_post`, `fragment` | +| [Implicit Flow] | `token` | `form_post`, `fragment` | +| [Hybrid Flow] | `code token` | `form_post`, `fragment` | +| [Hybrid Flow] | `code id_token` | `form_post`, `fragment` | +| [Hybrid Flow] | `code id_token token` | `form_post`, `fragment` | [Authorization Code Flow]: https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth [Implicit Flow]: https://openid.net/specs/openid-connect-core-1_0.html#ImplicitFlowAuth @@ -139,16 +131,60 @@ more technical information. ### Response Modes The following describes the supported response modes. See the [OAuth 2.0 Multiple Response Type Encoding Practices] for -more technical information. +more technical information. The default response modes of a client is based on the [Response Types](#response-types) +configuration. | Name | Value | |:---------------------:|:-----------:| +| [OAuth 2.0 Form Post] | `form_post` | | Query String | `query` | | Fragment | `fragment` | -| [OAuth 2.0 Form Post] | `form_post` | [OAuth 2.0 Form Post]: https://openid.net/specs/oauth-v2-form-post-response-mode-1_0.html +### Grant Types + +The following describes the various [OAuth 2.0] and [OpenID Connect 1.0] grant types and their support level. The value +field is both the required value for the `grant_type` parameter in the authorization request and the `grant_types` +configuration option. + +| Grant Type | Supported | Value | Notes | +|:-----------------------------------------------:|:---------:|:----------------------------------------------:|:-----------------------------------------------------------------------------------------------:| +| [OAuth 2.0 Authorization Code] | Yes | `authorization_code` | | +| [OAuth 2.0 Resource Owner Password Credentials] | No | `password` | This Grant Type has been deprecated and should not normally be used | +| [OAuth 2.0 Client Credentials] | No | `client_credentials` | | +| [OAuth 2.0 Implicit] | Yes | `implicit` | This Grant Type has been deprecated and should not normally be used | +| [OAuth 2.0 Refresh Token] | Yes | `refresh_token` | This Grant Type should genreally only be used for clients which have the `offline_access` scope | +| [OAuth 2.0 Device Code] | No | `urn:ietf:params:oauth:grant-type:device_code` | | +| + +[OAuth 2.0 Authorization Code]: https://datatracker.ietf.org/doc/html/rfc6749#section-1.3.1 +[OAuth 2.0 Implicit]: https://datatracker.ietf.org/doc/html/rfc6749#section-1.3.2 +[OAuth 2.0 Resource Owner Password Credentials]: https://datatracker.ietf.org/doc/html/rfc6749#section-1.3.3 +[OAuth 2.0 Client Credentials]: https://datatracker.ietf.org/doc/html/rfc6749#section-1.3.4 +[OAuth 2.0 Refresh Token]: https://datatracker.ietf.org/doc/html/rfc6749#section-1.5 +[OAuth 2.0 Device Code]: https://datatracker.ietf.org/doc/html/rfc8628#section-3.4 + +### Client Authentication Method + +The following describes the supported client authentication methods. See the [OpenID Connect 1.0 Client Authentication] +specification and the [OAuth 2.0 - Client Types] specification for more information. + +| Description | Value / Name | Supported Client Types | Default for Client Type | Assertion Type | +|:------------------------------------:|:-----------------------------:|:----------------------:|:-----------------------:|:--------------------------------------------------------:| +| Secret via HTTP Basic Auth Scheme | `client_secret_basic` | `confidential` | N/A | N/A | +| Secret via HTTP POST Body | `client_secret_post` | `confidential` | N/A | N/A | +| JWT (signed by secret) | `client_secret_jwt` | Not Supported | N/A | `urn:ietf:params:oauth:client-assertion-type:jwt-bearer` | +| JWT (signed by private key) | `private_key_jwt` | Not Supported | N/A | `urn:ietf:params:oauth:client-assertion-type:jwt-bearer` | +| [OAuth 2.0 Mutual-TLS] | `tls_client_auth` | Not Supported | N/A | N/A | +| [OAuth 2.0 Mutual-TLS] (Self Signed) | `self_signed_tls_client_auth` | Not Supported | N/A | N/A | +| No Authentication | `none` | `public` | `public` | N/A | + + +[OpenID Connect 1.0 Client Authentication]: https://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication +[OAuth 2.0 Mutual-TLS]: https://datatracker.ietf.org/doc/html/rfc8705 +[OAuth 2.0 - Client Types]: https://datatracker.ietf.org/doc/html/rfc8705#section-2.1 + ## Authentication Method References Authelia currently supports adding the `amr` [Claim] to the [ID Token] utilizing the [RFC8176] Authentication Method @@ -289,10 +325,13 @@ The advantages of this approach are as follows: [JSON Web Key Set]: https://datatracker.ietf.org/doc/html/rfc7517#section-5 +[Offline Access]: https://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess + [Authorization]: https://openid.net/specs/openid-connect-core-1_0.html#AuthorizationEndpoint -[Pushed Authorization Requests]: https://datatracker.ietf.org/doc/html/rfc9126 [Token]: https://openid.net/specs/openid-connect-core-1_0.html#TokenEndpoint [UserInfo]: https://openid.net/specs/openid-connect-core-1_0.html#UserInfo + +[Pushed Authorization Requests]: https://datatracker.ietf.org/doc/html/rfc9126 [Introspection]: https://datatracker.ietf.org/doc/html/rfc7662 [Revocation]: https://datatracker.ietf.org/doc/html/rfc7009 [Proof Key Code Exchange]: https://www.rfc-editor.org/rfc/rfc7636.html diff --git a/docs/content/en/integration/proxies/envoy.md b/docs/content/en/integration/proxies/envoy.md index 0d45b7cea..af37d3cc8 100644 --- a/docs/content/en/integration/proxies/envoy.md +++ b/docs/content/en/integration/proxies/envoy.md @@ -87,7 +87,7 @@ Below you will find commented examples of the following configuration: ### Example -Support for [Envoy] is possible with Authelia v4.37.0 and higher via [Envoy]'s [external authorization] filter. +Support for [Envoy] is possible with Authelia v4.37.0 and higher via the [Envoy] proxy [external authorization] filter. [external authorization]: https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/http/ext_authz/v3/ext_authz.proto.html#extensions-filters-http-ext-authz-v3-extauthz diff --git a/docs/content/en/integration/proxies/support.md b/docs/content/en/integration/proxies/support.md index 6364fde3a..9b33f8de4 100644 --- a/docs/content/en/integration/proxies/support.md +++ b/docs/content/en/integration/proxies/support.md @@ -92,7 +92,7 @@ available in [Kubernetes]. You would likely have to build your own [HAProxy] ima ### Envoy -[Envoy] is supported with Authelia v4.37.0 and higher via [Envoy]'s [external authorization] filter. +[Envoy] is supported with Authelia v4.37.0 and higher via the [Envoy] proxy [external authorization] filter. [external authorization]: https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/http/ext_authz/v3/ext_authz.proto.html#extensions-filters-http-ext-authz-v3-extauthz diff --git a/internal/configuration/config.template.yml b/internal/configuration/config.template.yml index 1058fecc1..077237358 100644 --- a/internal/configuration/config.template.yml +++ b/internal/configuration/config.template.yml @@ -1480,12 +1480,6 @@ notifier: # - email # - profile - ## Grant Types configures which grants this client can obtain. - ## It's not recommended to define this unless you know what you're doing. - # grant_types: - # - refresh_token - # - authorization_code - ## Response Types configures which responses this client can be sent. ## It's not recommended to define this unless you know what you're doing. # response_types: @@ -1495,7 +1489,14 @@ notifier: # response_modes: # - form_post # - query - # - fragment + + ## Grant Types configures which grants this client can obtain. + ## It's not recommended to define this unless you know what you're doing. + # grant_types: + # - authorization_code + + ## The permitted client authentication method for the Token Endpoint for this client. + # token_endpoint_auth_method: client_secret_basic ## The policy to require for this client; one_factor or two_factor. # authorization_policy: two_factor diff --git a/internal/configuration/schema/identity_providers.go b/internal/configuration/schema/identity_providers.go index 57376dc87..d253a4d07 100644 --- a/internal/configuration/schema/identity_providers.go +++ b/internal/configuration/schema/identity_providers.go @@ -64,6 +64,8 @@ type OpenIDConnectClientConfiguration struct { ResponseTypes []string `koanf:"response_types"` ResponseModes []string `koanf:"response_modes"` + TokenEndpointAuthMethod string `koanf:"token_endpoint_auth_method"` + Policy string `koanf:"authorization_policy"` EnforcePAR bool `koanf:"enforce_par"` @@ -91,9 +93,8 @@ var defaultOIDCClientConsentPreConfiguredDuration = time.Hour * 24 * 7 var DefaultOpenIDConnectClientConfiguration = OpenIDConnectClientConfiguration{ Policy: "two_factor", Scopes: []string{"openid", "groups", "profile", "email"}, - GrantTypes: []string{"refresh_token", "authorization_code"}, ResponseTypes: []string{"code"}, - ResponseModes: []string{"form_post", "query", "fragment"}, + ResponseModes: []string{"form_post"}, UserinfoSigningAlgorithm: "none", ConsentMode: "auto", diff --git a/internal/configuration/schema/keys.go b/internal/configuration/schema/keys.go index 526913918..85923eb1a 100644 --- a/internal/configuration/schema/keys.go +++ b/internal/configuration/schema/keys.go @@ -45,6 +45,7 @@ var Keys = []string{ "identity_providers.oidc.clients[].grant_types", "identity_providers.oidc.clients[].response_types", "identity_providers.oidc.clients[].response_modes", + "identity_providers.oidc.clients[].token_endpoint_auth_method", "identity_providers.oidc.clients[].authorization_policy", "identity_providers.oidc.clients[].enforce_par", "identity_providers.oidc.clients[].enforce_pkce", diff --git a/internal/configuration/validator/access_control.go b/internal/configuration/validator/access_control.go index 994d7559c..93f1efa4c 100644 --- a/internal/configuration/validator/access_control.go +++ b/internal/configuration/validator/access_control.go @@ -59,7 +59,7 @@ func ValidateAccessControl(config *schema.Configuration, validator *schema.Struc } if !IsPolicyValid(config.AccessControl.DefaultPolicy) { - validator.Push(fmt.Errorf(errFmtAccessControlDefaultPolicyValue, strings.Join(validACLRulePolicies, "', '"), config.AccessControl.DefaultPolicy)) + validator.Push(fmt.Errorf(errFmtAccessControlDefaultPolicyValue, strJoinOr(validACLRulePolicies), config.AccessControl.DefaultPolicy)) } if config.AccessControl.Networks != nil { @@ -92,8 +92,13 @@ func ValidateRules(config *schema.Configuration, validator *schema.StructValidat validateDomains(rulePosition, rule, validator) - if !IsPolicyValid(rule.Policy) { - validator.Push(fmt.Errorf(errFmtAccessControlRuleInvalidPolicy, ruleDescriptor(rulePosition, rule), rule.Policy)) + switch rule.Policy { + case "": + validator.Push(fmt.Errorf(errFmtAccessControlRuleNoPolicy, ruleDescriptor(rulePosition, rule))) + default: + if !IsPolicyValid(rule.Policy) { + validator.Push(fmt.Errorf(errFmtAccessControlRuleInvalidPolicy, ruleDescriptor(rulePosition, rule), strJoinOr(validACLRulePolicies), rule.Policy)) + } } validateNetworks(rulePosition, rule, config.AccessControl, validator) @@ -156,10 +161,14 @@ func validateSubjects(rulePosition int, rule schema.ACLRule, validator *schema.S } func validateMethods(rulePosition int, rule schema.ACLRule, validator *schema.StructValidator) { - for _, method := range rule.Methods { - if !utils.IsStringInSliceFold(method, validACLHTTPMethodVerbs) { - validator.Push(fmt.Errorf(errFmtAccessControlRuleMethodInvalid, ruleDescriptor(rulePosition, rule), method, strings.Join(validACLHTTPMethodVerbs, "', '"))) - } + invalid, duplicates := validateList(rule.Methods, validACLHTTPMethodVerbs, true) + + if len(invalid) != 0 { + validator.Push(fmt.Errorf(errFmtAccessControlRuleInvalidEntries, ruleDescriptor(rulePosition, rule), "methods", strJoinOr(validACLHTTPMethodVerbs), strJoinAnd(invalid))) + } + + if len(duplicates) != 0 { + validator.Push(fmt.Errorf(errFmtAccessControlRuleInvalidDuplicates, ruleDescriptor(rulePosition, rule), "methods", strJoinAnd(duplicates))) } } @@ -177,7 +186,7 @@ func validateQuery(i int, rule schema.ACLRule, config *schema.Configuration, val } } } else if !utils.IsStringInSliceFold(config.AccessControl.Rules[i].Query[j][k].Operator, validACLRuleOperators) { - validator.Push(fmt.Errorf(errFmtAccessControlRuleQueryInvalid, ruleDescriptor(i+1, rule), config.AccessControl.Rules[i].Query[j][k].Operator, strings.Join(validACLRuleOperators, "', '"))) + validator.Push(fmt.Errorf(errFmtAccessControlRuleQueryInvalid, ruleDescriptor(i+1, rule), strJoinOr(validACLRuleOperators), config.AccessControl.Rules[i].Query[j][k].Operator)) } if config.AccessControl.Rules[i].Query[j][k].Key == "" { diff --git a/internal/configuration/validator/access_control_test.go b/internal/configuration/validator/access_control_test.go index 0671455a1..1543959e0 100644 --- a/internal/configuration/validator/access_control_test.go +++ b/internal/configuration/validator/access_control_test.go @@ -58,7 +58,7 @@ func (suite *AccessControl) TestShouldValidateEitherDomainsOrDomainsRegex() { suite.Assert().Len(suite.validator.Warnings(), 0) suite.Require().Len(suite.validator.Errors(), 1) - assert.EqualError(suite.T(), suite.validator.Errors()[0], "access control: rule #3: rule is invalid: must have the option 'domain' or 'domain_regex' configured") + assert.EqualError(suite.T(), suite.validator.Errors()[0], "access control: rule #3: option 'domain' or 'domain_regex' must be present but are both absent") } func (suite *AccessControl) TestShouldRaiseErrorInvalidDefaultPolicy() { @@ -69,7 +69,7 @@ func (suite *AccessControl) TestShouldRaiseErrorInvalidDefaultPolicy() { suite.Assert().Len(suite.validator.Warnings(), 0) suite.Require().Len(suite.validator.Errors(), 1) - suite.Assert().EqualError(suite.validator.Errors()[0], "access control: option 'default_policy' must be one of 'bypass', 'one_factor', 'two_factor', 'deny' but it is configured as 'invalid'") + suite.Assert().EqualError(suite.validator.Errors()[0], "access control: option 'default_policy' must be one of 'bypass', 'one_factor', 'two_factor', or 'deny' but it's configured as 'invalid'") } func (suite *AccessControl) TestShouldRaiseErrorInvalidNetworkGroupNetwork() { @@ -141,10 +141,10 @@ func (suite *AccessControl) TestShouldRaiseErrorsWithEmptyRules() { suite.Assert().Len(suite.validator.Warnings(), 0) suite.Require().Len(suite.validator.Errors(), 4) - suite.Assert().EqualError(suite.validator.Errors()[0], "access control: rule #1: rule is invalid: must have the option 'domain' or 'domain_regex' configured") - suite.Assert().EqualError(suite.validator.Errors()[1], "access control: rule #1: rule 'policy' option '' is invalid: must be one of 'deny', 'two_factor', 'one_factor' or 'bypass'") - suite.Assert().EqualError(suite.validator.Errors()[2], "access control: rule #2: rule is invalid: must have the option 'domain' or 'domain_regex' configured") - suite.Assert().EqualError(suite.validator.Errors()[3], "access control: rule #2: rule 'policy' option 'wrong' is invalid: must be one of 'deny', 'two_factor', 'one_factor' or 'bypass'") + suite.Assert().EqualError(suite.validator.Errors()[0], "access control: rule #1: option 'domain' or 'domain_regex' must be present but are both absent") + suite.Assert().EqualError(suite.validator.Errors()[1], "access control: rule #1: option 'policy' must be present but it's absent") + suite.Assert().EqualError(suite.validator.Errors()[2], "access control: rule #2: option 'domain' or 'domain_regex' must be present but are both absent") + suite.Assert().EqualError(suite.validator.Errors()[3], "access control: rule #2: option 'policy' must be one of 'bypass', 'one_factor', 'two_factor', or 'deny' but it's configured as 'wrong'") } func (suite *AccessControl) TestShouldRaiseErrorInvalidPolicy() { @@ -160,7 +160,7 @@ func (suite *AccessControl) TestShouldRaiseErrorInvalidPolicy() { suite.Assert().Len(suite.validator.Warnings(), 0) suite.Require().Len(suite.validator.Errors(), 1) - suite.Assert().EqualError(suite.validator.Errors()[0], "access control: rule #1 (domain 'public.example.com'): rule 'policy' option 'invalid' is invalid: must be one of 'deny', 'two_factor', 'one_factor' or 'bypass'") + suite.Assert().EqualError(suite.validator.Errors()[0], "access control: rule #1 (domain 'public.example.com'): option 'policy' must be one of 'bypass', 'one_factor', 'two_factor', or 'deny' but it's configured as 'invalid'") } func (suite *AccessControl) TestShouldRaiseErrorInvalidNetwork() { @@ -194,7 +194,24 @@ func (suite *AccessControl) TestShouldRaiseErrorInvalidMethod() { suite.Assert().Len(suite.validator.Warnings(), 0) suite.Require().Len(suite.validator.Errors(), 1) - suite.Assert().EqualError(suite.validator.Errors()[0], "access control: rule #1 (domain 'public.example.com'): 'methods' option 'HOP' is invalid: must be one of 'GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE', 'TRACE', 'CONNECT', 'OPTIONS', 'COPY', 'LOCK', 'MKCOL', 'MOVE', 'PROPFIND', 'PROPPATCH', 'UNLOCK'") + suite.Assert().EqualError(suite.validator.Errors()[0], "access control: rule #1 (domain 'public.example.com'): option 'methods' must only have the values 'GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE', 'TRACE', 'CONNECT', 'OPTIONS', 'COPY', 'LOCK', 'MKCOL', 'MOVE', 'PROPFIND', 'PROPPATCH', or 'UNLOCK' but the values 'HOP' are present") +} + +func (suite *AccessControl) TestShouldRaiseErrorDuplicateMethod() { + suite.config.AccessControl.Rules = []schema.ACLRule{ + { + Domains: []string{"public.example.com"}, + Policy: "bypass", + Methods: []string{"GET", "GET"}, + }, + } + + ValidateRules(suite.config, suite.validator) + + suite.Assert().Len(suite.validator.Warnings(), 0) + suite.Require().Len(suite.validator.Errors(), 1) + + suite.Assert().EqualError(suite.validator.Errors()[0], "access control: rule #1 (domain 'public.example.com'): option 'methods' must have unique values but the values 'GET' are duplicated") } func (suite *AccessControl) TestShouldRaiseErrorInvalidSubject() { @@ -367,13 +384,13 @@ func (suite *AccessControl) TestShouldErrorOnInvalidRulesQuery() { suite.Assert().Len(suite.validator.Warnings(), 0) suite.Require().Len(suite.validator.Errors(), 7) - suite.Assert().EqualError(suite.validator.Errors()[0], "access control: rule #1 (domain 'public.example.com'): 'query' option 'value' is invalid: must have a value when the operator is 'equal'") - suite.Assert().EqualError(suite.validator.Errors()[1], "access control: rule #2 (domain 'public.example.com'): 'query' option 'key' is invalid: must have a value") - suite.Assert().EqualError(suite.validator.Errors()[2], "access control: rule #5 (domain 'public.example.com'): 'query' option 'key' is invalid: must have a value") - suite.Assert().EqualError(suite.validator.Errors()[3], "access control: rule #6 (domain 'public.example.com'): 'query' option 'operator' with value 'not' is invalid: must be one of 'present', 'absent', 'equal', 'not equal', 'pattern', 'not pattern'") - suite.Assert().EqualError(suite.validator.Errors()[4], "access control: rule #7 (domain 'public.example.com'): 'query' option 'value' is invalid: error parsing regexp: missing closing ): `(bad pattern`") - suite.Assert().EqualError(suite.validator.Errors()[5], "access control: rule #8 (domain 'public.example.com'): 'query' option 'value' is invalid: must not have a value when the operator is 'present'") - suite.Assert().EqualError(suite.validator.Errors()[6], "access control: rule #9 (domain 'public.example.com'): 'query' option 'value' is invalid: expected type was string but got int") + suite.Assert().EqualError(suite.validator.Errors()[0], "access control: rule #1 (domain 'public.example.com'): query: option 'value' must be present when the option 'operator' is 'equal' but it's absent") + suite.Assert().EqualError(suite.validator.Errors()[1], "access control: rule #2 (domain 'public.example.com'): query: option 'key' is required but it's absent") + suite.Assert().EqualError(suite.validator.Errors()[2], "access control: rule #5 (domain 'public.example.com'): query: option 'key' is required but it's absent") + suite.Assert().EqualError(suite.validator.Errors()[3], "access control: rule #6 (domain 'public.example.com'): query: option 'operator' must be one of 'present', 'absent', 'equal', 'not equal', 'pattern', or 'not pattern' but it's configured as 'not'") + suite.Assert().EqualError(suite.validator.Errors()[4], "access control: rule #7 (domain 'public.example.com'): query: option 'value' is invalid: error parsing regexp: missing closing ): `(bad pattern`") + suite.Assert().EqualError(suite.validator.Errors()[5], "access control: rule #8 (domain 'public.example.com'): query: option 'value' must not be present when the option 'operator' is 'present' but it's present") + suite.Assert().EqualError(suite.validator.Errors()[6], "access control: rule #9 (domain 'public.example.com'): query: option 'value' is invalid: expected type was string but got int") } func TestAccessControl(t *testing.T) { diff --git a/internal/configuration/validator/authentication.go b/internal/configuration/validator/authentication.go index bcd64fabf..fb209f179 100644 --- a/internal/configuration/validator/authentication.go +++ b/internal/configuration/validator/authentication.go @@ -71,7 +71,7 @@ func ValidatePasswordConfiguration(config *schema.Password, validator *schema.St case utils.IsStringInSlice(config.Algorithm, validHashAlgorithms): break default: - validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordUnknownAlg, config.Algorithm, strings.Join(validHashAlgorithms, "', '"))) + validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordUnknownAlg, strJoinOr(validHashAlgorithms), config.Algorithm)) } validateFileAuthenticationBackendPasswordConfigArgon2(config, validator) @@ -89,7 +89,7 @@ func validateFileAuthenticationBackendPasswordConfigArgon2(config *schema.Passwo case utils.IsStringInSlice(config.Argon2.Variant, validArgon2Variants): break default: - validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordInvalidVariant, hashArgon2, config.Argon2.Variant, strings.Join(validArgon2Variants, "', '"))) + validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordInvalidVariant, hashArgon2, strJoinOr(validArgon2Variants), config.Argon2.Variant)) } switch { @@ -147,7 +147,7 @@ func validateFileAuthenticationBackendPasswordConfigSHA2Crypt(config *schema.Pas case utils.IsStringInSlice(config.SHA2Crypt.Variant, validSHA2CryptVariants): break default: - validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordInvalidVariant, hashSHA2Crypt, config.SHA2Crypt.Variant, strings.Join(validSHA2CryptVariants, "', '"))) + validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordInvalidVariant, hashSHA2Crypt, strJoinOr(validSHA2CryptVariants), config.SHA2Crypt.Variant)) } switch { @@ -176,7 +176,7 @@ func validateFileAuthenticationBackendPasswordConfigPBKDF2(config *schema.Passwo case utils.IsStringInSlice(config.PBKDF2.Variant, validPBKDF2Variants): break default: - validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordInvalidVariant, hashPBKDF2, config.PBKDF2.Variant, strings.Join(validPBKDF2Variants, "', '"))) + validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordInvalidVariant, hashPBKDF2, strJoinOr(validPBKDF2Variants), config.PBKDF2.Variant)) } switch { @@ -205,7 +205,7 @@ func validateFileAuthenticationBackendPasswordConfigBCrypt(config *schema.Passwo case utils.IsStringInSlice(config.BCrypt.Variant, validBCryptVariants): break default: - validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordInvalidVariant, hashBCrypt, config.BCrypt.Variant, strings.Join(validBCryptVariants, "', '"))) + validator.Push(fmt.Errorf(errFmtFileAuthBackendPasswordInvalidVariant, hashBCrypt, strJoinOr(validBCryptVariants), config.BCrypt.Variant)) } switch { @@ -369,7 +369,7 @@ func validateLDAPAuthenticationBackendImplementation(config *schema.Authenticati case schema.LDAPImplementationGLAuth: implementation = &schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth default: - validator.Push(fmt.Errorf(errFmtLDAPAuthBackendImplementation, config.LDAP.Implementation, strings.Join(validLDAPImplementations, "', '"))) + validator.Push(fmt.Errorf(errFmtLDAPAuthBackendImplementation, strJoinOr(validLDAPImplementations), config.LDAP.Implementation)) } tlsconfig := &schema.TLSConfig{} diff --git a/internal/configuration/validator/authentication_test.go b/internal/configuration/validator/authentication_test.go index cc540f064..9abec6e1a 100644 --- a/internal/configuration/validator/authentication_test.go +++ b/internal/configuration/validator/authentication_test.go @@ -256,7 +256,7 @@ func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorOnInvalidArgon2 suite.Assert().Len(suite.validator.Warnings(), 0) suite.Require().Len(suite.validator.Errors(), 1) - suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: file: password: argon2: option 'variant' is configured as 'invalid' but must be one of the following values: 'argon2id', 'id', 'argon2i', 'i', 'argon2d', 'd'") + suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: file: password: argon2: option 'variant' must be one of 'argon2id', 'id', 'argon2i', 'i', 'argon2d', or 'd' but it's configured as 'invalid'") } func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorOnInvalidSHA2CryptVariant() { @@ -270,7 +270,7 @@ func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorOnInvalidSHA2Cr suite.Assert().Len(suite.validator.Warnings(), 0) suite.Require().Len(suite.validator.Errors(), 1) - suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: file: password: sha2crypt: option 'variant' is configured as 'invalid' but must be one of the following values: 'sha256', 'sha512'") + suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: file: password: sha2crypt: option 'variant' must be one of 'sha256' or 'sha512' but it's configured as 'invalid'") } func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorOnInvalidSHA2CryptSaltLength() { @@ -298,7 +298,7 @@ func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorOnInvalidPBKDF2 suite.Assert().Len(suite.validator.Warnings(), 0) suite.Require().Len(suite.validator.Errors(), 1) - suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: file: password: pbkdf2: option 'variant' is configured as 'invalid' but must be one of the following values: 'sha1', 'sha224', 'sha256', 'sha384', 'sha512'") + suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: file: password: pbkdf2: option 'variant' must be one of 'sha1', 'sha224', 'sha256', 'sha384', or 'sha512' but it's configured as 'invalid'") } func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorOnInvalidBCryptVariant() { @@ -312,7 +312,7 @@ func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorOnInvalidBCrypt suite.Assert().Len(suite.validator.Warnings(), 0) suite.Require().Len(suite.validator.Errors(), 1) - suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: file: password: bcrypt: option 'variant' is configured as 'invalid' but must be one of the following values: 'standard', 'sha256'") + suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: file: password: bcrypt: option 'variant' must be one of 'standard' or 'sha256' but it's configured as 'invalid'") } func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorWhenSHA2CryptOptionsTooLow() { @@ -497,7 +497,7 @@ func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorWhenBadAlgorith suite.Assert().Len(suite.validator.Warnings(), 0) suite.Require().Len(suite.validator.Errors(), 1) - suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: file: password: option 'algorithm' is configured as 'bogus' but must be one of the following values: 'sha2crypt', 'pbkdf2', 'scrypt', 'bcrypt', 'argon2'") + suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: file: password: option 'algorithm' must be one of 'sha2crypt', 'pbkdf2', 'scrypt', 'bcrypt', or 'argon2' but it's configured as 'bogus'") } func (suite *FileBasedAuthenticationBackend) TestShouldSetDefaultValues() { @@ -609,7 +609,7 @@ func (suite *LDAPAuthenticationBackendSuite) TestShouldRaiseErrorWhenImplementat suite.Assert().Len(suite.validator.Warnings(), 0) suite.Require().Len(suite.validator.Errors(), 1) - suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: option 'implementation' is configured as 'masd' but must be one of the following values: 'custom', 'activedirectory', 'rfc2307bis', 'freeipa', 'lldap', 'glauth'") + suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: option 'implementation' must be one of 'custom', 'activedirectory', 'rfc2307bis', 'freeipa', 'lldap', or 'glauth' but it's configured as 'masd'") } func (suite *LDAPAuthenticationBackendSuite) TestShouldRaiseErrorWhenURLNotProvided() { @@ -755,7 +755,7 @@ func (suite *LDAPAuthenticationBackendSuite) TestShouldRaiseErrorOnBadFilterPlac suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: option 'users_filter' has an invalid placeholder: '{0}' has been removed, please use '{input}' instead") suite.Assert().EqualError(suite.validator.Errors()[1], "authentication_backend: ldap: option 'groups_filter' has an invalid placeholder: '{0}' has been removed, please use '{input}' instead") suite.Assert().EqualError(suite.validator.Errors()[2], "authentication_backend: ldap: option 'groups_filter' has an invalid placeholder: '{1}' has been removed, please use '{username}' instead") - suite.Assert().EqualError(suite.validator.Errors()[3], "authentication_backend: ldap: option 'users_filter' must contain the placeholder '{input}' but it is required") + suite.Assert().EqualError(suite.validator.Errors()[3], "authentication_backend: ldap: option 'users_filter' must contain the placeholder '{input}' but it's absent") } func (suite *LDAPAuthenticationBackendSuite) TestShouldSetDefaultGroupNameAttribute() { @@ -823,7 +823,7 @@ func (suite *LDAPAuthenticationBackendSuite) TestShouldRaiseWhenUsersFilterDoesN suite.Assert().Len(suite.validator.Warnings(), 0) suite.Require().Len(suite.validator.Errors(), 1) - suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: option 'users_filter' must contain the placeholder '{username_attribute}' but it is required") + suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: option 'users_filter' must contain the placeholder '{username_attribute}' but it's absent") } func (suite *LDAPAuthenticationBackendSuite) TestShouldHelpDetectNoInputPlaceholder() { @@ -834,7 +834,7 @@ func (suite *LDAPAuthenticationBackendSuite) TestShouldHelpDetectNoInputPlacehol suite.Assert().Len(suite.validator.Warnings(), 0) suite.Require().Len(suite.validator.Errors(), 1) - suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: option 'users_filter' must contain the placeholder '{input}' but it is required") + suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: option 'users_filter' must contain the placeholder '{input}' but it's absent") } func (suite *LDAPAuthenticationBackendSuite) TestShouldSetDefaultTLSMinimumVersion() { @@ -986,7 +986,7 @@ func (suite *ActiveDirectoryAuthenticationBackendSuite) TestShouldRaiseErrorOnIn validateLDAPAuthenticationBackendURL(suite.config.LDAP, suite.validator) suite.Require().Len(suite.validator.Errors(), 1) - suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: option 'url' must have either the 'ldap' or 'ldaps' scheme but it is configured as 'http'") + suite.Assert().EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: option 'url' must have either the 'ldap' or 'ldaps' scheme but it's configured as 'http'") } func (suite *ActiveDirectoryAuthenticationBackendSuite) TestShouldRaiseErrorOnInvalidURLWithBadCharacters() { diff --git a/internal/configuration/validator/configuration.go b/internal/configuration/validator/configuration.go index 13045b86a..874e0809b 100644 --- a/internal/configuration/validator/configuration.go +++ b/internal/configuration/validator/configuration.go @@ -78,7 +78,7 @@ func validateDefault2FAMethod(config *schema.Configuration, validator *schema.St } if !utils.IsStringInSlice(config.Default2FAMethod, validDefault2FAMethods) { - validator.Push(fmt.Errorf(errFmtInvalidDefault2FAMethod, config.Default2FAMethod, strings.Join(validDefault2FAMethods, "', '"))) + validator.Push(fmt.Errorf(errFmtInvalidDefault2FAMethod, strJoinOr(validDefault2FAMethods), config.Default2FAMethod)) return } @@ -98,6 +98,6 @@ func validateDefault2FAMethod(config *schema.Configuration, validator *schema.St } if !utils.IsStringInSlice(config.Default2FAMethod, enabledMethods) { - validator.Push(fmt.Errorf(errFmtInvalidDefault2FAMethodDisabled, config.Default2FAMethod, strings.Join(enabledMethods, "', '"))) + validator.Push(fmt.Errorf(errFmtInvalidDefault2FAMethodDisabled, strJoinOr(enabledMethods), config.Default2FAMethod)) } } diff --git a/internal/configuration/validator/configuration_test.go b/internal/configuration/validator/configuration_test.go index 77e35c3b7..7fee1355e 100644 --- a/internal/configuration/validator/configuration_test.go +++ b/internal/configuration/validator/configuration_test.go @@ -221,7 +221,7 @@ func TestValidateDefault2FAMethod(t *testing.T) { TOTP: schema.TOTPConfiguration{Disable: true}, }, expectedErrs: []string{ - "option 'default_2fa_method' is configured as 'totp' but must be one of the following enabled method values: 'webauthn', 'mobile_push'", + "option 'default_2fa_method' must be one of the enabled options 'webauthn' or 'mobile_push' but it's configured as 'totp'", }, }, { @@ -236,7 +236,7 @@ func TestValidateDefault2FAMethod(t *testing.T) { Webauthn: schema.WebauthnConfiguration{Disable: true}, }, expectedErrs: []string{ - "option 'default_2fa_method' is configured as 'webauthn' but must be one of the following enabled method values: 'totp', 'mobile_push'", + "option 'default_2fa_method' must be one of the enabled options 'totp' or 'mobile_push' but it's configured as 'webauthn'", }, }, { @@ -246,7 +246,7 @@ func TestValidateDefault2FAMethod(t *testing.T) { DuoAPI: schema.DuoAPIConfiguration{Disable: true}, }, expectedErrs: []string{ - "option 'default_2fa_method' is configured as 'mobile_push' but must be one of the following enabled method values: 'totp', 'webauthn'", + "option 'default_2fa_method' must be one of the enabled options 'totp' or 'webauthn' but it's configured as 'mobile_push'", }, }, { @@ -255,7 +255,7 @@ func TestValidateDefault2FAMethod(t *testing.T) { Default2FAMethod: "duo", }, expectedErrs: []string{ - "option 'default_2fa_method' is configured as 'duo' but must be one of the following values: 'totp', 'webauthn', 'mobile_push'", + "option 'default_2fa_method' must be one of 'totp', 'webauthn', or 'mobile_push' but it's configured as 'duo'", }, }, } diff --git a/internal/configuration/validator/const.go b/internal/configuration/validator/const.go index 44ac2622b..5f697f7e8 100644 --- a/internal/configuration/validator/const.go +++ b/internal/configuration/validator/const.go @@ -67,7 +67,7 @@ const ( ) const ( - errSuffixMustBeOneOf = "is configured as '%s' but must be one of the following values: '%s'" + errSuffixMustBeOneOf = "must be one of %s but it's configured as '%s'" ) // Authentication Backend Error constants. @@ -105,19 +105,19 @@ const ( errFmtLDAPAuthBackendURLNotParsable = "authentication_backend: ldap: option " + "'url' could not be parsed: %w" errFmtLDAPAuthBackendURLInvalidScheme = "authentication_backend: ldap: option " + - "'url' must have either the 'ldap' or 'ldaps' scheme but it is configured as '%s'" + "'url' must have either the 'ldap' or 'ldaps' scheme but it's configured as '%s'" errFmtLDAPAuthBackendFilterEnclosingParenthesis = "authentication_backend: ldap: option " + "'%s' must contain enclosing parenthesis: '%s' should probably be '(%s)'" errFmtLDAPAuthBackendFilterMissingPlaceholder = "authentication_backend: ldap: option " + - "'%s' must contain the placeholder '{%s}' but it is required" + "'%s' must contain the placeholder '{%s}' but it's absent" ) // TOTP Error constants. const ( - errFmtTOTPInvalidAlgorithm = "totp: option 'algorithm' must be one of '%s' but it is configured as '%s'" - errFmtTOTPInvalidPeriod = "totp: option 'period' option must be 15 or more but it is configured as '%d'" - errFmtTOTPInvalidDigits = "totp: option 'digits' must be 6 or 8 but it is configured as '%d'" - errFmtTOTPInvalidSecretSize = "totp: option 'secret_size' must be %d or higher but it is configured as '%d'" //nolint:gosec + errFmtTOTPInvalidAlgorithm = "totp: option 'algorithm' must be one of %s but it's configured as '%s'" + errFmtTOTPInvalidPeriod = "totp: option 'period' option must be 15 or more but it's configured as '%d'" + errFmtTOTPInvalidDigits = "totp: option 'digits' must be 6 or 8 but it's configured as '%d'" + errFmtTOTPInvalidSecretSize = "totp: option 'secret_size' must be %d or higher but it's configured as '%d'" //nolint:gosec ) // Storage Error constants. @@ -128,14 +128,14 @@ const ( errFmtStorageUserPassMustBeProvided = "storage: %s: option 'username' and 'password' are required" //nolint:gosec errFmtStorageOptionMustBeProvided = "storage: %s: option '%s' is required" errFmtStorageTLSConfigInvalid = "storage: %s: tls: %w" - errFmtStoragePostgreSQLInvalidSSLMode = "storage: postgres: ssl: option 'mode' must be one of '%s' but it is configured as '%s'" + errFmtStoragePostgreSQLInvalidSSLMode = "storage: postgres: ssl: option 'mode' must be one of %s but it's configured as '%s'" errFmtStoragePostgreSQLInvalidSSLAndTLSConfig = "storage: postgres: can't define both 'tls' and 'ssl' configuration options" warnFmtStoragePostgreSQLInvalidSSLDeprecated = "storage: postgres: ssl: the ssl configuration options are deprecated and we recommend the tls options instead" ) // Telemetry Error constants. const ( - errFmtTelemetryMetricsScheme = "telemetry: metrics: option 'address' must have a scheme 'tcp://' but it is configured as '%s'" + errFmtTelemetryMetricsScheme = "telemetry: metrics: option 'address' must have a scheme 'tcp://' but it's configured as '%s'" ) // OpenID Error constants. @@ -148,17 +148,16 @@ const ( errFmtOIDCCertificateMismatch = "identity_providers: oidc: option 'issuer_private_key' does not appear to be the private key the certificate provided by option 'issuer_certificate_chain'" errFmtOIDCCertificateChain = "identity_providers: oidc: option 'issuer_certificate_chain' produced an error during validation of the chain: %w" errFmtOIDCEnforcePKCEInvalidValue = "identity_providers: oidc: option 'enforce_pkce' must be 'never', " + - "'public_clients_only' or 'always', but it is configured as '%s'" + "'public_clients_only' or 'always', but it's configured as '%s'" errFmtOIDCCORSInvalidOrigin = "identity_providers: oidc: cors: option 'allowed_origins' contains an invalid value '%s' as it has a %s: origins must only be scheme, hostname, and an optional port" errFmtOIDCCORSInvalidOriginWildcard = "identity_providers: oidc: cors: option 'allowed_origins' contains the wildcard origin '*' with more than one origin but the wildcard origin must be defined by itself" errFmtOIDCCORSInvalidOriginWildcardWithClients = "identity_providers: oidc: cors: option 'allowed_origins' contains the wildcard origin '*' cannot be specified with option 'allowed_origins_from_client_redirect_uris' enabled" - errFmtOIDCCORSInvalidEndpoint = "identity_providers: oidc: cors: option 'endpoints' contains an invalid value '%s': must be one of '%s'" + errFmtOIDCCORSInvalidEndpoint = "identity_providers: oidc: cors: option 'endpoints' contains an invalid value '%s': must be one of %s" - errFmtOIDCClientsDuplicateID = "identity_providers: oidc: one or more clients have the same id but all client" + - "id's must be unique" - errFmtOIDCClientsWithEmptyID = "identity_providers: oidc: one or more clients have been configured with " + - "an empty id" + errFmtOIDCClientsDuplicateID = "identity_providers: oidc: clients: option 'id' must be unique for every client but one or more clients share the following 'id' values %s" + errFmtOIDCClientsWithEmptyID = "identity_providers: oidc: clients: option 'id' is required but was absent on the clients in positions %s" + errFmtOIDCClientsDeprecated = "identity_providers: oidc: clients: warnings for clients above indicate deprecated functionality and it's strongly suggested these issues are checked and fixed if they're legitimate issues or reported if they are not as in a future version these warnings will become errors" errFmtOIDCClientInvalidSecret = "identity_providers: oidc: client '%s': option 'secret' is required" errFmtOIDCClientInvalidSecretPlainText = "identity_providers: oidc: client '%s': option 'secret' is plaintext but it should be a hashed value as plaintext values are deprecated and will be removed when oidc becomes stable" @@ -170,36 +169,43 @@ const ( "redirect uri '%s' when option 'public' is false but this is invalid as this uri is not valid " + "for the openid connect confidential client type" errFmtOIDCClientRedirectURIAbsolute = "identity_providers: oidc: client '%s': option 'redirect_uris' has an " + - "invalid value: redirect uri '%s' must have the scheme but it is absent" - errFmtOIDCClientInvalidPolicy = "identity_providers: oidc: client '%s': option 'policy' must be 'one_factor' " + - "or 'two_factor' but it is configured as '%s'" - errFmtOIDCClientInvalidPKCEChallengeMethod = "identity_providers: oidc: client '%s': option 'pkce_challenge_method' must be 'plain' " + - "or 'S256' but it is configured as '%s'" + "invalid value: redirect uri '%s' must have a scheme but it's absent" errFmtOIDCClientInvalidConsentMode = "identity_providers: oidc: client '%s': consent: option 'mode' must be one of " + - "'%s' but it is configured as '%s'" - errFmtOIDCClientInvalidEntry = "identity_providers: oidc: client '%s': option '%s' must only have the values " + - "'%s' but one option is configured as '%s'" - errFmtOIDCClientInvalidUserinfoAlgorithm = "identity_providers: oidc: client '%s': option " + - "'userinfo_signing_algorithm' must be one of '%s' but it is configured as '%s'" + "%s but it's configured as '%s'" + errFmtOIDCClientInvalidEntries = "identity_providers: oidc: client '%s': option '%s' must only have the values " + + "%s but the values %s are present" + errFmtOIDCClientInvalidEntryDuplicates = "identity_providers: oidc: client '%s': option '%s' must have unique values but the values %s are duplicated" + errFmtOIDCClientInvalidValue = "identity_providers: oidc: client '%s': option " + + "'%s' must be one of %s but it's configured as '%s'" + errFmtOIDCClientInvalidTokenEndpointAuthMethod = "identity_providers: oidc: client '%s': option " + + "'token_endpoint_auth_method' must be one of %s when configured as the confidential client type unless it only includes implicit flow response types such as %s but it's configured as '%s'" + errFmtOIDCClientInvalidTokenEndpointAuthMethodPublic = "identity_providers: oidc: client '%s': option " + + "'token_endpoint_auth_method' must be 'none' when configured as the public client type but it's configured as '%s'" errFmtOIDCClientInvalidSectorIdentifier = "identity_providers: oidc: client '%s': option " + "'sector_identifier' with value '%s': must be a URL with only the host component for example '%s' but it has a %s with the value '%s'" errFmtOIDCClientInvalidSectorIdentifierWithoutValue = "identity_providers: oidc: client '%s': option " + "'sector_identifier' with value '%s': must be a URL with only the host component for example '%s' but it has a %s" errFmtOIDCClientInvalidSectorIdentifierHost = "identity_providers: oidc: client '%s': option " + "'sector_identifier' with value '%s': must be a URL with only the host component but appears to be invalid" + errFmtOIDCClientInvalidGrantTypeMatch = "identity_providers: oidc: client '%s': option " + + "'grant_types' should only have grant type values which are valid with the configured 'response_types' for the client but '%s' expects a response type %s such as %s but the response types are %s" + errFmtOIDCClientInvalidGrantTypeRefresh = "identity_providers: oidc: client '%s': option " + + "'grant_types' should only have the 'refresh_token' value if the client is also configured with the 'offline_access' scope" + errFmtOIDCClientInvalidRefreshTokenOptionWithoutCodeResponseType = "identity_providers: oidc: client '%s': option " + + "'%s' should only have the values %s if the client is also configured with a 'response_type' such as %s which respond with authorization codes" errFmtOIDCServerInsecureParameterEntropy = "openid connect provider: SECURITY ISSUE - minimum parameter entropy is " + "configured to an unsafe value, it should be above 8 but it's configured to %d" ) // Webauthn Error constants. const ( - errFmtWebauthnConveyancePreference = "webauthn: option 'attestation_conveyance_preference' must be one of '%s' but it is configured as '%s'" - errFmtWebauthnUserVerification = "webauthn: option 'user_verification' must be one of 'discouraged', 'preferred', 'required' but it is configured as '%s'" + errFmtWebauthnConveyancePreference = "webauthn: option 'attestation_conveyance_preference' must be one of %s but it's configured as '%s'" + errFmtWebauthnUserVerification = "webauthn: option 'user_verification' must be one of %s but it's configured as '%s'" ) // Access Control error constants. const ( - errFmtAccessControlDefaultPolicyValue = "access control: option 'default_policy' must be one of '%s' but it is " + + errFmtAccessControlDefaultPolicyValue = "access control: option 'default_policy' must be one of %s but it's " + "configured as '%s'" errFmtAccessControlDefaultPolicyWithoutRules = "access control: 'default_policy' option '%s' is invalid: when " + "no rules are specified it must be 'two_factor' or 'one_factor'" @@ -207,10 +213,9 @@ const ( "network '%s' is not a valid IP or CIDR notation" errFmtAccessControlWarnNoRulesDefaultPolicy = "access control: no rules have been specified so the " + "'default_policy' of '%s' is going to be applied to all requests" - errFmtAccessControlRuleNoDomains = "access control: rule %s: rule is invalid: must have the option " + - "'domain' or 'domain_regex' configured" - errFmtAccessControlRuleInvalidPolicy = "access control: rule %s: rule 'policy' option '%s' " + - "is invalid: must be one of 'deny', 'two_factor', 'one_factor' or 'bypass'" + errFmtAccessControlRuleNoDomains = "access control: rule %s: option 'domain' or 'domain_regex' must be present but are both absent" + errFmtAccessControlRuleNoPolicy = "access control: rule %s: option 'policy' must be present but it's absent" + errFmtAccessControlRuleInvalidPolicy = "access control: rule %s: option 'policy' must be one of %s but it's configured as '%s'" errAccessControlRuleBypassPolicyInvalidWithSubjects = "access control: rule %s: 'policy' option 'bypass' is " + "not supported when 'subject' option is configured: see " + "https://www.authelia.com/c/acl#bypass" @@ -221,39 +226,35 @@ const ( "valid Group Name, IP, or CIDR notation" errFmtAccessControlRuleSubjectInvalid = "access control: rule %s: 'subject' option '%s' is " + "invalid: must start with 'user:' or 'group:'" - errFmtAccessControlRuleMethodInvalid = "access control: rule %s: 'methods' option '%s' is " + - "invalid: must be one of '%s'" - errFmtAccessControlRuleQueryInvalid = "access control: rule %s: 'query' option 'operator' with value '%s' is " + - "invalid: must be one of '%s'" - errFmtAccessControlRuleQueryInvalidNoValue = "access control: rule %s: 'query' option '%s' is " + - "invalid: must have a value" - errFmtAccessControlRuleQueryInvalidNoValueOperator = "access control: rule %s: 'query' option '%s' is " + - "invalid: must have a value when the operator is '%s'" - errFmtAccessControlRuleQueryInvalidValue = "access control: rule %s: 'query' option '%s' is " + - "invalid: must not have a value when the operator is '%s'" - errFmtAccessControlRuleQueryInvalidValueParse = "access control: rule %s: 'query' option '%s' is " + + errFmtAccessControlRuleInvalidEntries = "access control: rule %s: option '%s' must only have the values %s but the values %s are present" + errFmtAccessControlRuleInvalidDuplicates = "access control: rule %s: option '%s' must have unique values but the values %s are duplicated" + errFmtAccessControlRuleQueryInvalid = "access control: rule %s: query: option 'operator' must be one of %s but it's configured as '%s'" + errFmtAccessControlRuleQueryInvalidNoValue = "access control: rule %s: query: option '%s' is required but it's absent" + errFmtAccessControlRuleQueryInvalidNoValueOperator = "access control: rule %s: query: option '%s' must be present when the option 'operator' is '%s' but it's absent" + errFmtAccessControlRuleQueryInvalidValue = "access control: rule %s: query: option '%s' must not be present when the option 'operator' is '%s' but it's present" + errFmtAccessControlRuleQueryInvalidValueParse = "access control: rule %s: query: option '%s' is " + "invalid: %w" - errFmtAccessControlRuleQueryInvalidValueType = "access control: rule %s: 'query' option 'value' is " + + errFmtAccessControlRuleQueryInvalidValueType = "access control: rule %s: query: option 'value' is " + "invalid: expected type was string but got %T" ) // Theme Error constants. const ( - errFmtThemeName = "option 'theme' must be one of '%s' but it is configured as '%s'" + errFmtThemeName = "option 'theme' must be one of %s but it's configured as '%s'" ) // NTP Error constants. const ( - errFmtNTPVersion = "ntp: option 'version' must be either 3 or 4 but it is configured as '%d'" + errFmtNTPVersion = "ntp: option 'version' must be either 3 or 4 but it's configured as '%d'" ) // Session error constants. const ( errFmtSessionOptionRequired = "session: option '%s' is required" errFmtSessionLegacyAndWarning = "session: option 'domain' and option 'cookies' can't be specified at the same time" - errFmtSessionSameSite = "session: option 'same_site' must be one of '%s' but is configured as '%s'" + errFmtSessionSameSite = "session: option 'same_site' must be one of %s but it's configured as '%s'" errFmtSessionSecretRequired = "session: option 'secret' is required when using the '%s' provider" - errFmtSessionRedisPortRange = "session: redis: option 'port' must be between 1 and 65535 but is configured as '%d'" + errFmtSessionRedisPortRange = "session: redis: option 'port' must be between 1 and 65535 but it's configured as '%d'" errFmtSessionRedisHostRequired = "session: redis: option 'host' is required" errFmtSessionRedisHostOrNodesRequired = "session: redis: option 'host' or the 'high_availability' option 'nodes' is required" errFmtSessionRedisTLSConfigInvalid = "session: redis: tls: %w" @@ -261,8 +262,8 @@ const ( errFmtSessionRedisSentinelMissingName = "session: redis: high_availability: option 'sentinel_name' is required" errFmtSessionRedisSentinelNodeHostMissing = "session: redis: high_availability: option 'nodes': option 'host' is required for each node but one or more nodes are missing this" - errFmtSessionDomainMustBeRoot = "session: domain config %s: option 'domain' must be the domain you wish to protect not a wildcard domain but it is configured as '%s'" - errFmtSessionDomainSameSite = "session: domain config %s: option 'same_site' must be one of '%s' but is configured as '%s'" + errFmtSessionDomainMustBeRoot = "session: domain config %s: option 'domain' must be the domain you wish to protect not a wildcard domain but it's configured as '%s'" + errFmtSessionDomainSameSite = "session: domain config %s: option 'same_site' must be one of %s but it's configured as '%s'" errFmtSessionDomainRequired = "session: domain config %s: option 'domain' is required" errFmtSessionDomainHasPeriodPrefix = "session: domain config %s: option 'domain' has a prefix of '.' which is not supported or intended behaviour: you can use this at your own risk but we recommend removing it" errFmtSessionDomainDuplicate = "session: domain config %s: option 'domain' is a duplicate value for another configured session domain" @@ -291,8 +292,8 @@ const ( errFmtServerPathNoForwardSlashes = "server: option 'path' must not contain any forward slashes" errFmtServerPathAlphaNum = "server: option 'path' must only contain alpha numeric characters" - errFmtServerEndpointsAuthzImplementation = "server: endpoints: authz: %s: option 'implementation' must be one of '%s' but is configured as '%s'" - errFmtServerEndpointsAuthzStrategy = "server: endpoints: authz: %s: authn_strategies: option 'name' must be one of '%s' but is 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'" errFmtServerEndpointsAuthzStrategyDuplicate = "server: endpoints: authz: %s: authn_strategies: duplicate strategy name detected with name '%s'" errFmtServerEndpointsAuthzPrefixDuplicate = "server: endpoints: authz: %s: endpoint starts with the same prefix as the '%s' endpoint with the '%s' implementation which accepts prefixes as part of its implementation" errFmtServerEndpointsAuthzInvalidName = "server: endpoints: authz: %s: contains invalid characters" @@ -302,7 +303,7 @@ const ( const ( errPasswordPolicyMultipleDefined = "password_policy: only a single password policy mechanism can be specified" - errFmtPasswordPolicyStandardMinLengthNotGreaterThanZero = "password_policy: standard: option 'min_length' must be greater than 0 but is configured as %d" + errFmtPasswordPolicyStandardMinLengthNotGreaterThanZero = "password_policy: standard: option 'min_length' must be greater than 0 but it's configured as %d" errFmtPasswordPolicyZXCVBNMinScoreInvalid = "password_policy: zxcvbn: option 'min_score' is invalid: must be between 1 and 4 but it's configured as %d" ) @@ -312,19 +313,17 @@ const ( ) const ( - errFmtDuoMissingOption = "duo_api: option '%s' is required when duo is enabled but it is missing" + errFmtDuoMissingOption = "duo_api: option '%s' is required when duo is enabled but it's absent" ) // Error constants. const ( - errFmtInvalidDefault2FAMethod = "option 'default_2fa_method' is configured as '%s' but must be one of " + - "the following values: '%s'" - errFmtInvalidDefault2FAMethodDisabled = "option 'default_2fa_method' is configured as '%s' " + - "but must be one of the following enabled method values: '%s'" + errFmtInvalidDefault2FAMethod = "option 'default_2fa_method' must be one of %s but it's configured as '%s'" + errFmtInvalidDefault2FAMethodDisabled = "option 'default_2fa_method' must be one of the enabled options %s but it's configured as '%s'" errFmtReplacedConfigurationKey = "invalid configuration key '%s' was replaced by '%s'" - errFmtLoggingLevelInvalid = "log: option 'level' must be one of '%s' but it is configured as '%s'" + errFmtLoggingLevelInvalid = "log: option 'level' must be one of %s but it's configured as '%s'" errFileHashing = "config key incorrect: authentication_backend.file.hashing should be authentication_backend.file.password" errFilePHashing = "config key incorrect: authentication_backend.file.password_hashing should be authentication_backend.file.password" @@ -357,6 +356,10 @@ const ( authzImplementationExtAuthz = "ExtAuthz" ) +const ( + auto = "auto" +) + var ( validAuthzImplementations = []string{"AuthRequest", "ForwardAuth", authzImplementationExtAuthz, authzImplementationLegacy} validAuthzAuthnStrategies = []string{"CookieSession", "HeaderAuthorization", "HeaderProxyAuthorization", "HeaderAuthRequestProxyAuthorization", "HeaderLegacy"} @@ -372,7 +375,7 @@ var ( var ( validStoragePostgreSQLSSLModes = []string{"disable", "require", "verify-ca", "verify-full"} - validThemeNames = []string{"light", "dark", "grey", "auto"} + validThemeNames = []string{"light", "dark", "grey", auto} validSessionSameSiteValues = []string{"none", "lax", "strict"} validLogLevels = []string{"trace", "debug", "info", "warn", "error"} validWebauthnConveyancePreferences = []string{string(protocol.PreferNoAttestation), string(protocol.PreferIndirectAttestation), string(protocol.PreferDirectAttestation)} @@ -389,19 +392,38 @@ var ( var validDefault2FAMethods = []string{"totp", "webauthn", "mobile_push"} +const ( + attrOIDCScopes = "scopes" + attrOIDCResponseTypes = "response_types" + attrOIDCResponseModes = "response_modes" + attrOIDCGrantTypes = "grant_types" + attrOIDCRedirectURIs = "redirect_uris" + attrOIDCTokenAuthMethod = "token_endpoint_auth_method" + attrOIDCUsrSigAlg = "userinfo_signing_algorithm" + attrOIDCPKCEChallengeMethod = "pkce_challenge_method" +) + var ( - validOIDCScopes = []string{oidc.ScopeOpenID, oidc.ScopeEmail, oidc.ScopeProfile, oidc.ScopeGroups, oidc.ScopeOfflineAccess} - validOIDCGrantTypes = []string{oidc.GrantTypeImplicit, oidc.GrantTypeRefreshToken, oidc.GrantTypeAuthorizationCode, oidc.GrantTypePassword, oidc.GrantTypeClientCredentials} - validOIDCResponseModes = []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery, oidc.ResponseModeFragment} - validOIDCUserinfoAlgorithms = []string{oidc.SigningAlgorithmNone, oidc.SigningAlgorithmRSAWithSHA256} - validOIDCCORSEndpoints = []string{oidc.EndpointAuthorization, oidc.EndpointPushedAuthorizationRequest, oidc.EndpointToken, oidc.EndpointIntrospection, oidc.EndpointRevocation, oidc.EndpointUserinfo} - validOIDCClientConsentModes = []string{"auto", oidc.ClientConsentModeImplicit.String(), oidc.ClientConsentModeExplicit.String(), oidc.ClientConsentModePreConfigured.String()} + validOIDCCORSEndpoints = []string{oidc.EndpointAuthorization, oidc.EndpointPushedAuthorizationRequest, oidc.EndpointToken, oidc.EndpointIntrospection, oidc.EndpointRevocation, oidc.EndpointUserinfo} + + validOIDCClientScopes = []string{oidc.ScopeOpenID, oidc.ScopeEmail, oidc.ScopeProfile, oidc.ScopeGroups, oidc.ScopeOfflineAccess} + validOIDCClientUserinfoAlgorithms = []string{oidc.SigningAlgorithmNone, oidc.SigningAlgorithmRSAWithSHA256} + validOIDCClientConsentModes = []string{auto, oidc.ClientConsentModeImplicit.String(), oidc.ClientConsentModeExplicit.String(), oidc.ClientConsentModePreConfigured.String()} + validOIDCClientResponseModes = []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery, oidc.ResponseModeFragment} + validOIDCClientResponseTypes = []string{oidc.ResponseTypeAuthorizationCodeFlow, oidc.ResponseTypeImplicitFlowIDToken, oidc.ResponseTypeImplicitFlowToken, oidc.ResponseTypeImplicitFlowBoth, oidc.ResponseTypeHybridFlowIDToken, oidc.ResponseTypeHybridFlowToken, oidc.ResponseTypeHybridFlowBoth} + validOIDCClientResponseTypesImplicitFlow = []string{oidc.ResponseTypeImplicitFlowIDToken, oidc.ResponseTypeImplicitFlowToken, oidc.ResponseTypeImplicitFlowBoth} + validOIDCClientResponseTypesHybridFlow = []string{oidc.ResponseTypeHybridFlowIDToken, oidc.ResponseTypeHybridFlowToken, oidc.ResponseTypeHybridFlowBoth} + validOIDCClientResponseTypesRefreshToken = []string{oidc.ResponseTypeAuthorizationCodeFlow, oidc.ResponseTypeHybridFlowIDToken, oidc.ResponseTypeHybridFlowToken, oidc.ResponseTypeHybridFlowBoth} + validOIDCClientGrantTypes = []string{oidc.GrantTypeImplicit, oidc.GrantTypeRefreshToken, oidc.GrantTypeAuthorizationCode} + + validOIDCClientTokenEndpointAuthMethods = []string{oidc.ClientAuthMethodNone, oidc.ClientAuthMethodClientSecretPost, oidc.ClientAuthMethodClientSecretBasic} + validOIDCClientTokenEndpointAuthMethodsConfidential = []string{oidc.ClientAuthMethodClientSecretPost, oidc.ClientAuthMethodClientSecretBasic} ) var ( reKeyReplacer = regexp.MustCompile(`\[\d+]`) reDomainCharacters = regexp.MustCompile(`^[a-z0-9-]+(\.[a-z0-9-]+)+[a-z0-9]$`) - reAuthzEndpointName = regexp.MustCompile(`^[a-zA-Z](([a-zA-Z0-9/\._-]*)([a-zA-Z]))?$`) + reAuthzEndpointName = regexp.MustCompile(`^[a-zA-Z](([a-zA-Z0-9/._-]*)([a-zA-Z]))?$`) ) var replacedKeys = map[string]string{ diff --git a/internal/configuration/validator/duo_test.go b/internal/configuration/validator/duo_test.go index ef4856b56..21cdbf6b1 100644 --- a/internal/configuration/validator/duo_test.go +++ b/internal/configuration/validator/duo_test.go @@ -22,6 +22,11 @@ func TestValidateDuo(t *testing.T) { have: &schema.Configuration{}, expected: schema.DuoAPIConfiguration{Disable: true}, }, + { + desc: "ShouldDisableDuoConfigured", + have: &schema.Configuration{DuoAPI: schema.DuoAPIConfiguration{Disable: true, Hostname: "example.com"}}, + expected: schema.DuoAPIConfiguration{Disable: true, Hostname: "example.com"}, + }, { desc: "ShouldNotDisableDuo", have: &schema.Configuration{DuoAPI: schema.DuoAPIConfiguration{ @@ -46,7 +51,7 @@ func TestValidateDuo(t *testing.T) { IntegrationKey: "test", }, errs: []string{ - "duo_api: option 'secret_key' is required when duo is enabled but it is missing", + "duo_api: option 'secret_key' is required when duo is enabled but it's absent", }, }, { @@ -60,7 +65,7 @@ func TestValidateDuo(t *testing.T) { SecretKey: "test", }, errs: []string{ - "duo_api: option 'integration_key' is required when duo is enabled but it is missing", + "duo_api: option 'integration_key' is required when duo is enabled but it's absent", }, }, { @@ -74,7 +79,7 @@ func TestValidateDuo(t *testing.T) { SecretKey: "test", }, errs: []string{ - "duo_api: option 'hostname' is required when duo is enabled but it is missing", + "duo_api: option 'hostname' is required when duo is enabled but it's absent", }, }, } diff --git a/internal/configuration/validator/identity_providers.go b/internal/configuration/validator/identity_providers.go index b9ea7e9b9..cb61e31db 100644 --- a/internal/configuration/validator/identity_providers.go +++ b/internal/configuration/validator/identity_providers.go @@ -3,6 +3,7 @@ package validator import ( "fmt" "net/url" + "strconv" "strings" "time" @@ -125,10 +126,10 @@ func validateOIDCOptionsCORSAllowedOriginsFromClientRedirectURIs(config *schema. continue } - origin := utils.OriginFromURL(*uri) + origin := utils.OriginFromURL(uri) - if !utils.IsURLInSlice(origin, config.CORS.AllowedOrigins) { - config.CORS.AllowedOrigins = append(config.CORS.AllowedOrigins, origin) + if !utils.IsURLInSlice(*origin, config.CORS.AllowedOrigins) { + config.CORS.AllowedOrigins = append(config.CORS.AllowedOrigins, *origin) } } } @@ -137,113 +138,135 @@ func validateOIDCOptionsCORSAllowedOriginsFromClientRedirectURIs(config *schema. func validateOIDCOptionsCORSEndpoints(config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) { for _, endpoint := range config.CORS.Endpoints { if !utils.IsStringInSlice(endpoint, validOIDCCORSEndpoints) { - val.Push(fmt.Errorf(errFmtOIDCCORSInvalidEndpoint, endpoint, strings.Join(validOIDCCORSEndpoints, "', '"))) + val.Push(fmt.Errorf(errFmtOIDCCORSInvalidEndpoint, endpoint, strJoinOr(validOIDCCORSEndpoints))) } } } func validateOIDCClients(config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) { - invalidID, duplicateIDs := false, false + var ( + errDeprecated bool - var ids []string + clientIDs, duplicateClientIDs, blankClientIDs []string + ) + + errDeprecatedFunc := func() { errDeprecated = true } for c, client := range config.Clients { if client.ID == "" { - invalidID = true + blankClientIDs = append(blankClientIDs, "#"+strconv.Itoa(c+1)) } else { if client.Description == "" { config.Clients[c].Description = client.ID } - if utils.IsStringInSliceFold(client.ID, ids) { - duplicateIDs = true - } - ids = append(ids, client.ID) - } - - if client.Public { - if client.Secret != nil { - val.Push(fmt.Errorf(errFmtOIDCClientPublicInvalidSecret, client.ID)) - } - } else { - if client.Secret == nil { - val.Push(fmt.Errorf(errFmtOIDCClientInvalidSecret, client.ID)) - } else if client.Secret.IsPlainText() { - val.PushWarning(fmt.Errorf(errFmtOIDCClientInvalidSecretPlainText, client.ID)) + if id := strings.ToLower(client.ID); utils.IsStringInSlice(id, clientIDs) { + if !utils.IsStringInSlice(id, duplicateClientIDs) { + duplicateClientIDs = append(duplicateClientIDs, id) + } + } else { + clientIDs = append(clientIDs, id) } } - if client.Policy == "" { - config.Clients[c].Policy = schema.DefaultOpenIDConnectClientConfiguration.Policy - } else if client.Policy != policyOneFactor && client.Policy != policyTwoFactor { - val.Push(fmt.Errorf(errFmtOIDCClientInvalidPolicy, client.ID, client.Policy)) - } - - switch client.PKCEChallengeMethod { - case "", "plain", "S256": - break - default: - val.Push(fmt.Errorf(errFmtOIDCClientInvalidPKCEChallengeMethod, client.ID, client.PKCEChallengeMethod)) - } - - validateOIDCClientConsentMode(c, config, val) - validateOIDCClientSectorIdentifier(client, val) - validateOIDCClientScopes(c, config, val) - validateOIDCClientGrantTypes(c, config, val) - validateOIDCClientResponseTypes(c, config, val) - validateOIDCClientResponseModes(c, config, val) - validateOIDDClientUserinfoAlgorithm(c, config, val) - validateOIDCClientRedirectURIs(client, val) + validateOIDCClient(c, config, val, errDeprecatedFunc) } - if invalidID { - val.Push(fmt.Errorf(errFmtOIDCClientsWithEmptyID)) + if errDeprecated { + val.PushWarning(fmt.Errorf(errFmtOIDCClientsDeprecated)) } - if duplicateIDs { - val.Push(fmt.Errorf(errFmtOIDCClientsDuplicateID)) + if len(blankClientIDs) != 0 { + val.Push(fmt.Errorf(errFmtOIDCClientsWithEmptyID, buildJoinedString(", ", "or", "", blankClientIDs))) + } + + if len(duplicateClientIDs) != 0 { + val.Push(fmt.Errorf(errFmtOIDCClientsDuplicateID, strJoinOr(duplicateClientIDs))) } } -func validateOIDCClientSectorIdentifier(client schema.OpenIDConnectClientConfiguration, val *schema.StructValidator) { - if client.SectorIdentifier.String() != "" { - if utils.IsURLHostComponent(client.SectorIdentifier) || utils.IsURLHostComponentWithPort(client.SectorIdentifier) { +func validateOIDCClient(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator, errDeprecatedFunc func()) { + if config.Clients[c].Public { + if config.Clients[c].Secret != nil { + val.Push(fmt.Errorf(errFmtOIDCClientPublicInvalidSecret, config.Clients[c].ID)) + } + } else { + if config.Clients[c].Secret == nil { + val.Push(fmt.Errorf(errFmtOIDCClientInvalidSecret, config.Clients[c].ID)) + } else if config.Clients[c].Secret.IsPlainText() { + val.PushWarning(fmt.Errorf(errFmtOIDCClientInvalidSecretPlainText, config.Clients[c].ID)) + } + } + + switch config.Clients[c].Policy { + case "": + config.Clients[c].Policy = schema.DefaultOpenIDConnectClientConfiguration.Policy + case policyOneFactor, policyTwoFactor: + break + default: + val.Push(fmt.Errorf(errFmtOIDCClientInvalidValue, config.Clients[c].ID, "policy", strJoinOr([]string{policyOneFactor, policyTwoFactor}), config.Clients[c].Policy)) + } + + switch config.Clients[c].PKCEChallengeMethod { + case "", oidc.PKCEChallengeMethodPlain, oidc.PKCEChallengeMethodSHA256: + break + default: + val.Push(fmt.Errorf(errFmtOIDCClientInvalidValue, config.Clients[c].ID, attrOIDCPKCEChallengeMethod, strJoinOr([]string{oidc.PKCEChallengeMethodPlain, oidc.PKCEChallengeMethodSHA256}), config.Clients[c].PKCEChallengeMethod)) + } + + validateOIDCClientConsentMode(c, config, val) + + validateOIDCClientScopes(c, config, val, errDeprecatedFunc) + validateOIDCClientResponseTypes(c, config, val, errDeprecatedFunc) + validateOIDCClientResponseModes(c, config, val, errDeprecatedFunc) + validateOIDCClientGrantTypes(c, config, val, errDeprecatedFunc) + validateOIDCClientRedirectURIs(c, config, val, errDeprecatedFunc) + + validateOIDCClientTokenEndpointAuthMethod(c, config, val) + validateOIDDClientUserinfoAlgorithm(c, config, val) + + validateOIDCClientSectorIdentifier(c, config, val) +} + +func validateOIDCClientSectorIdentifier(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) { + if config.Clients[c].SectorIdentifier.String() != "" { + if utils.IsURLHostComponent(config.Clients[c].SectorIdentifier) || utils.IsURLHostComponentWithPort(config.Clients[c].SectorIdentifier) { return } - if client.SectorIdentifier.Scheme != "" { - val.Push(fmt.Errorf(errFmtOIDCClientInvalidSectorIdentifier, client.ID, client.SectorIdentifier.String(), client.SectorIdentifier.Host, "scheme", client.SectorIdentifier.Scheme)) + if config.Clients[c].SectorIdentifier.Scheme != "" { + val.Push(fmt.Errorf(errFmtOIDCClientInvalidSectorIdentifier, config.Clients[c].ID, config.Clients[c].SectorIdentifier.String(), config.Clients[c].SectorIdentifier.Host, "scheme", config.Clients[c].SectorIdentifier.Scheme)) - if client.SectorIdentifier.Path != "" { - val.Push(fmt.Errorf(errFmtOIDCClientInvalidSectorIdentifier, client.ID, client.SectorIdentifier.String(), client.SectorIdentifier.Host, "path", client.SectorIdentifier.Path)) + if config.Clients[c].SectorIdentifier.Path != "" { + val.Push(fmt.Errorf(errFmtOIDCClientInvalidSectorIdentifier, config.Clients[c].ID, config.Clients[c].SectorIdentifier.String(), config.Clients[c].SectorIdentifier.Host, "path", config.Clients[c].SectorIdentifier.Path)) } - if client.SectorIdentifier.RawQuery != "" { - val.Push(fmt.Errorf(errFmtOIDCClientInvalidSectorIdentifier, client.ID, client.SectorIdentifier.String(), client.SectorIdentifier.Host, "query", client.SectorIdentifier.RawQuery)) + if config.Clients[c].SectorIdentifier.RawQuery != "" { + val.Push(fmt.Errorf(errFmtOIDCClientInvalidSectorIdentifier, config.Clients[c].ID, config.Clients[c].SectorIdentifier.String(), config.Clients[c].SectorIdentifier.Host, "query", config.Clients[c].SectorIdentifier.RawQuery)) } - if client.SectorIdentifier.Fragment != "" { - val.Push(fmt.Errorf(errFmtOIDCClientInvalidSectorIdentifier, client.ID, client.SectorIdentifier.String(), client.SectorIdentifier.Host, "fragment", client.SectorIdentifier.Fragment)) + if config.Clients[c].SectorIdentifier.Fragment != "" { + val.Push(fmt.Errorf(errFmtOIDCClientInvalidSectorIdentifier, config.Clients[c].ID, config.Clients[c].SectorIdentifier.String(), config.Clients[c].SectorIdentifier.Host, "fragment", config.Clients[c].SectorIdentifier.Fragment)) } - if client.SectorIdentifier.User != nil { - if client.SectorIdentifier.User.Username() != "" { - val.Push(fmt.Errorf(errFmtOIDCClientInvalidSectorIdentifier, client.ID, client.SectorIdentifier.String(), client.SectorIdentifier.Host, "username", client.SectorIdentifier.User.Username())) + if config.Clients[c].SectorIdentifier.User != nil { + if config.Clients[c].SectorIdentifier.User.Username() != "" { + val.Push(fmt.Errorf(errFmtOIDCClientInvalidSectorIdentifier, config.Clients[c].ID, config.Clients[c].SectorIdentifier.String(), config.Clients[c].SectorIdentifier.Host, "username", config.Clients[c].SectorIdentifier.User.Username())) } - if _, set := client.SectorIdentifier.User.Password(); set { - val.Push(fmt.Errorf(errFmtOIDCClientInvalidSectorIdentifierWithoutValue, client.ID, client.SectorIdentifier.String(), client.SectorIdentifier.Host, "password")) + if _, set := config.Clients[c].SectorIdentifier.User.Password(); set { + val.Push(fmt.Errorf(errFmtOIDCClientInvalidSectorIdentifierWithoutValue, config.Clients[c].ID, config.Clients[c].SectorIdentifier.String(), config.Clients[c].SectorIdentifier.Host, "password")) } } - } else if client.SectorIdentifier.Host == "" { - val.Push(fmt.Errorf(errFmtOIDCClientInvalidSectorIdentifierHost, client.ID, client.SectorIdentifier.String())) + } else if config.Clients[c].SectorIdentifier.Host == "" { + val.Push(fmt.Errorf(errFmtOIDCClientInvalidSectorIdentifierHost, config.Clients[c].ID, config.Clients[c].SectorIdentifier.String())) } } } func validateOIDCClientConsentMode(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) { switch { - case utils.IsStringInSlice(config.Clients[c].ConsentMode, []string{"", "auto"}): + case utils.IsStringInSlice(config.Clients[c].ConsentMode, []string{"", auto}): if config.Clients[c].ConsentPreConfiguredDuration != nil { config.Clients[c].ConsentMode = oidc.ClientConsentModePreConfigured.String() } else { @@ -252,7 +275,7 @@ func validateOIDCClientConsentMode(c int, config *schema.OpenIDConnectConfigurat case utils.IsStringInSlice(config.Clients[c].ConsentMode, validOIDCClientConsentModes): break default: - val.Push(fmt.Errorf(errFmtOIDCClientInvalidConsentMode, config.Clients[c].ID, strings.Join(append(validOIDCClientConsentModes, "auto"), "', '"), config.Clients[c].ConsentMode)) + val.Push(fmt.Errorf(errFmtOIDCClientInvalidConsentMode, config.Clients[c].ID, strJoinOr(append(validOIDCClientConsentModes, auto)), config.Clients[c].ConsentMode)) } if config.Clients[c].ConsentMode == oidc.ClientConsentModePreConfigured.String() && config.Clients[c].ConsentPreConfiguredDuration == nil { @@ -260,92 +283,233 @@ func validateOIDCClientConsentMode(c int, config *schema.OpenIDConnectConfigurat } } -func validateOIDCClientScopes(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) { +func validateOIDCClientScopes(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator, errDeprecatedFunc func()) { if len(config.Clients[c].Scopes) == 0 { config.Clients[c].Scopes = schema.DefaultOpenIDConnectClientConfiguration.Scopes - return } if !utils.IsStringInSlice(oidc.ScopeOpenID, config.Clients[c].Scopes) { - config.Clients[c].Scopes = append(config.Clients[c].Scopes, oidc.ScopeOpenID) + config.Clients[c].Scopes = append([]string{oidc.ScopeOpenID}, config.Clients[c].Scopes...) } - for _, scope := range config.Clients[c].Scopes { - if !utils.IsStringInSlice(scope, validOIDCScopes) { - val.Push(fmt.Errorf( - errFmtOIDCClientInvalidEntry, - config.Clients[c].ID, "scopes", strings.Join(validOIDCScopes, "', '"), scope)) - } + invalid, duplicates := validateList(config.Clients[c].Scopes, validOIDCClientScopes, true) + + if len(invalid) != 0 { + val.Push(fmt.Errorf(errFmtOIDCClientInvalidEntries, config.Clients[c].ID, attrOIDCScopes, strJoinOr(validOIDCClientScopes), strJoinAnd(invalid))) + } + + if len(duplicates) != 0 { + errDeprecatedFunc() + + val.PushWarning(fmt.Errorf(errFmtOIDCClientInvalidEntryDuplicates, config.Clients[c].ID, attrOIDCScopes, strJoinAnd(duplicates))) + } + + if utils.IsStringSliceContainsAny([]string{oidc.ScopeOfflineAccess, oidc.ScopeOffline}, config.Clients[c].Scopes) && + !utils.IsStringSliceContainsAny(validOIDCClientResponseTypesRefreshToken, config.Clients[c].ResponseTypes) { + errDeprecatedFunc() + + val.PushWarning(fmt.Errorf(errFmtOIDCClientInvalidRefreshTokenOptionWithoutCodeResponseType, + config.Clients[c].ID, attrOIDCScopes, + strJoinOr([]string{oidc.ScopeOfflineAccess, oidc.ScopeOffline}), + strJoinOr(validOIDCClientResponseTypesRefreshToken)), + ) } } -func validateOIDCClientGrantTypes(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) { - if len(config.Clients[c].GrantTypes) == 0 { - config.Clients[c].GrantTypes = schema.DefaultOpenIDConnectClientConfiguration.GrantTypes - return - } - - for _, grantType := range config.Clients[c].GrantTypes { - if !utils.IsStringInSlice(grantType, validOIDCGrantTypes) { - val.Push(fmt.Errorf( - errFmtOIDCClientInvalidEntry, - config.Clients[c].ID, "grant_types", strings.Join(validOIDCGrantTypes, "', '"), grantType)) - } - } -} - -func validateOIDCClientResponseTypes(c int, config *schema.OpenIDConnectConfiguration, _ *schema.StructValidator) { +func validateOIDCClientResponseTypes(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator, errDeprecatedFunc func()) { if len(config.Clients[c].ResponseTypes) == 0 { config.Clients[c].ResponseTypes = schema.DefaultOpenIDConnectClientConfiguration.ResponseTypes - return + } + + invalid, duplicates := validateList(config.Clients[c].ResponseTypes, validOIDCClientResponseTypes, true) + + if len(invalid) != 0 { + val.PushWarning(fmt.Errorf(errFmtOIDCClientInvalidEntries, config.Clients[c].ID, attrOIDCResponseTypes, strJoinOr(validOIDCClientResponseTypes), strJoinAnd(invalid))) + } + + if len(duplicates) != 0 { + errDeprecatedFunc() + + val.PushWarning(fmt.Errorf(errFmtOIDCClientInvalidEntryDuplicates, config.Clients[c].ID, attrOIDCResponseTypes, strJoinAnd(duplicates))) } } -func validateOIDCClientResponseModes(c int, config *schema.OpenIDConnectConfiguration, validator *schema.StructValidator) { +func validateOIDCClientResponseModes(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator, errDeprecatedFunc func()) { if len(config.Clients[c].ResponseModes) == 0 { config.Clients[c].ResponseModes = schema.DefaultOpenIDConnectClientConfiguration.ResponseModes - return + + for _, responseType := range config.Clients[c].ResponseTypes { + switch responseType { + case oidc.ResponseTypeAuthorizationCodeFlow: + if !utils.IsStringInSlice(oidc.ResponseModeQuery, config.Clients[c].ResponseModes) { + config.Clients[c].ResponseModes = append(config.Clients[c].ResponseModes, oidc.ResponseModeQuery) + } + case oidc.ResponseTypeImplicitFlowIDToken, oidc.ResponseTypeImplicitFlowToken, oidc.ResponseTypeImplicitFlowBoth, + oidc.ResponseTypeHybridFlowIDToken, oidc.ResponseTypeHybridFlowToken, oidc.ResponseTypeHybridFlowBoth: + if !utils.IsStringInSlice(oidc.ResponseModeFragment, config.Clients[c].ResponseModes) { + config.Clients[c].ResponseModes = append(config.Clients[c].ResponseModes, oidc.ResponseModeFragment) + } + } + } } - for _, responseMode := range config.Clients[c].ResponseModes { - if !utils.IsStringInSlice(responseMode, validOIDCResponseModes) { - validator.Push(fmt.Errorf( - errFmtOIDCClientInvalidEntry, - config.Clients[c].ID, "response_modes", strings.Join(validOIDCResponseModes, "', '"), responseMode)) + invalid, duplicates := validateList(config.Clients[c].ResponseModes, validOIDCClientResponseModes, true) + + if len(invalid) != 0 { + val.Push(fmt.Errorf(errFmtOIDCClientInvalidEntries, config.Clients[c].ID, attrOIDCResponseModes, strJoinOr(validOIDCClientResponseModes), strJoinAnd(invalid))) + } + + if len(duplicates) != 0 { + errDeprecatedFunc() + + val.PushWarning(fmt.Errorf(errFmtOIDCClientInvalidEntryDuplicates, config.Clients[c].ID, attrOIDCResponseModes, strJoinAnd(duplicates))) + } +} + +func validateOIDCClientGrantTypes(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator, errDeprecatedFunc func()) { + if len(config.Clients[c].GrantTypes) == 0 { + validateOIDCClientGrantTypesSetDefaults(c, config) + } + + validateOIDCClientGrantTypesCheckRelated(c, config, val, errDeprecatedFunc) + + invalid, duplicates := validateList(config.Clients[c].GrantTypes, validOIDCClientGrantTypes, true) + + if len(invalid) != 0 { + val.Push(fmt.Errorf(errFmtOIDCClientInvalidEntries, config.Clients[c].ID, attrOIDCGrantTypes, strJoinOr(validOIDCClientGrantTypes), strJoinAnd(invalid))) + } + + if len(duplicates) != 0 { + errDeprecatedFunc() + + val.PushWarning(fmt.Errorf(errFmtOIDCClientInvalidEntryDuplicates, config.Clients[c].ID, attrOIDCGrantTypes, strJoinAnd(duplicates))) + } +} + +func validateOIDCClientGrantTypesSetDefaults(c int, config *schema.OpenIDConnectConfiguration) { + for _, responseType := range config.Clients[c].ResponseTypes { + switch responseType { + case oidc.ResponseTypeAuthorizationCodeFlow: + if !utils.IsStringInSlice(oidc.GrantTypeAuthorizationCode, config.Clients[c].GrantTypes) { + config.Clients[c].GrantTypes = append(config.Clients[c].GrantTypes, oidc.GrantTypeAuthorizationCode) + } + case oidc.ResponseTypeImplicitFlowIDToken, oidc.ResponseTypeImplicitFlowToken, oidc.ResponseTypeImplicitFlowBoth: + if !utils.IsStringInSlice(oidc.GrantTypeImplicit, config.Clients[c].GrantTypes) { + config.Clients[c].GrantTypes = append(config.Clients[c].GrantTypes, oidc.GrantTypeImplicit) + } + case oidc.ResponseTypeHybridFlowIDToken, oidc.ResponseTypeHybridFlowToken, oidc.ResponseTypeHybridFlowBoth: + if !utils.IsStringInSlice(oidc.GrantTypeAuthorizationCode, config.Clients[c].GrantTypes) { + config.Clients[c].GrantTypes = append(config.Clients[c].GrantTypes, oidc.GrantTypeAuthorizationCode) + } + + if !utils.IsStringInSlice(oidc.GrantTypeImplicit, config.Clients[c].GrantTypes) { + config.Clients[c].GrantTypes = append(config.Clients[c].GrantTypes, oidc.GrantTypeImplicit) + } } } } +func validateOIDCClientGrantTypesCheckRelated(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator, errDeprecatedFunc func()) { + for _, grantType := range config.Clients[c].GrantTypes { + switch grantType { + case oidc.GrantTypeImplicit: + if !utils.IsStringSliceContainsAny(validOIDCClientResponseTypesImplicitFlow, config.Clients[c].ResponseTypes) && !utils.IsStringSliceContainsAny(validOIDCClientResponseTypesHybridFlow, config.Clients[c].ResponseTypes) { + errDeprecatedFunc() + + val.PushWarning(fmt.Errorf(errFmtOIDCClientInvalidGrantTypeMatch, config.Clients[c].ID, grantType, "for either the implicit or hybrid flow", strJoinOr(append(append([]string{}, validOIDCClientResponseTypesImplicitFlow...), validOIDCClientResponseTypesHybridFlow...)), strJoinAnd(config.Clients[c].ResponseTypes))) + } + case oidc.GrantTypeAuthorizationCode: + if !utils.IsStringInSlice(oidc.ResponseTypeAuthorizationCodeFlow, config.Clients[c].ResponseTypes) && !utils.IsStringSliceContainsAny(validOIDCClientResponseTypesHybridFlow, config.Clients[c].ResponseTypes) { + errDeprecatedFunc() + + val.PushWarning(fmt.Errorf(errFmtOIDCClientInvalidGrantTypeMatch, config.Clients[c].ID, grantType, "for either the authorization code or hybrid flow", strJoinOr(append([]string{oidc.ResponseTypeAuthorizationCodeFlow}, validOIDCClientResponseTypesHybridFlow...)), strJoinAnd(config.Clients[c].ResponseTypes))) + } + case oidc.GrantTypeRefreshToken: + if !utils.IsStringSliceContainsAny([]string{oidc.ScopeOfflineAccess, oidc.ScopeOffline}, config.Clients[c].Scopes) { + errDeprecatedFunc() + + val.PushWarning(fmt.Errorf(errFmtOIDCClientInvalidGrantTypeRefresh, config.Clients[c].ID)) + } + + if !utils.IsStringSliceContainsAny(validOIDCClientResponseTypesRefreshToken, config.Clients[c].ResponseTypes) { + errDeprecatedFunc() + + val.PushWarning(fmt.Errorf(errFmtOIDCClientInvalidRefreshTokenOptionWithoutCodeResponseType, + config.Clients[c].ID, attrOIDCGrantTypes, + strJoinOr([]string{oidc.GrantTypeRefreshToken}), + strJoinOr(validOIDCClientResponseTypesRefreshToken)), + ) + } + } + } +} + +func validateOIDCClientRedirectURIs(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator, errDeprecatedFunc func()) { + var ( + parsedRedirectURI *url.URL + err error + ) + + for _, redirectURI := range config.Clients[c].RedirectURIs { + if redirectURI == oauth2InstalledApp { + if config.Clients[c].Public { + continue + } + + val.Push(fmt.Errorf(errFmtOIDCClientRedirectURIPublic, config.Clients[c].ID, oauth2InstalledApp)) + + continue + } + + if parsedRedirectURI, err = url.Parse(redirectURI); err != nil { + val.Push(fmt.Errorf(errFmtOIDCClientRedirectURICantBeParsed, config.Clients[c].ID, redirectURI, err)) + continue + } + + if !parsedRedirectURI.IsAbs() || (!config.Clients[c].Public && parsedRedirectURI.Scheme == "") { + val.Push(fmt.Errorf(errFmtOIDCClientRedirectURIAbsolute, config.Clients[c].ID, redirectURI)) + return + } + } + + _, duplicates := validateList(config.Clients[c].RedirectURIs, nil, true) + + if len(duplicates) != 0 { + errDeprecatedFunc() + + val.PushWarning(fmt.Errorf(errFmtOIDCClientInvalidEntryDuplicates, config.Clients[c].ID, attrOIDCRedirectURIs, strJoinAnd(duplicates))) + } +} + +func validateOIDCClientTokenEndpointAuthMethod(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) { + implcit := len(config.Clients[c].ResponseTypes) != 0 && utils.IsStringSliceContainsAll(config.Clients[c].ResponseTypes, validOIDCClientResponseTypesImplicitFlow) + + if config.Clients[c].TokenEndpointAuthMethod == "" && (config.Clients[c].Public || implcit) { + config.Clients[c].TokenEndpointAuthMethod = oidc.ClientAuthMethodNone + } + + switch { + case config.Clients[c].TokenEndpointAuthMethod == "": + break + case !utils.IsStringInSlice(config.Clients[c].TokenEndpointAuthMethod, validOIDCClientTokenEndpointAuthMethods): + val.Push(fmt.Errorf(errFmtOIDCClientInvalidValue, + config.Clients[c].ID, attrOIDCTokenAuthMethod, strJoinOr(validOIDCClientTokenEndpointAuthMethods), config.Clients[c].TokenEndpointAuthMethod)) + case config.Clients[c].TokenEndpointAuthMethod == oidc.ClientAuthMethodNone && !config.Clients[c].Public && !implcit: + val.Push(fmt.Errorf(errFmtOIDCClientInvalidTokenEndpointAuthMethod, + config.Clients[c].ID, strJoinOr(validOIDCClientTokenEndpointAuthMethodsConfidential), strJoinAnd(validOIDCClientResponseTypesImplicitFlow), config.Clients[c].TokenEndpointAuthMethod)) + case config.Clients[c].TokenEndpointAuthMethod != oidc.ClientAuthMethodNone && config.Clients[c].Public: + val.Push(fmt.Errorf(errFmtOIDCClientInvalidTokenEndpointAuthMethodPublic, + config.Clients[c].ID, config.Clients[c].TokenEndpointAuthMethod)) + } +} + func validateOIDDClientUserinfoAlgorithm(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) { if config.Clients[c].UserinfoSigningAlgorithm == "" { config.Clients[c].UserinfoSigningAlgorithm = schema.DefaultOpenIDConnectClientConfiguration.UserinfoSigningAlgorithm - } else if !utils.IsStringInSlice(config.Clients[c].UserinfoSigningAlgorithm, validOIDCUserinfoAlgorithms) { - val.Push(fmt.Errorf(errFmtOIDCClientInvalidUserinfoAlgorithm, - config.Clients[c].ID, strings.Join(validOIDCUserinfoAlgorithms, ", "), config.Clients[c].UserinfoSigningAlgorithm)) - } -} - -func validateOIDCClientRedirectURIs(client schema.OpenIDConnectClientConfiguration, val *schema.StructValidator) { - for _, redirectURI := range client.RedirectURIs { - if redirectURI == oauth2InstalledApp { - if client.Public { - continue - } - - val.Push(fmt.Errorf(errFmtOIDCClientRedirectURIPublic, client.ID, oauth2InstalledApp)) - - continue - } - - parsedURL, err := url.Parse(redirectURI) - if err != nil { - val.Push(fmt.Errorf(errFmtOIDCClientRedirectURICantBeParsed, client.ID, redirectURI, err)) - continue - } - - if !parsedURL.IsAbs() || (!client.Public && parsedURL.Scheme == "") { - val.Push(fmt.Errorf(errFmtOIDCClientRedirectURIAbsolute, client.ID, redirectURI)) - return - } + } + + if !utils.IsStringInSlice(config.Clients[c].UserinfoSigningAlgorithm, validOIDCClientUserinfoAlgorithms) { + val.Push(fmt.Errorf(errFmtOIDCClientInvalidValue, + config.Clients[c].ID, attrOIDCUsrSigAlg, strJoinOr(validOIDCClientUserinfoAlgorithms), config.Clients[c].UserinfoSigningAlgorithm)) } } diff --git a/internal/configuration/validator/identity_providers_test.go b/internal/configuration/validator/identity_providers_test.go index 8957c61f8..bdb11f3d0 100644 --- a/internal/configuration/validator/identity_providers_test.go +++ b/internal/configuration/validator/identity_providers_test.go @@ -7,7 +7,6 @@ import ( "errors" "fmt" "net/url" - "strings" "testing" "time" @@ -31,8 +30,8 @@ func TestShouldRaiseErrorWhenInvalidOIDCServerConfiguration(t *testing.T) { require.Len(t, validator.Errors(), 2) - assert.EqualError(t, validator.Errors()[0], errFmtOIDCNoPrivateKey) - assert.EqualError(t, validator.Errors()[1], errFmtOIDCNoClientsConfigured) + assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: option 'issuer_private_key' is required") + assert.EqualError(t, validator.Errors()[1], "identity_providers: oidc: option 'clients' must have one or more clients configured") } func TestShouldNotRaiseErrorWhenCORSEndpointsValid(t *testing.T) { @@ -80,7 +79,7 @@ func TestShouldRaiseErrorWhenCORSEndpointsNotValid(t *testing.T) { require.Len(t, validator.Errors(), 1) - assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: cors: option 'endpoints' contains an invalid value 'invalid_endpoint': must be one of 'authorization', 'pushed-authorization-request', 'token', 'introspection', 'revocation', 'userinfo'") + assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: cors: option 'endpoints' contains an invalid value 'invalid_endpoint': must be one of 'authorization', 'pushed-authorization-request', 'token', 'introspection', 'revocation', or 'userinfo'") } func TestShouldRaiseErrorWhenOIDCPKCEEnforceValueInvalid(t *testing.T) { @@ -97,8 +96,8 @@ func TestShouldRaiseErrorWhenOIDCPKCEEnforceValueInvalid(t *testing.T) { require.Len(t, validator.Errors(), 2) - assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: option 'enforce_pkce' must be 'never', 'public_clients_only' or 'always', but it is configured as 'invalid'") - assert.EqualError(t, validator.Errors()[1], errFmtOIDCNoClientsConfigured) + assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: option 'enforce_pkce' must be 'never', 'public_clients_only' or 'always', but it's configured as 'invalid'") + assert.EqualError(t, validator.Errors()[1], "identity_providers: oidc: option 'clients' must have one or more clients configured") } func TestShouldRaiseErrorWhenOIDCCORSOriginsHasInvalidValues(t *testing.T) { @@ -150,7 +149,7 @@ func TestShouldRaiseErrorWhenOIDCServerNoClients(t *testing.T) { require.Len(t, validator.Errors(), 1) - assert.EqualError(t, validator.Errors()[0], errFmtOIDCNoClientsConfigured) + assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: option 'clients' must have one or more clients configured") } func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) { @@ -180,7 +179,7 @@ func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) { }, Errors: []string{ "identity_providers: oidc: client '': option 'secret' is required", - "identity_providers: oidc: one or more clients have been configured with an empty id", + "identity_providers: oidc: clients: option 'id' is required but was absent on the clients in positions #1", }, }, { @@ -195,7 +194,9 @@ func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) { }, }, }, - Errors: []string{"identity_providers: oidc: client 'client-1': option 'policy' must be 'one_factor' or 'two_factor' but it is configured as 'a-policy'"}, + Errors: []string{ + "identity_providers: oidc: client 'client-1': option 'policy' must be one of 'one_factor' or 'two_factor' but it's configured as 'a-policy'", + }, }, { Name: "ClientIDDuplicated", @@ -213,7 +214,9 @@ func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) { RedirectURIs: []string{}, }, }, - Errors: []string{errFmtOIDCClientsDuplicateID}, + Errors: []string{ + "identity_providers: oidc: clients: option 'id' must be unique for every client but one or more clients share the following 'id' values 'client-x'", + }, }, { Name: "RedirectURIInvalid", @@ -228,7 +231,7 @@ func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) { }, }, Errors: []string{ - fmt.Sprintf(errFmtOIDCClientRedirectURICantBeParsed, "client-check-uri-parse", "http://abc@%two", errors.New("parse \"http://abc@%two\": invalid URL escape \"%tw\"")), + "identity_providers: oidc: client 'client-check-uri-parse': option 'redirect_uris' has an invalid value: redirect uri 'http://abc@%two' could not be parsed: parse \"http://abc@%two\": invalid URL escape \"%tw\"", }, }, { @@ -244,7 +247,7 @@ func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) { }, }, Errors: []string{ - fmt.Sprintf(errFmtOIDCClientRedirectURIAbsolute, "client-check-uri-abs", "google.com"), + "identity_providers: oidc: client 'client-check-uri-abs': option 'redirect_uris' has an invalid value: redirect uri 'google.com' must have a scheme but it's absent", }, }, { @@ -289,12 +292,12 @@ func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) { }, }, Errors: []string{ - fmt.Sprintf(errFmtOIDCClientInvalidSectorIdentifier, "client-invalid-sector", "https://user:pass@example.com/path?query=abc#fragment", exampleDotCom, "scheme", "https"), - fmt.Sprintf(errFmtOIDCClientInvalidSectorIdentifier, "client-invalid-sector", "https://user:pass@example.com/path?query=abc#fragment", exampleDotCom, "path", "/path"), - fmt.Sprintf(errFmtOIDCClientInvalidSectorIdentifier, "client-invalid-sector", "https://user:pass@example.com/path?query=abc#fragment", exampleDotCom, "query", "query=abc"), - fmt.Sprintf(errFmtOIDCClientInvalidSectorIdentifier, "client-invalid-sector", "https://user:pass@example.com/path?query=abc#fragment", exampleDotCom, "fragment", "fragment"), - fmt.Sprintf(errFmtOIDCClientInvalidSectorIdentifier, "client-invalid-sector", "https://user:pass@example.com/path?query=abc#fragment", exampleDotCom, "username", "user"), - fmt.Sprintf(errFmtOIDCClientInvalidSectorIdentifierWithoutValue, "client-invalid-sector", "https://user:pass@example.com/path?query=abc#fragment", exampleDotCom, "password"), + "identity_providers: oidc: client 'client-invalid-sector': option 'sector_identifier' with value 'https://user:pass@example.com/path?query=abc#fragment': must be a URL with only the host component for example 'example.com' but it has a scheme with the value 'https'", + "identity_providers: oidc: client 'client-invalid-sector': option 'sector_identifier' with value 'https://user:pass@example.com/path?query=abc#fragment': must be a URL with only the host component for example 'example.com' but it has a path with the value '/path'", + "identity_providers: oidc: client 'client-invalid-sector': option 'sector_identifier' with value 'https://user:pass@example.com/path?query=abc#fragment': must be a URL with only the host component for example 'example.com' but it has a query with the value 'query=abc'", + "identity_providers: oidc: client 'client-invalid-sector': option 'sector_identifier' with value 'https://user:pass@example.com/path?query=abc#fragment': must be a URL with only the host component for example 'example.com' but it has a fragment with the value 'fragment'", + "identity_providers: oidc: client 'client-invalid-sector': option 'sector_identifier' with value 'https://user:pass@example.com/path?query=abc#fragment': must be a URL with only the host component for example 'example.com' but it has a username with the value 'user'", + "identity_providers: oidc: client 'client-invalid-sector': option 'sector_identifier' with value 'https://user:pass@example.com/path?query=abc#fragment': must be a URL with only the host component for example 'example.com' but it has a password", }, }, { @@ -311,7 +314,7 @@ func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) { }, }, Errors: []string{ - fmt.Sprintf(errFmtOIDCClientInvalidSectorIdentifierHost, "client-invalid-sector", "example.com/path?query=abc#fragment"), + "identity_providers: oidc: client 'client-invalid-sector': option 'sector_identifier' with value 'example.com/path?query=abc#fragment': must be a URL with only the host component but appears to be invalid", }, }, { @@ -328,7 +331,7 @@ func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) { }, }, Errors: []string{ - fmt.Sprintf(errFmtOIDCClientInvalidConsentMode, "client-bad-consent-mode", strings.Join(append(validOIDCClientConsentModes, "auto"), "', '"), "cap"), + "identity_providers: oidc: client 'client-bad-consent-mode': consent: option 'mode' must be one of 'auto', 'implicit', 'explicit', 'pre-configured', or 'auto' but it's configured as 'cap'", }, }, { @@ -345,7 +348,7 @@ func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) { }, }, Errors: []string{ - fmt.Sprintf(errFmtOIDCClientInvalidPKCEChallengeMethod, "client-bad-pkce-mode", "abc"), + "identity_providers: oidc: client 'client-bad-pkce-mode': option 'pkce_challenge_method' must be one of 'plain' or 'S256' but it's configured as 'abc'", }, }, { @@ -362,7 +365,7 @@ func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) { }, }, Errors: []string{ - fmt.Sprintf(errFmtOIDCClientInvalidPKCEChallengeMethod, "client-bad-pkce-mode-s256", "s256"), + "identity_providers: oidc: client 'client-bad-pkce-mode-s256': option 'pkce_challenge_method' must be one of 'plain' or 'S256' but it's configured as 's256'", }, }, } @@ -415,7 +418,7 @@ func TestShouldRaiseErrorWhenOIDCClientConfiguredWithBadScopes(t *testing.T) { ValidateIdentityProviders(config, validator) require.Len(t, validator.Errors(), 1) - assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: client 'good_id': option 'scopes' must only have the values 'openid', 'email', 'profile', 'groups', 'offline_access' but one option is configured as 'bad_scope'") + assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: client 'good_id': option 'scopes' must only have the values 'openid', 'email', 'profile', 'groups', or 'offline_access' but the values 'bad_scope' are present") } func TestShouldRaiseErrorWhenOIDCClientConfiguredWithBadGrantTypes(t *testing.T) { @@ -441,7 +444,7 @@ func TestShouldRaiseErrorWhenOIDCClientConfiguredWithBadGrantTypes(t *testing.T) ValidateIdentityProviders(config, validator) require.Len(t, validator.Errors(), 1) - assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: client 'good_id': option 'grant_types' must only have the values 'implicit', 'refresh_token', 'authorization_code', 'password', 'client_credentials' but one option is configured as 'bad_grant_type'") + assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: client 'good_id': option 'grant_types' must only have the values 'implicit', 'refresh_token', or 'authorization_code' but the values 'bad_grant_type' are present") } func TestShouldNotErrorOnCertificateValid(t *testing.T) { @@ -577,7 +580,7 @@ func TestShouldRaiseErrorWhenOIDCClientConfiguredWithBadResponseModes(t *testing ValidateIdentityProviders(config, validator) require.Len(t, validator.Errors(), 1) - assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: client 'good_id': option 'response_modes' must only have the values 'form_post', 'query', 'fragment' but one option is configured as 'bad_responsemode'") + assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: client 'good_id': option 'response_modes' must only have the values 'form_post', 'query', or 'fragment' but the values 'bad_responsemode' are present") } func TestShouldRaiseErrorWhenOIDCClientConfiguredWithBadUserinfoAlg(t *testing.T) { @@ -603,7 +606,7 @@ func TestShouldRaiseErrorWhenOIDCClientConfiguredWithBadUserinfoAlg(t *testing.T ValidateIdentityProviders(config, validator) require.Len(t, validator.Errors(), 1) - assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: client 'good_id': option 'userinfo_signing_algorithm' must be one of 'none, RS256' but it is configured as 'rs256'") + assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: client 'good_id': option 'userinfo_signing_algorithm' must be one of 'none' or 'RS256' but it's configured as 'rs256'") } func TestValidateIdentityProvidersShouldRaiseWarningOnSecurityIssue(t *testing.T) { @@ -668,8 +671,8 @@ func TestValidateIdentityProvidersShouldRaiseErrorsOnInvalidClientTypes(t *testi require.Len(t, validator.Errors(), 2) assert.Len(t, validator.Warnings(), 0) - assert.EqualError(t, validator.Errors()[0], fmt.Sprintf(errFmtOIDCClientPublicInvalidSecret, "client-with-invalid-secret")) - assert.EqualError(t, validator.Errors()[1], fmt.Sprintf(errFmtOIDCClientRedirectURIPublic, "client-with-bad-redirect-uri", oauth2InstalledApp)) + assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: client 'client-with-invalid-secret': option 'secret' is required to be empty when option 'public' is true") + assert.EqualError(t, validator.Errors()[1], "identity_providers: oidc: client 'client-with-bad-redirect-uri': option 'redirect_uris' has the redirect uri 'urn:ietf:wg:oauth:2.0:oob' when option 'public' is false but this is invalid as this uri is not valid for the openid connect confidential client type") } func TestValidateIdentityProvidersShouldNotRaiseErrorsOnValidClientOptions(t *testing.T) { @@ -758,175 +761,28 @@ func TestValidateIdentityProvidersShouldRaiseWarningOnPlainTextClients(t *testin assert.EqualError(t, validator.Warnings()[0], "identity_providers: oidc: client 'client-with-invalid-secret_standard': option 'secret' is plaintext but it should be a hashed value as plaintext values are deprecated and will be removed when oidc becomes stable") } -func TestValidateIdentityProvidersShouldSetDefaultValues(t *testing.T) { - timeDay := time.Hour * 24 - - validator := schema.NewStructValidator() - config := &schema.IdentityProvidersConfiguration{ - OIDC: &schema.OpenIDConnectConfiguration{ - HMACSecret: "rLABDrx87et5KvRHVUgTm3pezWWd8LMN", - IssuerPrivateKey: MustParseRSAPrivateKey(testKey1), - Clients: []schema.OpenIDConnectClientConfiguration{ - { - ID: "a-client", - Secret: MustDecodeSecret(goodOpenIDConnectClientSecret), - RedirectURIs: []string{ - "https://google.com", - }, - ConsentPreConfiguredDuration: &timeDay, - }, - { - ID: "b-client", - Description: "Normal Description", - Secret: MustDecodeSecret(goodOpenIDConnectClientSecret), - Policy: policyOneFactor, - UserinfoSigningAlgorithm: "RS256", - RedirectURIs: []string{ - "https://google.com", - }, - Scopes: []string{ - "groups", - }, - GrantTypes: []string{ - "refresh_token", - }, - ResponseTypes: []string{ - "token", - "code", - }, - ResponseModes: []string{ - "form_post", - "fragment", - }, - }, - { - ID: "c-client", - Secret: MustDecodeSecret(goodOpenIDConnectClientSecret), - RedirectURIs: []string{ - "https://google.com", - }, - ConsentMode: "implicit", - }, - { - ID: "d-client", - Secret: MustDecodeSecret(goodOpenIDConnectClientSecret), - RedirectURIs: []string{ - "https://google.com", - }, - ConsentMode: "explicit", - }, - { - ID: "e-client", - Secret: MustDecodeSecret(goodOpenIDConnectClientSecret), - RedirectURIs: []string{ - "https://google.com", - }, - ConsentMode: "pre-configured", +// All valid schemes are supported as defined in https://datatracker.ietf.org/doc/html/rfc8252#section-7.1 +func TestValidateOIDCClientRedirectURIsSupportingPrivateUseURISchemes(t *testing.T) { + have := &schema.OpenIDConnectConfiguration{ + Clients: []schema.OpenIDConnectClientConfiguration{ + { + ID: "owncloud", + RedirectURIs: []string{ + "https://www.mywebsite.com", + "http://www.mywebsite.com", + "oc://ios.owncloud.com", + // example given in the RFC https://datatracker.ietf.org/doc/html/rfc8252#section-7.1 + "com.example.app:/oauth2redirect/example-provider", + oauth2InstalledApp, }, }, }, } - ValidateIdentityProviders(config, validator) - - assert.Len(t, validator.Warnings(), 0) - assert.Len(t, validator.Errors(), 0) - - // Assert Clients[0] Policy is set to the default, and the default doesn't override Clients[1]'s Policy. - assert.Equal(t, policyTwoFactor, config.OIDC.Clients[0].Policy) - assert.Equal(t, policyOneFactor, config.OIDC.Clients[1].Policy) - - assert.Equal(t, "none", config.OIDC.Clients[0].UserinfoSigningAlgorithm) - assert.Equal(t, "RS256", config.OIDC.Clients[1].UserinfoSigningAlgorithm) - - // Assert Clients[0] Description is set to the Clients[0] ID, and Clients[1]'s Description is not overridden. - assert.Equal(t, config.OIDC.Clients[0].ID, config.OIDC.Clients[0].Description) - assert.Equal(t, "Normal Description", config.OIDC.Clients[1].Description) - - // Assert Clients[0] ends up configured with the default Scopes. - require.Len(t, config.OIDC.Clients[0].Scopes, 4) - assert.Equal(t, "openid", config.OIDC.Clients[0].Scopes[0]) - assert.Equal(t, "groups", config.OIDC.Clients[0].Scopes[1]) - assert.Equal(t, "profile", config.OIDC.Clients[0].Scopes[2]) - assert.Equal(t, "email", config.OIDC.Clients[0].Scopes[3]) - - // Assert Clients[1] ends up configured with the configured Scopes and the openid Scope. - require.Len(t, config.OIDC.Clients[1].Scopes, 2) - assert.Equal(t, "groups", config.OIDC.Clients[1].Scopes[0]) - assert.Equal(t, "openid", config.OIDC.Clients[1].Scopes[1]) - - // Assert Clients[0] ends up configured with the correct consent mode. - require.NotNil(t, config.OIDC.Clients[0].ConsentPreConfiguredDuration) - assert.Equal(t, time.Hour*24, *config.OIDC.Clients[0].ConsentPreConfiguredDuration) - assert.Equal(t, "pre-configured", config.OIDC.Clients[0].ConsentMode) - - // Assert Clients[1] ends up configured with the correct consent mode. - assert.Nil(t, config.OIDC.Clients[1].ConsentPreConfiguredDuration) - assert.Equal(t, "explicit", config.OIDC.Clients[1].ConsentMode) - - // Assert Clients[0] ends up configured with the default GrantTypes. - require.Len(t, config.OIDC.Clients[0].GrantTypes, 2) - assert.Equal(t, "refresh_token", config.OIDC.Clients[0].GrantTypes[0]) - assert.Equal(t, "authorization_code", config.OIDC.Clients[0].GrantTypes[1]) - - // Assert Clients[1] ends up configured with only the configured GrantTypes. - require.Len(t, config.OIDC.Clients[1].GrantTypes, 1) - assert.Equal(t, "refresh_token", config.OIDC.Clients[1].GrantTypes[0]) - - // Assert Clients[0] ends up configured with the default ResponseTypes. - require.Len(t, config.OIDC.Clients[0].ResponseTypes, 1) - assert.Equal(t, "code", config.OIDC.Clients[0].ResponseTypes[0]) - - // Assert Clients[1] ends up configured only with the configured ResponseTypes. - require.Len(t, config.OIDC.Clients[1].ResponseTypes, 2) - assert.Equal(t, "token", config.OIDC.Clients[1].ResponseTypes[0]) - assert.Equal(t, "code", config.OIDC.Clients[1].ResponseTypes[1]) - - // Assert Clients[0] ends up configured with the default ResponseModes. - require.Len(t, config.OIDC.Clients[0].ResponseModes, 3) - assert.Equal(t, "form_post", config.OIDC.Clients[0].ResponseModes[0]) - assert.Equal(t, "query", config.OIDC.Clients[0].ResponseModes[1]) - assert.Equal(t, "fragment", config.OIDC.Clients[0].ResponseModes[2]) - - // Assert Clients[1] ends up configured only with the configured ResponseModes. - require.Len(t, config.OIDC.Clients[1].ResponseModes, 2) - assert.Equal(t, "form_post", config.OIDC.Clients[1].ResponseModes[0]) - assert.Equal(t, "fragment", config.OIDC.Clients[1].ResponseModes[1]) - - assert.Equal(t, false, config.OIDC.EnableClientDebugMessages) - assert.Equal(t, time.Hour, config.OIDC.AccessTokenLifespan) - assert.Equal(t, time.Minute, config.OIDC.AuthorizeCodeLifespan) - assert.Equal(t, time.Hour, config.OIDC.IDTokenLifespan) - assert.Equal(t, time.Minute*90, config.OIDC.RefreshTokenLifespan) - - assert.Equal(t, "implicit", config.OIDC.Clients[2].ConsentMode) - assert.Nil(t, config.OIDC.Clients[2].ConsentPreConfiguredDuration) - - assert.Equal(t, "explicit", config.OIDC.Clients[3].ConsentMode) - assert.Nil(t, config.OIDC.Clients[3].ConsentPreConfiguredDuration) - - assert.Equal(t, "pre-configured", config.OIDC.Clients[4].ConsentMode) - assert.Equal(t, schema.DefaultOpenIDConnectClientConfiguration.ConsentPreConfiguredDuration, config.OIDC.Clients[4].ConsentPreConfiguredDuration) -} - -// All valid schemes are supported as defined in https://datatracker.ietf.org/doc/html/rfc8252#section-7.1 -func TestValidateOIDCClientRedirectURIsSupportingPrivateUseURISchemes(t *testing.T) { - conf := schema.OpenIDConnectClientConfiguration{ - ID: "owncloud", - RedirectURIs: []string{ - "https://www.mywebsite.com", - "http://www.mywebsite.com", - "oc://ios.owncloud.com", - // example given in the RFC https://datatracker.ietf.org/doc/html/rfc8252#section-7.1 - "com.example.app:/oauth2redirect/example-provider", - oauth2InstalledApp, - }, - } - t.Run("public", func(t *testing.T) { validator := schema.NewStructValidator() - conf.Public = true - validateOIDCClientRedirectURIs(conf, validator) + have.Clients[0].Public = true + validateOIDCClientRedirectURIs(0, have, validator, nil) assert.Len(t, validator.Warnings(), 0) assert.Len(t, validator.Errors(), 0) @@ -934,8 +790,8 @@ func TestValidateOIDCClientRedirectURIsSupportingPrivateUseURISchemes(t *testing t.Run("not public", func(t *testing.T) { validator := schema.NewStructValidator() - conf.Public = false - validateOIDCClientRedirectURIs(conf, validator) + have.Clients[0].Public = false + validateOIDCClientRedirectURIs(0, have, validator, nil) assert.Len(t, validator.Warnings(), 0) assert.Len(t, validator.Errors(), 1) @@ -945,6 +801,1143 @@ func TestValidateOIDCClientRedirectURIsSupportingPrivateUseURISchemes(t *testing }) } +func TestValidateOIDCClients(t *testing.T) { + type tcv struct { + Scopes []string + ResponseTypes []string + ResponseModes []string + GrantTypes []string + } + + testCasses := []struct { + name string + setup func(have *schema.OpenIDConnectConfiguration) + validate func(t *testing.T, have *schema.OpenIDConnectConfiguration) + have tcv + expected tcv + serrs []string // Soft errors which will be warnings before GA. + errs []string + }{ + { + "ShouldSetDefaultResponseTypeAndResponseModes", + nil, + nil, + tcv{ + nil, + nil, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeAuthorizationCodeFlow}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery}, + []string{oidc.GrantTypeAuthorizationCode}, + }, + nil, + nil, + }, + { + "ShouldIncludeMinimalScope", + nil, + nil, + tcv{ + []string{oidc.ScopeEmail}, + nil, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeEmail}, + []string{oidc.ResponseTypeAuthorizationCodeFlow}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery}, + []string{oidc.GrantTypeAuthorizationCode}, + }, + nil, + nil, + }, + { + "ShouldSetDefaultResponseModesFlowAuthorizeCode", + nil, + nil, + tcv{ + nil, + []string{oidc.ResponseTypeAuthorizationCodeFlow}, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeAuthorizationCodeFlow}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery}, + []string{oidc.GrantTypeAuthorizationCode}, + }, + nil, + nil, + }, + { + "ShouldSetDefaultResponseModesFlowImplicit", + nil, + nil, + tcv{ + nil, + []string{oidc.ResponseTypeImplicitFlowBoth}, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeImplicitFlowBoth}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeFragment}, + []string{oidc.GrantTypeImplicit}, + }, + nil, + nil, + }, + { + "ShouldSetDefaultResponseModesFlowHybrid", + nil, + nil, + tcv{ + nil, + []string{oidc.ResponseTypeHybridFlowBoth}, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeHybridFlowBoth}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeFragment}, + []string{oidc.GrantTypeAuthorizationCode, oidc.GrantTypeImplicit}, + }, + nil, + nil, + }, + { + "ShouldSetDefaultResponseModesFlowMixedAuthorizeCodeHybrid", + nil, + nil, + tcv{ + nil, + []string{oidc.ResponseTypeAuthorizationCodeFlow, oidc.ResponseTypeHybridFlowBoth}, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeAuthorizationCodeFlow, oidc.ResponseTypeHybridFlowBoth}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery, oidc.ResponseModeFragment}, + []string{oidc.GrantTypeAuthorizationCode, oidc.GrantTypeImplicit}, + }, + nil, + nil, + }, + { + "ShouldSetDefaultResponseModesFlowMixedAuthorizeCodeImplicit", + nil, + nil, + tcv{ + nil, + []string{oidc.ResponseTypeAuthorizationCodeFlow, oidc.ResponseTypeImplicitFlowBoth}, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeAuthorizationCodeFlow, oidc.ResponseTypeImplicitFlowBoth}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery, oidc.ResponseModeFragment}, + []string{oidc.GrantTypeAuthorizationCode, oidc.GrantTypeImplicit}, + }, + nil, + nil, + }, + { + "ShouldSetDefaultResponseModesFlowMixedAll", + nil, + nil, + tcv{ + nil, + []string{oidc.ResponseTypeAuthorizationCodeFlow, oidc.ResponseTypeImplicitFlowBoth, oidc.ResponseTypeHybridFlowBoth}, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeAuthorizationCodeFlow, oidc.ResponseTypeImplicitFlowBoth, oidc.ResponseTypeHybridFlowBoth}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery, oidc.ResponseModeFragment}, + []string{oidc.GrantTypeAuthorizationCode, oidc.GrantTypeImplicit}, + }, + nil, + nil, + }, + { + "ShouldNotOverrideValues", + nil, + nil, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups}, + []string{oidc.ResponseTypeAuthorizationCodeFlow, oidc.ResponseTypeImplicitFlowBoth, oidc.ResponseTypeHybridFlowBoth}, + []string{oidc.ResponseModeFormPost}, + []string{oidc.GrantTypeAuthorizationCode}, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups}, + []string{oidc.ResponseTypeAuthorizationCodeFlow, oidc.ResponseTypeImplicitFlowBoth, oidc.ResponseTypeHybridFlowBoth}, + []string{oidc.ResponseModeFormPost}, + []string{oidc.GrantTypeAuthorizationCode}, + }, + nil, + nil, + }, + { + "ShouldRaiseErrorOnDuplicateScopes", + nil, + nil, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeProfile, oidc.ScopeOpenID}, + nil, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeProfile, oidc.ScopeOpenID}, + []string{oidc.ResponseTypeAuthorizationCodeFlow}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery}, + []string{oidc.GrantTypeAuthorizationCode}, + }, + []string{ + "identity_providers: oidc: client 'test': option 'scopes' must have unique values but the values 'openid' are duplicated", + }, + nil, + }, + { + "ShouldRaiseErrorOnInvalidScopes", + nil, + nil, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeProfile, "group"}, + nil, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeProfile, "group"}, + []string{oidc.ResponseTypeAuthorizationCodeFlow}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery}, + []string{oidc.GrantTypeAuthorizationCode}, + }, + nil, + []string{ + "identity_providers: oidc: client 'test': option 'scopes' must only have the values 'openid', 'email', 'profile', 'groups', or 'offline_access' but the values 'group' are present", + }, + }, + { + "ShouldRaiseErrorOnMissingAuthorizationCodeFlowResponseTypeWithRefreshTokenValues", + nil, + nil, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeProfile, oidc.ScopeOfflineAccess}, + []string{oidc.ResponseTypeImplicitFlowBoth}, + nil, + []string{oidc.GrantTypeImplicit, oidc.GrantTypeRefreshToken}, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeProfile, oidc.ScopeOfflineAccess}, + []string{oidc.ResponseTypeImplicitFlowBoth}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeFragment}, + []string{oidc.GrantTypeImplicit, oidc.GrantTypeRefreshToken}, + }, + []string{ + "identity_providers: oidc: client 'test': option 'scopes' should only have the values 'offline_access' or 'offline' if the client is also configured with a 'response_type' such as 'code', 'code id_token', 'code token', or 'code id_token token' which respond with authorization codes", + "identity_providers: oidc: client 'test': option 'grant_types' should only have the values 'refresh_token' if the client is also configured with a 'response_type' such as 'code', 'code id_token', 'code token', or 'code id_token token' which respond with authorization codes", + }, + nil, + }, + { + "ShouldRaiseErrorOnDuplicateResponseTypes", + nil, + nil, + tcv{ + nil, + []string{oidc.ResponseTypeAuthorizationCodeFlow, oidc.ResponseTypeImplicitFlowBoth, oidc.ResponseTypeAuthorizationCodeFlow}, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeAuthorizationCodeFlow, oidc.ResponseTypeImplicitFlowBoth, oidc.ResponseTypeAuthorizationCodeFlow}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery, oidc.ResponseModeFragment}, + []string{oidc.GrantTypeAuthorizationCode, oidc.GrantTypeImplicit}, + }, + []string{ + "identity_providers: oidc: client 'test': option 'response_types' must have unique values but the values 'code' are duplicated", + }, + nil, + }, + { + "ShouldRaiseErrorOnInvalidResponseTypesOrder", + nil, + nil, + tcv{ + nil, + []string{oidc.ResponseTypeImplicitFlowBoth, "token id_token"}, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeImplicitFlowBoth, "token id_token"}, + []string{"form_post", "fragment"}, + []string{"implicit"}, + }, + []string{ + "identity_providers: oidc: client 'test': option 'response_types' must only have the values 'code', 'id_token', 'token', 'id_token token', 'code id_token', 'code token', or 'code id_token token' but the values 'token id_token' are present", + }, + nil, + }, + { + "ShouldRaiseErrorOnInvalidResponseTypes", + nil, + nil, + tcv{ + nil, + []string{"not_valid"}, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{"not_valid"}, + []string{oidc.ResponseModeFormPost}, + nil, + }, + []string{ + "identity_providers: oidc: client 'test': option 'response_types' must only have the values 'code', 'id_token', 'token', 'id_token token', 'code id_token', 'code token', or 'code id_token token' but the values 'not_valid' are present", + }, + nil, + }, + { + "ShouldRaiseErrorOnInvalidResponseModes", + nil, + nil, + tcv{ + nil, + nil, + []string{"not_valid"}, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeAuthorizationCodeFlow}, + []string{"not_valid"}, + []string{oidc.GrantTypeAuthorizationCode}, + }, + nil, + []string{ + "identity_providers: oidc: client 'test': option 'response_modes' must only have the values 'form_post', 'query', or 'fragment' but the values 'not_valid' are present", + }, + }, + { + "ShouldRaiseErrorOnDuplicateResponseModes", + nil, + nil, + tcv{ + nil, + nil, + []string{oidc.ResponseModeQuery, oidc.ResponseModeQuery}, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeAuthorizationCodeFlow}, + []string{oidc.ResponseModeQuery, oidc.ResponseModeQuery}, + []string{oidc.GrantTypeAuthorizationCode}, + }, + []string{ + "identity_providers: oidc: client 'test': option 'response_modes' must have unique values but the values 'query' are duplicated", + }, + nil, + }, + { + "ShouldRaiseErrorOnInvalidGrantTypes", + nil, + nil, + tcv{ + nil, + nil, + nil, + []string{"invalid"}, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeAuthorizationCodeFlow}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery}, + []string{"invalid"}, + }, + nil, + []string{ + "identity_providers: oidc: client 'test': option 'grant_types' must only have the values 'implicit', 'refresh_token', or 'authorization_code' but the values 'invalid' are present", + }, + }, + { + "ShouldRaiseErrorOnDuplicateGrantTypes", + nil, + nil, + tcv{ + nil, + nil, + nil, + []string{oidc.GrantTypeAuthorizationCode, oidc.GrantTypeAuthorizationCode}, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeAuthorizationCodeFlow}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery}, + []string{oidc.GrantTypeAuthorizationCode, oidc.GrantTypeAuthorizationCode}, + }, + []string{ + "identity_providers: oidc: client 'test': option 'grant_types' must have unique values but the values 'authorization_code' are duplicated", + }, + nil, + }, + { + "ShouldRaiseErrorOnGrantTypeRefreshTokenWithoutScopeOfflineAccess", + nil, + nil, + tcv{ + nil, + nil, + nil, + []string{oidc.GrantTypeAuthorizationCode, oidc.GrantTypeRefreshToken}, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeAuthorizationCodeFlow}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery}, + []string{oidc.GrantTypeAuthorizationCode, oidc.GrantTypeRefreshToken}, + }, + []string{ + "identity_providers: oidc: client 'test': option 'grant_types' should only have the 'refresh_token' value if the client is also configured with the 'offline_access' scope", + }, + nil, + }, + { + "ShouldRaiseErrorOnGrantTypeAuthorizationCodeWithoutAuthorizationCodeOrHybridFlow", + nil, + nil, + tcv{ + nil, + []string{oidc.ResponseTypeImplicitFlowBoth}, + nil, + []string{oidc.GrantTypeAuthorizationCode}, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeImplicitFlowBoth}, + []string{"form_post", "fragment"}, + []string{oidc.GrantTypeAuthorizationCode}, + }, + []string{ + "identity_providers: oidc: client 'test': option 'grant_types' should only have grant type values which are valid with the configured 'response_types' for the client but 'authorization_code' expects a response type for either the authorization code or hybrid flow such as 'code', 'code id_token', 'code token', or 'code id_token token' but the response types are 'id_token token'", + }, + nil, + }, + { + "ShouldRaiseErrorOnGrantTypeImplicitWithoutImplicitOrHybridFlow", + nil, + nil, + tcv{ + nil, + []string{oidc.ResponseTypeAuthorizationCodeFlow}, + nil, + []string{oidc.GrantTypeImplicit}, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeAuthorizationCodeFlow}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery}, + []string{oidc.GrantTypeImplicit}, + }, + []string{ + "identity_providers: oidc: client 'test': option 'grant_types' should only have grant type values which are valid with the configured 'response_types' for the client but 'implicit' expects a response type for either the implicit or hybrid flow such as 'id_token', 'token', 'id_token token', 'code id_token', 'code token', or 'code id_token token' but the response types are 'code'", + }, + nil, + }, + { + "ShouldValidateCorrectRedirectURIsConfidentialClientType", + func(have *schema.OpenIDConnectConfiguration) { + have.Clients[0].RedirectURIs = []string{ + "https://google.com", + } + }, + func(t *testing.T, have *schema.OpenIDConnectConfiguration) { + assert.Equal(t, []string{"https://google.com"}, have.Clients[0].RedirectURIs) + }, + tcv{ + nil, + nil, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeAuthorizationCodeFlow}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery}, + []string{oidc.GrantTypeAuthorizationCode}, + }, + nil, + nil, + }, + { + "ShouldValidateCorrectRedirectURIsPublicClientType", + func(have *schema.OpenIDConnectConfiguration) { + have.Clients[0].Public = true + have.Clients[0].Secret = nil + have.Clients[0].RedirectURIs = []string{ + oauth2InstalledApp, + } + }, + func(t *testing.T, have *schema.OpenIDConnectConfiguration) { + assert.Equal(t, []string{oauth2InstalledApp}, have.Clients[0].RedirectURIs) + }, + tcv{ + nil, + nil, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeAuthorizationCodeFlow}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery}, + []string{oidc.GrantTypeAuthorizationCode}, + }, + nil, + nil, + }, + { + "ShouldRaiseErrorOnInvalidRedirectURIsPublicOnly", + func(have *schema.OpenIDConnectConfiguration) { + have.Clients[0].RedirectURIs = []string{ + "urn:ietf:wg:oauth:2.0:oob", + } + }, + func(t *testing.T, have *schema.OpenIDConnectConfiguration) { + assert.Equal(t, []string{oauth2InstalledApp}, have.Clients[0].RedirectURIs) + }, + tcv{ + nil, + nil, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeAuthorizationCodeFlow}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery}, + []string{oidc.GrantTypeAuthorizationCode}, + }, + nil, + []string{ + "identity_providers: oidc: client 'test': option 'redirect_uris' has the redirect uri 'urn:ietf:wg:oauth:2.0:oob' when option 'public' is false but this is invalid as this uri is not valid for the openid connect confidential client type", + }, + }, + { + "ShouldRaiseErrorOnInvalidRedirectURIsMalformedURI", + func(have *schema.OpenIDConnectConfiguration) { + have.Clients[0].RedirectURIs = []string{ + "http://abc@%two", + } + }, + func(t *testing.T, have *schema.OpenIDConnectConfiguration) { + assert.Equal(t, []string{"http://abc@%two"}, have.Clients[0].RedirectURIs) + }, + tcv{ + nil, + nil, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeAuthorizationCodeFlow}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery}, + []string{oidc.GrantTypeAuthorizationCode}, + }, + nil, + []string{ + "identity_providers: oidc: client 'test': option 'redirect_uris' has an invalid value: redirect uri 'http://abc@%two' could not be parsed: parse \"http://abc@%two\": invalid URL escape \"%tw\"", + }, + }, + { + "ShouldRaiseErrorOnInvalidRedirectURIsNotAbsolute", + func(have *schema.OpenIDConnectConfiguration) { + have.Clients[0].RedirectURIs = []string{ + "google.com", + } + }, + func(t *testing.T, have *schema.OpenIDConnectConfiguration) { + assert.Equal(t, []string{"google.com"}, have.Clients[0].RedirectURIs) + }, + tcv{ + nil, + nil, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeAuthorizationCodeFlow}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery}, + []string{oidc.GrantTypeAuthorizationCode}, + }, + nil, + []string{ + "identity_providers: oidc: client 'test': option 'redirect_uris' has an invalid value: redirect uri 'google.com' must have a scheme but it's absent", + }, + }, + { + "ShouldRaiseErrorOnDuplicateRedirectURI", + func(have *schema.OpenIDConnectConfiguration) { + have.Clients[0].RedirectURIs = []string{ + "https://google.com", + "https://google.com", + } + }, + func(t *testing.T, have *schema.OpenIDConnectConfiguration) { + assert.Equal(t, []string{"https://google.com", "https://google.com"}, have.Clients[0].RedirectURIs) + }, + tcv{ + nil, + nil, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeAuthorizationCodeFlow}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery}, + []string{oidc.GrantTypeAuthorizationCode}, + }, + []string{ + "identity_providers: oidc: client 'test': option 'redirect_uris' must have unique values but the values 'https://google.com' are duplicated", + }, + nil, + }, + { + "ShouldNotSetDefaultTokenEndpointClientAuthMethodConfidentialClientType", + nil, + func(t *testing.T, have *schema.OpenIDConnectConfiguration) { + assert.Equal(t, "", have.Clients[0].TokenEndpointAuthMethod) + }, + tcv{ + nil, + nil, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeAuthorizationCodeFlow}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery}, + []string{oidc.GrantTypeAuthorizationCode}, + }, + nil, + nil, + }, + { + "ShouldSetDefaultTokenEndpointClientAuthMethodPublicClientType", + func(have *schema.OpenIDConnectConfiguration) { + have.Clients[0].Public = true + have.Clients[0].Secret = nil + }, + func(t *testing.T, have *schema.OpenIDConnectConfiguration) { + assert.Equal(t, oidc.ClientAuthMethodNone, have.Clients[0].TokenEndpointAuthMethod) + }, + tcv{ + nil, + nil, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeAuthorizationCodeFlow}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery}, + []string{oidc.GrantTypeAuthorizationCode}, + }, + nil, + nil, + }, + { + "ShouldSetDefaultTokenEndpointClientAuthMethodConfidentialClientTypeImplicitFlow", + nil, + func(t *testing.T, have *schema.OpenIDConnectConfiguration) { + assert.Equal(t, oidc.ClientAuthMethodNone, have.Clients[0].TokenEndpointAuthMethod) + }, + tcv{ + nil, + []string{oidc.ResponseTypeImplicitFlowIDToken, oidc.ResponseTypeImplicitFlowToken, oidc.ResponseTypeImplicitFlowBoth}, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeImplicitFlowIDToken, oidc.ResponseTypeImplicitFlowToken, oidc.ResponseTypeImplicitFlowBoth}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeFragment}, + []string{oidc.GrantTypeImplicit}, + }, + nil, + nil, + }, + { + "ShouldNotOverrideValidClientAuthMethod", + func(have *schema.OpenIDConnectConfiguration) { + have.Clients[0].TokenEndpointAuthMethod = oidc.ClientAuthMethodClientSecretPost + }, + func(t *testing.T, have *schema.OpenIDConnectConfiguration) { + assert.Equal(t, oidc.ClientAuthMethodClientSecretPost, have.Clients[0].TokenEndpointAuthMethod) + }, + tcv{ + nil, + nil, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeAuthorizationCodeFlow}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery}, + []string{oidc.GrantTypeAuthorizationCode}, + }, + nil, + nil, + }, + { + "ShouldRaiseErrorOnInvalidClientAuthMethod", + func(have *schema.OpenIDConnectConfiguration) { + have.Clients[0].TokenEndpointAuthMethod = "client_credentials" + }, + func(t *testing.T, have *schema.OpenIDConnectConfiguration) { + assert.Equal(t, "client_credentials", have.Clients[0].TokenEndpointAuthMethod) + }, + tcv{ + nil, + nil, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeAuthorizationCodeFlow}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery}, + []string{oidc.GrantTypeAuthorizationCode}, + }, + nil, + []string{ + "identity_providers: oidc: client 'test': option 'token_endpoint_auth_method' must be one of 'none', 'client_secret_post', or 'client_secret_basic' but it's configured as 'client_credentials'", + }, + }, + { + "ShouldRaiseErrorOnInvalidClientAuthMethodForPublicClientType", + func(have *schema.OpenIDConnectConfiguration) { + have.Clients[0].TokenEndpointAuthMethod = oidc.ClientAuthMethodClientSecretBasic + have.Clients[0].Public = true + have.Clients[0].Secret = nil + }, + func(t *testing.T, have *schema.OpenIDConnectConfiguration) { + assert.Equal(t, oidc.ClientAuthMethodClientSecretBasic, have.Clients[0].TokenEndpointAuthMethod) + }, + tcv{ + nil, + nil, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeAuthorizationCodeFlow}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery}, + []string{oidc.GrantTypeAuthorizationCode}, + }, + nil, + []string{ + "identity_providers: oidc: client 'test': option 'token_endpoint_auth_method' must be 'none' when configured as the public client type but it's configured as 'client_secret_basic'", + }, + }, + { + "ShouldRaiseErrorOnInvalidClientAuthMethodForConfidentialClientTypeAuthorizationCodeFlow", + func(have *schema.OpenIDConnectConfiguration) { + have.Clients[0].TokenEndpointAuthMethod = oidc.ClientAuthMethodNone + }, + func(t *testing.T, have *schema.OpenIDConnectConfiguration) { + assert.Equal(t, oidc.ClientAuthMethodNone, have.Clients[0].TokenEndpointAuthMethod) + }, + tcv{ + nil, + nil, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeAuthorizationCodeFlow}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery}, + []string{oidc.GrantTypeAuthorizationCode}, + }, + nil, + []string{ + "identity_providers: oidc: client 'test': option 'token_endpoint_auth_method' must be one of 'client_secret_post' or 'client_secret_basic' when configured as the confidential client type unless it only includes implicit flow response types such as 'id_token', 'token', and 'id_token token' but it's configured as 'none'", + }, + }, + { + "ShouldRaiseErrorOnInvalidClientAuthMethodForConfidentialClientTypeHybridFlow", + func(have *schema.OpenIDConnectConfiguration) { + have.Clients[0].TokenEndpointAuthMethod = oidc.ClientAuthMethodNone + }, + func(t *testing.T, have *schema.OpenIDConnectConfiguration) { + assert.Equal(t, oidc.ClientAuthMethodNone, have.Clients[0].TokenEndpointAuthMethod) + }, + tcv{ + nil, + []string{oidc.ResponseTypeHybridFlowToken}, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeHybridFlowToken}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeFragment}, + []string{oidc.GrantTypeAuthorizationCode, oidc.GrantTypeImplicit}, + }, + nil, + []string{ + "identity_providers: oidc: client 'test': option 'token_endpoint_auth_method' must be one of 'client_secret_post' or 'client_secret_basic' when configured as the confidential client type unless it only includes implicit flow response types such as 'id_token', 'token', and 'id_token token' but it's configured as 'none'", + }, + }, + { + "ShouldSetDefaultUserInfoAlg", + nil, + func(t *testing.T, have *schema.OpenIDConnectConfiguration) { + assert.Equal(t, oidc.SigningAlgorithmNone, have.Clients[0].UserinfoSigningAlgorithm) + }, + tcv{ + nil, + nil, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeAuthorizationCodeFlow}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery}, + []string{oidc.GrantTypeAuthorizationCode}, + }, + nil, + nil, + }, + { + "ShouldNotOverrideUserInfoAlg", + func(have *schema.OpenIDConnectConfiguration) { + have.Clients[0].UserinfoSigningAlgorithm = oidc.SigningAlgorithmRSAWithSHA256 + }, + func(t *testing.T, have *schema.OpenIDConnectConfiguration) { + assert.Equal(t, oidc.SigningAlgorithmRSAWithSHA256, have.Clients[0].UserinfoSigningAlgorithm) + }, + tcv{ + nil, + nil, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeAuthorizationCodeFlow}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery}, + []string{oidc.GrantTypeAuthorizationCode}, + }, + nil, + nil, + }, + { + "ShouldRaiseErrorOnInvalidUserInfoAlg", + func(have *schema.OpenIDConnectConfiguration) { + have.Clients[0].UserinfoSigningAlgorithm = "rs256" + }, + func(t *testing.T, have *schema.OpenIDConnectConfiguration) { + assert.Equal(t, "rs256", have.Clients[0].UserinfoSigningAlgorithm) + }, + tcv{ + nil, + nil, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeAuthorizationCodeFlow}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery}, + []string{oidc.GrantTypeAuthorizationCode}, + }, + nil, + []string{ + "identity_providers: oidc: client 'test': option 'userinfo_signing_algorithm' must be one of 'none' or 'RS256' but it's configured as 'rs256'", + }, + }, + { + "ShouldSetDefaultConsentMode", + nil, + func(t *testing.T, have *schema.OpenIDConnectConfiguration) { + assert.Equal(t, "explicit", have.Clients[0].ConsentMode) + }, + tcv{ + nil, + nil, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeAuthorizationCodeFlow}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery}, + []string{oidc.GrantTypeAuthorizationCode}, + }, + nil, + nil, + }, + { + "ShouldSetDefaultConsentModeAuto", + func(have *schema.OpenIDConnectConfiguration) { + have.Clients[0].ConsentMode = auto + }, + func(t *testing.T, have *schema.OpenIDConnectConfiguration) { + assert.Equal(t, "explicit", have.Clients[0].ConsentMode) + }, + tcv{ + nil, + nil, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeAuthorizationCodeFlow}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery}, + []string{oidc.GrantTypeAuthorizationCode}, + }, + nil, + nil, + }, + { + "ShouldSetDefaultConsentModePreConfigured", + func(have *schema.OpenIDConnectConfiguration) { + d := time.Minute + + have.Clients[0].ConsentMode = "" + have.Clients[0].ConsentPreConfiguredDuration = &d + }, + func(t *testing.T, have *schema.OpenIDConnectConfiguration) { + assert.Equal(t, "pre-configured", have.Clients[0].ConsentMode) + }, + tcv{ + nil, + nil, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeAuthorizationCodeFlow}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery}, + []string{oidc.GrantTypeAuthorizationCode}, + }, + nil, + nil, + }, + { + "ShouldSetDefaultConsentModeAutoPreConfigured", + func(have *schema.OpenIDConnectConfiguration) { + d := time.Minute + + have.Clients[0].ConsentMode = auto + have.Clients[0].ConsentPreConfiguredDuration = &d + }, + func(t *testing.T, have *schema.OpenIDConnectConfiguration) { + assert.Equal(t, "pre-configured", have.Clients[0].ConsentMode) + }, + tcv{ + nil, + nil, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeAuthorizationCodeFlow}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery}, + []string{oidc.GrantTypeAuthorizationCode}, + }, + nil, + nil, + }, + { + "ShouldNotOverrideConsentMode", + func(have *schema.OpenIDConnectConfiguration) { + have.Clients[0].ConsentMode = "implicit" + }, + func(t *testing.T, have *schema.OpenIDConnectConfiguration) { + assert.Equal(t, "implicit", have.Clients[0].ConsentMode) + }, + tcv{ + nil, + nil, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeAuthorizationCodeFlow}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery}, + []string{oidc.GrantTypeAuthorizationCode}, + }, + nil, + nil, + }, + { + "ShouldSentConsentPreConfiguredDefaultDuration", + func(have *schema.OpenIDConnectConfiguration) { + have.Clients[0].ConsentMode = "pre-configured" + }, + func(t *testing.T, have *schema.OpenIDConnectConfiguration) { + assert.Equal(t, "pre-configured", have.Clients[0].ConsentMode) + assert.Equal(t, schema.DefaultOpenIDConnectClientConfiguration.ConsentPreConfiguredDuration, have.Clients[0].ConsentPreConfiguredDuration) + }, + tcv{ + nil, + nil, + nil, + nil, + }, + tcv{ + []string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail}, + []string{oidc.ResponseTypeAuthorizationCodeFlow}, + []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery}, + []string{oidc.GrantTypeAuthorizationCode}, + }, + nil, + nil, + }, + } + + errDeprecatedFunc := func() {} + + for _, tc := range testCasses { + t.Run(tc.name, func(t *testing.T) { + have := &schema.OpenIDConnectConfiguration{ + Clients: []schema.OpenIDConnectClientConfiguration{ + { + ID: "test", + Secret: MustDecodeSecret("$pbkdf2-sha512$310000$c8p78n7pUMln0jzvd4aK4Q$JNRBzwAo0ek5qKn50cFzzvE9RXV88h1wJn5KGiHrD0YKtZaR/nCb2CJPOsKaPK0hjf.9yHxzQGZziziccp6Yng"), + Scopes: tc.have.Scopes, + ResponseModes: tc.have.ResponseModes, + ResponseTypes: tc.have.ResponseTypes, + GrantTypes: tc.have.GrantTypes, + }, + }, + } + + if tc.setup != nil { + tc.setup(have) + } + + val := schema.NewStructValidator() + + validateOIDCClient(0, have, val, errDeprecatedFunc) + + t.Run("General", func(t *testing.T) { + assert.Equal(t, tc.expected.Scopes, have.Clients[0].Scopes) + assert.Equal(t, tc.expected.ResponseTypes, have.Clients[0].ResponseTypes) + assert.Equal(t, tc.expected.ResponseModes, have.Clients[0].ResponseModes) + assert.Equal(t, tc.expected.GrantTypes, have.Clients[0].GrantTypes) + + if tc.validate != nil { + tc.validate(t, have) + } + }) + + t.Run("Warnings", func(t *testing.T) { + require.Len(t, val.Warnings(), len(tc.serrs)) + for i, err := range tc.serrs { + assert.EqualError(t, val.Warnings()[i], err) + } + }) + + t.Run("Errors", func(t *testing.T) { + require.Len(t, val.Errors(), len(tc.errs)) + for i, err := range tc.errs { + assert.EqualError(t, val.Errors()[i], err) + } + }) + }) + } +} + +func TestValidateOIDCClientTokenEndpointAuthMethod(t *testing.T) { + testCasses := []struct { + name string + have string + public bool + expected string + errs []string + }{ + {"ShouldSetDefaultValueConfidential", "", false, "", nil}, + {"ShouldSetDefaultValuePublic", "", true, oidc.ClientAuthMethodNone, nil}, + {"ShouldErrorOnInvalidValue", "abc", false, "abc", + []string{ + "identity_providers: oidc: client 'test': option 'token_endpoint_auth_method' must be one of 'none', 'client_secret_post', or 'client_secret_basic' but it's configured as 'abc'", + }, + }, + {"ShouldErrorOnInvalidValueForPublicClient", "client_secret_post", true, "client_secret_post", + []string{ + "identity_providers: oidc: client 'test': option 'token_endpoint_auth_method' must be 'none' when configured as the public client type but it's configured as 'client_secret_post'", + }, + }, + {"ShouldErrorOnInvalidValueForConfidentialClient", "none", false, "none", + []string{ + "identity_providers: oidc: client 'test': option 'token_endpoint_auth_method' must be one of 'client_secret_post' or 'client_secret_basic' when configured as the confidential client type unless it only includes implicit flow response types such as 'id_token', 'token', and 'id_token token' but it's configured as 'none'", + }, + }, + } + + for _, tc := range testCasses { + t.Run(tc.name, func(t *testing.T) { + have := &schema.OpenIDConnectConfiguration{ + Clients: []schema.OpenIDConnectClientConfiguration{ + { + ID: "test", + Public: tc.public, + TokenEndpointAuthMethod: tc.have, + }, + }, + } + + val := schema.NewStructValidator() + + validateOIDCClientTokenEndpointAuthMethod(0, have, val) + + assert.Equal(t, tc.expected, have.Clients[0].TokenEndpointAuthMethod) + assert.Len(t, val.Warnings(), 0) + require.Len(t, val.Errors(), len(tc.errs)) + + if tc.errs != nil { + for i, err := range tc.errs { + assert.EqualError(t, val.Errors()[i], err) + } + } + }) + } +} + func MustDecodeSecret(value string) *schema.PasswordDigest { if secret, err := schema.DecodePasswordDigest(value); err != nil { panic(err) diff --git a/internal/configuration/validator/keys.go b/internal/configuration/validator/keys.go index 67b9d964d..863d2030e 100644 --- a/internal/configuration/validator/keys.go +++ b/internal/configuration/validator/keys.go @@ -100,7 +100,7 @@ func NewKeyMapPattern(key string) (pattern *regexp.Regexp, err error) { } if i < n { - buf.WriteString("\\.[a-z0-9]([a-z0-9-_]+)?[a-z0-9]") + buf.WriteString("\\.[a-z0-9](([a-z0-9-_]+)?[a-z0-9])?") } } diff --git a/internal/configuration/validator/keys_test.go b/internal/configuration/validator/keys_test.go index 989b4b815..a5123ef8c 100644 --- a/internal/configuration/validator/keys_test.go +++ b/internal/configuration/validator/keys_test.go @@ -101,6 +101,20 @@ func TestSpecificErrorKeys(t *testing.T) { assert.EqualError(t, errs[4], specificErrorKeys["authentication_backend.file.hashing.algorithm"]) } +func TestPatternKeys(t *testing.T) { + configKeys := []string{ + "server.endpoints.authz.xx.implementation", + "server.endpoints.authz.x.implementation", + } + + val := schema.NewStructValidator() + ValidateKeys(configKeys, "AUTHELIA_", val) + + errs := val.Errors() + + require.Len(t, errs, 0) +} + func TestReplacedErrors(t *testing.T) { configKeys := []string{ "authentication_backend.ldap.skip_verify", diff --git a/internal/configuration/validator/log.go b/internal/configuration/validator/log.go index 5c7a0761b..7b8c7f6ea 100644 --- a/internal/configuration/validator/log.go +++ b/internal/configuration/validator/log.go @@ -2,7 +2,6 @@ package validator import ( "fmt" - "strings" "github.com/authelia/authelia/v4/internal/configuration/schema" "github.com/authelia/authelia/v4/internal/utils" @@ -19,6 +18,6 @@ func ValidateLog(config *schema.Configuration, validator *schema.StructValidator } if !utils.IsStringInSlice(config.Log.Level, validLogLevels) { - validator.Push(fmt.Errorf(errFmtLoggingLevelInvalid, strings.Join(validLogLevels, "', '"), config.Log.Level)) + validator.Push(fmt.Errorf(errFmtLoggingLevelInvalid, strJoinOr(validLogLevels), config.Log.Level)) } } diff --git a/internal/configuration/validator/log_test.go b/internal/configuration/validator/log_test.go index 56cff19de..cf3de2736 100644 --- a/internal/configuration/validator/log_test.go +++ b/internal/configuration/validator/log_test.go @@ -40,5 +40,5 @@ func TestShouldRaiseErrorOnInvalidLoggingLevel(t *testing.T) { assert.Len(t, validator.Warnings(), 0) require.Len(t, validator.Errors(), 1) - assert.EqualError(t, validator.Errors()[0], "log: option 'level' must be one of 'trace', 'debug', 'info', 'warn', 'error' but it is configured as 'TRACE'") + assert.EqualError(t, validator.Errors()[0], "log: option 'level' must be one of 'trace', 'debug', 'info', 'warn', or 'error' but it's configured as 'TRACE'") } diff --git a/internal/configuration/validator/notifier_test.go b/internal/configuration/validator/notifier_test.go index c41776a29..17819993f 100644 --- a/internal/configuration/validator/notifier_test.go +++ b/internal/configuration/validator/notifier_test.go @@ -4,8 +4,10 @@ import ( "crypto/tls" "fmt" "net/mail" + "path/filepath" "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" "github.com/authelia/authelia/v4/internal/configuration/schema" @@ -187,6 +189,32 @@ func (suite *NotifierSuite) TestSMTPShouldEnsureSenderIsProvided() { suite.Assert().EqualError(suite.validator.Errors()[0], fmt.Sprintf(errFmtNotifierSMTPNotConfigured, "sender")) } +func (suite *NotifierSuite) TestTemplatesEmptyDir() { + dir := suite.T().TempDir() + + suite.config.TemplatePath = dir + + ValidateNotifier(&suite.config, suite.validator) + + suite.Assert().Len(suite.validator.Warnings(), 0) + suite.Assert().Len(suite.validator.Errors(), 0) +} + +func (suite *NotifierSuite) TestTemplatesEmptyDirNoExist() { + dir := suite.T().TempDir() + + p := filepath.Join(dir, "notexist") + + suite.config.TemplatePath = p + + ValidateNotifier(&suite.config, suite.validator) + + suite.Assert().Len(suite.validator.Warnings(), 0) + suite.Assert().Len(suite.validator.Errors(), 1) + + assert.EqualError(suite.T(), suite.validator.Errors()[0], fmt.Sprintf("notifier: option 'template_path' refers to location '%s' which does not exist", p)) +} + /* File Tests. */ diff --git a/internal/configuration/validator/ntp_test.go b/internal/configuration/validator/ntp_test.go index 9780245f9..0bae5b830 100644 --- a/internal/configuration/validator/ntp_test.go +++ b/internal/configuration/validator/ntp_test.go @@ -49,5 +49,5 @@ func TestShouldRaiseErrorOnInvalidNTPVersion(t *testing.T) { require.Len(t, validator.Errors(), 1) - assert.EqualError(t, validator.Errors()[0], "ntp: option 'version' must be either 3 or 4 but it is configured as '1'") + assert.EqualError(t, validator.Errors()[0], "ntp: option 'version' must be either 3 or 4 but it's configured as '1'") } diff --git a/internal/configuration/validator/password_policy_test.go b/internal/configuration/validator/password_policy_test.go index 3d27f08d6..5ce417c01 100644 --- a/internal/configuration/validator/password_policy_test.go +++ b/internal/configuration/validator/password_policy_test.go @@ -39,7 +39,7 @@ func TestValidatePasswordPolicy(t *testing.T) { }, expectedErrs: []string{ "password_policy: only a single password policy mechanism can be specified", - "password_policy: standard: option 'min_length' must be greater than 0 but is configured as -1", + "password_policy: standard: option 'min_length' must be greater than 0 but it's configured as -1", }, }, { diff --git a/internal/configuration/validator/server.go b/internal/configuration/validator/server.go index 66a12d150..c38850634 100644 --- a/internal/configuration/validator/server.go +++ b/internal/configuration/validator/server.go @@ -155,13 +155,13 @@ func validateServerEndpointsAuthzEndpoint(config *schema.Configuration, name str config.Server.Endpoints.Authz[name] = endpoint default: if !utils.IsStringInSlice(endpoint.Implementation, validAuthzImplementations) { - validator.Push(fmt.Errorf(errFmtServerEndpointsAuthzImplementation, name, strings.Join(validAuthzImplementations, "', '"), endpoint.Implementation)) + validator.Push(fmt.Errorf(errFmtServerEndpointsAuthzImplementation, name, strJoinOr(validAuthzImplementations), endpoint.Implementation)) } else { validator.Push(fmt.Errorf(errFmtServerEndpointsAuthzLegacyInvalidImplementation, name)) } } } else if !utils.IsStringInSlice(endpoint.Implementation, validAuthzImplementations) { - validator.Push(fmt.Errorf(errFmtServerEndpointsAuthzImplementation, name, strings.Join(validAuthzImplementations, "', '"), endpoint.Implementation)) + validator.Push(fmt.Errorf(errFmtServerEndpointsAuthzImplementation, name, strJoinOr(validAuthzImplementations), endpoint.Implementation)) } if !reAuthzEndpointName.MatchString(name) { @@ -180,7 +180,7 @@ func validateServerEndpointsAuthzStrategies(name string, strategies []schema.Ser names = append(names, strategy.Name) if !utils.IsStringInSlice(strategy.Name, validAuthzAuthnStrategies) { - validator.Push(fmt.Errorf(errFmtServerEndpointsAuthzStrategy, name, strings.Join(validAuthzAuthnStrategies, "', '"), strategy.Name)) + validator.Push(fmt.Errorf(errFmtServerEndpointsAuthzStrategy, name, strJoinOr(validAuthzAuthnStrategies), strategy.Name)) } } } diff --git a/internal/configuration/validator/server_test.go b/internal/configuration/validator/server_test.go index c70e7124e..cf330d393 100644 --- a/internal/configuration/validator/server_test.go +++ b/internal/configuration/validator/server_test.go @@ -314,14 +314,18 @@ func TestServerAuthzEndpointErrors(t *testing.T) { map[string]schema.ServerAuthzEndpoint{ "example": {Implementation: "zero"}, }, - []string{"server: endpoints: authz: example: option 'implementation' must be one of 'AuthRequest', 'ForwardAuth', 'ExtAuthz', 'Legacy' but is configured as 'zero'"}, + []string{ + "server: endpoints: authz: example: option 'implementation' must be one of 'AuthRequest', 'ForwardAuth', 'ExtAuthz', or 'Legacy' but it's configured as 'zero'", + }, }, { "ShouldErrorOnInvalidEndpointImplementationLegacy", map[string]schema.ServerAuthzEndpoint{ "legacy": {Implementation: "zero"}, }, - []string{"server: endpoints: authz: legacy: option 'implementation' must be one of 'AuthRequest', 'ForwardAuth', 'ExtAuthz', 'Legacy' but is configured as 'zero'"}, + []string{ + "server: endpoints: authz: legacy: option 'implementation' must be one of 'AuthRequest', 'ForwardAuth', 'ExtAuthz', or 'Legacy' but it's configured as 'zero'", + }, }, { "ShouldErrorOnInvalidEndpointLegacyImplementation", @@ -335,7 +339,9 @@ func TestServerAuthzEndpointErrors(t *testing.T) { map[string]schema.ServerAuthzEndpoint{ "example": {Implementation: "ExtAuthz", AuthnStrategies: []schema.ServerAuthzEndpointAuthnStrategy{{Name: "bad-name"}}}, }, - []string{"server: endpoints: authz: example: authn_strategies: option 'name' must be one of 'CookieSession', 'HeaderAuthorization', 'HeaderProxyAuthorization', 'HeaderAuthRequestProxyAuthorization', 'HeaderLegacy' but is configured as 'bad-name'"}, + []string{ + "server: endpoints: authz: example: authn_strategies: option 'name' must be one of 'CookieSession', 'HeaderAuthorization', 'HeaderProxyAuthorization', 'HeaderAuthRequestProxyAuthorization', or 'HeaderLegacy' but it's configured as 'bad-name'", + }, }, { "ShouldErrorOnDuplicateName", diff --git a/internal/configuration/validator/session.go b/internal/configuration/validator/session.go index f63d24ded..1de078ef8 100644 --- a/internal/configuration/validator/session.go +++ b/internal/configuration/validator/session.go @@ -45,7 +45,7 @@ func validateSession(config *schema.SessionConfiguration, validator *schema.Stru if config.SameSite == "" { config.SameSite = schema.DefaultSessionConfiguration.SameSite } else if !utils.IsStringInSlice(config.SameSite, validSessionSameSiteValues) { - validator.Push(fmt.Errorf(errFmtSessionSameSite, strings.Join(validSessionSameSiteValues, "', '"), config.SameSite)) + validator.Push(fmt.Errorf(errFmtSessionSameSite, strJoinOr(validSessionSameSiteValues), config.SameSite)) } cookies := len(config.Cookies) @@ -73,7 +73,7 @@ func validateSession(config *schema.SessionConfiguration, validator *schema.Stru func validateSessionCookieDomains(config *schema.SessionConfiguration, validator *schema.StructValidator) { if len(config.Cookies) == 0 { - validator.Push(fmt.Errorf(errFmtSessionOptionRequired, "domain")) + validator.Push(fmt.Errorf(errFmtSessionOptionRequired, "cookies")) } domains := make([]string, 0) @@ -182,7 +182,7 @@ func validateSessionSameSite(i int, config *schema.SessionConfiguration, validat config.Cookies[i].SameSite = schema.DefaultSessionConfiguration.SameSite } } else if !utils.IsStringInSlice(config.Cookies[i].SameSite, validSessionSameSiteValues) { - validator.Push(fmt.Errorf(errFmtSessionDomainSameSite, sessionDomainDescriptor(i, config.Cookies[i]), strings.Join(validSessionSameSiteValues, "', '"), config.Cookies[i].SameSite)) + validator.Push(fmt.Errorf(errFmtSessionDomainSameSite, sessionDomainDescriptor(i, config.Cookies[i]), strJoinOr(validSessionSameSiteValues), config.Cookies[i].SameSite)) } } diff --git a/internal/configuration/validator/session_test.go b/internal/configuration/validator/session_test.go index f8db62b5b..1f18eaea4 100644 --- a/internal/configuration/validator/session_test.go +++ b/internal/configuration/validator/session_test.go @@ -95,7 +95,7 @@ func TestShouldSetDefaultSessionDomainsValues(t *testing.T) { }, }, []string{ - "session: option 'same_site' must be one of 'none', 'lax', 'strict' but is configured as 'BAD VALUE'", + "session: option 'same_site' must be one of 'none', 'lax', or 'strict' but it's configured as 'BAD VALUE'", }, }, { @@ -140,6 +140,24 @@ func TestShouldSetDefaultSessionDomainsValues(t *testing.T) { }, nil, }, + { + "ShouldErrorOnEmptyConfig", + schema.SessionConfiguration{ + SessionCookieCommonConfiguration: schema.SessionCookieCommonConfiguration{ + Name: "", SameSite: "", Domain: "", + }, + Cookies: []schema.SessionCookieConfiguration{}, + }, + schema.SessionConfiguration{ + SessionCookieCommonConfiguration: schema.SessionCookieCommonConfiguration{ + Name: "authelia_session", SameSite: "lax", Expiration: time.Hour, Inactivity: time.Minute * 5, RememberMe: time.Hour * 24 * 30, + }, + Cookies: []schema.SessionCookieConfiguration{}, + }, + []string{ + "session: option 'cookies' is required", + }, + }, } validator := schema.NewStructValidator() @@ -302,7 +320,7 @@ func TestShouldRaiseErrorWhenRedisHasHostnameButNoPort(t *testing.T) { assert.False(t, validator.HasWarnings()) assert.Len(t, validator.Errors(), 1) - assert.EqualError(t, validator.Errors()[0], "session: redis: option 'port' must be between 1 and 65535 but is configured as '0'") + assert.EqualError(t, validator.Errors()[0], "session: redis: option 'port' must be between 1 and 65535 but it's configured as '0'") } func TestShouldRaiseOneErrorWhenRedisHighAvailabilityHasNodesWithNoHost(t *testing.T) { @@ -646,7 +664,7 @@ func TestShouldRaiseErrorWhenDomainIsInvalid(t *testing.T) { {"ShouldRaiseErrorOnPublicDomainDuckDNS", "duckdns.org", nil, []string{"session: domain config #1 (domain 'duckdns.org'): option 'domain' is not a valid cookie domain: the domain is part of the special public suffix list"}}, {"ShouldNotRaiseErrorOnSuffixOfPublicDomainDuckDNS", "example.duckdns.org", nil, nil}, {"ShouldRaiseWarningOnDomainWithLeadingDot", ".example.com", []string{"session: domain config #1 (domain '.example.com'): option 'domain' has a prefix of '.' which is not supported or intended behaviour: you can use this at your own risk but we recommend removing it"}, nil}, - {"ShouldRaiseErrorOnDomainWithLeadingStarDot", "*.example.com", nil, []string{"session: domain config #1 (domain '*.example.com'): option 'domain' must be the domain you wish to protect not a wildcard domain but it is configured as '*.example.com'"}}, + {"ShouldRaiseErrorOnDomainWithLeadingStarDot", "*.example.com", nil, []string{"session: domain config #1 (domain '*.example.com'): option 'domain' must be the domain you wish to protect not a wildcard domain but it's configured as '*.example.com'"}}, {"ShouldRaiseErrorOnDomainNotSet", "", nil, []string{"session: domain config #1 (domain ''): option 'domain' is required"}}, } @@ -726,8 +744,8 @@ func TestShouldRaiseErrorWhenSameSiteSetIncorrectly(t *testing.T) { assert.False(t, validator.HasWarnings()) require.Len(t, validator.Errors(), 2) - assert.EqualError(t, validator.Errors()[0], "session: option 'same_site' must be one of 'none', 'lax', 'strict' but is configured as 'NOne'") - assert.EqualError(t, validator.Errors()[1], "session: domain config #1 (domain 'example.com'): option 'same_site' must be one of 'none', 'lax', 'strict' but is configured as 'NOne'") + assert.EqualError(t, validator.Errors()[0], "session: option 'same_site' must be one of 'none', 'lax', or 'strict' but it's configured as 'NOne'") + assert.EqualError(t, validator.Errors()[1], "session: domain config #1 (domain 'example.com'): option 'same_site' must be one of 'none', 'lax', or 'strict' but it's configured as 'NOne'") } func TestShouldNotRaiseErrorWhenSameSiteSetCorrectly(t *testing.T) { diff --git a/internal/configuration/validator/storage.go b/internal/configuration/validator/storage.go index 7172383a7..035ac62e8 100644 --- a/internal/configuration/validator/storage.go +++ b/internal/configuration/validator/storage.go @@ -3,7 +3,6 @@ package validator import ( "errors" "fmt" - "strings" "github.com/authelia/authelia/v4/internal/configuration/schema" "github.com/authelia/authelia/v4/internal/utils" @@ -92,7 +91,7 @@ func validatePostgreSQLConfiguration(config *schema.PostgreSQLStorageConfigurati case config.SSL.Mode == "": config.SSL.Mode = schema.DefaultPostgreSQLStorageConfiguration.SSL.Mode case !utils.IsStringInSlice(config.SSL.Mode, validStoragePostgreSQLSSLModes): - validator.Push(fmt.Errorf(errFmtStoragePostgreSQLInvalidSSLMode, strings.Join(validStoragePostgreSQLSSLModes, "', '"), config.SSL.Mode)) + validator.Push(fmt.Errorf(errFmtStoragePostgreSQLInvalidSSLMode, strJoinOr(validStoragePostgreSQLSSLModes), config.SSL.Mode)) } } } diff --git a/internal/configuration/validator/storage_test.go b/internal/configuration/validator/storage_test.go index 8ac7d9dbb..f69bffab1 100644 --- a/internal/configuration/validator/storage_test.go +++ b/internal/configuration/validator/storage_test.go @@ -360,7 +360,7 @@ func (suite *StorageSuite) TestShouldValidatePostgresSSLModeMustBeValid() { suite.Assert().Len(suite.validator.Warnings(), 1) suite.Require().Len(suite.validator.Errors(), 1) - suite.Assert().EqualError(suite.validator.Errors()[0], "storage: postgres: ssl: option 'mode' must be one of 'disable', 'require', 'verify-ca', 'verify-full' but it is configured as 'unknown'") + suite.Assert().EqualError(suite.validator.Errors()[0], "storage: postgres: ssl: option 'mode' must be one of 'disable', 'require', 'verify-ca', or 'verify-full' but it's configured as 'unknown'") } func (suite *StorageSuite) TestShouldRaiseErrorOnNoEncryptionKey() { diff --git a/internal/configuration/validator/telemetry_test.go b/internal/configuration/validator/telemetry_test.go index aa3e59683..1643a5c40 100644 --- a/internal/configuration/validator/telemetry_test.go +++ b/internal/configuration/validator/telemetry_test.go @@ -58,7 +58,7 @@ func TestValidateTelemetry(t *testing.T) { &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")}}}, nil, - []string{"telemetry: metrics: option 'address' must have a scheme 'tcp://' but it is configured as 'udp'"}, + []string{"telemetry: metrics: option 'address' must have a scheme 'tcp://' but it's configured as 'udp'"}, }, } diff --git a/internal/configuration/validator/theme.go b/internal/configuration/validator/theme.go index f6c1a68d7..ccb8b7f52 100644 --- a/internal/configuration/validator/theme.go +++ b/internal/configuration/validator/theme.go @@ -2,7 +2,6 @@ package validator import ( "fmt" - "strings" "github.com/authelia/authelia/v4/internal/configuration/schema" "github.com/authelia/authelia/v4/internal/utils" @@ -15,6 +14,6 @@ func ValidateTheme(config *schema.Configuration, validator *schema.StructValidat } if !utils.IsStringInSlice(config.Theme, validThemeNames) { - validator.Push(fmt.Errorf(errFmtThemeName, strings.Join(validThemeNames, "', '"), config.Theme)) + validator.Push(fmt.Errorf(errFmtThemeName, strJoinOr(validThemeNames), config.Theme)) } } diff --git a/internal/configuration/validator/theme_test.go b/internal/configuration/validator/theme_test.go index abe796611..b1c8b89bc 100644 --- a/internal/configuration/validator/theme_test.go +++ b/internal/configuration/validator/theme_test.go @@ -36,7 +36,7 @@ func (suite *Theme) TestShouldRaiseErrorWhenInvalidThemeProvided() { suite.Assert().Len(suite.validator.Warnings(), 0) suite.Require().Len(suite.validator.Errors(), 1) - suite.Assert().EqualError(suite.validator.Errors()[0], "option 'theme' must be one of 'light', 'dark', 'grey', 'auto' but it is configured as 'invalid'") + suite.Assert().EqualError(suite.validator.Errors()[0], "option 'theme' must be one of 'light', 'dark', 'grey', or 'auto' but it's configured as 'invalid'") } func TestThemes(t *testing.T) { diff --git a/internal/configuration/validator/totp.go b/internal/configuration/validator/totp.go index 0a379a067..01a763f88 100644 --- a/internal/configuration/validator/totp.go +++ b/internal/configuration/validator/totp.go @@ -24,7 +24,7 @@ func ValidateTOTP(config *schema.Configuration, validator *schema.StructValidato config.TOTP.Algorithm = strings.ToUpper(config.TOTP.Algorithm) if !utils.IsStringInSlice(config.TOTP.Algorithm, schema.TOTPPossibleAlgorithms) { - validator.Push(fmt.Errorf(errFmtTOTPInvalidAlgorithm, strings.Join(schema.TOTPPossibleAlgorithms, "', '"), config.TOTP.Algorithm)) + validator.Push(fmt.Errorf(errFmtTOTPInvalidAlgorithm, strJoinOr(schema.TOTPPossibleAlgorithms), config.TOTP.Algorithm)) } } diff --git a/internal/configuration/validator/totp_test.go b/internal/configuration/validator/totp_test.go index 956f074c4..b94f7f00b 100644 --- a/internal/configuration/validator/totp_test.go +++ b/internal/configuration/validator/totp_test.go @@ -56,7 +56,9 @@ func TestValidateTOTP(t *testing.T) { Skew: schema.DefaultTOTPConfiguration.Skew, Issuer: "abc", }, - errs: []string{"totp: option 'algorithm' must be one of 'SHA1', 'SHA256', 'SHA512' but it is configured as 'SHA3'"}, + errs: []string{ + "totp: option 'algorithm' must be one of 'SHA1', 'SHA256', or 'SHA512' but it's configured as 'SHA3'", + }, }, { desc: "ShouldRaiseErrorWhenInvalidTOTPValue", @@ -69,10 +71,10 @@ func TestValidateTOTP(t *testing.T) { Issuer: "abc", }, errs: []string{ - "totp: option 'algorithm' must be one of 'SHA1', 'SHA256', 'SHA512' but it is configured as 'SHA3'", - "totp: option 'period' option must be 15 or more but it is configured as '5'", - "totp: option 'digits' must be 6 or 8 but it is configured as '20'", - "totp: option 'secret_size' must be 20 or higher but it is configured as '10'", + "totp: option 'algorithm' must be one of 'SHA1', 'SHA256', or 'SHA512' but it's configured as 'SHA3'", + "totp: option 'period' option must be 15 or more but it's configured as '5'", + "totp: option 'digits' must be 6 or 8 but it's configured as '20'", + "totp: option 'secret_size' must be 20 or higher but it's configured as '10'", }, }, } diff --git a/internal/configuration/validator/util.go b/internal/configuration/validator/util.go index b68bfee7a..59b9411b1 100644 --- a/internal/configuration/validator/util.go +++ b/internal/configuration/validator/util.go @@ -4,6 +4,8 @@ import ( "strings" "golang.org/x/net/publicsuffix" + + "github.com/authelia/authelia/v4/internal/utils" ) func isCookieDomainAPublicSuffix(domain string) (valid bool) { @@ -13,3 +15,95 @@ func isCookieDomainAPublicSuffix(domain string) (valid bool) { return len(strings.TrimLeft(domain, ".")) == len(suffix) } + +func strJoinOr(items []string) string { + return strJoinComma("or", items) +} + +func strJoinAnd(items []string) string { + return strJoinComma("and", items) +} + +func strJoinComma(word string, items []string) string { + if word == "" { + return buildJoinedString(",", "", "'", items) + } + + return buildJoinedString(",", word, "'", items) +} + +func buildJoinedString(sep, sepFinal, quote string, items []string) string { + n := len(items) + + if n == 0 { + return "" + } + + b := &strings.Builder{} + + for i := 0; i < n; i++ { + if quote != "" { + b.WriteString(quote) + } + + b.WriteString(items[i]) + + if quote != "" { + b.WriteString(quote) + } + + if i == (n - 1) { + continue + } + + if sep != "" { + if sepFinal == "" || n != 2 { + b.WriteString(sep) + } + + b.WriteString(" ") + } + + if sepFinal != "" && i == (n-2) { + b.WriteString(strings.Trim(sepFinal, " ")) + b.WriteString(" ") + } + } + + return b.String() +} + +func validateList(values, valid []string, chkDuplicate bool) (invalid, duplicates []string) { //nolint:unparam + chkValid := len(valid) != 0 + + for i, value := range values { + if chkValid { + if !utils.IsStringInSlice(value, valid) { + invalid = append(invalid, value) + + // Skip checking duplicates for invalid values. + continue + } + } + + if chkDuplicate { + for j, valueAlt := range values { + if i == j { + continue + } + + if value != valueAlt { + continue + } + + if utils.IsStringInSlice(value, duplicates) { + continue + } + + duplicates = append(duplicates, value) + } + } + } + + return +} diff --git a/internal/configuration/validator/webauthn.go b/internal/configuration/validator/webauthn.go index 47aaa2704..406ccc7e6 100644 --- a/internal/configuration/validator/webauthn.go +++ b/internal/configuration/validator/webauthn.go @@ -2,7 +2,6 @@ package validator import ( "fmt" - "strings" "github.com/authelia/authelia/v4/internal/configuration/schema" "github.com/authelia/authelia/v4/internal/utils" @@ -22,13 +21,13 @@ func ValidateWebauthn(config *schema.Configuration, validator *schema.StructVali case config.Webauthn.ConveyancePreference == "": config.Webauthn.ConveyancePreference = schema.DefaultWebauthnConfiguration.ConveyancePreference case !utils.IsStringInSlice(string(config.Webauthn.ConveyancePreference), validWebauthnConveyancePreferences): - validator.Push(fmt.Errorf(errFmtWebauthnConveyancePreference, strings.Join(validWebauthnConveyancePreferences, "', '"), config.Webauthn.ConveyancePreference)) + validator.Push(fmt.Errorf(errFmtWebauthnConveyancePreference, strJoinOr(validWebauthnConveyancePreferences), config.Webauthn.ConveyancePreference)) } switch { case config.Webauthn.UserVerification == "": config.Webauthn.UserVerification = schema.DefaultWebauthnConfiguration.UserVerification case !utils.IsStringInSlice(string(config.Webauthn.UserVerification), validWebauthnUserVerificationRequirement): - validator.Push(fmt.Errorf(errFmtWebauthnUserVerification, config.Webauthn.UserVerification)) + validator.Push(fmt.Errorf(errFmtWebauthnUserVerification, strJoinOr(validWebauthnConveyancePreferences), config.Webauthn.UserVerification)) } } diff --git a/internal/configuration/validator/webauthn_test.go b/internal/configuration/validator/webauthn_test.go index bfa746a32..9aaaa0aac 100644 --- a/internal/configuration/validator/webauthn_test.go +++ b/internal/configuration/validator/webauthn_test.go @@ -93,6 +93,6 @@ func TestWebauthnShouldRaiseErrorsOnInvalidOptions(t *testing.T) { require.Len(t, validator.Errors(), 2) - assert.EqualError(t, validator.Errors()[0], "webauthn: option 'attestation_conveyance_preference' must be one of 'none', 'indirect', 'direct' but it is configured as 'no'") - assert.EqualError(t, validator.Errors()[1], "webauthn: option 'user_verification' must be one of 'discouraged', 'preferred', 'required' but it is configured as 'yes'") + assert.EqualError(t, validator.Errors()[0], "webauthn: option 'attestation_conveyance_preference' must be one of 'none', 'indirect', or 'direct' but it's configured as 'no'") + assert.EqualError(t, validator.Errors()[1], "webauthn: option 'user_verification' must be one of 'none', 'indirect', or 'direct' but it's configured as 'yes'") } diff --git a/internal/handlers/handler_oidc_authorization.go b/internal/handlers/handler_oidc_authorization.go index 5cb193920..9924c5ec1 100644 --- a/internal/handlers/handler_oidc_authorization.go +++ b/internal/handlers/handler_oidc_authorization.go @@ -21,7 +21,7 @@ func OpenIDConnectAuthorization(ctx *middlewares.AutheliaCtx, rw http.ResponseWr var ( requester fosite.AuthorizeRequester responder fosite.AuthorizeResponder - client *oidc.Client + client oidc.Client authTime time.Time issuer *url.URL err error @@ -117,7 +117,7 @@ func OpenIDConnectAuthorization(ctx *middlewares.AutheliaCtx, rw http.ResponseWr extraClaims := oidcGrantRequests(requester, consent, &userSession) - if authTime, err = userSession.AuthenticatedTime(client.Policy); err != nil { + if authTime, err = userSession.AuthenticatedTime(client.GetAuthorizationPolicy()); err != nil { ctx.Logger.Errorf("Authorization Request with id '%s' on client with id '%s' could not be processed: error occurred checking authentication time: %+v", requester.GetID(), client.GetID(), err) ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, fosite.ErrServerError.WithHint("Could not obtain the authentication time.")) @@ -178,7 +178,7 @@ func OpenIDConnectPushedAuthorizationRequest(ctx *middlewares.AutheliaCtx, rw ht return } - var client *oidc.Client + var client oidc.Client clientID := requester.GetClient().GetID() diff --git a/internal/handlers/handler_oidc_authorization_consent.go b/internal/handlers/handler_oidc_authorization_consent.go index fef764701..1a9c400f8 100644 --- a/internal/handlers/handler_oidc_authorization_consent.go +++ b/internal/handlers/handler_oidc_authorization_consent.go @@ -18,7 +18,7 @@ import ( "github.com/authelia/authelia/v4/internal/utils" ) -func handleOIDCAuthorizationConsent(ctx *middlewares.AutheliaCtx, issuer *url.URL, client *oidc.Client, +func handleOIDCAuthorizationConsent(ctx *middlewares.AutheliaCtx, issuer *url.URL, client oidc.Client, userSession session.UserSession, rw http.ResponseWriter, r *http.Request, requester fosite.AuthorizeRequester) (consent *model.OAuth2ConsentSession, handled bool) { var ( @@ -33,14 +33,14 @@ func handleOIDCAuthorizationConsent(ctx *middlewares.AutheliaCtx, issuer *url.UR handler = handleOIDCAuthorizationConsentNotAuthenticated case client.IsAuthenticationLevelSufficient(userSession.AuthenticationLevel): if subject, err = ctx.Providers.OpenIDConnect.GetSubject(ctx, client.GetSectorIdentifier(), userSession.Username); err != nil { - ctx.Logger.Errorf(logFmtErrConsentCantGetSubject, requester.GetID(), client.GetID(), client.Consent, userSession.Username, client.GetSectorIdentifier(), err) + ctx.Logger.Errorf(logFmtErrConsentCantGetSubject, requester.GetID(), client.GetID(), client.GetConsentPolicy(), userSession.Username, client.GetSectorIdentifier(), err) ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrSubjectCouldNotLookup) return nil, true } - switch client.Consent.Mode { + switch client.GetConsentPolicy().Mode { case oidc.ClientConsentModeExplicit: handler = handleOIDCAuthorizationConsentModeExplicit case oidc.ClientConsentModeImplicit: @@ -56,7 +56,7 @@ func handleOIDCAuthorizationConsent(ctx *middlewares.AutheliaCtx, issuer *url.UR } default: if subject, err = ctx.Providers.OpenIDConnect.GetSubject(ctx, client.GetSectorIdentifier(), userSession.Username); err != nil { - ctx.Logger.Errorf(logFmtErrConsentCantGetSubject, requester.GetID(), client.GetID(), client.Consent, userSession.Username, client.GetSectorIdentifier(), err) + ctx.Logger.Errorf(logFmtErrConsentCantGetSubject, requester.GetID(), client.GetID(), client.GetConsentPolicy(), userSession.Username, client.GetSectorIdentifier(), err) ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrSubjectCouldNotLookup) @@ -69,7 +69,7 @@ func handleOIDCAuthorizationConsent(ctx *middlewares.AutheliaCtx, issuer *url.UR return handler(ctx, issuer, client, userSession, subject, rw, r, requester) } -func handleOIDCAuthorizationConsentNotAuthenticated(_ *middlewares.AutheliaCtx, issuer *url.URL, _ *oidc.Client, +func handleOIDCAuthorizationConsentNotAuthenticated(_ *middlewares.AutheliaCtx, issuer *url.URL, _ oidc.Client, _ session.UserSession, _ uuid.UUID, rw http.ResponseWriter, r *http.Request, requester fosite.AuthorizeRequester) (consent *model.OAuth2ConsentSession, handled bool) { redirectionURL := handleOIDCAuthorizationConsentGetRedirectionURL(issuer, nil, requester) @@ -79,17 +79,17 @@ func handleOIDCAuthorizationConsentNotAuthenticated(_ *middlewares.AutheliaCtx, return nil, true } -func handleOIDCAuthorizationConsentGenerate(ctx *middlewares.AutheliaCtx, issuer *url.URL, client *oidc.Client, +func handleOIDCAuthorizationConsentGenerate(ctx *middlewares.AutheliaCtx, issuer *url.URL, client oidc.Client, userSession session.UserSession, subject uuid.UUID, rw http.ResponseWriter, r *http.Request, requester fosite.AuthorizeRequester) (consent *model.OAuth2ConsentSession, handled bool) { var ( err error ) - ctx.Logger.Debugf(logFmtDbgConsentGenerate, requester.GetID(), client.GetID(), client.Consent) + ctx.Logger.Debugf(logFmtDbgConsentGenerate, requester.GetID(), client.GetID(), client.GetConsentPolicy()) if len(ctx.QueryArgs().PeekBytes(qryArgConsentID)) != 0 { - ctx.Logger.Errorf(logFmtErrConsentGenerateError, requester.GetID(), client.GetID(), client.Consent, "generating", errors.New("consent id value was present when it should be absent")) + ctx.Logger.Errorf(logFmtErrConsentGenerateError, requester.GetID(), client.GetID(), client.GetConsentPolicy(), "generating", errors.New("consent id value was present when it should be absent")) ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotGenerate) @@ -97,7 +97,7 @@ func handleOIDCAuthorizationConsentGenerate(ctx *middlewares.AutheliaCtx, issuer } if consent, err = model.NewOAuth2ConsentSession(subject, requester); err != nil { - ctx.Logger.Errorf(logFmtErrConsentGenerateError, requester.GetID(), client.GetID(), client.Consent, "generating", err) + ctx.Logger.Errorf(logFmtErrConsentGenerateError, requester.GetID(), client.GetID(), client.GetConsentPolicy(), "generating", err) ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotGenerate) @@ -105,7 +105,7 @@ func handleOIDCAuthorizationConsentGenerate(ctx *middlewares.AutheliaCtx, issuer } if err = ctx.Providers.StorageProvider.SaveOAuth2ConsentSession(ctx, *consent); err != nil { - ctx.Logger.Errorf(logFmtErrConsentGenerateError, requester.GetID(), client.GetID(), client.Consent, "saving", err) + ctx.Logger.Errorf(logFmtErrConsentGenerateError, requester.GetID(), client.GetID(), client.GetConsentPolicy(), "saving", err) ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotSave) @@ -117,7 +117,7 @@ func handleOIDCAuthorizationConsentGenerate(ctx *middlewares.AutheliaCtx, issuer return consent, true } -func handleOIDCAuthorizationConsentRedirect(ctx *middlewares.AutheliaCtx, issuer *url.URL, consent *model.OAuth2ConsentSession, client *oidc.Client, +func handleOIDCAuthorizationConsentRedirect(ctx *middlewares.AutheliaCtx, issuer *url.URL, consent *model.OAuth2ConsentSession, client oidc.Client, userSession session.UserSession, rw http.ResponseWriter, r *http.Request, requester fosite.AuthorizeRequester) { var location *url.URL @@ -130,14 +130,14 @@ func handleOIDCAuthorizationConsentRedirect(ctx *middlewares.AutheliaCtx, issuer location.RawQuery = query.Encode() - ctx.Logger.Debugf(logFmtDbgConsentAuthenticationSufficiency, requester.GetID(), client.GetID(), client.Consent, userSession.AuthenticationLevel.String(), "sufficient", client.Policy) + ctx.Logger.Debugf(logFmtDbgConsentAuthenticationSufficiency, requester.GetID(), client.GetID(), client.GetConsentPolicy(), userSession.AuthenticationLevel.String(), "sufficient", client.GetAuthorizationPolicy()) } else { location = handleOIDCAuthorizationConsentGetRedirectionURL(issuer, consent, requester) - ctx.Logger.Debugf(logFmtDbgConsentAuthenticationSufficiency, requester.GetID(), client.GetID(), client.Consent, userSession.AuthenticationLevel.String(), "insufficient", client.Policy) + ctx.Logger.Debugf(logFmtDbgConsentAuthenticationSufficiency, requester.GetID(), client.GetID(), client.GetConsentPolicy(), userSession.AuthenticationLevel.String(), "insufficient", client.GetAuthorizationPolicy()) } - ctx.Logger.Debugf(logFmtDbgConsentRedirect, requester.GetID(), client.GetID(), client.Consent, location) + ctx.Logger.Debugf(logFmtDbgConsentRedirect, requester.GetID(), client.GetID(), client.GetConsentPolicy(), location) http.Redirect(rw, r, location.String(), http.StatusFound) } @@ -170,7 +170,7 @@ func handleOIDCAuthorizationConsentGetRedirectionURL(issuer *url.URL, consent *m return redirectURL } -func verifyOIDCUserAuthorizedForConsent(ctx *middlewares.AutheliaCtx, client *oidc.Client, userSession session.UserSession, consent *model.OAuth2ConsentSession, subject uuid.UUID) (err error) { +func verifyOIDCUserAuthorizedForConsent(ctx *middlewares.AutheliaCtx, client oidc.Client, userSession session.UserSession, consent *model.OAuth2ConsentSession, subject uuid.UUID) (err error) { var sid uint32 if client == nil { diff --git a/internal/handlers/handler_oidc_authorization_consent_explicit.go b/internal/handlers/handler_oidc_authorization_consent_explicit.go index 9388c74d8..901806c9c 100644 --- a/internal/handlers/handler_oidc_authorization_consent_explicit.go +++ b/internal/handlers/handler_oidc_authorization_consent_explicit.go @@ -13,7 +13,7 @@ import ( "github.com/authelia/authelia/v4/internal/session" ) -func handleOIDCAuthorizationConsentModeExplicit(ctx *middlewares.AutheliaCtx, issuer *url.URL, client *oidc.Client, +func handleOIDCAuthorizationConsentModeExplicit(ctx *middlewares.AutheliaCtx, issuer *url.URL, client oidc.Client, userSession session.UserSession, subject uuid.UUID, rw http.ResponseWriter, r *http.Request, requester fosite.AuthorizeRequester) (consent *model.OAuth2ConsentSession, handled bool) { var ( @@ -28,7 +28,7 @@ func handleOIDCAuthorizationConsentModeExplicit(ctx *middlewares.AutheliaCtx, is return handleOIDCAuthorizationConsentGenerate(ctx, issuer, client, userSession, subject, rw, r, requester) default: if consentID, err = uuid.ParseBytes(bytesConsentID); err != nil { - ctx.Logger.Errorf(logFmtErrConsentParseChallengeID, requester.GetID(), client.GetID(), client.Consent, bytesConsentID, err) + ctx.Logger.Errorf(logFmtErrConsentParseChallengeID, requester.GetID(), client.GetID(), client.GetConsentPolicy(), bytesConsentID, err) ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentMalformedChallengeID) @@ -39,7 +39,7 @@ func handleOIDCAuthorizationConsentModeExplicit(ctx *middlewares.AutheliaCtx, is } } -func handleOIDCAuthorizationConsentModeExplicitWithID(ctx *middlewares.AutheliaCtx, issuer *url.URL, client *oidc.Client, +func handleOIDCAuthorizationConsentModeExplicitWithID(ctx *middlewares.AutheliaCtx, issuer *url.URL, client oidc.Client, userSession session.UserSession, subject uuid.UUID, consentID uuid.UUID, rw http.ResponseWriter, r *http.Request, requester fosite.AuthorizeRequester) (consent *model.OAuth2ConsentSession, handled bool) { var ( @@ -47,7 +47,7 @@ func handleOIDCAuthorizationConsentModeExplicitWithID(ctx *middlewares.AutheliaC ) if consentID.ID() == 0 { - ctx.Logger.Errorf(logFmtErrConsentZeroID, requester.GetID(), client.GetID(), client.Consent) + ctx.Logger.Errorf(logFmtErrConsentZeroID, requester.GetID(), client.GetID(), client.GetConsentPolicy()) ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotLookup) @@ -55,7 +55,7 @@ func handleOIDCAuthorizationConsentModeExplicitWithID(ctx *middlewares.AutheliaC } if consent, err = ctx.Providers.StorageProvider.LoadOAuth2ConsentSessionByChallengeID(ctx, consentID); err != nil { - ctx.Logger.Errorf(logFmtErrConsentLookupLoadingSession, requester.GetID(), client.GetID(), client.Consent, consentID, err) + ctx.Logger.Errorf(logFmtErrConsentLookupLoadingSession, requester.GetID(), client.GetID(), client.GetConsentPolicy(), consentID, err) ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotLookup) @@ -63,7 +63,7 @@ func handleOIDCAuthorizationConsentModeExplicitWithID(ctx *middlewares.AutheliaC } if subject.ID() != consent.Subject.UUID.ID() { - ctx.Logger.Errorf(logFmtErrConsentSessionSubjectNotAuthorized, requester.GetID(), client.GetID(), client.Consent, consent.ChallengeID, userSession.Username, subject, consent.Subject.UUID) + ctx.Logger.Errorf(logFmtErrConsentSessionSubjectNotAuthorized, requester.GetID(), client.GetID(), client.GetConsentPolicy(), consent.ChallengeID, userSession.Username, subject, consent.Subject.UUID) ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotLookup) @@ -71,7 +71,7 @@ func handleOIDCAuthorizationConsentModeExplicitWithID(ctx *middlewares.AutheliaC } if !consent.CanGrant() { - ctx.Logger.Errorf(logFmtErrConsentCantGrant, requester.GetID(), client.GetID(), client.Consent, consent.ChallengeID, "explicit") + ctx.Logger.Errorf(logFmtErrConsentCantGrant, requester.GetID(), client.GetID(), client.GetConsentPolicy(), consent.ChallengeID, "explicit") ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotPerform) @@ -80,7 +80,7 @@ func handleOIDCAuthorizationConsentModeExplicitWithID(ctx *middlewares.AutheliaC if !consent.IsAuthorized() { if consent.Responded() { - ctx.Logger.Errorf(logFmtErrConsentCantGrantRejected, requester.GetID(), client.GetID(), client.Consent, consent.ChallengeID) + ctx.Logger.Errorf(logFmtErrConsentCantGrantRejected, requester.GetID(), client.GetID(), client.GetConsentPolicy(), consent.ChallengeID) ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, fosite.ErrAccessDenied) diff --git a/internal/handlers/handler_oidc_authorization_consent_implicit.go b/internal/handlers/handler_oidc_authorization_consent_implicit.go index 5ec5b3ad1..b0f38c0b0 100644 --- a/internal/handlers/handler_oidc_authorization_consent_implicit.go +++ b/internal/handlers/handler_oidc_authorization_consent_implicit.go @@ -13,7 +13,7 @@ import ( "github.com/authelia/authelia/v4/internal/session" ) -func handleOIDCAuthorizationConsentModeImplicit(ctx *middlewares.AutheliaCtx, issuer *url.URL, client *oidc.Client, +func handleOIDCAuthorizationConsentModeImplicit(ctx *middlewares.AutheliaCtx, issuer *url.URL, client oidc.Client, userSession session.UserSession, subject uuid.UUID, rw http.ResponseWriter, r *http.Request, requester fosite.AuthorizeRequester) (consent *model.OAuth2ConsentSession, handled bool) { var ( @@ -26,7 +26,7 @@ func handleOIDCAuthorizationConsentModeImplicit(ctx *middlewares.AutheliaCtx, is return handleOIDCAuthorizationConsentModeImplicitWithoutID(ctx, issuer, client, userSession, subject, rw, r, requester) default: if consentID, err = uuid.ParseBytes(bytesConsentID); err != nil { - ctx.Logger.Errorf(logFmtErrConsentParseChallengeID, requester.GetID(), client.GetID(), client.Consent, bytesConsentID, err) + ctx.Logger.Errorf(logFmtErrConsentParseChallengeID, requester.GetID(), client.GetID(), client.GetConsentPolicy(), bytesConsentID, err) ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentMalformedChallengeID) @@ -37,7 +37,7 @@ func handleOIDCAuthorizationConsentModeImplicit(ctx *middlewares.AutheliaCtx, is } } -func handleOIDCAuthorizationConsentModeImplicitWithID(ctx *middlewares.AutheliaCtx, _ *url.URL, client *oidc.Client, +func handleOIDCAuthorizationConsentModeImplicitWithID(ctx *middlewares.AutheliaCtx, _ *url.URL, client oidc.Client, userSession session.UserSession, subject uuid.UUID, consentID uuid.UUID, rw http.ResponseWriter, _ *http.Request, requester fosite.AuthorizeRequester) (consent *model.OAuth2ConsentSession, handled bool) { var ( @@ -45,7 +45,7 @@ func handleOIDCAuthorizationConsentModeImplicitWithID(ctx *middlewares.AutheliaC ) if consentID.ID() == 0 { - ctx.Logger.Errorf(logFmtErrConsentZeroID, requester.GetID(), client.GetID(), client.Consent) + ctx.Logger.Errorf(logFmtErrConsentZeroID, requester.GetID(), client.GetID(), client.GetConsentPolicy()) ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotLookup) @@ -53,7 +53,7 @@ func handleOIDCAuthorizationConsentModeImplicitWithID(ctx *middlewares.AutheliaC } if consent, err = ctx.Providers.StorageProvider.LoadOAuth2ConsentSessionByChallengeID(ctx, consentID); err != nil { - ctx.Logger.Errorf(logFmtErrConsentLookupLoadingSession, requester.GetID(), client.GetID(), client.Consent, consentID, err) + ctx.Logger.Errorf(logFmtErrConsentLookupLoadingSession, requester.GetID(), client.GetID(), client.GetConsentPolicy(), consentID, err) ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotLookup) @@ -61,7 +61,7 @@ func handleOIDCAuthorizationConsentModeImplicitWithID(ctx *middlewares.AutheliaC } if subject.ID() != consent.Subject.UUID.ID() { - ctx.Logger.Errorf(logFmtErrConsentSessionSubjectNotAuthorized, requester.GetID(), client.GetID(), client.Consent, consent.ChallengeID, userSession.Username, subject, consent.Subject.UUID) + ctx.Logger.Errorf(logFmtErrConsentSessionSubjectNotAuthorized, requester.GetID(), client.GetID(), client.GetConsentPolicy(), consent.ChallengeID, userSession.Username, subject, consent.Subject.UUID) ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotLookup) @@ -69,7 +69,7 @@ func handleOIDCAuthorizationConsentModeImplicitWithID(ctx *middlewares.AutheliaC } if !consent.CanGrant() { - ctx.Logger.Errorf(logFmtErrConsentCantGrant, requester.GetID(), client.GetID(), client.Consent, consent.ChallengeID, "implicit") + ctx.Logger.Errorf(logFmtErrConsentCantGrant, requester.GetID(), client.GetID(), client.GetConsentPolicy(), consent.ChallengeID, "implicit") ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotPerform) @@ -79,7 +79,7 @@ func handleOIDCAuthorizationConsentModeImplicitWithID(ctx *middlewares.AutheliaC consent.Grant() if err = ctx.Providers.StorageProvider.SaveOAuth2ConsentSessionResponse(ctx, *consent, false); err != nil { - ctx.Logger.Errorf(logFmtErrConsentSaveSessionResponse, requester.GetID(), client.GetID(), client.Consent, consent.ChallengeID, err) + ctx.Logger.Errorf(logFmtErrConsentSaveSessionResponse, requester.GetID(), client.GetID(), client.GetConsentPolicy(), consent.ChallengeID, err) ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotSave) @@ -89,7 +89,7 @@ func handleOIDCAuthorizationConsentModeImplicitWithID(ctx *middlewares.AutheliaC return consent, false } -func handleOIDCAuthorizationConsentModeImplicitWithoutID(ctx *middlewares.AutheliaCtx, _ *url.URL, client *oidc.Client, +func handleOIDCAuthorizationConsentModeImplicitWithoutID(ctx *middlewares.AutheliaCtx, _ *url.URL, client oidc.Client, _ session.UserSession, subject uuid.UUID, rw http.ResponseWriter, _ *http.Request, requester fosite.AuthorizeRequester) (consent *model.OAuth2ConsentSession, handled bool) { var ( @@ -97,7 +97,7 @@ func handleOIDCAuthorizationConsentModeImplicitWithoutID(ctx *middlewares.Authel ) if consent, err = model.NewOAuth2ConsentSession(subject, requester); err != nil { - ctx.Logger.Errorf(logFmtErrConsentGenerate, requester.GetID(), client.GetID(), client.Consent, err) + ctx.Logger.Errorf(logFmtErrConsentGenerate, requester.GetID(), client.GetID(), client.GetConsentPolicy(), err) ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotGenerate) @@ -105,7 +105,7 @@ func handleOIDCAuthorizationConsentModeImplicitWithoutID(ctx *middlewares.Authel } if err = ctx.Providers.StorageProvider.SaveOAuth2ConsentSession(ctx, *consent); err != nil { - ctx.Logger.Errorf(logFmtErrConsentSaveSession, requester.GetID(), client.GetID(), client.Consent, consent.ChallengeID, err) + ctx.Logger.Errorf(logFmtErrConsentSaveSession, requester.GetID(), client.GetID(), client.GetConsentPolicy(), consent.ChallengeID, err) ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotSave) @@ -113,7 +113,7 @@ func handleOIDCAuthorizationConsentModeImplicitWithoutID(ctx *middlewares.Authel } if consent, err = ctx.Providers.StorageProvider.LoadOAuth2ConsentSessionByChallengeID(ctx, consent.ChallengeID); err != nil { - ctx.Logger.Errorf(logFmtErrConsentSaveSession, requester.GetID(), client.GetID(), client.Consent, consent.ChallengeID, err) + ctx.Logger.Errorf(logFmtErrConsentSaveSession, requester.GetID(), client.GetID(), client.GetConsentPolicy(), consent.ChallengeID, err) ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotSave) @@ -123,7 +123,7 @@ func handleOIDCAuthorizationConsentModeImplicitWithoutID(ctx *middlewares.Authel consent.Grant() if err = ctx.Providers.StorageProvider.SaveOAuth2ConsentSessionResponse(ctx, *consent, false); err != nil { - ctx.Logger.Errorf(logFmtErrConsentSaveSessionResponse, requester.GetID(), client.GetID(), client.Consent, consent.ChallengeID, err) + ctx.Logger.Errorf(logFmtErrConsentSaveSessionResponse, requester.GetID(), client.GetID(), client.GetConsentPolicy(), consent.ChallengeID, err) ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotSave) diff --git a/internal/handlers/handler_oidc_authorization_consent_pre_configured.go b/internal/handlers/handler_oidc_authorization_consent_pre_configured.go index 0b8b26229..9f9e708f8 100644 --- a/internal/handlers/handler_oidc_authorization_consent_pre_configured.go +++ b/internal/handlers/handler_oidc_authorization_consent_pre_configured.go @@ -17,7 +17,7 @@ import ( "github.com/authelia/authelia/v4/internal/storage" ) -func handleOIDCAuthorizationConsentModePreConfigured(ctx *middlewares.AutheliaCtx, issuer *url.URL, client *oidc.Client, +func handleOIDCAuthorizationConsentModePreConfigured(ctx *middlewares.AutheliaCtx, issuer *url.URL, client oidc.Client, userSession session.UserSession, subject uuid.UUID, rw http.ResponseWriter, r *http.Request, requester fosite.AuthorizeRequester) (consent *model.OAuth2ConsentSession, handled bool) { var ( @@ -32,7 +32,7 @@ func handleOIDCAuthorizationConsentModePreConfigured(ctx *middlewares.AutheliaCt return handleOIDCAuthorizationConsentModePreConfiguredWithoutID(ctx, issuer, client, userSession, subject, rw, r, requester) default: if consentID, err = uuid.ParseBytes(bytesConsentID); err != nil { - ctx.Logger.Errorf(logFmtErrConsentParseChallengeID, requester.GetID(), client.GetID(), client.Consent, bytesConsentID, err) + ctx.Logger.Errorf(logFmtErrConsentParseChallengeID, requester.GetID(), client.GetID(), client.GetConsentPolicy(), bytesConsentID, err) ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentMalformedChallengeID) @@ -43,7 +43,7 @@ func handleOIDCAuthorizationConsentModePreConfigured(ctx *middlewares.AutheliaCt } } -func handleOIDCAuthorizationConsentModePreConfiguredWithID(ctx *middlewares.AutheliaCtx, issuer *url.URL, client *oidc.Client, +func handleOIDCAuthorizationConsentModePreConfiguredWithID(ctx *middlewares.AutheliaCtx, issuer *url.URL, client oidc.Client, userSession session.UserSession, subject uuid.UUID, consentID uuid.UUID, rw http.ResponseWriter, r *http.Request, requester fosite.AuthorizeRequester) (consent *model.OAuth2ConsentSession, handled bool) { var ( @@ -52,7 +52,7 @@ func handleOIDCAuthorizationConsentModePreConfiguredWithID(ctx *middlewares.Auth ) if consentID.ID() == 0 { - ctx.Logger.Errorf(logFmtErrConsentZeroID, requester.GetID(), client.GetID(), client.Consent) + ctx.Logger.Errorf(logFmtErrConsentZeroID, requester.GetID(), client.GetID(), client.GetConsentPolicy()) ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotLookup) @@ -60,7 +60,7 @@ func handleOIDCAuthorizationConsentModePreConfiguredWithID(ctx *middlewares.Auth } if consent, err = ctx.Providers.StorageProvider.LoadOAuth2ConsentSessionByChallengeID(ctx, consentID); err != nil { - ctx.Logger.Errorf(logFmtErrConsentLookupLoadingSession, requester.GetID(), client.GetID(), client.Consent, consentID, err) + ctx.Logger.Errorf(logFmtErrConsentLookupLoadingSession, requester.GetID(), client.GetID(), client.GetConsentPolicy(), consentID, err) ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotLookup) @@ -68,7 +68,7 @@ func handleOIDCAuthorizationConsentModePreConfiguredWithID(ctx *middlewares.Auth } if subject.ID() != consent.Subject.UUID.ID() { - ctx.Logger.Errorf(logFmtErrConsentSessionSubjectNotAuthorized, requester.GetID(), client.GetID(), client.Consent, consent.ChallengeID, userSession.Username, subject, consent.Subject.UUID) + ctx.Logger.Errorf(logFmtErrConsentSessionSubjectNotAuthorized, requester.GetID(), client.GetID(), client.GetConsentPolicy(), consent.ChallengeID, userSession.Username, subject, consent.Subject.UUID) ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotLookup) @@ -76,7 +76,7 @@ func handleOIDCAuthorizationConsentModePreConfiguredWithID(ctx *middlewares.Auth } if !consent.CanGrant() { - ctx.Logger.Errorf(logFmtErrConsentCantGrantPreConf, requester.GetID(), client.GetID(), client.Consent, consent.ChallengeID) + ctx.Logger.Errorf(logFmtErrConsentCantGrantPreConf, requester.GetID(), client.GetID(), client.GetConsentPolicy(), consent.ChallengeID) ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotPerform) @@ -84,7 +84,7 @@ func handleOIDCAuthorizationConsentModePreConfiguredWithID(ctx *middlewares.Auth } if config, err = handleOIDCAuthorizationConsentModePreConfiguredGetPreConfig(ctx, client, subject, requester); err != nil { - ctx.Logger.Errorf(logFmtErrConsentPreConfLookup, requester.GetID(), client.GetID(), client.Consent, err) + ctx.Logger.Errorf(logFmtErrConsentPreConfLookup, requester.GetID(), client.GetID(), client.GetConsentPolicy(), err) ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotLookup) @@ -97,7 +97,7 @@ func handleOIDCAuthorizationConsentModePreConfiguredWithID(ctx *middlewares.Auth consent.PreConfiguration = sql.NullInt64{Int64: config.ID, Valid: true} if err = ctx.Providers.StorageProvider.SaveOAuth2ConsentSessionResponse(ctx, *consent, false); err != nil { - ctx.Logger.Errorf(logFmtErrConsentSaveSessionResponse, requester.GetID(), client.GetID(), client.Consent, consent.ChallengeID, err) + ctx.Logger.Errorf(logFmtErrConsentSaveSessionResponse, requester.GetID(), client.GetID(), client.GetConsentPolicy(), consent.ChallengeID, err) ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotSave) @@ -109,7 +109,7 @@ func handleOIDCAuthorizationConsentModePreConfiguredWithID(ctx *middlewares.Auth if !consent.IsAuthorized() { if consent.Responded() { - ctx.Logger.Errorf(logFmtErrConsentCantGrantRejected, requester.GetID(), client.GetID(), client.Consent, consent.ChallengeID) + ctx.Logger.Errorf(logFmtErrConsentCantGrantRejected, requester.GetID(), client.GetID(), client.GetConsentPolicy(), consent.ChallengeID) ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, fosite.ErrAccessDenied) @@ -124,7 +124,7 @@ func handleOIDCAuthorizationConsentModePreConfiguredWithID(ctx *middlewares.Auth return consent, false } -func handleOIDCAuthorizationConsentModePreConfiguredWithoutID(ctx *middlewares.AutheliaCtx, issuer *url.URL, client *oidc.Client, +func handleOIDCAuthorizationConsentModePreConfiguredWithoutID(ctx *middlewares.AutheliaCtx, issuer *url.URL, client oidc.Client, userSession session.UserSession, subject uuid.UUID, rw http.ResponseWriter, r *http.Request, requester fosite.AuthorizeRequester) (consent *model.OAuth2ConsentSession, handled bool) { var ( @@ -133,7 +133,7 @@ func handleOIDCAuthorizationConsentModePreConfiguredWithoutID(ctx *middlewares.A ) if config, err = handleOIDCAuthorizationConsentModePreConfiguredGetPreConfig(ctx, client, subject, requester); err != nil { - ctx.Logger.Errorf(logFmtErrConsentPreConfLookup, requester.GetID(), client.GetID(), client.Consent, err) + ctx.Logger.Errorf(logFmtErrConsentPreConfLookup, requester.GetID(), client.GetID(), client.GetConsentPolicy(), err) ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotLookup) @@ -145,7 +145,7 @@ func handleOIDCAuthorizationConsentModePreConfiguredWithoutID(ctx *middlewares.A } if consent, err = model.NewOAuth2ConsentSession(subject, requester); err != nil { - ctx.Logger.Errorf(logFmtErrConsentGenerate, requester.GetID(), client.GetID(), client.Consent, err) + ctx.Logger.Errorf(logFmtErrConsentGenerate, requester.GetID(), client.GetID(), client.GetConsentPolicy(), err) ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotGenerate) @@ -153,7 +153,7 @@ func handleOIDCAuthorizationConsentModePreConfiguredWithoutID(ctx *middlewares.A } if err = ctx.Providers.StorageProvider.SaveOAuth2ConsentSession(ctx, *consent); err != nil { - ctx.Logger.Errorf(logFmtErrConsentSaveSession, requester.GetID(), client.GetID(), client.Consent, consent.ChallengeID, err) + ctx.Logger.Errorf(logFmtErrConsentSaveSession, requester.GetID(), client.GetID(), client.GetConsentPolicy(), consent.ChallengeID, err) ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotSave) @@ -161,7 +161,7 @@ func handleOIDCAuthorizationConsentModePreConfiguredWithoutID(ctx *middlewares.A } if consent, err = ctx.Providers.StorageProvider.LoadOAuth2ConsentSessionByChallengeID(ctx, consent.ChallengeID); err != nil { - ctx.Logger.Errorf(logFmtErrConsentSaveSession, requester.GetID(), client.GetID(), client.Consent, consent.ChallengeID, err) + ctx.Logger.Errorf(logFmtErrConsentSaveSession, requester.GetID(), client.GetID(), client.GetConsentPolicy(), consent.ChallengeID, err) ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotSave) @@ -173,7 +173,7 @@ func handleOIDCAuthorizationConsentModePreConfiguredWithoutID(ctx *middlewares.A consent.PreConfiguration = sql.NullInt64{Int64: config.ID, Valid: true} if err = ctx.Providers.StorageProvider.SaveOAuth2ConsentSessionResponse(ctx, *consent, false); err != nil { - ctx.Logger.Errorf(logFmtErrConsentSaveSessionResponse, requester.GetID(), client.GetID(), client.Consent, consent.ChallengeID, err) + ctx.Logger.Errorf(logFmtErrConsentSaveSessionResponse, requester.GetID(), client.GetID(), client.GetConsentPolicy(), consent.ChallengeID, err) ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, oidc.ErrConsentCouldNotSave) @@ -183,12 +183,12 @@ func handleOIDCAuthorizationConsentModePreConfiguredWithoutID(ctx *middlewares.A return consent, false } -func handleOIDCAuthorizationConsentModePreConfiguredGetPreConfig(ctx *middlewares.AutheliaCtx, client *oidc.Client, subject uuid.UUID, requester fosite.Requester) (config *model.OAuth2ConsentPreConfig, err error) { +func handleOIDCAuthorizationConsentModePreConfiguredGetPreConfig(ctx *middlewares.AutheliaCtx, client oidc.Client, subject uuid.UUID, requester fosite.Requester) (config *model.OAuth2ConsentPreConfig, err error) { var ( rows *storage.ConsentPreConfigRows ) - ctx.Logger.Debugf(logFmtDbgConsentPreConfTryingLookup, requester.GetID(), client.GetID(), client.Consent, client.GetID(), subject, strings.Join(requester.GetRequestedScopes(), " ")) + ctx.Logger.Debugf(logFmtDbgConsentPreConfTryingLookup, requester.GetID(), client.GetID(), client.GetConsentPolicy(), client.GetID(), subject, strings.Join(requester.GetRequestedScopes(), " ")) if rows, err = ctx.Providers.StorageProvider.LoadOAuth2ConsentPreConfigurations(ctx, client.GetID(), subject); err != nil { return nil, fmt.Errorf("error loading rows: %w", err) @@ -196,7 +196,7 @@ func handleOIDCAuthorizationConsentModePreConfiguredGetPreConfig(ctx *middleware defer func() { if err := rows.Close(); err != nil { - ctx.Logger.Errorf(logFmtErrConsentPreConfRowsClose, requester.GetID(), client.GetID(), client.Consent, err) + ctx.Logger.Errorf(logFmtErrConsentPreConfRowsClose, requester.GetID(), client.GetID(), client.GetConsentPolicy(), err) } }() @@ -208,13 +208,13 @@ func handleOIDCAuthorizationConsentModePreConfiguredGetPreConfig(ctx *middleware } if config.HasExactGrants(scopes, audience) && config.CanConsent() { - ctx.Logger.Debugf(logFmtDbgConsentPreConfSuccessfulLookup, requester.GetID(), client.GetID(), client.Consent, client.GetID(), subject, strings.Join(requester.GetRequestedScopes(), " "), config.ID) + ctx.Logger.Debugf(logFmtDbgConsentPreConfSuccessfulLookup, requester.GetID(), client.GetID(), client.GetConsentPolicy(), client.GetID(), subject, strings.Join(requester.GetRequestedScopes(), " "), config.ID) return config, nil } } - ctx.Logger.Debugf(logFmtDbgConsentPreConfUnsuccessfulLookup, requester.GetID(), client.GetID(), client.Consent, client.GetID(), subject, strings.Join(requester.GetRequestedScopes(), " ")) + ctx.Logger.Debugf(logFmtDbgConsentPreConfUnsuccessfulLookup, requester.GetID(), client.GetID(), client.GetConsentPolicy(), client.GetID(), subject, strings.Join(requester.GetRequestedScopes(), " ")) return nil, nil } diff --git a/internal/handlers/handler_oidc_consent.go b/internal/handlers/handler_oidc_consent.go index bea6e9067..cc97a2889 100644 --- a/internal/handlers/handler_oidc_consent.go +++ b/internal/handlers/handler_oidc_consent.go @@ -32,7 +32,7 @@ func OpenIDConnectConsentGET(ctx *middlewares.AutheliaCtx) { var ( consent *model.OAuth2ConsentSession - client *oidc.Client + client oidc.Client handled bool ) @@ -70,7 +70,7 @@ func OpenIDConnectConsentPOST(ctx *middlewares.AutheliaCtx) { var ( userSession session.UserSession consent *model.OAuth2ConsentSession - client *oidc.Client + client oidc.Client handled bool ) @@ -90,12 +90,12 @@ func OpenIDConnectConsentPOST(ctx *middlewares.AutheliaCtx) { consent.Grant() if bodyJSON.PreConfigure { - if client.Consent.Mode == oidc.ClientConsentModePreConfigured { + if client.GetConsentPolicy().Mode == oidc.ClientConsentModePreConfigured { config := model.OAuth2ConsentPreConfig{ ClientID: consent.ClientID, Subject: consent.Subject.UUID, CreatedAt: time.Now(), - ExpiresAt: sql.NullTime{Time: time.Now().Add(client.Consent.Duration), Valid: true}, + ExpiresAt: sql.NullTime{Time: time.Now().Add(client.GetConsentPolicy().Duration), Valid: true}, Scopes: consent.GrantedScopes, Audience: consent.GrantedAudience, } @@ -151,7 +151,7 @@ func OpenIDConnectConsentPOST(ctx *middlewares.AutheliaCtx) { } } -func oidcConsentGetSessionsAndClient(ctx *middlewares.AutheliaCtx, consentID uuid.UUID) (userSession session.UserSession, consent *model.OAuth2ConsentSession, client *oidc.Client, handled bool) { +func oidcConsentGetSessionsAndClient(ctx *middlewares.AutheliaCtx, consentID uuid.UUID) (userSession session.UserSession, consent *model.OAuth2ConsentSession, client oidc.Client, handled bool) { var ( err error ) @@ -185,7 +185,7 @@ func oidcConsentGetSessionsAndClient(ctx *middlewares.AutheliaCtx, consentID uui return userSession, nil, nil, true } - switch client.Consent.Mode { + switch client.GetConsentPolicy().Mode { case oidc.ClientConsentModeImplicit: ctx.Logger.Errorf("Unable to perform OpenID Connect Consent for user '%s' and client id '%s': the client is using the implicit consent mode", userSession.Username, consent.ClientID) ctx.ReplyForbidden() diff --git a/internal/handlers/handler_oidc_userinfo.go b/internal/handlers/handler_oidc_userinfo.go index 3e1314d72..c3dc4f9e6 100644 --- a/internal/handlers/handler_oidc_userinfo.go +++ b/internal/handlers/handler_oidc_userinfo.go @@ -23,7 +23,7 @@ func OpenIDConnectUserinfo(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter, var ( tokenType fosite.TokenType requester fosite.AccessRequester - client *oidc.Client + client oidc.Client err error ) @@ -99,7 +99,7 @@ func OpenIDConnectUserinfo(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter, ctx.Logger.Tracef("UserInfo Response with id '%s' on client with id '%s' is being sent with the following claims: %+v", requester.GetID(), clientID, claims) - switch client.UserinfoSigningAlgorithm { + switch client.GetUserinfoSigningAlgorithm() { case oidc.SigningAlgorithmRSAWithSHA256: var jti uuid.UUID @@ -129,6 +129,6 @@ func OpenIDConnectUserinfo(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter, case oidc.SigningAlgorithmNone, "": ctx.Providers.OpenIDConnect.Write(rw, req, claims) default: - ctx.Providers.OpenIDConnect.WriteError(rw, req, errors.WithStack(fosite.ErrServerError.WithHintf("Unsupported UserInfo signing algorithm '%s'.", client.UserinfoSigningAlgorithm))) + ctx.Providers.OpenIDConnect.WriteError(rw, req, errors.WithStack(fosite.ErrServerError.WithHintf("Unsupported UserInfo signing algorithm '%s'.", client.GetUserinfoSigningAlgorithm()))) } } diff --git a/internal/handlers/response.go b/internal/handlers/response.go index b3de77c3c..9b8bda53f 100644 --- a/internal/handlers/response.go +++ b/internal/handlers/response.go @@ -178,7 +178,7 @@ func handleOIDCWorkflowResponseWithTargetURL(ctx *middlewares.AutheliaCtx, targe func handleOIDCWorkflowResponseWithID(ctx *middlewares.AutheliaCtx, id string) { var ( workflowID uuid.UUID - client *oidc.Client + client oidc.Client consent *model.OAuth2ConsentSession err error ) @@ -210,19 +210,19 @@ func handleOIDCWorkflowResponseWithID(ctx *middlewares.AutheliaCtx, id string) { var userSession session.UserSession if userSession, err = ctx.GetSession(); err != nil { - ctx.Error(fmt.Errorf("unable to redirect for authorization/consent for client with id '%s' with consent challenge id '%s': failed to lookup session: %w", client.ID, consent.ChallengeID, err), messageAuthenticationFailed) + ctx.Error(fmt.Errorf("unable to redirect for authorization/consent for client with id '%s' with consent challenge id '%s': failed to lookup session: %w", client.GetID(), consent.ChallengeID, err), messageAuthenticationFailed) return } if userSession.IsAnonymous() { - ctx.Error(fmt.Errorf("unable to redirect for authorization/consent for client with id '%s' with consent challenge id '%s': user is anonymous", client.ID, consent.ChallengeID), messageAuthenticationFailed) + ctx.Error(fmt.Errorf("unable to redirect for authorization/consent for client with id '%s' with consent challenge id '%s': user is anonymous", client.GetID(), consent.ChallengeID), messageAuthenticationFailed) return } if !client.IsAuthenticationLevelSufficient(userSession.AuthenticationLevel) { - ctx.Logger.Warnf("OpenID Connect client '%s' requires 2FA, cannot be redirected yet", client.ID) + ctx.Logger.Warnf("OpenID Connect client '%s' requires 2FA, cannot be redirected yet", client.GetID()) ctx.ReplyOK() return diff --git a/internal/handlers/types.go b/internal/handlers/types.go index 6079f1ac0..b69fbce95 100644 --- a/internal/handlers/types.go +++ b/internal/handlers/types.go @@ -143,7 +143,7 @@ type PasswordPolicyBody struct { } type handlerAuthorizationConsent func( - ctx *middlewares.AutheliaCtx, issuer *url.URL, client *oidc.Client, + ctx *middlewares.AutheliaCtx, issuer *url.URL, client oidc.Client, userSession session.UserSession, subject uuid.UUID, rw http.ResponseWriter, r *http.Request, requester fosite.AuthorizeRequester) (consent *model.OAuth2ConsentSession, handled bool) diff --git a/internal/oidc/client.go b/internal/oidc/client.go index 41d302d03..71b43d867 100644 --- a/internal/oidc/client.go +++ b/internal/oidc/client.go @@ -1,8 +1,10 @@ package oidc import ( + "github.com/go-crypt/crypt/algorithm" "github.com/ory/fosite" "github.com/ory/x/errorsx" + "gopkg.in/square/go-jose.v2" "github.com/authelia/authelia/v4/internal/authentication" "github.com/authelia/authelia/v4/internal/authorization" @@ -11,8 +13,8 @@ import ( ) // NewClient creates a new Client. -func NewClient(config schema.OpenIDConnectClientConfiguration) (client *Client) { - client = &Client{ +func NewClient(config schema.OpenIDConnectClientConfiguration) (client Client) { + base := &BaseClient{ ID: config.ID, Description: config.Description, Secret: config.Secret, @@ -40,14 +42,165 @@ func NewClient(config schema.OpenIDConnectClientConfiguration) (client *Client) } for _, mode := range config.ResponseModes { - client.ResponseModes = append(client.ResponseModes, fosite.ResponseModeType(mode)) + base.ResponseModes = append(base.ResponseModes, fosite.ResponseModeType(mode)) + } + + if config.TokenEndpointAuthMethod != "" && config.TokenEndpointAuthMethod != "auto" { + client = &FullClient{ + BaseClient: base, + TokenEndpointAuthMethod: config.TokenEndpointAuthMethod, + } + } else { + client = base } return client } +// GetID returns the ID. +func (c *BaseClient) GetID() string { + return c.ID +} + +// GetDescription returns the Description. +func (c *BaseClient) GetDescription() string { + if c.Description == "" { + c.Description = c.GetID() + } + + return c.Description +} + +// GetSecret returns the Secret. +func (c *BaseClient) GetSecret() algorithm.Digest { + return c.Secret +} + +// GetSectorIdentifier returns the SectorIdentifier for this client. +func (c *BaseClient) GetSectorIdentifier() string { + return c.SectorIdentifier +} + +// GetHashedSecret returns the Secret. +func (c *BaseClient) GetHashedSecret() (secret []byte) { + if c.Secret == nil { + return []byte(nil) + } + + return []byte(c.Secret.Encode()) +} + +// GetRedirectURIs returns the RedirectURIs. +func (c *BaseClient) GetRedirectURIs() (redirectURIs []string) { + return c.RedirectURIs +} + +// GetGrantTypes returns the GrantTypes. +func (c *BaseClient) GetGrantTypes() fosite.Arguments { + if len(c.GrantTypes) == 0 { + return fosite.Arguments{"authorization_code"} + } + + return c.GrantTypes +} + +// GetResponseTypes returns the ResponseTypes. +func (c *BaseClient) GetResponseTypes() fosite.Arguments { + if len(c.ResponseTypes) == 0 { + return fosite.Arguments{"code"} + } + + return c.ResponseTypes +} + +// GetScopes returns the Scopes. +func (c *BaseClient) GetScopes() fosite.Arguments { + return c.Scopes +} + +// GetAudience returns the Audience. +func (c *BaseClient) GetAudience() fosite.Arguments { + return c.Audience +} + +// GetResponseModes returns the valid response modes for this client. +// +// Implements the fosite.ResponseModeClient. +func (c *BaseClient) GetResponseModes() []fosite.ResponseModeType { + return c.ResponseModes +} + +// GetUserinfoSigningAlgorithm returns the UserinfoSigningAlgorithm. +func (c *BaseClient) GetUserinfoSigningAlgorithm() string { + if c.UserinfoSigningAlgorithm == "" { + c.UserinfoSigningAlgorithm = SigningAlgorithmNone + } + + return c.UserinfoSigningAlgorithm +} + +// GetPAREnforcement returns EnforcePAR. +func (c *BaseClient) GetPAREnforcement() bool { + return c.EnforcePAR +} + +// GetPKCEEnforcement returns EnforcePKCE. +func (c *BaseClient) GetPKCEEnforcement() bool { + return c.EnforcePKCE +} + +// GetPKCEChallengeMethodEnforcement returns EnforcePKCEChallengeMethod. +func (c *BaseClient) GetPKCEChallengeMethodEnforcement() bool { + return c.EnforcePKCEChallengeMethod +} + +// GetPKCEChallengeMethod returns PKCEChallengeMethod. +func (c *BaseClient) GetPKCEChallengeMethod() string { + return c.PKCEChallengeMethod +} + +// GetAuthorizationPolicy returns Policy. +func (c *BaseClient) GetAuthorizationPolicy() authorization.Level { + return c.Policy +} + +// GetConsentPolicy returns Consent. +func (c *BaseClient) GetConsentPolicy() ClientConsent { + return c.Consent +} + +// GetConsentResponseBody returns the proper consent response body for this session.OIDCWorkflowSession. +func (c *BaseClient) GetConsentResponseBody(consent *model.OAuth2ConsentSession) ConsentGetResponseBody { + body := ConsentGetResponseBody{ + ClientID: c.ID, + ClientDescription: c.Description, + PreConfiguration: c.Consent.Mode == ClientConsentModePreConfigured, + } + + if consent != nil { + body.Scopes = consent.RequestedScopes + body.Audience = consent.RequestedAudience + } + + return body +} + +// IsPublic returns the value of the Public property. +func (c *BaseClient) IsPublic() bool { + return c.Public +} + +// IsAuthenticationLevelSufficient returns if the provided authentication.Level is sufficient for the client of the AutheliaClient. +func (c *BaseClient) IsAuthenticationLevelSufficient(level authentication.Level) bool { + if level == authentication.NotAuthenticated { + return false + } + + return authorization.IsAuthLevelSufficient(level, c.Policy) +} + // ValidatePKCEPolicy is a helper function to validate PKCE policy constraints on a per-client basis. -func (c *Client) ValidatePKCEPolicy(r fosite.Requester) (err error) { +func (c *BaseClient) ValidatePKCEPolicy(r fosite.Requester) (err error) { form := r.GetRequestForm() if c.EnforcePKCE { @@ -70,7 +223,7 @@ func (c *Client) ValidatePKCEPolicy(r fosite.Requester) (err error) { } // ValidatePARPolicy is a helper function to validate additional policy constraints on a per-client basis. -func (c *Client) ValidatePARPolicy(r fosite.Requester, prefix string) (err error) { +func (c *BaseClient) ValidatePARPolicy(r fosite.Requester, prefix string) (err error) { if c.EnforcePAR { if !IsPushedAuthorizedRequest(r, prefix) { switch requestURI := r.GetRequestForm().Get(FormParameterRequestURI); requestURI { @@ -87,7 +240,7 @@ func (c *Client) ValidatePARPolicy(r fosite.Requester, prefix string) (err error // ValidateResponseModePolicy is an additional check to the response mode parameter to ensure if it's omitted that the // default response mode for the fosite.AuthorizeRequester is permitted. -func (c *Client) ValidateResponseModePolicy(r fosite.AuthorizeRequester) (err error) { +func (c *BaseClient) ValidateResponseModePolicy(r fosite.AuthorizeRequester) (err error) { if r.GetResponseMode() != fosite.ResponseModeDefault { return nil } @@ -109,91 +262,52 @@ func (c *Client) ValidateResponseModePolicy(r fosite.AuthorizeRequester) (err er return errorsx.WithStack(fosite.ErrUnsupportedResponseMode.WithHintf(`The request omitted the response_mode making the default response_mode "%s" based on the other authorization request parameters but registered OAuth 2.0 client doesn't support this response_mode`, m)) } -// IsAuthenticationLevelSufficient returns if the provided authentication.Level is sufficient for the client of the AutheliaClient. -func (c *Client) IsAuthenticationLevelSufficient(level authentication.Level) bool { - if level == authentication.NotAuthenticated { - return false +// GetRequestURIs is an array of request_uri values that are pre-registered by the RP for use at the OP. Servers MAY +// cache the contents of the files referenced by these URIs and not retrieve them at the time they are used in a request. +// OPs can require that request_uri values used be pre-registered with the require_request_uri_registration +// discovery parameter. +func (c *FullClient) GetRequestURIs() []string { + return c.RequestURIs +} + +// GetJSONWebKeys returns the JSON Web Key Set containing the public key used by the client to authenticate. +func (c *FullClient) GetJSONWebKeys() *jose.JSONWebKeySet { + return c.JSONWebKeys +} + +// GetJSONWebKeysURI returns the URL for lookup of JSON Web Key Set containing the +// public key used by the client to authenticate. +func (c *FullClient) GetJSONWebKeysURI() string { + return c.JSONWebKeysURI +} + +// GetRequestObjectSigningAlgorithm returns the JWS [JWS] alg algorithm [JWA] that MUST be used for signing Request +// Objects sent to the OP. All Request Objects from this Client MUST be rejected, if not signed with this algorithm. +func (c *FullClient) GetRequestObjectSigningAlgorithm() string { + return c.RequestObjectSigningAlgorithm +} + +// GetTokenEndpointAuthMethod returns the requested Client Authentication Method for the Token Endpoint. The options are +// client_secret_post, client_secret_basic, client_secret_jwt, private_key_jwt, and none. +func (c *FullClient) GetTokenEndpointAuthMethod() string { + if c.TokenEndpointAuthMethod == "" { + if c.Public { + c.TokenEndpointAuthMethod = ClientAuthMethodNone + } else { + c.TokenEndpointAuthMethod = ClientAuthMethodClientSecretPost + } } - return authorization.IsAuthLevelSufficient(level, c.Policy) + return c.TokenEndpointAuthMethod } -// GetSectorIdentifier returns the SectorIdentifier for this client. -func (c *Client) GetSectorIdentifier() string { - return c.SectorIdentifier -} - -// GetConsentResponseBody returns the proper consent response body for this session.OIDCWorkflowSession. -func (c *Client) GetConsentResponseBody(consent *model.OAuth2ConsentSession) ConsentGetResponseBody { - body := ConsentGetResponseBody{ - ClientID: c.ID, - ClientDescription: c.Description, - PreConfiguration: c.Consent.Mode == ClientConsentModePreConfigured, +// GetTokenEndpointAuthSigningAlgorithm returns the JWS [JWS] alg algorithm [JWA] that MUST be used for signing the JWT +// [JWT] used to authenticate the Client at the Token Endpoint for the private_key_jwt and client_secret_jwt +// authentication methods. +func (c *FullClient) GetTokenEndpointAuthSigningAlgorithm() string { + if c.TokenEndpointAuthSigningAlgorithm == "" { + c.TokenEndpointAuthSigningAlgorithm = SigningAlgorithmRSAWithSHA256 } - if consent != nil { - body.Scopes = consent.RequestedScopes - body.Audience = consent.RequestedAudience - } - - return body -} - -// GetID returns the ID. -func (c *Client) GetID() string { - return c.ID -} - -// GetHashedSecret returns the Secret. -func (c *Client) GetHashedSecret() (secret []byte) { - if c.Secret == nil { - return []byte(nil) - } - - return []byte(c.Secret.Encode()) -} - -// GetRedirectURIs returns the RedirectURIs. -func (c *Client) GetRedirectURIs() (redirectURIs []string) { - return c.RedirectURIs -} - -// GetGrantTypes returns the GrantTypes. -func (c *Client) GetGrantTypes() fosite.Arguments { - if len(c.GrantTypes) == 0 { - return fosite.Arguments{"authorization_code"} - } - - return c.GrantTypes -} - -// GetResponseTypes returns the ResponseTypes. -func (c *Client) GetResponseTypes() fosite.Arguments { - if len(c.ResponseTypes) == 0 { - return fosite.Arguments{"code"} - } - - return c.ResponseTypes -} - -// GetScopes returns the Scopes. -func (c *Client) GetScopes() fosite.Arguments { - return c.Scopes -} - -// IsPublic returns the value of the Public property. -func (c *Client) IsPublic() bool { - return c.Public -} - -// GetAudience returns the Audience. -func (c *Client) GetAudience() fosite.Arguments { - return c.Audience -} - -// GetResponseModes returns the valid response modes for this client. -// -// Implements the fosite.ResponseModeClient. -func (c *Client) GetResponseModes() []fosite.ResponseModeType { - return c.ResponseModes + return c.TokenEndpointAuthSigningAlgorithm } diff --git a/internal/oidc/client_test.go b/internal/oidc/client_test.go index 1546644c2..a4be63346 100644 --- a/internal/oidc/client_test.go +++ b/internal/oidc/client_test.go @@ -7,6 +7,7 @@ import ( "github.com/ory/fosite" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "gopkg.in/square/go-jose.v2" "github.com/authelia/authelia/v4/internal/authentication" "github.com/authelia/authelia/v4/internal/authorization" @@ -15,36 +16,136 @@ import ( ) func TestNewClient(t *testing.T) { - blankConfig := schema.OpenIDConnectClientConfiguration{} - blankClient := NewClient(blankConfig) - assert.Equal(t, "", blankClient.ID) - assert.Equal(t, "", blankClient.Description) - assert.Equal(t, "", blankClient.Description) - assert.Len(t, blankClient.ResponseModes, 0) + config := schema.OpenIDConnectClientConfiguration{} + client := NewClient(config) + assert.Equal(t, "", client.GetID()) + assert.Equal(t, "", client.GetDescription()) + assert.Len(t, client.GetResponseModes(), 0) + assert.Len(t, client.GetResponseTypes(), 1) + assert.Equal(t, "", client.GetSectorIdentifier()) - exampleConfig := schema.OpenIDConnectClientConfiguration{ - ID: "myapp", - Description: "My App", - Policy: "two_factor", - Secret: MustDecodeSecret("$plaintext$abcdef"), - RedirectURIs: []string{"https://google.com/callback"}, + bclient, ok := client.(*BaseClient) + require.True(t, ok) + assert.Equal(t, "", bclient.UserinfoSigningAlgorithm) + assert.Equal(t, SigningAlgorithmNone, client.GetUserinfoSigningAlgorithm()) + + _, ok = client.(*FullClient) + assert.False(t, ok) + + config = schema.OpenIDConnectClientConfiguration{ + ID: myclient, + Description: myclientdesc, + Policy: twofactor, + Secret: MustDecodeSecret(badsecret), + RedirectURIs: []string{examplecom}, Scopes: schema.DefaultOpenIDConnectClientConfiguration.Scopes, ResponseTypes: schema.DefaultOpenIDConnectClientConfiguration.ResponseTypes, GrantTypes: schema.DefaultOpenIDConnectClientConfiguration.GrantTypes, ResponseModes: schema.DefaultOpenIDConnectClientConfiguration.ResponseModes, } - exampleClient := NewClient(exampleConfig) - assert.Equal(t, "myapp", exampleClient.ID) - require.Len(t, exampleClient.ResponseModes, 3) - assert.Equal(t, fosite.ResponseModeFormPost, exampleClient.ResponseModes[0]) - assert.Equal(t, fosite.ResponseModeQuery, exampleClient.ResponseModes[1]) - assert.Equal(t, fosite.ResponseModeFragment, exampleClient.ResponseModes[2]) - assert.Equal(t, authorization.TwoFactor, exampleClient.Policy) + client = NewClient(config) + assert.Equal(t, myclient, client.GetID()) + require.Len(t, client.GetResponseModes(), 1) + assert.Equal(t, fosite.ResponseModeFormPost, client.GetResponseModes()[0]) + assert.Equal(t, authorization.TwoFactor, client.GetAuthorizationPolicy()) + + config = schema.OpenIDConnectClientConfiguration{ + TokenEndpointAuthMethod: ClientAuthMethodClientSecretBasic, + } + + client = NewClient(config) + + fclient, ok := client.(*FullClient) + + var niljwks *jose.JSONWebKeySet + + require.True(t, ok) + assert.Equal(t, "", fclient.UserinfoSigningAlgorithm) + assert.Equal(t, ClientAuthMethodClientSecretBasic, fclient.TokenEndpointAuthMethod) + assert.Equal(t, ClientAuthMethodClientSecretBasic, fclient.GetTokenEndpointAuthMethod()) + assert.Equal(t, SigningAlgorithmNone, client.GetUserinfoSigningAlgorithm()) + assert.Equal(t, "", fclient.TokenEndpointAuthSigningAlgorithm) + assert.Equal(t, SigningAlgorithmRSAWithSHA256, fclient.GetTokenEndpointAuthSigningAlgorithm()) + assert.Equal(t, "", fclient.RequestObjectSigningAlgorithm) + assert.Equal(t, "", fclient.GetRequestObjectSigningAlgorithm()) + assert.Equal(t, "", fclient.JSONWebKeysURI) + assert.Equal(t, "", fclient.GetJSONWebKeysURI()) + assert.Equal(t, niljwks, fclient.JSONWebKeys) + assert.Equal(t, niljwks, fclient.GetJSONWebKeys()) + assert.Equal(t, []string(nil), fclient.RequestURIs) + assert.Equal(t, []string(nil), fclient.GetRequestURIs()) +} + +func TestBaseClient_ValidatePARPolicy(t *testing.T) { + testCases := []struct { + name string + client *BaseClient + have *fosite.Request + expected string + }{ + { + "ShouldNotEnforcePAR", + &BaseClient{ + EnforcePAR: false, + }, + &fosite.Request{}, + "", + }, + { + "ShouldEnforcePARAndErrorWithoutCorrectRequestURI", + &BaseClient{ + EnforcePAR: true, + }, + &fosite.Request{ + Form: map[string][]string{ + FormParameterRequestURI: {"https://google.com"}, + }, + }, + "invalid_request", + }, + { + "ShouldEnforcePARAndErrorWithEmptyRequestURI", + &BaseClient{ + EnforcePAR: true, + }, + &fosite.Request{ + Form: map[string][]string{ + FormParameterRequestURI: {""}, + }, + }, + "invalid_request", + }, + { + "ShouldEnforcePARAndNotErrorWithCorrectRequestURI", + &BaseClient{ + EnforcePAR: true, + }, + &fosite.Request{ + Form: map[string][]string{ + FormParameterRequestURI: {urnPARPrefix + "abc"}, + }, + }, + "", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := tc.client.ValidatePARPolicy(tc.have, urnPARPrefix) + + switch tc.expected { + case "": + assert.NoError(t, err) + default: + assert.EqualError(t, err, tc.expected) + } + }) + } } func TestIsAuthenticationLevelSufficient(t *testing.T) { - c := Client{} + c := &FullClient{BaseClient: &BaseClient{}} c.Policy = authorization.Bypass assert.False(t, c.IsAuthenticationLevelSufficient(authentication.NotAuthenticated)) @@ -68,7 +169,7 @@ func TestIsAuthenticationLevelSufficient(t *testing.T) { } func TestClient_GetConsentResponseBody(t *testing.T) { - c := Client{} + c := &FullClient{BaseClient: &BaseClient{}} consentRequestBody := c.GetConsentResponseBody(nil) assert.Equal(t, "", consentRequestBody.ClientID) @@ -76,56 +177,56 @@ func TestClient_GetConsentResponseBody(t *testing.T) { assert.Equal(t, []string(nil), consentRequestBody.Scopes) assert.Equal(t, []string(nil), consentRequestBody.Audience) - c.ID = "myclient" - c.Description = "My Client" + c.ID = myclient + c.Description = myclientdesc consent := &model.OAuth2ConsentSession{ - RequestedAudience: []string{"https://example.com"}, - RequestedScopes: []string{"openid", "groups"}, + RequestedAudience: []string{examplecom}, + RequestedScopes: []string{ScopeOpenID, ScopeGroups}, } - expectedScopes := []string{"openid", "groups"} - expectedAudiences := []string{"https://example.com"} + expectedScopes := []string{ScopeOpenID, ScopeGroups} + expectedAudiences := []string{examplecom} consentRequestBody = c.GetConsentResponseBody(consent) - assert.Equal(t, "myclient", consentRequestBody.ClientID) - assert.Equal(t, "My Client", consentRequestBody.ClientDescription) + assert.Equal(t, myclient, consentRequestBody.ClientID) + assert.Equal(t, myclientdesc, consentRequestBody.ClientDescription) assert.Equal(t, expectedScopes, consentRequestBody.Scopes) assert.Equal(t, expectedAudiences, consentRequestBody.Audience) } func TestClient_GetAudience(t *testing.T) { - c := Client{} + c := &FullClient{BaseClient: &BaseClient{}} audience := c.GetAudience() assert.Len(t, audience, 0) - c.Audience = []string{"https://example.com"} + c.Audience = []string{examplecom} audience = c.GetAudience() require.Len(t, audience, 1) - assert.Equal(t, "https://example.com", audience[0]) + assert.Equal(t, examplecom, audience[0]) } func TestClient_GetScopes(t *testing.T) { - c := Client{} + c := &FullClient{BaseClient: &BaseClient{}} scopes := c.GetScopes() assert.Len(t, scopes, 0) - c.Scopes = []string{"openid"} + c.Scopes = []string{ScopeOpenID} scopes = c.GetScopes() require.Len(t, scopes, 1) - assert.Equal(t, "openid", scopes[0]) + assert.Equal(t, ScopeOpenID, scopes[0]) } func TestClient_GetGrantTypes(t *testing.T) { - c := Client{} + c := &FullClient{BaseClient: &BaseClient{}} grantTypes := c.GetGrantTypes() require.Len(t, grantTypes, 1) - assert.Equal(t, "authorization_code", grantTypes[0]) + assert.Equal(t, GrantTypeAuthorizationCode, grantTypes[0]) c.GrantTypes = []string{"device_code"} @@ -135,55 +236,55 @@ func TestClient_GetGrantTypes(t *testing.T) { } func TestClient_Hashing(t *testing.T) { - c := Client{} + c := &FullClient{BaseClient: &BaseClient{}} hashedSecret := c.GetHashedSecret() assert.Equal(t, []byte(nil), hashedSecret) - c.Secret = MustDecodeSecret("$plaintext$a_bad_secret") + c.Secret = MustDecodeSecret(badsecret) assert.True(t, c.Secret.MatchBytes([]byte("a_bad_secret"))) } func TestClient_GetHashedSecret(t *testing.T) { - c := Client{} + c := &FullClient{BaseClient: &BaseClient{}} hashedSecret := c.GetHashedSecret() assert.Equal(t, []byte(nil), hashedSecret) - c.Secret = MustDecodeSecret("$plaintext$a_bad_secret") + c.Secret = MustDecodeSecret(badsecret) hashedSecret = c.GetHashedSecret() - assert.Equal(t, []byte("$plaintext$a_bad_secret"), hashedSecret) + assert.Equal(t, []byte(badsecret), hashedSecret) } func TestClient_GetID(t *testing.T) { - c := Client{} + c := &FullClient{BaseClient: &BaseClient{}} id := c.GetID() assert.Equal(t, "", id) - c.ID = "myid" + c.ID = myclient id = c.GetID() - assert.Equal(t, "myid", id) + assert.Equal(t, myclient, id) } func TestClient_GetRedirectURIs(t *testing.T) { - c := Client{} + c := &FullClient{BaseClient: &BaseClient{}} redirectURIs := c.GetRedirectURIs() require.Len(t, redirectURIs, 0) - c.RedirectURIs = []string{"https://example.com/oauth2/callback"} + c.RedirectURIs = []string{examplecom} redirectURIs = c.GetRedirectURIs() require.Len(t, redirectURIs, 1) - assert.Equal(t, "https://example.com/oauth2/callback", redirectURIs[0]) + assert.Equal(t, examplecom, redirectURIs[0]) } func TestClient_GetResponseModes(t *testing.T) { - c := Client{} + c := &FullClient{BaseClient: &BaseClient{}} responseModes := c.GetResponseModes() require.Len(t, responseModes, 0) @@ -202,18 +303,18 @@ func TestClient_GetResponseModes(t *testing.T) { } func TestClient_GetResponseTypes(t *testing.T) { - c := Client{} + c := &FullClient{BaseClient: &BaseClient{}} responseTypes := c.GetResponseTypes() require.Len(t, responseTypes, 1) - assert.Equal(t, "code", responseTypes[0]) + assert.Equal(t, ResponseTypeAuthorizationCodeFlow, responseTypes[0]) - c.ResponseTypes = []string{"code", "id_token"} + c.ResponseTypes = []string{ResponseTypeAuthorizationCodeFlow, ResponseTypeImplicitFlowIDToken} responseTypes = c.GetResponseTypes() require.Len(t, responseTypes, 2) - assert.Equal(t, "code", responseTypes[0]) - assert.Equal(t, "id_token", responseTypes[1]) + assert.Equal(t, ResponseTypeAuthorizationCodeFlow, responseTypes[0]) + assert.Equal(t, ResponseTypeImplicitFlowIDToken, responseTypes[1]) } func TestNewClientPKCE(t *testing.T) { @@ -290,9 +391,9 @@ func TestNewClientPKCE(t *testing.T) { t.Run(tc.name, func(t *testing.T) { client := NewClient(tc.have) - assert.Equal(t, tc.expectedEnforcePKCE, client.EnforcePKCE) - assert.Equal(t, tc.expectedEnforcePKCEChallengeMethod, client.EnforcePKCEChallengeMethod) - assert.Equal(t, tc.expected, client.PKCEChallengeMethod) + assert.Equal(t, tc.expectedEnforcePKCE, client.GetPKCEEnforcement()) + assert.Equal(t, tc.expectedEnforcePKCEChallengeMethod, client.GetPKCEChallengeMethodEnforcement()) + assert.Equal(t, tc.expected, client.GetPKCEChallengeMethod()) if tc.r != nil { err := client.ValidatePKCEPolicy(tc.r) @@ -355,7 +456,7 @@ func TestNewClientPAR(t *testing.T) { t.Run(tc.name, func(t *testing.T) { client := NewClient(tc.have) - assert.Equal(t, tc.expected, client.EnforcePAR) + assert.Equal(t, tc.expected, client.GetPAREnforcement()) if tc.r != nil { err := client.ValidatePARPolicy(tc.r, urnPARPrefix) @@ -437,7 +538,7 @@ func TestNewClientResponseModes(t *testing.T) { } func TestClient_IsPublic(t *testing.T) { - c := Client{} + c := &FullClient{BaseClient: &BaseClient{}} assert.False(t, c.IsPublic()) diff --git a/internal/oidc/config.go b/internal/oidc/config.go index 1cc7bf098..1db4e757a 100644 --- a/internal/oidc/config.go +++ b/internal/oidc/config.go @@ -169,12 +169,6 @@ type LifespanConfig struct { RefreshToken time.Duration } -const ( - PromptNone = none - PromptLogin = "login" - PromptConsent = "consent" -) - // LoadHandlers reloads the handlers based on the current configuration. func (c *Config) LoadHandlers(store *Store, strategy jwt.Signer) { validator := openid.NewOpenIDConnectRequestValidator(strategy, c) diff --git a/internal/oidc/const.go b/internal/oidc/const.go index db8c3a23d..01670e317 100644 --- a/internal/oidc/const.go +++ b/internal/oidc/const.go @@ -69,15 +69,12 @@ const ( GrantTypeImplicit = implicit GrantTypeRefreshToken = "refresh_token" GrantTypeAuthorizationCode = "authorization_code" - GrantTypePassword = "password" - GrantTypeClientCredentials = "client_credentials" ) // Client Auth Method strings. const ( ClientAuthMethodClientSecretBasic = "client_secret_basic" ClientAuthMethodClientSecretPost = "client_secret_post" - ClientAuthMethodClientSecretJWT = "client_secret_jwt" ClientAuthMethodNone = "none" ) @@ -117,6 +114,13 @@ const ( FormParameterCodeChallengeMethod = "code_challenge_method" ) +const ( + PromptNone = none + PromptLogin = "login" + PromptConsent = "consent" + // PromptCreate = "create" // This prompt value is currently unused. +) + // Endpoints. const ( EndpointAuthorization = "authorization" diff --git a/internal/oidc/const_test.go b/internal/oidc/const_test.go new file mode 100644 index 000000000..b5e3d915b --- /dev/null +++ b/internal/oidc/const_test.go @@ -0,0 +1,12 @@ +package oidc + +const ( + myclient = "myclient" + myclientdesc = "My Client" + onefactor = "one_factor" + twofactor = "two_factor" + examplecom = "https://example.com" + examplecomsid = "example.com" + badsecret = "$plaintext$a_bad_secret" + badhmac = "asbdhaaskmdlkamdklasmdlkams" +) diff --git a/internal/oidc/discovery.go b/internal/oidc/discovery.go index 996e5e3f5..e57bad146 100644 --- a/internal/oidc/discovery.go +++ b/internal/oidc/discovery.go @@ -5,70 +5,76 @@ import ( ) // NewOpenIDConnectWellKnownConfiguration generates a new OpenIDConnectWellKnownConfiguration. -func NewOpenIDConnectWellKnownConfiguration(c *schema.OpenIDConnectConfiguration, clients map[string]*Client) (config OpenIDConnectWellKnownConfiguration) { +func NewOpenIDConnectWellKnownConfiguration(c *schema.OpenIDConnectConfiguration) (config OpenIDConnectWellKnownConfiguration) { config = OpenIDConnectWellKnownConfiguration{ - CommonDiscoveryOptions: CommonDiscoveryOptions{ - SubjectTypesSupported: []string{ - SubjectTypePublic, + OAuth2WellKnownConfiguration: OAuth2WellKnownConfiguration{ + CommonDiscoveryOptions: CommonDiscoveryOptions{ + SubjectTypesSupported: []string{ + SubjectTypePublic, + SubjectTypePairwise, + }, + ResponseTypesSupported: []string{ + ResponseTypeAuthorizationCodeFlow, + ResponseTypeImplicitFlowIDToken, + ResponseTypeImplicitFlowToken, + ResponseTypeImplicitFlowBoth, + ResponseTypeHybridFlowIDToken, + ResponseTypeHybridFlowToken, + ResponseTypeHybridFlowBoth, + }, + GrantTypesSupported: []string{ + GrantTypeAuthorizationCode, + GrantTypeImplicit, + GrantTypeRefreshToken, + }, + ResponseModesSupported: []string{ + ResponseModeFormPost, + ResponseModeQuery, + ResponseModeFragment, + }, + ScopesSupported: []string{ + ScopeOfflineAccess, + ScopeOpenID, + ScopeProfile, + ScopeGroups, + ScopeEmail, + }, + ClaimsSupported: []string{ + ClaimAuthenticationMethodsReference, + ClaimAudience, + ClaimAuthorizedParty, + ClaimClientIdentifier, + ClaimExpirationTime, + ClaimIssuedAt, + ClaimIssuer, + ClaimJWTID, + ClaimRequestedAt, + ClaimSubject, + ClaimAuthenticationTime, + ClaimNonce, + ClaimPreferredEmail, + ClaimEmailVerified, + ClaimEmailAlts, + ClaimGroups, + ClaimPreferredUsername, + ClaimFullName, + }, + TokenEndpointAuthMethodsSupported: []string{ + ClientAuthMethodClientSecretBasic, + ClientAuthMethodClientSecretPost, + ClientAuthMethodNone, + }, }, - ResponseTypesSupported: []string{ - ResponseTypeAuthorizationCodeFlow, - ResponseTypeImplicitFlowIDToken, - ResponseTypeImplicitFlowToken, - ResponseTypeImplicitFlowBoth, - ResponseTypeHybridFlowIDToken, - ResponseTypeHybridFlowToken, - ResponseTypeHybridFlowBoth, + OAuth2DiscoveryOptions: OAuth2DiscoveryOptions{ + CodeChallengeMethodsSupported: []string{ + PKCEChallengeMethodSHA256, + }, }, - GrantTypesSupported: []string{ - GrantTypeAuthorizationCode, - GrantTypeImplicit, - GrantTypeRefreshToken, - }, - ResponseModesSupported: []string{ - ResponseModeFormPost, - ResponseModeQuery, - ResponseModeFragment, - }, - ScopesSupported: []string{ - ScopeOfflineAccess, - ScopeOpenID, - ScopeProfile, - ScopeGroups, - ScopeEmail, - }, - ClaimsSupported: []string{ - ClaimAuthenticationMethodsReference, - ClaimAudience, - ClaimAuthorizedParty, - ClaimClientIdentifier, - ClaimExpirationTime, - ClaimIssuedAt, - ClaimIssuer, - ClaimJWTID, - ClaimRequestedAt, - ClaimSubject, - ClaimAuthenticationTime, - ClaimNonce, - ClaimPreferredEmail, - ClaimEmailVerified, - ClaimEmailAlts, - ClaimGroups, - ClaimPreferredUsername, - ClaimFullName, - }, - TokenEndpointAuthMethodsSupported: []string{ - ClientAuthMethodClientSecretBasic, - ClientAuthMethodClientSecretPost, - ClientAuthMethodClientSecretJWT, - ClientAuthMethodNone, - }, - }, - OAuth2DiscoveryOptions: OAuth2DiscoveryOptions{ - CodeChallengeMethodsSupported: []string{ - PKCEChallengeMethodSHA256, + OAuth2PushedAuthorizationDiscoveryOptions: &OAuth2PushedAuthorizationDiscoveryOptions{ + RequirePushedAuthorizationRequests: c.PAR.Enforce, }, }, + OpenIDConnectDiscoveryOptions: OpenIDConnectDiscoveryOptions{ IDTokenSigningAlgValuesSupported: []string{ SigningAlgorithmRSAWithSHA256, @@ -77,30 +83,15 @@ func NewOpenIDConnectWellKnownConfiguration(c *schema.OpenIDConnectConfiguration SigningAlgorithmNone, SigningAlgorithmRSAWithSHA256, }, - RequestObjectSigningAlgValuesSupported: []string{ - SigningAlgorithmNone, - SigningAlgorithmRSAWithSHA256, + }, + OpenIDConnectFrontChannelLogoutDiscoveryOptions: &OpenIDConnectFrontChannelLogoutDiscoveryOptions{}, + OpenIDConnectBackChannelLogoutDiscoveryOptions: &OpenIDConnectBackChannelLogoutDiscoveryOptions{}, + OpenIDConnectPromptCreateDiscoveryOptions: &OpenIDConnectPromptCreateDiscoveryOptions{ + PromptValuesSupported: []string{ + PromptNone, + PromptConsent, }, }, - PushedAuthorizationDiscoveryOptions: PushedAuthorizationDiscoveryOptions{ - RequirePushedAuthorizationRequests: c.PAR.Enforce, - }, - } - - var pairwise, public bool - - for _, client := range clients { - if pairwise && public { - break - } - - if client.SectorIdentifier != "" { - pairwise = true - } - } - - if pairwise { - config.SubjectTypesSupported = append(config.SubjectTypesSupported, SubjectTypePairwise) } if c.EnablePKCEPlainChallenge { @@ -109,3 +100,93 @@ func NewOpenIDConnectWellKnownConfiguration(c *schema.OpenIDConnectConfiguration return config } + +// Copy the values of the OAuth2WellKnownConfiguration and return it as a new struct. +func (opts OAuth2WellKnownConfiguration) Copy() (optsCopy OAuth2WellKnownConfiguration) { + optsCopy = OAuth2WellKnownConfiguration{ + CommonDiscoveryOptions: opts.CommonDiscoveryOptions, + OAuth2DiscoveryOptions: opts.OAuth2DiscoveryOptions, + } + + if opts.OAuth2DeviceAuthorizationGrantDiscoveryOptions != nil { + optsCopy.OAuth2DeviceAuthorizationGrantDiscoveryOptions = &OAuth2DeviceAuthorizationGrantDiscoveryOptions{} + *optsCopy.OAuth2DeviceAuthorizationGrantDiscoveryOptions = *opts.OAuth2DeviceAuthorizationGrantDiscoveryOptions + } + + if opts.OAuth2MutualTLSClientAuthenticationDiscoveryOptions != nil { + optsCopy.OAuth2MutualTLSClientAuthenticationDiscoveryOptions = &OAuth2MutualTLSClientAuthenticationDiscoveryOptions{} + *optsCopy.OAuth2MutualTLSClientAuthenticationDiscoveryOptions = *opts.OAuth2MutualTLSClientAuthenticationDiscoveryOptions + } + + if opts.OAuth2IssuerIdentificationDiscoveryOptions != nil { + optsCopy.OAuth2IssuerIdentificationDiscoveryOptions = &OAuth2IssuerIdentificationDiscoveryOptions{} + *optsCopy.OAuth2IssuerIdentificationDiscoveryOptions = *opts.OAuth2IssuerIdentificationDiscoveryOptions + } + + if opts.OAuth2JWTIntrospectionResponseDiscoveryOptions != nil { + optsCopy.OAuth2JWTIntrospectionResponseDiscoveryOptions = &OAuth2JWTIntrospectionResponseDiscoveryOptions{} + *optsCopy.OAuth2JWTIntrospectionResponseDiscoveryOptions = *opts.OAuth2JWTIntrospectionResponseDiscoveryOptions + } + + if opts.OAuth2JWTSecuredAuthorizationRequestDiscoveryOptions != nil { + optsCopy.OAuth2JWTSecuredAuthorizationRequestDiscoveryOptions = &OAuth2JWTSecuredAuthorizationRequestDiscoveryOptions{} + *optsCopy.OAuth2JWTSecuredAuthorizationRequestDiscoveryOptions = *opts.OAuth2JWTSecuredAuthorizationRequestDiscoveryOptions + } + + if opts.OAuth2PushedAuthorizationDiscoveryOptions != nil { + optsCopy.OAuth2PushedAuthorizationDiscoveryOptions = &OAuth2PushedAuthorizationDiscoveryOptions{} + *optsCopy.OAuth2PushedAuthorizationDiscoveryOptions = *opts.OAuth2PushedAuthorizationDiscoveryOptions + } + + return optsCopy +} + +// Copy the values of the OpenIDConnectWellKnownConfiguration and return it as a new struct. +func (opts OpenIDConnectWellKnownConfiguration) Copy() (optsCopy OpenIDConnectWellKnownConfiguration) { + optsCopy = OpenIDConnectWellKnownConfiguration{ + OAuth2WellKnownConfiguration: opts.OAuth2WellKnownConfiguration.Copy(), + OpenIDConnectDiscoveryOptions: opts.OpenIDConnectDiscoveryOptions, + } + + if opts.OpenIDConnectFrontChannelLogoutDiscoveryOptions != nil { + optsCopy.OpenIDConnectFrontChannelLogoutDiscoveryOptions = &OpenIDConnectFrontChannelLogoutDiscoveryOptions{} + *optsCopy.OpenIDConnectFrontChannelLogoutDiscoveryOptions = *opts.OpenIDConnectFrontChannelLogoutDiscoveryOptions + } + + if opts.OpenIDConnectBackChannelLogoutDiscoveryOptions != nil { + optsCopy.OpenIDConnectBackChannelLogoutDiscoveryOptions = &OpenIDConnectBackChannelLogoutDiscoveryOptions{} + *optsCopy.OpenIDConnectBackChannelLogoutDiscoveryOptions = *opts.OpenIDConnectBackChannelLogoutDiscoveryOptions + } + + if opts.OpenIDConnectSessionManagementDiscoveryOptions != nil { + optsCopy.OpenIDConnectSessionManagementDiscoveryOptions = &OpenIDConnectSessionManagementDiscoveryOptions{} + *optsCopy.OpenIDConnectSessionManagementDiscoveryOptions = *opts.OpenIDConnectSessionManagementDiscoveryOptions + } + + if opts.OpenIDConnectRPInitiatedLogoutDiscoveryOptions != nil { + optsCopy.OpenIDConnectRPInitiatedLogoutDiscoveryOptions = &OpenIDConnectRPInitiatedLogoutDiscoveryOptions{} + *optsCopy.OpenIDConnectRPInitiatedLogoutDiscoveryOptions = *opts.OpenIDConnectRPInitiatedLogoutDiscoveryOptions + } + + if opts.OpenIDConnectPromptCreateDiscoveryOptions != nil { + optsCopy.OpenIDConnectPromptCreateDiscoveryOptions = &OpenIDConnectPromptCreateDiscoveryOptions{} + *optsCopy.OpenIDConnectPromptCreateDiscoveryOptions = *opts.OpenIDConnectPromptCreateDiscoveryOptions + } + + if opts.OpenIDConnectClientInitiatedBackChannelAuthFlowDiscoveryOptions != nil { + optsCopy.OpenIDConnectClientInitiatedBackChannelAuthFlowDiscoveryOptions = &OpenIDConnectClientInitiatedBackChannelAuthFlowDiscoveryOptions{} + *optsCopy.OpenIDConnectClientInitiatedBackChannelAuthFlowDiscoveryOptions = *opts.OpenIDConnectClientInitiatedBackChannelAuthFlowDiscoveryOptions + } + + if opts.OpenIDConnectJWTSecuredAuthorizationResponseModeDiscoveryOptions != nil { + optsCopy.OpenIDConnectJWTSecuredAuthorizationResponseModeDiscoveryOptions = &OpenIDConnectJWTSecuredAuthorizationResponseModeDiscoveryOptions{} + *optsCopy.OpenIDConnectJWTSecuredAuthorizationResponseModeDiscoveryOptions = *opts.OpenIDConnectJWTSecuredAuthorizationResponseModeDiscoveryOptions + } + + if opts.OpenIDFederationDiscoveryOptions != nil { + optsCopy.OpenIDFederationDiscoveryOptions = &OpenIDFederationDiscoveryOptions{} + *optsCopy.OpenIDFederationDiscoveryOptions = *opts.OpenIDFederationDiscoveryOptions + } + + return optsCopy +} diff --git a/internal/oidc/discovery_test.go b/internal/oidc/discovery_test.go index 63ad18b65..34ca1f732 100644 --- a/internal/oidc/discovery_test.go +++ b/internal/oidc/discovery_test.go @@ -13,51 +13,51 @@ func TestNewOpenIDConnectWellKnownConfiguration(t *testing.T) { desc string pkcePlainChallenge bool enforcePAR bool - clients map[string]*Client + clients map[string]Client expectCodeChallengeMethodsSupported, expectSubjectTypesSupported []string }{ { desc: "ShouldHaveChallengeMethodsS256ANDSubjectTypesSupportedPublic", pkcePlainChallenge: false, - clients: map[string]*Client{"a": {}}, + clients: map[string]Client{"a": &BaseClient{}}, expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256}, - expectSubjectTypesSupported: []string{SubjectTypePublic}, + expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise}, }, { desc: "ShouldHaveChallengeMethodsS256PlainANDSubjectTypesSupportedPublic", pkcePlainChallenge: true, - clients: map[string]*Client{"a": {}}, + clients: map[string]Client{"a": &BaseClient{}}, expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256, PKCEChallengeMethodPlain}, - expectSubjectTypesSupported: []string{SubjectTypePublic}, + expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise}, }, { desc: "ShouldHaveChallengeMethodsS256ANDSubjectTypesSupportedPublicPairwise", pkcePlainChallenge: false, - clients: map[string]*Client{"a": {SectorIdentifier: "yes"}}, + clients: map[string]Client{"a": &BaseClient{SectorIdentifier: "yes"}}, expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256}, expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise}, }, { desc: "ShouldHaveChallengeMethodsS256PlainANDSubjectTypesSupportedPublicPairwise", pkcePlainChallenge: true, - clients: map[string]*Client{"a": {SectorIdentifier: "yes"}}, + clients: map[string]Client{"a": &BaseClient{SectorIdentifier: "yes"}}, expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256, PKCEChallengeMethodPlain}, expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise}, }, { desc: "ShouldHaveTokenAuthMethodsNone", pkcePlainChallenge: true, - clients: map[string]*Client{"a": {SectorIdentifier: "yes"}}, + clients: map[string]Client{"a": &BaseClient{SectorIdentifier: "yes"}}, expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256, PKCEChallengeMethodPlain}, expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise}, }, { desc: "ShouldHaveTokenAuthMethodsNone", pkcePlainChallenge: true, - clients: map[string]*Client{ - "a": {SectorIdentifier: "yes"}, - "b": {SectorIdentifier: "yes"}, + clients: map[string]Client{ + "a": &BaseClient{SectorIdentifier: "yes"}, + "b": &BaseClient{SectorIdentifier: "yes"}, }, expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256, PKCEChallengeMethodPlain}, expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise}, @@ -73,7 +73,7 @@ func TestNewOpenIDConnectWellKnownConfiguration(t *testing.T) { }, } - actual := NewOpenIDConnectWellKnownConfiguration(&c, tc.clients) + actual := NewOpenIDConnectWellKnownConfiguration(&c) for _, codeChallengeMethod := range tc.expectCodeChallengeMethodsSupported { assert.Contains(t, actual.CodeChallengeMethodsSupported, codeChallengeMethod) } diff --git a/internal/oidc/provider.go b/internal/oidc/provider.go index 54bd1595d..b8333351b 100644 --- a/internal/oidc/provider.go +++ b/internal/oidc/provider.go @@ -37,17 +37,14 @@ func NewOpenIDConnectProvider(config *schema.OpenIDConnectConfiguration, store s provider.Config.LoadHandlers(provider.Store, provider.KeyManager.Strategy()) - provider.discovery = NewOpenIDConnectWellKnownConfiguration(config, provider.Store.clients) + provider.discovery = NewOpenIDConnectWellKnownConfiguration(config) return provider, nil } // GetOAuth2WellKnownConfiguration returns the discovery document for the OAuth Configuration. func (p *OpenIDConnectProvider) GetOAuth2WellKnownConfiguration(issuer string) OAuth2WellKnownConfiguration { - options := OAuth2WellKnownConfiguration{ - CommonDiscoveryOptions: p.discovery.CommonDiscoveryOptions, - OAuth2DiscoveryOptions: p.discovery.OAuth2DiscoveryOptions, - } + options := p.discovery.OAuth2WellKnownConfiguration.Copy() options.Issuer = issuer @@ -63,13 +60,7 @@ func (p *OpenIDConnectProvider) GetOAuth2WellKnownConfiguration(issuer string) O // GetOpenIDConnectWellKnownConfiguration returns the discovery document for the OpenID Configuration. func (p *OpenIDConnectProvider) GetOpenIDConnectWellKnownConfiguration(issuer string) OpenIDConnectWellKnownConfiguration { - options := OpenIDConnectWellKnownConfiguration{ - CommonDiscoveryOptions: p.discovery.CommonDiscoveryOptions, - OAuth2DiscoveryOptions: p.discovery.OAuth2DiscoveryOptions, - OpenIDConnectDiscoveryOptions: p.discovery.OpenIDConnectDiscoveryOptions, - OpenIDConnectFrontChannelLogoutDiscoveryOptions: p.discovery.OpenIDConnectFrontChannelLogoutDiscoveryOptions, - OpenIDConnectBackChannelLogoutDiscoveryOptions: p.discovery.OpenIDConnectBackChannelLogoutDiscoveryOptions, - } + options := p.discovery.Copy() options.Issuer = issuer diff --git a/internal/oidc/provider_test.go b/internal/oidc/provider_test.go index 3045c6fc3..4a1a0b115 100644 --- a/internal/oidc/provider_test.go +++ b/internal/oidc/provider_test.go @@ -27,15 +27,15 @@ func TestNewOpenIDConnectProvider_ShouldEnableOptionalDiscoveryValues(t *testing IssuerCertificateChain: schema.X509CertificateChain{}, IssuerPrivateKey: mustParseRSAPrivateKey(exampleIssuerPrivateKey), EnablePKCEPlainChallenge: true, - HMACSecret: "asbdhaaskmdlkamdklasmdlkams", + HMACSecret: badhmac, Clients: []schema.OpenIDConnectClientConfiguration{ { - ID: "a-client", - Secret: MustDecodeSecret("$plaintext$a-client-secret"), - SectorIdentifier: url.URL{Host: "google.com"}, - Policy: "one_factor", + ID: myclient, + Secret: MustDecodeSecret(badsecret), + SectorIdentifier: url.URL{Host: examplecomsid}, + Policy: onefactor, RedirectURIs: []string{ - "https://google.com", + examplecom, }, }, }, @@ -43,7 +43,7 @@ func TestNewOpenIDConnectProvider_ShouldEnableOptionalDiscoveryValues(t *testing assert.NoError(t, err) - disco := provider.GetOpenIDConnectWellKnownConfiguration("https://example.com") + disco := provider.GetOpenIDConnectWellKnownConfiguration(examplecom) assert.Len(t, disco.SubjectTypesSupported, 2) assert.Contains(t, disco.SubjectTypesSupported, SubjectTypePublic) @@ -58,12 +58,12 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GoodConfiguration(t *tes provider, err := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{ IssuerCertificateChain: schema.X509CertificateChain{}, IssuerPrivateKey: mustParseRSAPrivateKey(exampleIssuerPrivateKey), - HMACSecret: "asbdhaaskmdlkamdklasmdlkams", + HMACSecret: badhmac, Clients: []schema.OpenIDConnectClientConfiguration{ { ID: "a-client", Secret: MustDecodeSecret("$plaintext$a-client-secret"), - Policy: "one_factor", + Policy: onefactor, RedirectURIs: []string{ "https://google.com", }, @@ -72,7 +72,7 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GoodConfiguration(t *tes ID: "b-client", Description: "Normal Description", Secret: MustDecodeSecret("$plaintext$b-client-secret"), - Policy: "two_factor", + Policy: twofactor, RedirectURIs: []string{ "https://google.com", }, @@ -103,7 +103,7 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnow { ID: "a-client", Secret: MustDecodeSecret("$plaintext$a-client-secret"), - Policy: "one_factor", + Policy: onefactor, RedirectURIs: []string{ "https://google.com", }, @@ -113,9 +113,9 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnow assert.NoError(t, err) - disco := provider.GetOpenIDConnectWellKnownConfiguration("https://example.com") + disco := provider.GetOpenIDConnectWellKnownConfiguration(examplecom) - assert.Equal(t, "https://example.com", disco.Issuer) + assert.Equal(t, examplecom, disco.Issuer) assert.Equal(t, "https://example.com/jwks.json", disco.JWKSURI) assert.Equal(t, "https://example.com/api/oidc/authorization", disco.AuthorizationEndpoint) assert.Equal(t, "https://example.com/api/oidc/token", disco.TokenEndpoint) @@ -139,8 +139,9 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnow assert.Contains(t, disco.ResponseModesSupported, ResponseModeQuery) assert.Contains(t, disco.ResponseModesSupported, ResponseModeFragment) - assert.Len(t, disco.SubjectTypesSupported, 1) + assert.Len(t, disco.SubjectTypesSupported, 2) assert.Contains(t, disco.SubjectTypesSupported, SubjectTypePublic) + assert.Contains(t, disco.SubjectTypesSupported, SubjectTypePairwise) assert.Len(t, disco.ResponseTypesSupported, 7) assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeAuthorizationCodeFlow) @@ -151,10 +152,9 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnow assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeHybridFlowToken) assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeHybridFlowBoth) - assert.Len(t, disco.TokenEndpointAuthMethodsSupported, 4) + assert.Len(t, disco.TokenEndpointAuthMethodsSupported, 3) assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretBasic) assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretPost) - assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretJWT) assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodNone) assert.Len(t, disco.GrantTypesSupported, 3) @@ -169,9 +169,7 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnow assert.Contains(t, disco.UserinfoSigningAlgValuesSupported, SigningAlgorithmRSAWithSHA256) assert.Contains(t, disco.UserinfoSigningAlgValuesSupported, SigningAlgorithmNone) - assert.Len(t, disco.RequestObjectSigningAlgValuesSupported, 2) - assert.Contains(t, disco.RequestObjectSigningAlgValuesSupported, SigningAlgorithmRSAWithSHA256) - assert.Contains(t, disco.RequestObjectSigningAlgValuesSupported, SigningAlgorithmNone) + assert.Len(t, disco.RequestObjectSigningAlgValuesSupported, 0) assert.Len(t, disco.ClaimsSupported, 18) assert.Contains(t, disco.ClaimsSupported, ClaimAuthenticationMethodsReference) @@ -203,7 +201,7 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOAuth2WellKnownConfig { ID: "a-client", Secret: MustDecodeSecret("$plaintext$a-client-secret"), - Policy: "one_factor", + Policy: onefactor, RedirectURIs: []string{ "https://google.com", }, @@ -213,9 +211,9 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOAuth2WellKnownConfig assert.NoError(t, err) - disco := provider.GetOAuth2WellKnownConfiguration("https://example.com") + disco := provider.GetOAuth2WellKnownConfiguration(examplecom) - assert.Equal(t, "https://example.com", disco.Issuer) + assert.Equal(t, examplecom, disco.Issuer) assert.Equal(t, "https://example.com/jwks.json", disco.JWKSURI) assert.Equal(t, "https://example.com/api/oidc/authorization", disco.AuthorizationEndpoint) assert.Equal(t, "https://example.com/api/oidc/token", disco.TokenEndpoint) @@ -238,8 +236,9 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOAuth2WellKnownConfig assert.Contains(t, disco.ResponseModesSupported, ResponseModeQuery) assert.Contains(t, disco.ResponseModesSupported, ResponseModeFragment) - assert.Len(t, disco.SubjectTypesSupported, 1) + assert.Len(t, disco.SubjectTypesSupported, 2) assert.Contains(t, disco.SubjectTypesSupported, SubjectTypePublic) + assert.Contains(t, disco.SubjectTypesSupported, SubjectTypePairwise) assert.Len(t, disco.ResponseTypesSupported, 7) assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeAuthorizationCodeFlow) @@ -250,10 +249,9 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOAuth2WellKnownConfig assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeHybridFlowToken) assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeHybridFlowBoth) - assert.Len(t, disco.TokenEndpointAuthMethodsSupported, 4) + assert.Len(t, disco.TokenEndpointAuthMethodsSupported, 3) assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretBasic) assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretPost) - assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretJWT) assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodNone) assert.Len(t, disco.GrantTypesSupported, 3) @@ -292,7 +290,7 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnow { ID: "a-client", Secret: MustDecodeSecret("$plaintext$a-client-secret"), - Policy: "one_factor", + Policy: onefactor, RedirectURIs: []string{ "https://google.com", }, @@ -302,7 +300,7 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnow assert.NoError(t, err) - disco := provider.GetOpenIDConnectWellKnownConfiguration("https://example.com") + disco := provider.GetOpenIDConnectWellKnownConfiguration(examplecom) require.Len(t, disco.CodeChallengeMethodsSupported, 2) assert.Equal(t, PKCEChallengeMethodSHA256, disco.CodeChallengeMethodsSupported[0]) diff --git a/internal/oidc/store.go b/internal/oidc/store.go index a5a181c2b..2f21b368c 100644 --- a/internal/oidc/store.go +++ b/internal/oidc/store.go @@ -24,7 +24,7 @@ func NewStore(config *schema.OpenIDConnectConfiguration, provider storage.Provid store = &Store{ provider: provider, - clients: map[string]*Client{}, + clients: map[string]Client{}, } for _, client := range config.Clients { @@ -72,11 +72,11 @@ func (s *Store) GetClientPolicy(id string) (level authorization.Level) { return authorization.TwoFactor } - return client.Policy + return client.GetAuthorizationPolicy() } // GetFullClient returns a fosite.Client asserted as an Client matching the provided id. -func (s *Store) GetFullClient(id string) (client *Client, err error) { +func (s *Store) GetFullClient(id string) (client Client, err error) { client, ok := s.clients[id] if !ok { return nil, fosite.ErrInvalidClient diff --git a/internal/oidc/store_test.go b/internal/oidc/store_test.go index 580e864e4..def1d4e8e 100644 --- a/internal/oidc/store_test.go +++ b/internal/oidc/store_test.go @@ -4,6 +4,7 @@ import ( "context" "testing" + "github.com/ory/fosite" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -17,23 +18,23 @@ func TestOpenIDConnectStore_GetClientPolicy(t *testing.T) { IssuerPrivateKey: mustParseRSAPrivateKey(exampleIssuerPrivateKey), Clients: []schema.OpenIDConnectClientConfiguration{ { - ID: "myclient", - Description: "myclient desc", - Policy: "one_factor", + ID: myclient, + Description: myclientdesc, + Policy: onefactor, Scopes: []string{ScopeOpenID, ScopeProfile}, Secret: MustDecodeSecret("$plaintext$mysecret"), }, { ID: "myotherclient", - Description: "myclient desc", - Policy: "two_factor", + Description: myclientdesc, + Policy: twofactor, Scopes: []string{ScopeOpenID, ScopeProfile}, Secret: MustDecodeSecret("$plaintext$mysecret"), }, }, }, nil) - policyOne := s.GetClientPolicy("myclient") + policyOne := s.GetClientPolicy(myclient) assert.Equal(t, authorization.OneFactor, policyOne) policyTwo := s.GetClientPolicy("myotherclient") @@ -49,9 +50,9 @@ func TestOpenIDConnectStore_GetInternalClient(t *testing.T) { IssuerPrivateKey: mustParseRSAPrivateKey(exampleIssuerPrivateKey), Clients: []schema.OpenIDConnectClientConfiguration{ { - ID: "myclient", - Description: "myclient desc", - Policy: "one_factor", + ID: myclient, + Description: myclientdesc, + Policy: onefactor, Scopes: []string{ScopeOpenID, ScopeProfile}, Secret: MustDecodeSecret("$plaintext$mysecret"), }, @@ -62,17 +63,19 @@ func TestOpenIDConnectStore_GetInternalClient(t *testing.T) { assert.EqualError(t, err, "invalid_client") assert.Nil(t, client) - client, err = s.GetClient(context.Background(), "myclient") + client, err = s.GetClient(context.Background(), myclient) require.NoError(t, err) require.NotNil(t, client) - assert.Equal(t, "myclient", client.GetID()) + assert.Equal(t, myclient, client.GetID()) } func TestOpenIDConnectStore_GetInternalClient_ValidClient(t *testing.T) { + id := myclient + c1 := schema.OpenIDConnectClientConfiguration{ - ID: "myclient", - Description: "myclient desc", - Policy: "one_factor", + ID: id, + Description: myclientdesc, + Policy: onefactor, Scopes: []string{ScopeOpenID, ScopeProfile}, Secret: MustDecodeSecret("$plaintext$mysecret"), } @@ -83,24 +86,24 @@ func TestOpenIDConnectStore_GetInternalClient_ValidClient(t *testing.T) { Clients: []schema.OpenIDConnectClientConfiguration{c1}, }, nil) - client, err := s.GetFullClient(c1.ID) + client, err := s.GetFullClient(id) require.NoError(t, err) require.NotNil(t, client) - assert.Equal(t, client.ID, c1.ID) - assert.Equal(t, client.Description, c1.Description) - assert.Equal(t, client.Scopes, c1.Scopes) - assert.Equal(t, client.GrantTypes, c1.GrantTypes) - assert.Equal(t, client.ResponseTypes, c1.ResponseTypes) - assert.Equal(t, client.RedirectURIs, c1.RedirectURIs) - assert.Equal(t, client.Policy, authorization.OneFactor) - assert.Equal(t, client.Secret.Encode(), "$plaintext$mysecret") + assert.Equal(t, id, client.GetID()) + assert.Equal(t, myclientdesc, client.GetDescription()) + assert.Equal(t, fosite.Arguments(c1.Scopes), client.GetScopes()) + assert.Equal(t, fosite.Arguments([]string{GrantTypeAuthorizationCode}), client.GetGrantTypes()) + assert.Equal(t, fosite.Arguments([]string{ResponseTypeAuthorizationCodeFlow}), client.GetResponseTypes()) + assert.Equal(t, []string(nil), client.GetRedirectURIs()) + assert.Equal(t, authorization.OneFactor, client.GetAuthorizationPolicy()) + assert.Equal(t, "$plaintext$mysecret", client.GetSecret().Encode()) } func TestOpenIDConnectStore_GetInternalClient_InvalidClient(t *testing.T) { c1 := schema.OpenIDConnectClientConfiguration{ - ID: "myclient", - Description: "myclient desc", - Policy: "one_factor", + ID: myclient, + Description: myclientdesc, + Policy: onefactor, Scopes: []string{ScopeOpenID, ScopeProfile}, Secret: MustDecodeSecret("$plaintext$mysecret"), } @@ -122,16 +125,16 @@ func TestOpenIDConnectStore_IsValidClientID(t *testing.T) { IssuerPrivateKey: mustParseRSAPrivateKey(exampleIssuerPrivateKey), Clients: []schema.OpenIDConnectClientConfiguration{ { - ID: "myclient", - Description: "myclient desc", - Policy: "one_factor", + ID: myclient, + Description: myclientdesc, + Policy: onefactor, Scopes: []string{ScopeOpenID, ScopeProfile}, Secret: MustDecodeSecret("$plaintext$mysecret"), }, }, }, nil) - validClient := s.IsValidClientID("myclient") + validClient := s.IsValidClientID(myclient) invalidClient := s.IsValidClientID("myinvalidclient") assert.True(t, validClient) diff --git a/internal/oidc/types.go b/internal/oidc/types.go index 7403f2fed..8606fe3a3 100644 --- a/internal/oidc/types.go +++ b/internal/oidc/types.go @@ -12,6 +12,7 @@ import ( "github.com/ory/herodot" "gopkg.in/square/go-jose.v2" + "github.com/authelia/authelia/v4/internal/authentication" "github.com/authelia/authelia/v4/internal/authorization" "github.com/authelia/authelia/v4/internal/model" "github.com/authelia/authelia/v4/internal/storage" @@ -97,17 +98,19 @@ type OpenIDConnectProvider struct { // openid.OpenIDConnectRequestStorage, and partially implements rfc7523.RFC7523KeyStorage. type Store struct { provider storage.Provider - clients map[string]*Client + clients map[string]Client } -// Client represents the client internally. -type Client struct { +// BaseClient is the base for all clients. +type BaseClient struct { ID string Description string Secret algorithm.Digest SectorIdentifier string Public bool + EnforcePAR bool + EnforcePKCE bool EnforcePKCEChallengeMethod bool PKCEChallengeMethod string @@ -119,8 +122,6 @@ type Client struct { ResponseTypes []string ResponseModes []fosite.ResponseModeType - EnforcePAR bool - UserinfoSigningAlgorithm string Policy authorization.Level @@ -128,6 +129,43 @@ type Client struct { Consent ClientConsent } +// FullClient is the client with comprehensive supported features. +type FullClient struct { + *BaseClient + + RequestURIs []string + JSONWebKeys *jose.JSONWebKeySet + JSONWebKeysURI string + RequestObjectSigningAlgorithm string + TokenEndpointAuthMethod string + TokenEndpointAuthSigningAlgorithm string +} + +// Client represents the internal client definitions. +type Client interface { + fosite.Client + fosite.ResponseModeClient + + GetDescription() string + GetSecret() algorithm.Digest + GetSectorIdentifier() string + GetConsentResponseBody(consent *model.OAuth2ConsentSession) ConsentGetResponseBody + GetUserinfoSigningAlgorithm() string + + GetPAREnforcement() bool + GetPKCEEnforcement() bool + GetPKCEChallengeMethodEnforcement() bool + GetPKCEChallengeMethod() string + GetAuthorizationPolicy() authorization.Level + GetConsentPolicy() ClientConsent + + IsAuthenticationLevelSufficient(level authentication.Level) bool + + ValidatePKCEPolicy(r fosite.Requester) (err error) + ValidatePARPolicy(r fosite.Requester, prefix string) (err error) + ValidateResponseModePolicy(r fosite.AuthorizeRequester) (err error) +} + // NewClientConsent converts the schema.OpenIDConnectClientConsentConfig into a oidc.ClientConsent. func NewClientConsent(mode string, duration *time.Duration) ClientConsent { switch mode { @@ -344,6 +382,12 @@ type CommonDiscoveryOptions struct { Client if it is given. */ OPTOSURI string `json:"op_tos_uri,omitempty"` + + /* + A JWT containing metadata values about the authorization server as claims. This is a string value consisting of + the entire signed JWT. A "signed_metadata" metadata value SHOULD NOT appear as a claim in the JWT. + */ + SignedMetadata string `json:"signed_metadata,omitempty"` } // OAuth2DiscoveryOptions represents the discovery options specific to OAuth 2.0. @@ -427,6 +471,98 @@ type OAuth2DiscoveryOptions struct { CodeChallengeMethodsSupported []string `json:"code_challenge_methods_supported,omitempty"` } +type OAuth2JWTIntrospectionResponseDiscoveryOptions struct { + /* + OPTIONAL. JSON array containing a list of the JWS [RFC7515] signing algorithms ("alg" values) as defined in JWA + [RFC7518] supported by the introspection endpoint to sign the response. + */ + IntrospectionSigningAlgValuesSupported []string `json:"introspection_signing_alg_values_supported,omitempty"` + + /* + OPTIONAL. JSON array containing a list of the JWE [RFC7516] encryption algorithms ("alg" values) as defined in + JWA [RFC7518] supported by the introspection endpoint to encrypt the content encryption key for introspection + responses (content key encryption). + */ + IntrospectionEncryptionAlgValuesSupported []string `json:"introspection_encryption_alg_values_supported"` + + /* + OPTIONAL. JSON array containing a list of the JWE [RFC7516] encryption algorithms ("enc" values) as defined in + JWA [RFC7518] supported by the introspection endpoint to encrypt the response (content encryption). + */ + IntrospectionEncryptionEncValuesSupported []string `json:"introspection_encryption_enc_values_supported"` +} + +type OAuth2DeviceAuthorizationGrantDiscoveryOptions struct { + /* + OPTIONAL. URL of the authorization server's device authorization endpoint, as defined in Section 3.1. + */ + DeviceAuthorizationEndpoint string `json:"device_authorization_endpoint"` +} + +type OAuth2MutualTLSClientAuthenticationDiscoveryOptions struct { + /* + OPTIONAL. Boolean value indicating server support for mutual-TLS client certificate-bound access tokens. If + omitted, the default value is false. + */ + TLSClientCertificateBoundAccessTokens bool `json:"tls_client_certificate_bound_access_tokens"` + + /* + OPTIONAL. A JSON object containing alternative authorization server endpoints that, when present, an OAuth + client intending to do mutual TLS uses in preference to the conventional endpoints. The parameter value itself + consists of one or more endpoint parameters, such as token_endpoint, revocation_endpoint, + introspection_endpoint, etc., conventionally defined for the top level of authorization server metadata. An + OAuth client intending to do mutual TLS (for OAuth client authentication and/or to acquire or use + certificate-bound tokens) when making a request directly to the authorization server MUST use the alias URL of + the endpoint within the mtls_endpoint_aliases, when present, in preference to the endpoint URL of the same name + at the top level of metadata. When an endpoint is not present in mtls_endpoint_aliases, then the client uses the + conventional endpoint URL defined at the top level of the authorization server metadata. Metadata parameters + within mtls_endpoint_aliases that do not define endpoints to which an OAuth client makes a direct request have + no meaning and SHOULD be ignored. + */ + MutualTLSEndpointAliases struct { + AuthorizationEndpoint string `json:"authorization_endpoint,omitempty"` + TokenEndpoint string `json:"token_endpoint,omitempty"` + IntrospectionEndpoint string `json:"introspection_endpoint,omitempty"` + RevocationEndpoint string `json:"revocation_endpoint,omitempty"` + EndSessionEndpoint string `json:"end_session_endpoint,omitempty"` + UserinfoEndpoint string `json:"userinfo_endpoint,omitempty"` + BackChannelAuthenticationEndpoint string `json:"backchannel_authentication_endpoint,omitempty"` + FederationRegistrationEndpoint string `json:"federation_registration_endpoint,omitempty"` + PushedAuthorizationRequestEndpoint string `json:"pushed_authorization_request_endpoint,omitempty"` + RegistrationEndpoint string `json:"registration_endpoint,omitempty"` + } `json:"mtls_endpoint_aliases"` +} + +type OAuth2JWTSecuredAuthorizationRequestDiscoveryOptions struct { + /* + Indicates where authorization request needs to be protected as Request Object and provided through either + request or request_uri parameter. + */ + RequireSignedRequestObject bool `json:"require_signed_request_object"` +} + +type OAuth2IssuerIdentificationDiscoveryOptions struct { + AuthorizationResponseIssuerParameterSupported bool `json:"authorization_response_iss_parameter_supported"` +} + +// OAuth2PushedAuthorizationDiscoveryOptions represents the well known discovery document specific to the +// OAuth 2.0 Pushed Authorization Requests (RFC9126) implementation. +// +// OAuth 2.0 Pushed Authorization Requests: https://datatracker.ietf.org/doc/html/rfc9126#section-5 +type OAuth2PushedAuthorizationDiscoveryOptions struct { + /* + The URL of the pushed authorization request endpoint at which a client can post an authorization request to + exchange for a "request_uri" value usable at the authorization server. + */ + PushedAuthorizationRequestEndpoint string `json:"pushed_authorization_request_endpoint"` + + /* + Boolean parameter indicating whether the authorization server accepts authorization request data only via PAR. + If omitted, the default value is "false". + */ + RequirePushedAuthorizationRequests bool `json:"require_pushed_authorization_requests"` +} + // OpenIDConnectDiscoveryOptions represents the discovery options specific to OpenID Connect. type OpenIDConnectDiscoveryOptions struct { /* @@ -552,6 +688,12 @@ type OpenIDConnectDiscoveryOptions struct { */ ClaimLocalesSupported []string `json:"claims_locales_supported,omitempty"` + /* + OPTIONAL. Boolean value specifying whether the OP supports use of the request parameter, with true indicating + support. If omitted, the default value is false. + */ + RequestParameterSupported bool `json:"request_parameter_supported"` + /* OPTIONAL. Boolean value specifying whether the OP supports use of the request_uri parameter, with true indicating support. If omitted, the default value is true. @@ -612,39 +754,202 @@ type OpenIDConnectBackChannelLogoutDiscoveryOptions struct { BackChannelLogoutSessionSupported bool `json:"backchannel_logout_session_supported"` } -// PushedAuthorizationDiscoveryOptions represents the well known discovery document specific to the -// OAuth 2.0 Pushed Authorization Requests (RFC9126) implementation. +// OpenIDConnectSessionManagementDiscoveryOptions represents the discovery options specific to OpenID Connect 1.0 +// Session Management. // -// OAuth 2.0 Pushed Authorization Requests: https://datatracker.ietf.org/doc/html/rfc9126#section-5 -type PushedAuthorizationDiscoveryOptions struct { +// To support OpenID Connect Session Management, the RP needs to obtain the Session Management related OP metadata. This +// OP metadata is normally obtained via the OP's Discovery response, as described in OpenID Connect Discovery 1.0, or +// MAY be learned via other mechanisms. This OpenID Provider Metadata parameter MUST be included in the Server's +// discovery responses when Session Management and Discovery are supported. +// +// See Also: +// +// OpenID Connect 1.0 Session Management: https://openid.net/specs/openid-connect-session-1_0.html +type OpenIDConnectSessionManagementDiscoveryOptions struct { /* - The URL of the pushed authorization request endpoint at which a client can post an authorization request to - exchange for a "request_uri" value usable at the authorization server. + REQUIRED. URL of an OP iframe that supports cross-origin communications for session state information with the + RP Client, using the HTML5 postMessage API. This URL MUST use the https scheme and MAY contain port, path, and + query parameter components. The page is loaded from an invisible iframe embedded in an RP page so that it can + run in the OP's security context. It accepts postMessage requests from the relevant RP iframe and uses + postMessage to post back the login status of the End-User at the OP. */ - PushedAuthorizationRequestEndpoint string `json:"pushed_authorization_request_endpoint,omitempty"` + CheckSessionIFrame string `json:"check_session_iframe"` +} + +// OpenIDConnectRPInitiatedLogoutDiscoveryOptions represents the discovery options specific to +// OpenID Connect RP-Initiated Logout 1.0. +// +// To support OpenID Connect RP-Initiated Logout, the RP needs to obtain the RP-Initiated Logout related OP metadata. +// This OP metadata is normally obtained via the OP's Discovery response, as described in OpenID Connect Discovery 1.0, +// or MAY be learned via other mechanisms. This OpenID Provider Metadata parameter MUST be included in the Server's +// discovery responses when RP-Initiated Logout and Discovery are supported. +// +// See Also: +// +// OpenID Connect RP-Initiated Logout 1.0: https://openid.net/specs/openid-connect-rpinitiated-1_0.html +type OpenIDConnectRPInitiatedLogoutDiscoveryOptions struct { + /* + REQUIRED. URL at the OP to which an RP can perform a redirect to request that the End-User be logged out at the + OP. This URL MUST use the https scheme and MAY contain port, path, and query parameter components. + */ + EndSessionEndpoint string `json:"end_session_endpoint"` +} + +// OpenIDConnectPromptCreateDiscoveryOptions represents the discovery options specific to Initiating User Registration +// via OpenID Connect 1.0 functionality. +// +// This specification extends the OpenID Connect Discovery Metadata Section 3. +// +// See Also: +// +// Initiating User Registration via OpenID Connect 1.0: https://openid.net/specs/openid-connect-prompt-create-1_0.html +type OpenIDConnectPromptCreateDiscoveryOptions struct { + /* + OPTIONAL. JSON array containing the list of prompt values that this OP supports. + + This metadata element is OPTIONAL in the context of the OpenID Provider not supporting the create value. If + omitted, the Relying Party should assume that this specification is not supported. The OpenID Provider MAY + provide this metadata element even if it doesn't support the create value. + Specific to this specification, a value of create in the array indicates to the Relying party that this OpenID + Provider supports this specification. If an OpenID Provider supports this specification it MUST define this metadata + element in the openid-configuration file. Additionally, if this metadata element is defined by the OpenID + Provider, the OP must also specify all other prompt values which it supports. + See Also: + OpenID.PromptCreate: https://openid.net/specs/openid-connect-prompt-create-1_0.html + */ + PromptValuesSupported []string `json:"prompt_values_supported,omitempty"` +} + +// OpenIDConnectClientInitiatedBackChannelAuthFlowDiscoveryOptions represents the discovery options specific to +// OpenID Connect Client-Initiated Backchannel Authentication Flow - Core 1.0 +// +// The following authorization server metadata parameters are introduced by this specification for OPs publishing their +// support of the CIBA flow and details thereof. +// +// See Also: +// +// OpenID Connect Client-Initiated Backchannel Authentication Flow - Core 1.0: +// https://openid.net/specs/openid-client-initiated-backchannel-authentication-core-1_0.html#rfc.section.4 +type OpenIDConnectClientInitiatedBackChannelAuthFlowDiscoveryOptions struct { + /* + REQUIRED. URL of the OP's Backchannel Authentication Endpoint as defined in Section 7. + */ + BackChannelAuthenticationEndpoint string `json:"backchannel_authentication_endpoint"` /* - Boolean parameter indicating whether the authorization server accepts authorization request data only via PAR. - If omitted, the default value is "false". + REQUIRED. JSON array containing one or more of the following values: poll, ping, and push. */ - RequirePushedAuthorizationRequests bool `json:"require_pushed_authorization_requests"` + BackChannelTokenDeliveryModesSupported []string `json:"backchannel_token_delivery_modes_supported"` + + /* + OPTIONAL. JSON array containing a list of the JWS signing algorithms (alg values) supported by the OP for signed + authentication requests, which are described in Section 7.1.1. If omitted, signed authentication requests are + not supported by the OP. + */ + BackChannelAuthRequestSigningAlgValuesSupported []string `json:"backchannel_authentication_request_signing_alg_values_supported,omitempty"` + + /* + OPTIONAL. Boolean value specifying whether the OP supports the use of the user_code parameter, with true + indicating support. If omitted, the default value is false. + */ + BackChannelUserCodeParameterSupported bool `json:"backchannel_user_code_parameter_supported"` +} + +// OpenIDConnectJWTSecuredAuthorizationResponseModeDiscoveryOptions represents the discovery options specific to +// JWT Secured Authorization Response Mode for OAuth 2.0 (JARM). +// +// Authorization servers SHOULD publish the supported algorithms for signing and encrypting the JWT of an authorization +// response by utilizing OAuth 2.0 Authorization Server Metadata [RFC8414] parameters. The following parameters are +// introduced by this specification. +// +// See Also: +// +// JWT Secured Authorization Response Mode for OAuth 2.0 (JARM): +// https://openid.net/specs/oauth-v2-jarm.html#name-authorization-server-metada +type OpenIDConnectJWTSecuredAuthorizationResponseModeDiscoveryOptions struct { + /* + OPTIONAL. A JSON array containing a list of the JWS [RFC7515] signing algorithms (alg values) supported by the + authorization endpoint to sign the response. + */ + AuthorizationSigningAlgValuesSupported []string `json:"authorization_signing_alg_values_supported,omitempty"` + + /* + OPTIONAL. A JSON array containing a list of the JWE [RFC7516] encryption algorithms (alg values) supported by + the authorization endpoint to encrypt the response. + */ + AuthorizationEncryptionAlgValuesSupported []string `json:"authorization_encryption_alg_values_supported,omitempty"` + + /* + OPTIONAL. A JSON array containing a list of the JWE [RFC7516] encryption algorithms (enc values) supported by + the authorization endpoint to encrypt the response. + */ + AuthorizationEncryptionEncValuesSupported []string `json:"authorization_encryption_enc_values_supported,omitempty"` +} + +type OpenIDFederationDiscoveryOptions struct { + /* + OPTIONAL. URL of the OP's federation-specific Dynamic Client Registration Endpoint. If the OP supports explicit + client registration as described in Section 10.2, then this claim is REQUIRED. + */ + FederationRegistrationEndpoint string `json:"federation_registration_endpoint,omitempty"` + + /* + REQUIRED. Array specifying the federation types supported. Federation-type values defined by this specification + are automatic and explicit. + */ + ClientRegistrationTypesSupported []string `json:"client_registration_types_supported"` + + /* + OPTIONAL. A JSON Object defining the client authentications supported for each endpoint. The endpoint names are + defined in the IANA "OAuth Authorization Server Metadata" registry [IANA.OAuth.Parameters]. Other endpoints and + authentication methods are possible if made recognizable according to established standards and not in conflict + with the operating principles of this specification. In OpenID Connect Core, no client authentication is + performed at the authentication endpoint. Instead, the request itself is authenticated. The OP maps information + in the request (like the redirect_uri) to information it has gained on the client through static or dynamic + registration. If the mapping is successful, the request can be processed. If the RP uses Automatic Registration, + as defined in Section 10.1, the OP has no prior knowledge of the RP. Therefore, the OP must start by gathering + information about the RP using the process outlined in Section 6. Once it has the RP's metadata, the OP can + verify the request in the same way as if it had known the RP's metadata beforehand. To make the request + verification more secure, we demand the use of a client authentication or verification method that proves that + the RP is in possession of a key that appears in the RP's metadata. + */ + RequestAuthenticationMethodsSupported []string `json:"request_authentication_methods_supported,omitempty"` + + /* + OPTIONAL. JSON array containing a list of the JWS signing algorithms (alg values) supported for the signature on + the JWT [RFC7519] used in the request_object contained in the request parameter of an authorization request or + in the private_key_jwt of a pushed authorization request. This entry MUST be present if either of these + authentication methods are specified in the request_authentication_methods_supported entry. No default + algorithms are implied if this entry is omitted. Servers SHOULD support RS256. The value none MUST NOT be used. + */ + RequestAuthenticationSigningAlgValuesSupproted []string `json:"request_authentication_signing_alg_values_supported,omitempty"` } // OAuth2WellKnownConfiguration represents the well known discovery document specific to OAuth 2.0. type OAuth2WellKnownConfiguration struct { CommonDiscoveryOptions OAuth2DiscoveryOptions - PushedAuthorizationDiscoveryOptions + *OAuth2DeviceAuthorizationGrantDiscoveryOptions + *OAuth2MutualTLSClientAuthenticationDiscoveryOptions + *OAuth2IssuerIdentificationDiscoveryOptions + *OAuth2JWTIntrospectionResponseDiscoveryOptions + *OAuth2JWTSecuredAuthorizationRequestDiscoveryOptions + *OAuth2PushedAuthorizationDiscoveryOptions } // OpenIDConnectWellKnownConfiguration represents the well known discovery document specific to OpenID Connect. type OpenIDConnectWellKnownConfiguration struct { - CommonDiscoveryOptions - OAuth2DiscoveryOptions - PushedAuthorizationDiscoveryOptions + OAuth2WellKnownConfiguration + OpenIDConnectDiscoveryOptions - OpenIDConnectFrontChannelLogoutDiscoveryOptions - OpenIDConnectBackChannelLogoutDiscoveryOptions + *OpenIDConnectFrontChannelLogoutDiscoveryOptions + *OpenIDConnectBackChannelLogoutDiscoveryOptions + *OpenIDConnectSessionManagementDiscoveryOptions + *OpenIDConnectRPInitiatedLogoutDiscoveryOptions + *OpenIDConnectPromptCreateDiscoveryOptions + *OpenIDConnectClientInitiatedBackChannelAuthFlowDiscoveryOptions + *OpenIDConnectJWTSecuredAuthorizationResponseModeDiscoveryOptions + *OpenIDFederationDiscoveryOptions } // OpenIDConnectContext represents the context implementation that is used by some OpenID Connect 1.0 implementations. diff --git a/internal/oidc/types_test.go b/internal/oidc/types_test.go index b84461a07..d604ad009 100644 --- a/internal/oidc/types_test.go +++ b/internal/oidc/types_test.go @@ -40,7 +40,7 @@ func TestNewSessionWithAuthorizeRequest(t *testing.T) { Request: fosite.Request{ ID: requestID.String(), Form: formValues, - Client: &Client{ID: "example"}, + Client: &BaseClient{ID: "example"}, }, } @@ -50,7 +50,7 @@ func TestNewSessionWithAuthorizeRequest(t *testing.T) { requested := time.Unix(1647332518, 0) authAt := time.Unix(1647332500, 0) - issuer := "https://example.com" + issuer := examplecom amr := []string{AMRPasswordBasedAuthentication} consent := &model.OAuth2ConsentSession{ diff --git a/internal/utils/strings.go b/internal/utils/strings.go index 3e9dc12cd..1afdfc89d 100644 --- a/internal/utils/strings.go +++ b/internal/utils/strings.go @@ -200,8 +200,8 @@ func URLsFromStringSlice(urls []string) []url.URL { } // OriginFromURL returns an origin url.URL given another url.URL. -func OriginFromURL(u url.URL) (origin url.URL) { - return url.URL{ +func OriginFromURL(u *url.URL) (origin *url.URL) { + return &url.URL{ Scheme: u.Scheme, Host: u.Host, } diff --git a/internal/utils/strings_test.go b/internal/utils/strings_test.go index fe7c6742e..c12978739 100644 --- a/internal/utils/strings_test.go +++ b/internal/utils/strings_test.go @@ -242,7 +242,7 @@ func TestOriginFromURL(t *testing.T) { google, err := url.Parse("https://google.com/abc?a=123#five") assert.NoError(t, err) - origin := OriginFromURL(*google) + origin := OriginFromURL(google) assert.Equal(t, "https://google.com", origin.String()) } From 56c10eab76745a484b1f1d43f6db0e504e8a5c0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20Nu=C3=B1ez?= <10672208+mind-ar@users.noreply.github.com> Date: Thu, 13 Apr 2023 08:15:28 -0300 Subject: [PATCH 10/15] test(configuration): add additional coverage (#4779) --- internal/configuration/provider_test.go | 70 +++++++++++++++++++++++++ internal/configuration/template_test.go | 17 ++++++ 2 files changed, 87 insertions(+) diff --git a/internal/configuration/provider_test.go b/internal/configuration/provider_test.go index f91b9629b..6c1658948 100644 --- a/internal/configuration/provider_test.go +++ b/internal/configuration/provider_test.go @@ -401,6 +401,9 @@ func TestShouldNotReadConfigurationOnFSAccessDenied(t *testing.T) { func TestShouldLoadDirectoryConfiguration(t *testing.T) { dir := t.TempDir() + cfg := filepath.Join(dir, "myconf.yml") + assert.NoError(t, testCreateFile(cfg, "server:\n port: 9091\n", 0700)) + val := schema.NewStructValidator() _, _, err := Load(val, NewFileSource(dir)) @@ -416,3 +419,70 @@ func testSetEnv(t *testing.T, key, value string) { func testCreateFile(path, value string, perm os.FileMode) (err error) { return os.WriteFile(path, []byte(value), perm) } + +func TestShouldErrorOnNoPath(t *testing.T) { + val := schema.NewStructValidator() + _, _, err := Load(val, NewFileSource("")) + + assert.NoError(t, err) + assert.Len(t, val.Errors(), 1) + assert.ErrorContains(t, val.Errors()[0], "invalid file path source configuration") +} + +func TestShouldErrorOnInvalidPath(t *testing.T) { + dir := t.TempDir() + cfg := filepath.Join(dir, "invalid-folder/config") + + val := schema.NewStructValidator() + _, _, err := Load(val, NewFileSource(cfg)) + + assert.NoError(t, err) + assert.Len(t, val.Errors(), 1) + assert.ErrorContains(t, val.Errors()[0], fmt.Sprintf("stat %s: no such file or directory", cfg)) +} + +func TestShouldErrorOnDirFSPermissionDenied(t *testing.T) { + if runtime.GOOS == constWindows { + t.Skip("skipping test due to being on windows") + } + + dir := t.TempDir() + err := os.Chmod(dir, 0200) + assert.NoError(t, err) + + val := schema.NewStructValidator() + _, _, err = Load(val, NewFileSource(dir)) + + assert.NoError(t, err) + assert.Len(t, val.Errors(), 1) + assert.ErrorContains(t, val.Errors()[0], fmt.Sprintf("open %s: permission denied", dir)) +} + +func TestShouldSkipDirOnLoad(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "some-dir") + + err := os.Mkdir(path, 0700) + assert.NoError(t, err) + + val := schema.NewStructValidator() + _, _, err = Load(val, NewFileSource(dir)) + + assert.NoError(t, err) + assert.Len(t, val.Errors(), 0) + assert.Len(t, val.Warnings(), 0) +} + +func TestShouldFailIfYmlIsInvalid(t *testing.T) { + dir := t.TempDir() + + cfg := filepath.Join(dir, "myconf.yml") + assert.NoError(t, testCreateFile(cfg, "an invalid contend\n", 0700)) + + val := schema.NewStructValidator() + _, _, err := Load(val, NewFileSource(dir)) + + assert.NoError(t, err) + assert.Len(t, val.Errors(), 1) + assert.ErrorContains(t, val.Errors()[0], "unmarshal errors") +} diff --git a/internal/configuration/template_test.go b/internal/configuration/template_test.go index 3be14868f..aab4b26ae 100644 --- a/internal/configuration/template_test.go +++ b/internal/configuration/template_test.go @@ -25,6 +25,23 @@ func TestShouldGenerateConfiguration(t *testing.T) { assert.NoError(t, err) } +func TestNotShouldGenerateConfigurationifExists(t *testing.T) { + dir := t.TempDir() + + cfg := filepath.Join(dir, "config.yml") + + created, err := EnsureConfigurationExists(cfg) + assert.NoError(t, err) + assert.True(t, created) + + created, err = EnsureConfigurationExists(cfg) + assert.NoError(t, err) + assert.False(t, created) + + _, err = os.Stat(cfg) + assert.NoError(t, err) +} + func TestShouldNotGenerateConfigurationOnFSAccessDenied(t *testing.T) { if runtime.GOOS == constWindows { t.Skip("skipping test due to being on windows") From 0e71793814c71696f102a0c36bde1c394a99ada3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 13 Apr 2023 23:42:16 +1000 Subject: [PATCH 11/15] build(deps): update module github.com/prometheus/client_golang to v1.15.0 (#5234) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- go.mod | 8 +++---- go.sum | 76 +++++++--------------------------------------------------- 2 files changed, 13 insertions(+), 71 deletions(-) diff --git a/go.mod b/go.mod index aa25792a0..29892f0ec 100644 --- a/go.mod +++ b/go.mod @@ -37,7 +37,7 @@ require ( github.com/otiai10/copy v1.10.0 github.com/pkg/errors v0.9.1 github.com/pquerna/otp v1.4.0 - github.com/prometheus/client_golang v1.14.0 + github.com/prometheus/client_golang v1.15.0 github.com/sirupsen/logrus v1.9.0 github.com/spf13/cobra v1.7.0 github.com/spf13/pflag v1.0.5 @@ -98,8 +98,8 @@ require ( github.com/philhofer/fwd v1.1.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_model v0.3.0 // indirect - github.com/prometheus/common v0.37.0 // indirect - github.com/prometheus/procfs v0.8.0 // indirect + github.com/prometheus/common v0.42.0 // indirect + github.com/prometheus/procfs v0.9.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect github.com/spf13/afero v1.9.5 // indirect @@ -116,7 +116,7 @@ require ( github.com/ysmood/leakless v0.8.0 // indirect golang.org/x/crypto v0.7.0 // indirect golang.org/x/mod v0.10.0 // indirect - golang.org/x/oauth2 v0.4.0 // indirect + golang.org/x/oauth2 v0.5.0 // indirect golang.org/x/sys v0.7.0 // indirect golang.org/x/tools v0.7.0 // indirect google.golang.org/appengine v1.6.7 // indirect diff --git a/go.sum b/go.sum index 8756d6778..24918fcea 100644 --- a/go.sum +++ b/go.sum @@ -44,10 +44,7 @@ github.com/Gurpartap/logrus-stack v0.0.0-20170710170904-89c00d8a28f4 h1:vdT7QwBh github.com/Gurpartap/logrus-stack v0.0.0-20170710170904-89c00d8a28f4/go.mod h1:SvXOG8ElV28oAiG9zv91SDe5+9PfIr7PPccpr8YyXNs= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= @@ -62,7 +59,6 @@ github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBW github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= @@ -138,15 +134,10 @@ 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-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.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= -github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= github.com/go-ldap/ldap/v3 v3.4.4 h1:qPjipEpt+qDa6SI/h1fzuGWoRUY+qqQ9sOZq67/PYUs= github.com/go-ldap/ldap/v3 v3.4.4/go.mod h1:fe1MsuN5eJJ1FeLT/LEBVdWfNWKh459R7aXgXtJC+aI= 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.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= 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-rod/rod v0.112.8 h1:lYFnHv/lFyjW/Ye0IhyKLeHw/zfhHbSTqawoCi2z/nI= @@ -194,7 +185,6 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= @@ -216,7 +206,6 @@ github.com/google/go-tpm v0.3.3 h1:P/ZFNBZYXRxc+z7i5uyd8VP7MaDteuLZInzrH2idRGo= github.com/google/go-tpm v0.3.3/go.mod h1:9Hyn3rgnzWF9XBWVk6ml6A6hNkbWjNFlDQL51BeghL4= github.com/google/go-tpm-tools v0.0.0-20190906225433-1614c142f845/go.mod h1:AVfHadzbdzHo54inR2x1v640jdi1YSi3NauM2DUsxk0= github.com/google/go-tpm-tools v0.2.0/go.mod h1:npUd03rQ60lxN7tzeBJreG38RvWwme2N1reF/eeiBk4= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -270,15 +259,9 @@ github.com/jandelgado/gcov2lcov v1.0.5/go.mod h1:NnSxK6TMlg1oGDBfGelGbjgorT5/L3c github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= -github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 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.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.16.3 h1:XuJt9zzcnaz6a16/OU53ZjWp/v7/42WcR5t2a0PcNQY= @@ -298,11 +281,10 @@ github.com/knadh/koanf/providers/rawbytes v0.1.0/go.mod h1:mMTB1/IcJ/yE++A2iEZbY github.com/knadh/koanf/v2 v2.0.1 h1:1dYGITt1I23x8cfx8ZnldtezdyaZtfAuRtIFOiRzK7g= github.com/knadh/koanf/v2 v2.0.1/go.mod h1:ZeiIlIDXTE7w1lMT6UVcNiRAS2/rCeLn/GdLNvY1Dus= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -329,15 +311,9 @@ github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyua github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/oleiade/reflections v1.0.1 h1:D1XO3LVEYroYskEsoSiGItp9RUxG6jWnCVvrqH0HHQM= @@ -367,7 +343,6 @@ github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoU github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw= github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= @@ -377,34 +352,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/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 v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= -github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= -github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= -github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= +github.com/prometheus/client_golang v1.15.0 h1:5fCgGYogn0hFdhyhLbw7hEsWxufKtY9klyvdNfFlFhM= +github.com/prometheus/client_golang v1.15.0/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-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.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 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/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.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= -github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= -github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= -github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE= -github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= +github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM= +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-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo= -github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= +github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= +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/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= @@ -417,8 +379,6 @@ 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/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.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= @@ -568,7 +528,6 @@ golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -592,10 +551,7 @@ golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= @@ -609,10 +565,8 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/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.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.4.0 h1:NF0gk8LVPg1Ml7SSbGyySuoxdsXitj7TvgvuRxIMc/M= -golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec= +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/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-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -635,7 +589,6 @@ golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -644,7 +597,6 @@ golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -657,8 +609,6 @@ golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -666,17 +616,13 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210629170331-7dc0b73dc9fb/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -864,7 +810,6 @@ google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= @@ -876,9 +821,6 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkep gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From f1a20a2469f99d01d63fbec32916060d5331b713 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 13 Apr 2023 23:56:37 +1000 Subject: [PATCH 12/15] build(deps): update commitlint monorepo to v17.6.0 (#5233) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- web/package.json | 4 ++-- web/pnpm-lock.yaml | 28 ++++++++++++++-------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/web/package.json b/web/package.json index c1b155a1c..100718696 100644 --- a/web/package.json +++ b/web/package.json @@ -66,8 +66,8 @@ ] }, "devDependencies": { - "@commitlint/cli": "17.5.1", - "@commitlint/config-conventional": "17.4.4", + "@commitlint/cli": "17.6.0", + "@commitlint/config-conventional": "17.6.0", "@limegrass/eslint-plugin-import-alias": "1.0.6", "@testing-library/jest-dom": "5.16.5", "@testing-library/react": "14.0.0", diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index e3fc85956..9579cbe07 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -76,11 +76,11 @@ dependencies: devDependencies: '@commitlint/cli': - specifier: 17.5.1 - version: 17.5.1 + specifier: 17.6.0 + version: 17.6.0 '@commitlint/config-conventional': - specifier: 17.4.4 - version: 17.4.4 + specifier: 17.6.0 + version: 17.6.0 '@limegrass/eslint-plugin-import-alias': specifier: 1.0.6 version: 1.0.6(eslint@8.38.0) @@ -1552,13 +1552,13 @@ packages: '@babel/helper-validator-identifier': 7.19.1 to-fast-properties: 2.0.0 - /@commitlint/cli@17.5.1: - resolution: {integrity: sha512-pRRgGSzdHQHehxZbGA3qF6wVPyl+EEQgTe/t321rtMLFbuJ7nRj2waS17s/v5oEbyZtiY5S8PGB6XtEIm0I+Sg==} + /@commitlint/cli@17.6.0: + resolution: {integrity: sha512-JaZeZ1p6kfkSiZlDoQjK09AuiI9zYQMiIUJzTOM8qNRHFOXOPmiTM56nI67yzeUSNTFu6M/DRqjmdjtA5q3hEg==} engines: {node: '>=v14'} hasBin: true dependencies: '@commitlint/format': 17.4.4 - '@commitlint/lint': 17.4.4 + '@commitlint/lint': 17.6.0 '@commitlint/load': 17.5.0 '@commitlint/read': 17.5.1 '@commitlint/types': 17.4.4 @@ -1572,8 +1572,8 @@ packages: - '@swc/wasm' dev: true - /@commitlint/config-conventional@17.4.4: - resolution: {integrity: sha512-u6ztvxqzi6NuhrcEDR7a+z0yrh11elY66nRrQIpqsqW6sZmpxYkDLtpRH8jRML+mmxYQ8s4qqF06Q/IQx5aJeQ==} + /@commitlint/config-conventional@17.6.0: + resolution: {integrity: sha512-2Y9M7MN942bTK5h70fJGknhXA02+OtWCkKeIzTSwsdwz1V7y6bxYv24x052E9XHKtZHJfvM3iLuTOsjRvLqWtA==} engines: {node: '>=v14'} dependencies: conventional-changelog-conventionalcommits: 5.0.0 @@ -1620,13 +1620,13 @@ packages: semver: 7.3.8 dev: true - /@commitlint/lint@17.4.4: - resolution: {integrity: sha512-qgkCRRFjyhbMDWsti/5jRYVJkgYZj4r+ZmweZObnbYqPUl5UKLWMf9a/ZZisOI4JfiPmRktYRZ2JmqlSvg+ccw==} + /@commitlint/lint@17.6.0: + resolution: {integrity: sha512-6cEXxpxZd7fbtYMxeosOum/Nnwu3VdSuZcrFSqP9lWNsrHRv4ijVsnLeomvo6WHPchGOeEWAazAI7Q6Ap22fJw==} engines: {node: '>=v14'} dependencies: '@commitlint/is-ignored': 17.4.4 '@commitlint/parse': 17.4.4 - '@commitlint/rules': 17.4.4 + '@commitlint/rules': 17.6.0 '@commitlint/types': 17.4.4 dev: true @@ -1690,8 +1690,8 @@ packages: resolve-global: 1.0.0 dev: true - /@commitlint/rules@17.4.4: - resolution: {integrity: sha512-0tgvXnHi/mVcyR8Y8mjTFZIa/FEQXA4uEutXS/imH2v1UNkYDSEMsK/68wiXRpfW1euSgEdwRkvE1z23+yhNrQ==} + /@commitlint/rules@17.6.0: + resolution: {integrity: sha512-Ka7AsRFvkKMYYE7itgo7hddRGCiV+0BgbTIAq4PWmnkHAECxYpdqMVzW5jaATmXZfwfRRTB57e7KZWj6EPmK1A==} engines: {node: '>=v14'} dependencies: '@commitlint/ensure': 17.4.4 From 5bbac7f7b33f701509ee7ab2857246a630f93f25 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 14 Apr 2023 05:28:25 +1000 Subject: [PATCH 13/15] build(deps): update dependency happy-dom to v9.5.0 (#5236) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- web/package.json | 2 +- web/pnpm-lock.yaml | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/web/package.json b/web/package.json index 100718696..8589cac10 100644 --- a/web/package.json +++ b/web/package.json @@ -92,7 +92,7 @@ "eslint-plugin-prettier": "4.2.1", "eslint-plugin-react": "7.32.2", "eslint-plugin-react-hooks": "4.6.0", - "happy-dom": "9.3.2", + "happy-dom": "9.5.0", "husky": "8.0.3", "prettier": "2.8.7", "react-test-renderer": "18.2.0", diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index 9579cbe07..3a421d586 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -154,8 +154,8 @@ devDependencies: specifier: 4.6.0 version: 4.6.0(eslint@8.38.0) happy-dom: - specifier: 9.3.2 - version: 9.3.2 + specifier: 9.5.0 + version: 9.5.0 husky: specifier: 8.0.3 version: 8.0.3 @@ -185,7 +185,7 @@ devDependencies: version: 4.2.0(typescript@5.0.4)(vite@4.2.1) vitest: specifier: 0.30.1 - version: 0.30.1(happy-dom@9.3.2) + version: 0.30.1(happy-dom@9.5.0) vitest-preview: specifier: 0.0.1 version: 0.0.1 @@ -3087,7 +3087,7 @@ packages: istanbul-lib-source-maps: 4.0.1 istanbul-reports: 3.1.5 test-exclude: 6.0.0 - vitest: 0.30.1(happy-dom@9.3.2) + vitest: 0.30.1(happy-dom@9.5.0) transitivePeerDependencies: - supports-color dev: true @@ -5100,8 +5100,8 @@ packages: resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==} dev: true - /happy-dom@9.3.2: - resolution: {integrity: sha512-WTsnOmYXCiHs4REtjWSpi4lWJfhFj3ag9NMRrl0a1gwmAXwjRGm7fppdf6e3v38HtOins1Lgv0WKY8+ZlL0kDQ==} + /happy-dom@9.5.0: + resolution: {integrity: sha512-pNdHSZRWIckzg8aDQRbBgaivr2Ef+uSTpCCRGnxIETyewHA6841T8EPE+cmfhPjGi5jQN6c+oloXGGYB5SrpcA==} dependencies: css.escape: 1.5.1 he: 1.2.0 @@ -7519,7 +7519,7 @@ packages: - terser dev: true - /vitest@0.30.1(happy-dom@9.3.2): + /vitest@0.30.1(happy-dom@9.5.0): resolution: {integrity: sha512-y35WTrSTlTxfMLttgQk4rHcaDkbHQwDP++SNwPb+7H8yb13Q3cu2EixrtHzF27iZ8v0XCciSsLg00RkPAzB/aA==} engines: {node: '>=v14.18.0'} hasBin: true @@ -7564,7 +7564,7 @@ packages: chai: 4.3.7 concordance: 5.0.4 debug: 4.3.4 - happy-dom: 9.3.2 + happy-dom: 9.5.0 local-pkg: 0.4.3 magic-string: 0.30.0 pathe: 1.1.0 From 81de035874bb64fb8f3c565d6caf4b51c1bdee8d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 14 Apr 2023 05:40:21 +1000 Subject: [PATCH 14/15] build(deps): update module github.com/ory/x to v0.0.552 (#5235) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 29892f0ec..5a45f5030 100644 --- a/go.mod +++ b/go.mod @@ -33,7 +33,7 @@ require ( github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 github.com/ory/fosite v0.44.0 github.com/ory/herodot v0.10.2 - github.com/ory/x v0.0.551 + github.com/ory/x v0.0.552 github.com/otiai10/copy v1.10.0 github.com/pkg/errors v0.9.1 github.com/pquerna/otp v1.4.0 diff --git a/go.sum b/go.sum index 24918fcea..81b8b4e89 100644 --- a/go.sum +++ b/go.sum @@ -327,8 +327,8 @@ 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/herodot v0.10.2 h1:gGvNMHgAwWzdP/eo+roSiT5CGssygHSjDU7MSQNlJ4E= github.com/ory/herodot v0.10.2/go.mod h1:MMNmY6MG1uB6fnXYFaHoqdV23DTWctlPsmRCeq/2+wc= -github.com/ory/x v0.0.551 h1:U3z2bvSzAwDP0SWmbAdjzfvWPu4k+oWrPctoCdalGk0= -github.com/ory/x v0.0.551/go.mod h1:oRVemI3SQQOLvOCJWIRinHQKlgmay/NbwSyRUIsS/Yk= +github.com/ory/x v0.0.552 h1:vgDw7FFQ7Ama3iyDLbjElY2Um1/ub82iIubK0pUj81M= +github.com/ory/x v0.0.552/go.mod h1:oRVemI3SQQOLvOCJWIRinHQKlgmay/NbwSyRUIsS/Yk= github.com/otiai10/copy v1.10.0 h1:znyI7l134wNg/wDktoVQPxPkgvhDfGCYUasey+h0rDQ= github.com/otiai10/copy v1.10.0/go.mod h1:rSaLseMUsZFFbsFGc7wCJnnkTAvdc5L6VWxPE4308Ww= github.com/otiai10/mint v1.5.1 h1:XaPLeE+9vGbuyEHem1JNk3bYc7KKqyI/na0/mLd/Kks= From 0f4f5d58482334129d6499321e73a025a474d533 Mon Sep 17 00:00:00 2001 From: James Elliott Date: Fri, 14 Apr 2023 20:46:43 +1000 Subject: [PATCH 15/15] fix(commands): no args not enforced on crypto hash generate (#5237) This fixes an issue where the authelia crypto hash generate command does not require no arguments leading to some confusing output. Signed-off-by: James Elliott --- internal/commands/crypto_hash.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/commands/crypto_hash.go b/internal/commands/crypto_hash.go index 7c85f3ef3..06cad1e7c 100644 --- a/internal/commands/crypto_hash.go +++ b/internal/commands/crypto_hash.go @@ -61,6 +61,7 @@ func newCryptoHashGenerateCmd(ctx *CmdCtx) (cmd *cobra.Command) { Short: cmdAutheliaCryptoHashGenerateShort, Long: cmdAutheliaCryptoHashGenerateLong, Example: cmdAutheliaCryptoHashGenerateExample, + Args: cobra.NoArgs, PreRunE: ctx.ChainRunE( ctx.ConfigSetDefaultsRunE(defaults), ctx.CryptoHashGenerateMapFlagsRunE,