Compare commits
76 Commits
feat-setti
...
master
Author | SHA1 | Date |
---|---|---|
Jonas Letzbor | dd673e0e82 | |
Jonas Letzbor | e9a383be0c | |
Jonas Letzbor | c13e0e12ea | |
renovate[bot] | 2128969afc | |
renovate[bot] | bb1e853b9f | |
renovate[bot] | 0a92f13f15 | |
renovate[bot] | 6af53a66dd | |
renovate[bot] | fb68177b23 | |
renovate[bot] | 035070414b | |
renovate[bot] | 2671151cae | |
renovate[bot] | 41e9c51245 | |
renovate[bot] | 360b672387 | |
renovate[bot] | 0f9a8994c2 | |
renovate[bot] | 231c34ae08 | |
renovate[bot] | 977d490d5e | |
renovate[bot] | 38723ec5c4 | |
James Elliott | 4c328e237d | |
renovate[bot] | aca740fdb7 | |
renovate[bot] | 0f5fae8646 | |
James Elliott | f79db588be | |
James Elliott | 68ac62acab | |
renovate[bot] | ecf742aa33 | |
renovate[bot] | ec4557b3db | |
renovate[bot] | 1d2899f665 | |
renovate[bot] | 31fd903e4d | |
renovate[bot] | 7ffb40042f | |
renovate[bot] | 89ee0690f8 | |
renovate[bot] | 3faeb75d65 | |
renovate[bot] | ec03cf567f | |
renovate[bot] | 026fe9fdbe | |
renovate[bot] | af538b793b | |
renovate[bot] | f09594f6d6 | |
renovate[bot] | 48f208b717 | |
renovate[bot] | f05db093be | |
renovate[bot] | ea1196f3f2 | |
renovate[bot] | e662fa2092 | |
renovate[bot] | 5144d5d615 | |
renovate[bot] | a6f1d10559 | |
renovate[bot] | fb665d0a5a | |
renovate[bot] | bbfd3d4467 | |
renovate[bot] | 17148b08e7 | |
renovate[bot] | a6ff56f364 | |
renovate[bot] | 276f11fda0 | |
renovate[bot] | 7be23820c3 | |
renovate[bot] | 42678e264f | |
James Elliott | 4adefd3ef6 | |
renovate[bot] | 19de724937 | |
renovate[bot] | 2ab69f7070 | |
renovate[bot] | 12aa3a8dff | |
renovate[bot] | b81ef53c7e | |
renovate[bot] | 899d58b827 | |
renovate[bot] | 8316cd4eb7 | |
renovate[bot] | d29a676de9 | |
renovate[bot] | 447356290b | |
renovate[bot] | 4a308ea42b | |
renovate[bot] | 6e7061092c | |
renovate[bot] | 878fd6b169 | |
renovate[bot] | 0036cc877a | |
renovate[bot] | eaa4fb5fb7 | |
renovate[bot] | 3028dff838 | |
renovate[bot] | ea7afc8cea | |
renovate[bot] | 0a8c1d4606 | |
renovate[bot] | 6d9ce70e9e | |
renovate[bot] | c57a05b134 | |
renovate[bot] | e385828e59 | |
renovate[bot] | 0a3aace653 | |
James Elliott | 4c98da0d29 | |
renovate[bot] | 8a24d422c1 | |
renovate[bot] | 34172de338 | |
renovate[bot] | a2075b4823 | |
renovate[bot] | 85ebc37411 | |
renovate[bot] | c11e44aa17 | |
renovate[bot] | 3dc258943f | |
James Elliott | 0e6adebc49 | |
James Elliott | 4577fce95b | |
James Elliott | f90c369b45 |
|
@ -7,3 +7,4 @@
|
||||||
!entrypoint.sh
|
!entrypoint.sh
|
||||||
!healthcheck.sh
|
!healthcheck.sh
|
||||||
!.healthcheck.env
|
!.healthcheck.env
|
||||||
|
!dist/public_html/
|
|
@ -1,7 +1,7 @@
|
||||||
# ===================================
|
# ===================================
|
||||||
# ===== Authelia official image =====
|
# ===== Authelia official image =====
|
||||||
# ===================================
|
# ===================================
|
||||||
FROM alpine:3.18.0
|
FROM alpine:3.18.2
|
||||||
|
|
||||||
ARG TARGETOS
|
ARG TARGETOS
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
|
|
|
@ -15,7 +15,7 @@ RUN yarn global add pnpm && \
|
||||||
# =======================================
|
# =======================================
|
||||||
# ===== Build image for the backend =====
|
# ===== Build image for the backend =====
|
||||||
# =======================================
|
# =======================================
|
||||||
FROM golang:1.20.4-alpine AS builder-backend
|
FROM golang:1.20.5-alpine AS builder-backend
|
||||||
|
|
||||||
WORKDIR /go/src/app
|
WORKDIR /go/src/app
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@ RUN \
|
||||||
# ===================================
|
# ===================================
|
||||||
# ===== Authelia official image =====
|
# ===== Authelia official image =====
|
||||||
# ===================================
|
# ===================================
|
||||||
FROM alpine:3.18.0
|
FROM alpine:3.18.2
|
||||||
|
|
||||||
RUN apk --no-cache add ca-certificates tzdata
|
RUN apk --no-cache add ca-certificates tzdata
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ RUN yarn install --frozen-lockfile && yarn build
|
||||||
# =======================================
|
# =======================================
|
||||||
# ===== Build image for the backend =====
|
# ===== Build image for the backend =====
|
||||||
# =======================================
|
# =======================================
|
||||||
FROM golang:1.20.4-alpine AS builder-backend
|
FROM golang:1.20.5-alpine AS builder-backend
|
||||||
|
|
||||||
WORKDIR /go/src/app
|
WORKDIR /go/src/app
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ RUN \
|
||||||
# ===================================
|
# ===================================
|
||||||
# ===== Authelia official image =====
|
# ===== Authelia official image =====
|
||||||
# ===================================
|
# ===================================
|
||||||
FROM alpine:3.18.0
|
FROM alpine:3.18.2
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,95 @@
|
||||||
|
# Ausführen
|
||||||
|
|
||||||
|
Um die Anwendung lokal auszuführen, können die folgenden Befehle verwendet werden.
|
||||||
|
|
||||||
|
```
|
||||||
|
export GOPATH=/tmp
|
||||||
|
source bootstrap.sh
|
||||||
|
authelia-scripts suites setup Standalone
|
||||||
|
```
|
||||||
|
|
||||||
|
Nun sollte der "Haupt-Enpunkt" unter `https://home.example.com:8080` und die API unter `https://authelia.example.com:9091` erreichbar sein. Achtung: es wird ein selbstsigniertes Zertifikat verwendet!
|
||||||
|
Mithilfe der Hot-Reload kann jetzt gecoded werden.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Nach der Entwicklung kann die Testumgebung durch den folgenden Befehl wieder zurückgesetzt werden.
|
||||||
|
|
||||||
|
```
|
||||||
|
go run ./cmd/authelia-scripts/ suites teardown Standalone
|
||||||
|
```
|
||||||
|
|
||||||
|
## Benutzerdefinierte Zertifikate
|
||||||
|
|
||||||
|
Um ein benutzerdefiniertes Zertifikat für die Ausführung zu verwenden, muss die Datai `public.backend.crt` und `private.bakend.pem` unter [diesem](/internal/suites/common/pki/) Verzeichnis abgeändert werden.
|
||||||
|
Um die Gültigkeit zu testen, kann der folgendende Befehl ausgeführt werden.
|
||||||
|
|
||||||
|
```
|
||||||
|
curl https://auth.rpjosh.de:9091 --connect-to 'auth.rpjosh.de:9091:authelia.example.com:9091'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Externe erreichbarkeit
|
||||||
|
|
||||||
|
Im aktuellen Zustand sind die Endpunkte nur unter den Docker internen IP-Adressen erreichbar. Daher muss noch ein NAT Regel angelegt werden.
|
||||||
|
|
||||||
|
```
|
||||||
|
ip=$(ping -c 1 authelia.example.com | gawk -F'[()]' '/PING/{print $2}')
|
||||||
|
sudo iptables -t nat -A PREROUTING -p tcp --dport 9091 -d 192.168.0.15 -j DNAT --to-destination 192.168.240.50:9091 -m comment --comment "Authelia-Test"
|
||||||
|
sudo iptables -t nat -A PREROUTING -p tcp --dport 9092 -d 192.168.0.15 -j DNAT --to-destination 192.168.240.50:9092 -m comment --comment "Authelia-Test"
|
||||||
|
sudo iptables -t nat -I OUTPUT -p tcp -o lo --dport 9091 -j DNAT --to-destination 192.168.240.50:9091
|
||||||
|
```
|
||||||
|
|
||||||
|
# Customizations
|
||||||
|
|
||||||
|
Für das Starten des *gRPC* Servers müssen die folgenden Abhängigkeiten installiert werden.
|
||||||
|
|
||||||
|
```
|
||||||
|
go get github.com/envoyproxy/go-control-plane
|
||||||
|
go get github.com/envoyproxy/go-control-plane/envoy/config/core/v3
|
||||||
|
go get github.com/gogo/googleapis/google/rpc
|
||||||
|
go get google.golang.org/grpc
|
||||||
|
```
|
||||||
|
|
||||||
|
## Konfiguration ändern
|
||||||
|
|
||||||
|
Wenn die Konfiguration geändert wurde, müssen die Keys zur Validierung wieder erneut gebaut werden.
|
||||||
|
|
||||||
|
```
|
||||||
|
go run ./cmd/authelia-gen code keys
|
||||||
|
```
|
||||||
|
|
||||||
|
## Mocks abgeändert
|
||||||
|
|
||||||
|
Wenn interfaces von den Mocks geändert werden, muss folgendes wieder ausgeführt werden:
|
||||||
|
|
||||||
|
```
|
||||||
|
export PATH=$PATH:$(go env GOPATH)/bin
|
||||||
|
go generate ./...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Bauen
|
||||||
|
|
||||||
|
Um ein Docker Image für authelia zu bauen, müssen die folgenden Befehle ausgeführt werden.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# Dieser Befehle funktionieren aktuell nicht
|
||||||
|
authelia-scripts docker build
|
||||||
|
authelia-scripts build
|
||||||
|
|
||||||
|
# => Manuell bauen
|
||||||
|
export CC=musl-gcc
|
||||||
|
|
||||||
|
authelia-scripts build
|
||||||
|
cp -r dist/public_html internal/server/
|
||||||
|
go build -buildmode=pie -ldflags "-linkmode=external -s -w" -trimpath -buildmode=pie -o authelia ./cmd/authelia
|
||||||
|
mv authelia authelia-linux-amd64-musl
|
||||||
|
# Build docker image
|
||||||
|
docker build --tag git.rpjosh.de/rpjosh/authelia/authelia:4.38.0-dev .
|
||||||
|
docker push git.rpjosh.de/rpjosh/authelia/authelia:4.38.0-dev
|
||||||
|
# Cleanup
|
||||||
|
rm -rf internal/server/public_html/ ./authelia-linux-amd64-musl
|
||||||
|
```
|
||||||
|
|
||||||
|
# gRCP
|
||||||
|
|
||||||
|
Um einen gRCP Endpunkt nutzen zu können, brauch mein eine *.proto* Datei. Für Envoy sieht diese wie in [dieser Datei](/ext-auth.proto) folgendermaßen aus.
|
|
@ -849,45 +849,6 @@ paths:
|
||||||
$ref: '#/components/schemas/middlewares.OkResponse'
|
$ref: '#/components/schemas/middlewares.OkResponse'
|
||||||
security:
|
security:
|
||||||
- authelia_auth: []
|
- authelia_auth: []
|
||||||
/api/secondfactor/webauthn/devices/{deviceID}:
|
|
||||||
delete:
|
|
||||||
tags:
|
|
||||||
- Second Factor
|
|
||||||
summary: WebAuthn Device Deletion
|
|
||||||
description: This endpoint deletes the specified WebAuthn credential.
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: Successful Operation
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/middlewares.OkResponse'
|
|
||||||
security:
|
|
||||||
- authelia_auth: []
|
|
||||||
parameters:
|
|
||||||
- $ref: '#/components/parameters/deviceID'
|
|
||||||
put:
|
|
||||||
tags:
|
|
||||||
- Second Factor
|
|
||||||
summary: WebAuthn Device Update
|
|
||||||
description: This endpoint updates the description of the specified WebAuthn credential.
|
|
||||||
requestBody:
|
|
||||||
required: true
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/webauthn.DeviceUpdateRequest'
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: Successful Operation
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/middlewares.OkResponse'
|
|
||||||
security:
|
|
||||||
- authelia_auth: []
|
|
||||||
parameters:
|
|
||||||
- $ref: '#/components/parameters/deviceID'
|
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- if .Duo }}
|
{{- if .Duo }}
|
||||||
/api/secondfactor/duo:
|
/api/secondfactor/duo:
|
||||||
|
@ -1472,13 +1433,6 @@ paths:
|
||||||
{{- end }}
|
{{- end }}
|
||||||
components:
|
components:
|
||||||
parameters:
|
parameters:
|
||||||
deviceID:
|
|
||||||
in: path
|
|
||||||
name: deviceID
|
|
||||||
schema:
|
|
||||||
type: integer
|
|
||||||
required: true
|
|
||||||
description: Numeric WebAuthn Device ID
|
|
||||||
originalMethodParam:
|
originalMethodParam:
|
||||||
name: X-Original-Method
|
name: X-Original-Method
|
||||||
in: header
|
in: header
|
||||||
|
@ -1963,9 +1917,6 @@ components:
|
||||||
type: string
|
type: string
|
||||||
format: byte
|
format: byte
|
||||||
webauthn.CredentialAttestationResponse:
|
webauthn.CredentialAttestationResponse:
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
credential:
|
|
||||||
allOf:
|
allOf:
|
||||||
- $ref: '#/components/schemas/webauthn.PublicKeyCredential'
|
- $ref: '#/components/schemas/webauthn.PublicKeyCredential'
|
||||||
- type: object
|
- type: object
|
||||||
|
@ -1983,8 +1934,6 @@ components:
|
||||||
attestationObject:
|
attestationObject:
|
||||||
type: string
|
type: string
|
||||||
format: byte
|
format: byte
|
||||||
description:
|
|
||||||
type: string
|
|
||||||
webauthn.CredentialAssertionResponse:
|
webauthn.CredentialAssertionResponse:
|
||||||
allOf:
|
allOf:
|
||||||
- $ref: '#/components/schemas/webauthn.PublicKeyCredential'
|
- $ref: '#/components/schemas/webauthn.PublicKeyCredential'
|
||||||
|
@ -2022,11 +1971,6 @@ components:
|
||||||
format: uuid
|
format: uuid
|
||||||
pattern: '^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$'
|
pattern: '^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$'
|
||||||
example: '3ebcfbc5-b0fd-4ee0-9d3c-080ae1e7298c'
|
example: '3ebcfbc5-b0fd-4ee0-9d3c-080ae1e7298c'
|
||||||
webauthn.DeviceUpdateRequest:
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
description:
|
|
||||||
type: string
|
|
||||||
webauthn.PublicKeyCredentialCreationOptions:
|
webauthn.PublicKeyCredentialCreationOptions:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
|
|
@ -61,6 +61,24 @@ func rootSubCommandsRunE(cmd *cobra.Command, args []string) (err error) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
subCmds := sortCmds(cmd)
|
||||||
|
|
||||||
|
for _, subCmd := range subCmds {
|
||||||
|
if subCmd.Use == cmdUseCompletion || strings.HasPrefix(subCmd.Use, "help ") || utils.IsStringSliceContainsAny([]string{resolveCmdName(subCmd), subCmd.Use}, exclude) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
rootCmd.SetArgs(rootCmdGetArgs(subCmd, args))
|
||||||
|
|
||||||
|
if err = rootCmd.Execute(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func sortCmds(cmd *cobra.Command) []*cobra.Command {
|
||||||
subCmds := cmd.Commands()
|
subCmds := cmd.Commands()
|
||||||
|
|
||||||
switch cmd.Use {
|
switch cmd.Use {
|
||||||
|
@ -90,19 +108,7 @@ func rootSubCommandsRunE(cmd *cobra.Command, args []string) (err error) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, subCmd := range subCmds {
|
return subCmds
|
||||||
if subCmd.Use == cmdUseCompletion || strings.HasPrefix(subCmd.Use, "help ") || utils.IsStringSliceContainsAny([]string{resolveCmdName(subCmd), subCmd.Use}, exclude) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
rootCmd.SetArgs(rootCmdGetArgs(subCmd, args))
|
|
||||||
|
|
||||||
if err = rootCmd.Execute(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func resolveCmdName(cmd *cobra.Command) string {
|
func resolveCmdName(cmd *cobra.Command) string {
|
||||||
|
@ -117,7 +123,7 @@ func resolveCmdName(cmd *cobra.Command) string {
|
||||||
|
|
||||||
func rootCmdGetArgs(cmd *cobra.Command, args []string) []string {
|
func rootCmdGetArgs(cmd *cobra.Command, args []string) []string {
|
||||||
for {
|
for {
|
||||||
if cmd == rootCmd {
|
if cmd == nil || cmd == rootCmd {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,125 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestResolveCmdName(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
have *cobra.Command
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"ShouldResolveRootCmd",
|
||||||
|
newRootCmd(),
|
||||||
|
"authelia-gen",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ShouldResolveDocsCmd",
|
||||||
|
newDocsCmd(),
|
||||||
|
"docs",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ShouldResolveDocsSubCmd",
|
||||||
|
newRootCmd().Commands()[0].Commands()[0],
|
||||||
|
"code.keys",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
assert.Equal(t, tc.expected, resolveCmdName(tc.have))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRootCmdGetArgs(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
have func() *cobra.Command
|
||||||
|
args []string
|
||||||
|
expected []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"ShouldReturnRootCmdArgs",
|
||||||
|
func() *cobra.Command {
|
||||||
|
cmd := newRootCmd()
|
||||||
|
|
||||||
|
cmd.SetArgs([]string{"a", "b"})
|
||||||
|
|
||||||
|
return cmd.Commands()[0]
|
||||||
|
},
|
||||||
|
[]string{"c", "d"},
|
||||||
|
[]string{"authelia-gen", "code", "c", "d"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ShouldReturnRootCmdWithoutArgs",
|
||||||
|
func() *cobra.Command {
|
||||||
|
cmd := newRootCmd()
|
||||||
|
|
||||||
|
return cmd.Commands()[0]
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
[]string{"authelia-gen", "code"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
assert.Equal(t, tc.expected, rootCmdGetArgs(tc.have(), tc.args))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSortCmds(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
have *cobra.Command
|
||||||
|
expected []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"ShouldSortRootCmd",
|
||||||
|
newRootCmd(),
|
||||||
|
[]string{"code", "commit-lint", "github", "locales", "docs"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ShouldSortDocsCmd",
|
||||||
|
newDocsCmd(),
|
||||||
|
[]string{"cli", "data", "date"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ShouldSortGitHubCmd",
|
||||||
|
newGitHubCmd(),
|
||||||
|
[]string{"issue-templates"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ShouldSortLocalesCmd",
|
||||||
|
newLocalesCmd(),
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ShouldSortDocsDataCmd",
|
||||||
|
newDocsDataCmd(),
|
||||||
|
[]string{"keys", "misc"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
actual := sortCmds(tc.have)
|
||||||
|
|
||||||
|
n := len(tc.expected)
|
||||||
|
|
||||||
|
require.Len(t, actual, n)
|
||||||
|
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
assert.Equal(t, tc.expected[i], actual[i].Use)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,145 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/mail"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/spf13/pflag"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetPFlagPath(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
have func(t *testing.T) *pflag.FlagSet
|
||||||
|
names []string
|
||||||
|
expected string
|
||||||
|
err string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"ShouldFailEmptyFlagSet",
|
||||||
|
func(t *testing.T) *pflag.FlagSet {
|
||||||
|
return pflag.NewFlagSet("example", pflag.ContinueOnError)
|
||||||
|
},
|
||||||
|
[]string{"abc", "123"},
|
||||||
|
"",
|
||||||
|
"failed to lookup flag 'abc': flag accessed but not defined: abc",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ShouldFailEmptyFlagNames",
|
||||||
|
func(t *testing.T) *pflag.FlagSet {
|
||||||
|
return pflag.NewFlagSet("example", pflag.ContinueOnError)
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
"",
|
||||||
|
"no flag names",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ShouldLookupFlagNames",
|
||||||
|
func(t *testing.T) *pflag.FlagSet {
|
||||||
|
flagset := pflag.NewFlagSet("example", pflag.ContinueOnError)
|
||||||
|
|
||||||
|
flagset.String("dir.one", "", "")
|
||||||
|
flagset.String("dir.two", "", "")
|
||||||
|
flagset.String("file.name", "", "")
|
||||||
|
|
||||||
|
require.NoError(t, flagset.Parse([]string{"--dir.one=abc", "--dir.two=123", "--file.name=path.txt"}))
|
||||||
|
|
||||||
|
return flagset
|
||||||
|
},
|
||||||
|
[]string{"dir.one", "dir.two", "file.name"},
|
||||||
|
"abc/123/path.txt",
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
actual, theError := getPFlagPath(tc.have(t), tc.names...)
|
||||||
|
|
||||||
|
if tc.err == "" {
|
||||||
|
assert.NoError(t, theError)
|
||||||
|
assert.Equal(t, tc.expected, actual)
|
||||||
|
} else {
|
||||||
|
assert.EqualError(t, theError, tc.err)
|
||||||
|
assert.Equal(t, "", actual)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildCSP(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
have string
|
||||||
|
ruleSets [][]CSPValue
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"ShouldParseDefault",
|
||||||
|
codeCSPProductionDefaultSrc,
|
||||||
|
[][]CSPValue{
|
||||||
|
codeCSPValuesCommon,
|
||||||
|
codeCSPValuesProduction,
|
||||||
|
},
|
||||||
|
"default-src 'self'; frame-src 'none'; object-src 'none'; style-src 'self' 'nonce-%s'; frame-ancestors 'none'; base-uri 'self'",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
assert.Equal(t, tc.expected, buildCSP(tc.have, tc.ruleSets...))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContainsType(t *testing.T) {
|
||||||
|
astring := ""
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
have any
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"ShouldContainMailAddress",
|
||||||
|
mail.Address{},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ShouldContainSchemaAddressPtr",
|
||||||
|
&schema.Address{},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ShouldNotContainString",
|
||||||
|
astring,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ShouldNotContainStringPtr",
|
||||||
|
&astring,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
assert.Equal(t, tc.expected, containsType(reflect.TypeOf(tc.have), decodedTypes))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReadTags(t *testing.T) {
|
||||||
|
assert.NotPanics(t, func() {
|
||||||
|
readTags("", reflect.TypeOf(schema.Configuration{}), false)
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.NotPanics(t, func() {
|
||||||
|
readTags("", reflect.TypeOf(schema.Configuration{}), true)
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestShouldFailToLoadBadTemplate(t *testing.T) {
|
||||||
|
assert.Panics(t, func() {
|
||||||
|
mustLoadTmplFS("bad tmpl")
|
||||||
|
})
|
||||||
|
}
|
|
@ -120,6 +120,11 @@ const (
|
||||||
labelAreaPrefixStatus = "status"
|
labelAreaPrefixStatus = "status"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type label interface {
|
||||||
|
String() string
|
||||||
|
LabelDescription() string
|
||||||
|
}
|
||||||
|
|
||||||
type labelPriority int
|
type labelPriority int
|
||||||
|
|
||||||
//nolint:deadcode,varcheck // Kept for future use.
|
//nolint:deadcode,varcheck // Kept for future use.
|
||||||
|
@ -129,6 +134,7 @@ const (
|
||||||
labelPriorityMedium
|
labelPriorityMedium
|
||||||
labelPriorityNormal
|
labelPriorityNormal
|
||||||
labelPriorityLow
|
labelPriorityLow
|
||||||
|
labelPriorityVeryLow
|
||||||
)
|
)
|
||||||
|
|
||||||
var labelPriorityDescriptions = [...]string{
|
var labelPriorityDescriptions = [...]string{
|
||||||
|
@ -137,14 +143,15 @@ var labelPriorityDescriptions = [...]string{
|
||||||
"Medium",
|
"Medium",
|
||||||
"Normal",
|
"Normal",
|
||||||
"Low",
|
"Low",
|
||||||
|
"Very Low",
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p labelPriority) String() string {
|
func (l labelPriority) String() string {
|
||||||
return fmt.Sprintf("%s/%d/%s", labelAreaPrefixPriority, p+1, strings.ToLower(labelPriorityDescriptions[p]))
|
return fmt.Sprintf("%s/%d/%s", labelAreaPrefixPriority, l+1, labelFormatString(labelPriorityDescriptions[l]))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p labelPriority) Description() string {
|
func (l labelPriority) LabelDescription() string {
|
||||||
return labelPriorityDescriptions[p]
|
return labelPriorityDescriptions[l]
|
||||||
}
|
}
|
||||||
|
|
||||||
type labelStatus int
|
type labelStatus int
|
||||||
|
@ -155,12 +162,16 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
var labelStatusDescriptions = [...]string{
|
var labelStatusDescriptions = [...]string{
|
||||||
"needs-design",
|
"Needs Design",
|
||||||
"needs-triage",
|
"Needs Triage",
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s labelStatus) String() string {
|
func (l labelStatus) String() string {
|
||||||
return fmt.Sprintf("%s/%s", labelAreaPrefixStatus, labelStatusDescriptions[s])
|
return fmt.Sprintf("%s/%s", labelAreaPrefixStatus, labelFormatString(labelStatusDescriptions[l]))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l labelStatus) LabelDescription() string {
|
||||||
|
return labelStatusDescriptions[l]
|
||||||
}
|
}
|
||||||
|
|
||||||
type labelType int
|
type labelType int
|
||||||
|
@ -173,13 +184,24 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
var labelTypeDescriptions = [...]string{
|
var labelTypeDescriptions = [...]string{
|
||||||
"feature",
|
"Feature",
|
||||||
"bug/unconfirmed",
|
"Bug: Unconfirmed",
|
||||||
"bug",
|
"Bug",
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t labelType) String() string {
|
func (l labelType) String() string {
|
||||||
return fmt.Sprintf("%s/%s", labelAreaPrefixType, labelTypeDescriptions[t])
|
return fmt.Sprintf("%s/%s", labelAreaPrefixType, labelFormatString(labelTypeDescriptions[l]))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l labelType) LabelDescription() string {
|
||||||
|
return labelTypeDescriptions[l]
|
||||||
|
}
|
||||||
|
|
||||||
|
func labelFormatString(in string) string {
|
||||||
|
in = strings.ReplaceAll(in, ": ", "/")
|
||||||
|
in = strings.ReplaceAll(in, " ", "-")
|
||||||
|
|
||||||
|
return strings.ToLower(in)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CSPValue represents individual CSP values.
|
// CSPValue represents individual CSP values.
|
||||||
|
|
|
@ -0,0 +1,90 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLabels(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
have label
|
||||||
|
expectedDescription string
|
||||||
|
expectedString string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"ShouldShowCorrectPriorityCriticalValues",
|
||||||
|
labelPriorityCritical,
|
||||||
|
"Critical",
|
||||||
|
"priority/1/critical",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ShouldShowCorrectPriorityHighValues",
|
||||||
|
labelPriorityHigh,
|
||||||
|
"High",
|
||||||
|
"priority/2/high",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ShouldShowCorrectPriorityMediumValues",
|
||||||
|
labelPriorityMedium,
|
||||||
|
"Medium",
|
||||||
|
"priority/3/medium",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ShouldShowCorrectPriorityNormalValues",
|
||||||
|
labelPriorityNormal,
|
||||||
|
"Normal",
|
||||||
|
"priority/4/normal",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ShouldShowCorrectPriorityLowValues",
|
||||||
|
labelPriorityLow,
|
||||||
|
"Low",
|
||||||
|
"priority/5/low",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ShouldShowCorrectPriorityVeryLowValues",
|
||||||
|
labelPriorityVeryLow,
|
||||||
|
"Very Low",
|
||||||
|
"priority/6/very-low",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ShouldShowCorrectStatusNeedsDesignValues",
|
||||||
|
labelStatusNeedsDesign,
|
||||||
|
"Needs Design",
|
||||||
|
"status/needs-design",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ShouldShowCorrectStatusNeedsTriageValues",
|
||||||
|
labelStatusNeedsTriage,
|
||||||
|
"Needs Triage",
|
||||||
|
"status/needs-triage",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ShouldShowCorrectTypeFeatureValues",
|
||||||
|
labelTypeFeature,
|
||||||
|
"Feature",
|
||||||
|
"type/feature",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ShouldShowCorrectTypeBugUnconfirmedValues",
|
||||||
|
labelTypeBugUnconfirmed,
|
||||||
|
"Bug: Unconfirmed",
|
||||||
|
"type/bug/unconfirmed",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ShouldShowCorrectTypeBugValues",
|
||||||
|
labelTypeBug,
|
||||||
|
"Bug",
|
||||||
|
"type/bug",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
assert.Equal(t, tc.expectedString, tc.have.String())
|
||||||
|
assert.Equal(t, tc.expectedDescription, tc.have.LabelDescription())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,5 +7,5 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
const (
|
const (
|
||||||
versionSwaggerUI = "4.18.3"
|
versionSwaggerUI = "5.0.0"
|
||||||
)
|
)
|
||||||
|
|
|
@ -39,7 +39,7 @@ default_redirection_url: 'https://home.example.com/'
|
||||||
## Set the default 2FA method for new users and for when a user has a preferred method configured that has been
|
## Set the default 2FA method for new users and for when a user has a preferred method configured that has been
|
||||||
## disabled. This setting must be a method that is enabled.
|
## disabled. This setting must be a method that is enabled.
|
||||||
## Options are totp, webauthn, mobile_push.
|
## Options are totp, webauthn, mobile_push.
|
||||||
default_2fa_method: ''
|
# default_2fa_method: ''
|
||||||
|
|
||||||
##
|
##
|
||||||
## Server Configuration
|
## Server Configuration
|
||||||
|
@ -47,15 +47,12 @@ default_2fa_method: ''
|
||||||
server:
|
server:
|
||||||
## The address for the Main server to listen on in the address common syntax.
|
## The address for the Main server to listen on in the address common syntax.
|
||||||
## Formats:
|
## Formats:
|
||||||
## - [<scheme>://]<hostname>[:<port>]
|
## - [<scheme>://]<hostname>[:<port>][/<path>]
|
||||||
## - [<scheme>://][hostname]:<port>
|
## - [<scheme>://][hostname]:<port>[/<path>]
|
||||||
## Square brackets indicate optional portions of the format. Scheme must be 'tcp', 'tcp4', 'tcp6', or 'unix'.
|
## Square brackets indicate optional portions of the format. Scheme must be 'tcp', 'tcp4', 'tcp6', or 'unix'.
|
||||||
## The default scheme is 'unix' if the address is an absolute path otherwise it's 'tcp'. The default port is '9091'.
|
## The default scheme is 'unix' if the address is an absolute path otherwise it's 'tcp'. The default port is '9091'.
|
||||||
address: 'tcp://:9091'
|
## If the path is specified this configures the router to handle both the `/` path and the configured path.
|
||||||
|
address: 'tcp://:9091/'
|
||||||
## Set the single level path Authelia listens on.
|
|
||||||
## Must be alphanumeric chars and should not contain any slashes.
|
|
||||||
path: ''
|
|
||||||
|
|
||||||
## Set the path on disk to Authelia assets.
|
## Set the path on disk to Authelia assets.
|
||||||
## Useful to allow overriding of specific static assets.
|
## Useful to allow overriding of specific static assets.
|
||||||
|
@ -65,6 +62,10 @@ server:
|
||||||
## This is disabled by default if either /app/.healthcheck.env or /app/healthcheck.sh do not exist.
|
## This is disabled by default if either /app/.healthcheck.env or /app/healthcheck.sh do not exist.
|
||||||
disable_healthcheck: false
|
disable_healthcheck: false
|
||||||
|
|
||||||
|
## If a request over the insecure http protocol is received from authelias gRPC endpoint (only for envoy),
|
||||||
|
## the request is by default redirected to the matching https URL (301)
|
||||||
|
disable_autho_https_redirect: false
|
||||||
|
|
||||||
## Authelia by default doesn't accept TLS communication on the server port. This section overrides this behaviour.
|
## Authelia by default doesn't accept TLS communication on the server port. This section overrides this behaviour.
|
||||||
tls:
|
tls:
|
||||||
## The path to the DER base64/PEM format private key.
|
## The path to the DER base64/PEM format private key.
|
||||||
|
@ -76,6 +77,17 @@ server:
|
||||||
## The list of certificates for client authentication.
|
## The list of certificates for client authentication.
|
||||||
client_certificates: []
|
client_certificates: []
|
||||||
|
|
||||||
|
## Enable the support for gRPC ext authentication for envoy. If TLS is enabled in the above section,
|
||||||
|
## the defined certificates will also be used for the gRPC endpoint
|
||||||
|
grpc:
|
||||||
|
address: 'tcp://:9092'
|
||||||
|
|
||||||
|
# Even if TLS is configured in the server setting (under server.tls), the grcp server won't use TLS
|
||||||
|
disableTLS: false
|
||||||
|
|
||||||
|
# By default the ban is issued for the user. With this options the IP instead of the user will be banned
|
||||||
|
use_ip_for_ban: true
|
||||||
|
|
||||||
## Server headers configuration/customization.
|
## Server headers configuration/customization.
|
||||||
headers:
|
headers:
|
||||||
|
|
||||||
|
@ -162,11 +174,12 @@ telemetry:
|
||||||
|
|
||||||
## The address for the Metrics server to listen on in the address common syntax.
|
## The address for the Metrics server to listen on in the address common syntax.
|
||||||
## Formats:
|
## Formats:
|
||||||
## - [<scheme>://]<hostname>[:<port>]
|
## - [<scheme>://]<hostname>[:<port>][/<path>]
|
||||||
## - [<scheme>://][hostname]:<port>
|
## - [<scheme>://][hostname]:<port>[/<path>]
|
||||||
## Square brackets indicate optional portions of the format. Scheme must be 'tcp', 'tcp4', 'tcp6', or 'unix'.
|
## Square brackets indicate optional portions of the format. Scheme must be 'tcp', 'tcp4', 'tcp6', or 'unix'.
|
||||||
## The default scheme is 'unix' if the address is an absolute path otherwise it's 'tcp'. The default port is '9959'.
|
## The default scheme is 'unix' if the address is an absolute path otherwise it's 'tcp'. The default port is '9959'.
|
||||||
address: 'tcp://:9959'
|
## If the path is not specified it defaults to `/metrics`.
|
||||||
|
address: 'tcp://:9959/metrics'
|
||||||
|
|
||||||
## Metrics Server Buffers configuration.
|
## Metrics Server Buffers configuration.
|
||||||
# buffers:
|
# buffers:
|
||||||
|
@ -203,7 +216,7 @@ totp:
|
||||||
## The TOTP algorithm to use.
|
## The TOTP algorithm to use.
|
||||||
## It is CRITICAL you read the documentation before changing this option:
|
## It is CRITICAL you read the documentation before changing this option:
|
||||||
## https://www.authelia.com/c/totp#algorithm
|
## https://www.authelia.com/c/totp#algorithm
|
||||||
algorithm: 'sha1'
|
algorithm: 'SHA1'
|
||||||
|
|
||||||
## The number of digits a user has to input. Must either be 6 or 8.
|
## The number of digits a user has to input. Must either be 6 or 8.
|
||||||
## Changing this option only affects newly generated TOTP configurations.
|
## Changing this option only affects newly generated TOTP configurations.
|
||||||
|
@ -300,7 +313,7 @@ authentication_backend:
|
||||||
|
|
||||||
## External reset password url that redirects the user to an external reset portal. This disables the internal reset
|
## External reset password url that redirects the user to an external reset portal. This disables the internal reset
|
||||||
## functionality.
|
## functionality.
|
||||||
custom_url: ''
|
# custom_url: ''
|
||||||
|
|
||||||
## The amount of time to wait before we refresh data from the authentication backend in the duration common syntax.
|
## The amount of time to wait before we refresh data from the authentication backend in the duration common syntax.
|
||||||
## To disable this feature set it to 'disable', this will slightly reduce security because for Authelia, users will
|
## To disable this feature set it to 'disable', this will slightly reduce security because for Authelia, users will
|
||||||
|
@ -317,7 +330,7 @@ authentication_backend:
|
||||||
## because it allows Authelia to offload the stateful operations
|
## because it allows Authelia to offload the stateful operations
|
||||||
## onto the LDAP service.
|
## onto the LDAP service.
|
||||||
# ldap:
|
# ldap:
|
||||||
## The address of the LDAP server to connect to in the address common syntax.
|
## The address of the directory server to connect to in the address common syntax.
|
||||||
## Format: [<scheme>://]<hostname>[:<port>].
|
## Format: [<scheme>://]<hostname>[:<port>].
|
||||||
## Square brackets indicate optional portions of the format. Scheme must be 'ldap', 'ldaps', or 'ldapi`.
|
## Square brackets indicate optional portions of the format. Scheme must be 'ldap', 'ldaps', or 'ldapi`.
|
||||||
## The default scheme is 'ldapi' if the address is an absolute path otherwise it's 'ldaps'.
|
## The default scheme is 'ldapi' if the address is an absolute path otherwise it's 'ldaps'.
|
||||||
|
@ -396,23 +409,13 @@ authentication_backend:
|
||||||
# SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH
|
# SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH
|
||||||
# ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg
|
# ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg
|
||||||
# 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg
|
# 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg
|
||||||
# HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRsDO_NOT_USE=
|
# HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRs_DO_NOT_USE=
|
||||||
# -----END RSA PRIVATE KEY-----
|
# -----END RSA PRIVATE KEY-----
|
||||||
|
|
||||||
## The distinguished name of the container searched for objects in the directory information tree.
|
## The distinguished name of the container searched for objects in the directory information tree.
|
||||||
## See also: additional_users_dn, additional_groups_dn.
|
## See also: additional_users_dn, additional_groups_dn.
|
||||||
# base_dn: 'dc=example,dc=com'
|
# base_dn: 'dc=example,dc=com'
|
||||||
|
|
||||||
## The attribute holding the username of the user. This attribute is used to populate the username in the session
|
|
||||||
## information. For your information, Microsoft Active Directory usually uses 'sAMAccountName' and OpenLDAP usually
|
|
||||||
## uses 'uid'. Beware that this attribute holds the unique identifiers for the users binding the user and the
|
|
||||||
## configuration stored in database. Therefore only single value attributes are allowed and the value must never be
|
|
||||||
## changed once attributed to a user otherwise it would break the configuration for that user. Technically,
|
|
||||||
## non-unique attributes like 'mail' can also be used but we don't recommend using them, we instead advise to use
|
|
||||||
## a filter to perform alternative lookups and the attributes mentioned above (sAMAccountName and uid) to
|
|
||||||
## follow https://datatracker.ietf.org/doc/html/rfc2307.
|
|
||||||
# username_attribute: 'uid'
|
|
||||||
|
|
||||||
## The additional_users_dn is prefixed to base_dn and delimited by a comma when searching for users.
|
## The additional_users_dn is prefixed to base_dn and delimited by a comma when searching for users.
|
||||||
## i.e. with this set to OU=Users and base_dn set to DC=a,DC=com; OU=Users,DC=a,DC=com is searched for users.
|
## i.e. with this set to OU=Users and base_dn set to DC=a,DC=com; OU=Users,DC=a,DC=com is searched for users.
|
||||||
# additional_users_dn: 'ou=users'
|
# additional_users_dn: 'ou=users'
|
||||||
|
@ -443,15 +446,9 @@ authentication_backend:
|
||||||
## (&(uniqueMember={dn})(objectClass=groupOfUniqueNames))
|
## (&(uniqueMember={dn})(objectClass=groupOfUniqueNames))
|
||||||
# groups_filter: '(&(member={dn})(objectClass=groupOfNames))'
|
# groups_filter: '(&(member={dn})(objectClass=groupOfNames))'
|
||||||
|
|
||||||
## The attribute holding the name of the group.
|
## The group search mode to use. Options are 'filter' or 'memberof'. It's essential to read the docs if you wish to
|
||||||
# group_name_attribute: 'cn'
|
## use 'memberof'. Also 'filter' is the best choice for most use cases.
|
||||||
|
# group_search_mode: 'filter'
|
||||||
## The attribute holding the mail address of the user. If multiple email addresses are defined for a user, only the
|
|
||||||
## first one returned by the LDAP server is used.
|
|
||||||
# mail_attribute: 'mail'
|
|
||||||
|
|
||||||
## The attribute holding the display name of the user. This will be used to greet an authenticated user.
|
|
||||||
# display_name_attribute: 'displayName'
|
|
||||||
|
|
||||||
## Follow referrals returned by the server.
|
## Follow referrals returned by the server.
|
||||||
## This is especially useful for environments where read-only servers exist. Only implemented for write operations.
|
## This is especially useful for environments where read-only servers exist. Only implemented for write operations.
|
||||||
|
@ -462,6 +459,37 @@ authentication_backend:
|
||||||
## Password can also be set using a secret: https://www.authelia.com/c/secrets
|
## Password can also be set using a secret: https://www.authelia.com/c/secrets
|
||||||
# password: 'password'
|
# password: 'password'
|
||||||
|
|
||||||
|
## The attributes for users and objects from the directory server.
|
||||||
|
# attributes:
|
||||||
|
|
||||||
|
## The distinguished name attribute if your directory server supports it. Users should read the docs before
|
||||||
|
## configuring. Only used for the 'memberof' group search mode.
|
||||||
|
# distinguished_name: ''
|
||||||
|
|
||||||
|
## The attribute holding the username of the user. This attribute is used to populate the username in the session
|
||||||
|
## information. For your information, Microsoft Active Directory usually uses 'sAMAccountName' and OpenLDAP
|
||||||
|
## usually uses 'uid'. Beware that this attribute holds the unique identifiers for the users binding the user and
|
||||||
|
## the configuration stored in database; therefore only single value attributes are allowed and the value must
|
||||||
|
## never be changed once attributed to a user otherwise it would break the configuration for that user.
|
||||||
|
## Technically non-unique attributes like 'mail' can also be used but we don't recommend using them, we instead
|
||||||
|
## advise to use a filter to perform alternative lookups and the attributes mentioned above
|
||||||
|
## (sAMAccountName and uid) to follow https://datatracker.ietf.org/doc/html/rfc2307.
|
||||||
|
# username: 'uid'
|
||||||
|
|
||||||
|
## The attribute holding the display name of the user. This will be used to greet an authenticated user.
|
||||||
|
# display_name: 'displayName'
|
||||||
|
|
||||||
|
## The attribute holding the mail address of the user. If multiple email addresses are defined for a user, only
|
||||||
|
## the first one returned by the directory server is used.
|
||||||
|
# mail: 'mail'
|
||||||
|
|
||||||
|
## The attribute which provides distinguished names of groups an object is a member of.
|
||||||
|
## Only used for the 'memberof' group search mode.
|
||||||
|
# member_of: 'memberOf'
|
||||||
|
|
||||||
|
## The attribute holding the name of the group.
|
||||||
|
# group_name: 'cn'
|
||||||
|
|
||||||
##
|
##
|
||||||
## File (Authentication Provider)
|
## File (Authentication Provider)
|
||||||
##
|
##
|
||||||
|
@ -820,7 +848,7 @@ session:
|
||||||
# SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH
|
# SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH
|
||||||
# ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg
|
# ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg
|
||||||
# 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg
|
# 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg
|
||||||
# HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRsDO_NOT_USE=
|
# HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRs_DO_NOT_USE=
|
||||||
# -----END RSA PRIVATE KEY-----
|
# -----END RSA PRIVATE KEY-----
|
||||||
|
|
||||||
## The Redis HA configuration options.
|
## The Redis HA configuration options.
|
||||||
|
@ -965,7 +993,7 @@ regulation:
|
||||||
# SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH
|
# SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH
|
||||||
# ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg
|
# ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg
|
||||||
# 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg
|
# 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg
|
||||||
# HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRsDO_NOT_USE=
|
# HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRs_DO_NOT_USE=
|
||||||
# -----END RSA PRIVATE KEY-----
|
# -----END RSA PRIVATE KEY-----
|
||||||
|
|
||||||
##
|
##
|
||||||
|
@ -1048,7 +1076,7 @@ regulation:
|
||||||
# SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH
|
# SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH
|
||||||
# ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg
|
# ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg
|
||||||
# 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg
|
# 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg
|
||||||
# HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRsDO_NOT_USE=
|
# HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRs_DO_NOT_USE=
|
||||||
# -----END RSA PRIVATE KEY-----
|
# -----END RSA PRIVATE KEY-----
|
||||||
|
|
||||||
##
|
##
|
||||||
|
@ -1166,7 +1194,7 @@ notifier:
|
||||||
# SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH
|
# SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH
|
||||||
# ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg
|
# ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg
|
||||||
# 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg
|
# 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg
|
||||||
# HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRsDO_NOT_USE=
|
# HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRs_DO_NOT_USE=
|
||||||
# -----END RSA PRIVATE KEY-----
|
# -----END RSA PRIVATE KEY-----
|
||||||
|
|
||||||
##
|
##
|
||||||
|
@ -1208,7 +1236,7 @@ notifier:
|
||||||
# SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH
|
# SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH
|
||||||
# ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg
|
# ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg
|
||||||
# 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg
|
# 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg
|
||||||
# HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRsDO_NOT_USE=
|
# HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRs_DO_NOT_USE=
|
||||||
# -----END RSA PRIVATE KEY-----
|
# -----END RSA PRIVATE KEY-----
|
||||||
|
|
||||||
|
|
||||||
|
@ -1238,7 +1266,7 @@ notifier:
|
||||||
# SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH
|
# SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH
|
||||||
# ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg
|
# ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg
|
||||||
# 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg
|
# 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg
|
||||||
# HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRsDO_NOT_USE=
|
# HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRs_DO_NOT_USE=
|
||||||
# -----END RSA PRIVATE KEY-----
|
# -----END RSA PRIVATE KEY-----
|
||||||
|
|
||||||
## Optional matching certificate chain in PEM DER form that matches the issuer_private_key. All certificates within
|
## Optional matching certificate chain in PEM DER form that matches the issuer_private_key. All certificates within
|
||||||
|
@ -1287,6 +1315,7 @@ notifier:
|
||||||
## List of endpoints in addition to the metadata endpoints to permit cross-origin requests on.
|
## List of endpoints in addition to the metadata endpoints to permit cross-origin requests on.
|
||||||
# endpoints:
|
# endpoints:
|
||||||
# - 'authorization'
|
# - 'authorization'
|
||||||
|
# - 'pushed-authorization-request'
|
||||||
# - 'token'
|
# - 'token'
|
||||||
# - 'revocation'
|
# - 'revocation'
|
||||||
# - 'introspection'
|
# - 'introspection'
|
||||||
|
@ -1412,7 +1441,7 @@ notifier:
|
||||||
# key: |
|
# key: |
|
||||||
# -----BEGIN RSA PUBLIC KEY-----
|
# -----BEGIN RSA PUBLIC KEY-----
|
||||||
# MEgCQQDAwV26ZA1lodtOQxNrJ491gWT+VzFum9IeZ+WTmMypYWyW1CzXKwsvTHDz
|
# MEgCQQDAwV26ZA1lodtOQxNrJ491gWT+VzFum9IeZ+WTmMypYWyW1CzXKwsvTHDz
|
||||||
# 9ec+jserR3EMQ0Rr24lj13FL1ib5AgMBAAE=
|
# 9ec+jserR3EMQ0Rr24lj13FL1ib5AgMBAAE_DO_NOT_USE=
|
||||||
# -----END RSA PUBLIC KEY----
|
# -----END RSA PUBLIC KEY----
|
||||||
|
|
||||||
## The matching certificate chain in PEM DER form that matches the key if available.
|
## The matching certificate chain in PEM DER form that matches the key if available.
|
||||||
|
|
|
@ -101,16 +101,20 @@ authentication_backend:
|
||||||
base_dn: 'DC=example,DC=com'
|
base_dn: 'DC=example,DC=com'
|
||||||
additional_users_dn: 'OU=users'
|
additional_users_dn: 'OU=users'
|
||||||
users_filter: '(&({username_attribute}={input})(objectClass=person))'
|
users_filter: '(&({username_attribute}={input})(objectClass=person))'
|
||||||
username_attribute: 'uid'
|
|
||||||
mail_attribute: 'mail'
|
|
||||||
display_name_attribute: 'displayName'
|
|
||||||
additional_groups_dn: 'OU=groups'
|
additional_groups_dn: 'OU=groups'
|
||||||
groups_filter: '(&(member={dn})(objectClass=groupOfNames))'
|
groups_filter: '(&(member={dn})(objectClass=groupOfNames))'
|
||||||
group_name_attribute: 'cn'
|
group_search_mode: 'filter'
|
||||||
permit_referrals: false
|
permit_referrals: false
|
||||||
permit_unauthenticated_bind: false
|
permit_unauthenticated_bind: false
|
||||||
user: 'CN=admin,DC=example,DC=com'
|
user: 'CN=admin,DC=example,DC=com'
|
||||||
password: 'password'
|
password: 'password'
|
||||||
|
attributes:
|
||||||
|
distinguished_name: 'distinguishedName'
|
||||||
|
username: 'uid'
|
||||||
|
display_name: 'displayName'
|
||||||
|
mail: 'mail'
|
||||||
|
member_of: 'memberOf'
|
||||||
|
group_name: 'cn'
|
||||||
```
|
```
|
||||||
|
|
||||||
## Options
|
## Options
|
||||||
|
@ -209,66 +213,33 @@ The LDAP filter to narrow down which users are valid. This is important to set c
|
||||||
The default value is dependent on the [implementation](#implementation), refer to the
|
The default value is dependent on the [implementation](#implementation), refer to the
|
||||||
[attribute defaults](../../reference/guides/ldap.md#attribute-defaults) for more information.
|
[attribute defaults](../../reference/guides/ldap.md#attribute-defaults) for more information.
|
||||||
|
|
||||||
### username_attribute
|
|
||||||
|
|
||||||
{{< confkey type="string" required="situational" >}}
|
|
||||||
|
|
||||||
*__Note:__ This option is technically required however the [implementation](#implementation) option can implicitly set a
|
|
||||||
default negating this requirement. Refer to the [attribute defaults](../../reference/guides/ldap.md#attribute-defaults)
|
|
||||||
for more information.*
|
|
||||||
|
|
||||||
The LDAP attribute that maps to the username in *Authelia*. This must contain the `{username_attribute}`
|
|
||||||
[placeholder](../../reference/guides/ldap.md#users-filter-replacements).
|
|
||||||
|
|
||||||
### mail_attribute
|
|
||||||
|
|
||||||
{{< confkey type="string" required="situational" >}}
|
|
||||||
|
|
||||||
*__Note:__ This option is technically required however the [implementation](#implementation) option can implicitly set a
|
|
||||||
default negating this requirement. Refer to the [attribute defaults](../../reference/guides/ldap.md#attribute-defaults)
|
|
||||||
for more information.*
|
|
||||||
|
|
||||||
The attribute to retrieve which contains the users email addresses. This is important for the device registration and
|
|
||||||
password reset processes. The user must have an email address in order for Authelia to perform identity verification
|
|
||||||
when a user attempts to reset their password or register a second factor device.
|
|
||||||
|
|
||||||
### display_name_attribute
|
|
||||||
|
|
||||||
{{< confkey type="string" required="situational" >}}
|
|
||||||
|
|
||||||
*__Note:__ This option is technically required however the [implementation](#implementation) option can implicitly set a
|
|
||||||
default negating this requirement. Refer to the [attribute defaults](#attribute-defaults) for more information.*
|
|
||||||
|
|
||||||
The attribute to retrieve which is shown on the Web UI to the user when they log in.
|
|
||||||
|
|
||||||
### additional_groups_dn
|
### additional_groups_dn
|
||||||
|
|
||||||
{{< confkey type="string" required="no" >}}
|
{{< confkey type="string" required="no" >}}
|
||||||
|
|
||||||
Similar to [additional_users_dn](#additional_users_dn) but it applies to group searches.
|
Similar to [additional_users_dn](#additionalusersdn) but it applies to group searches.
|
||||||
|
|
||||||
### groups_filter
|
### groups_filter
|
||||||
|
|
||||||
{{< confkey type="string" required="situational" >}}
|
{{< confkey type="string" required="situational" >}}
|
||||||
|
|
||||||
*__Note:__ This option is technically required however the [implementation](#implementation) option can implicitly set a
|
*__Note:__ This option is technically required however the [implementation](#implementation) option can implicitly set a
|
||||||
default negating this requirement. Refer to the [filter defaults](#filter-defaults) for more information.*
|
default negating this requirement. Refer to the [filter defaults](../../reference/guides/ldap.md#filter-defaults) for
|
||||||
|
more information.*
|
||||||
|
|
||||||
Similar to [users_filter](#users_filter) but it applies to group searches. In order to include groups the member is not
|
Similar to [users_filter](#usersfilter) but it applies to group searches. In order to include groups the member is not
|
||||||
a direct member of, but is a member of another group that is a member of those (i.e. recursive groups), you may try
|
a direct member of, but is a member of another group that is a member of those (i.e. recursive groups), you may try
|
||||||
using the following filter which is currently only tested against Microsoft Active Directory:
|
using the following filter which is currently only tested against Microsoft Active Directory:
|
||||||
|
|
||||||
`(&(member:1.2.840.113556.1.4.1941:={dn})(objectClass=group)(objectCategory=group))`
|
`(&(member:1.2.840.113556.1.4.1941:={dn})(objectClass=group)(objectCategory=group))`
|
||||||
|
|
||||||
### group_name_attribute
|
### group_search_mode
|
||||||
|
|
||||||
{{< confkey type="string" required="situational" >}}
|
{{< confkey type="string" default="filter" required="no" >}}
|
||||||
|
|
||||||
*__Note:__ This option is technically required however the [implementation](#implementation) option can implicitly set a
|
The group search mode controls how user groups are discovered. The default of `filter` directly uses the filter to
|
||||||
default negating this requirement. Refer to the [attribute defaults](#attribute-defaults) for more
|
determine the result. The `memberof` experimental mode does another special filtered search. See the
|
||||||
information.*
|
[Reference Documentation](../../reference/guides/ldap.md#group-search-modes) for more information.
|
||||||
|
|
||||||
The LDAP attribute that is used by Authelia to determine the group name.
|
|
||||||
|
|
||||||
### permit_referrals
|
### permit_referrals
|
||||||
|
|
||||||
|
@ -313,6 +284,71 @@ It's __strongly recommended__ this is a
|
||||||
[Random Alphanumeric String](../../reference/guides/generating-secure-values.md#generating-a-random-alphanumeric-string) with 64 or more
|
[Random Alphanumeric String](../../reference/guides/generating-secure-values.md#generating-a-random-alphanumeric-string) with 64 or more
|
||||||
characters and the user password is changed to this value.
|
characters and the user password is changed to this value.
|
||||||
|
|
||||||
|
### attributes
|
||||||
|
|
||||||
|
The following options configure The directory server attribute mappings.
|
||||||
|
|
||||||
|
#### distinguished_name
|
||||||
|
|
||||||
|
{{< confkey type="string" required="situational" >}}
|
||||||
|
|
||||||
|
*__Note:__ This option is technically not required however it is required when using the group search mode
|
||||||
|
`memberof` replacement `{memberof:dn}`.*
|
||||||
|
|
||||||
|
The directory server attribute which contains the distinguished name, primarily used to perform filtered searches. There
|
||||||
|
is a clear distinction between the actual distinguished name and a distinguished name attribute, all directories have
|
||||||
|
distinguished names for objects, but not all have an attribute representing this that can be searched on.
|
||||||
|
|
||||||
|
The only known support at this time is with Active Directory.
|
||||||
|
|
||||||
|
#### username
|
||||||
|
|
||||||
|
{{< confkey type="string" required="situational" >}}
|
||||||
|
|
||||||
|
*__Note:__ This option is technically required however the [implementation](#implementation) option can implicitly set a
|
||||||
|
default negating this requirement. Refer to the [attribute defaults] for more information.*
|
||||||
|
|
||||||
|
The directory server attribute that maps to the username in *Authelia*. This must contain the `{username_attribute}` [placeholder].
|
||||||
|
|
||||||
|
#### display_name
|
||||||
|
|
||||||
|
{{< confkey type="string" required="situational" >}}
|
||||||
|
|
||||||
|
*__Note:__ This option is technically required however the [implementation](#implementation) option can implicitly set a
|
||||||
|
default negating this requirement. Refer to the [attribute defaults] for more information.*
|
||||||
|
|
||||||
|
The directory server attribute to retrieve which is shown on the Web UI to the user when they log in.
|
||||||
|
|
||||||
|
#### mail
|
||||||
|
|
||||||
|
{{< confkey type="string" required="situational" >}}
|
||||||
|
|
||||||
|
*__Note:__ This option is technically required however the [implementation](#implementation) option can implicitly set a
|
||||||
|
default negating this requirement. Refer to the [attribute defaults] for more information.*
|
||||||
|
|
||||||
|
The directory server attribute to retrieve which contains the users email addresses. This is important for the device
|
||||||
|
registration and password reset processes. The user must have an email address in order for Authelia to perform
|
||||||
|
identity verification when a user attempts to reset their password or register a second factor device.
|
||||||
|
|
||||||
|
#### member_of
|
||||||
|
|
||||||
|
{{< confkey type="string" required="situational" >}}
|
||||||
|
|
||||||
|
*__Note:__ This option is technically required however the [implementation](#implementation) option can implicitly set a
|
||||||
|
default negating this requirement. Refer to the [attribute defaults] for more information.*
|
||||||
|
|
||||||
|
The directory server attribute which contains the groups a user is a member of. This is currently only used for the
|
||||||
|
`memberof` group search mode.
|
||||||
|
|
||||||
|
#### group_name
|
||||||
|
|
||||||
|
{{< confkey type="string" required="situational" >}}
|
||||||
|
|
||||||
|
*__Note:__ This option is technically required however the [implementation](#implementation) option can implicitly set a
|
||||||
|
default negating this requirement. Refer to the [attribute defaults] for more information.*
|
||||||
|
|
||||||
|
The directory server attribute that is used by Authelia to determine the group name.
|
||||||
|
|
||||||
## Refresh Interval
|
## Refresh Interval
|
||||||
|
|
||||||
It's recommended you either use the default [refresh interval](introduction.md#refreshinterval) or configure this to
|
It's recommended you either use the default [refresh interval](introduction.md#refreshinterval) or configure this to
|
||||||
|
@ -332,6 +368,8 @@ for your users.
|
||||||
|
|
||||||
- [LDAP Reference Guide](../../reference/guides/ldap.md)
|
- [LDAP Reference Guide](../../reference/guides/ldap.md)
|
||||||
|
|
||||||
[username attribute]: #usernameattribute
|
[username attribute]: #username
|
||||||
[TechNet wiki]: https://social.technet.microsoft.com/wiki/contents/articles/5392.active-directory-ldap-syntax-filters.aspx
|
[TechNet wiki]: https://social.technet.microsoft.com/wiki/contents/articles/5392.active-directory-ldap-syntax-filters.aspx
|
||||||
[RFC2307]: https://datatracker.ietf.org/doc/html/rfc2307
|
[RFC2307]: https://datatracker.ietf.org/doc/html/rfc2307
|
||||||
|
[attribute defaults]: ../../reference/guides/ldap.md#attribute-defaults
|
||||||
|
[placeholder]: ../../reference/guides/ldap.md#users-filter-replacements
|
||||||
|
|
|
@ -21,8 +21,7 @@ aliases:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
server:
|
server:
|
||||||
address: 'tcp://:9091'
|
address: 'tcp://:9091/'
|
||||||
path: ''
|
|
||||||
disable_healthcheck: false
|
disable_healthcheck: false
|
||||||
tls:
|
tls:
|
||||||
key: ''
|
key: ''
|
||||||
|
@ -59,19 +58,25 @@ server:
|
||||||
|
|
||||||
### address
|
### address
|
||||||
|
|
||||||
{{< confkey type="address" default="tcp://:9091" required="no" >}}
|
{{< confkey type="address" default="tcp://:9091/" required="no" >}}
|
||||||
|
|
||||||
*__Reference Note:__ This configuration option uses the [address common syntax](../prologue/common.md#address). Please
|
*__Reference Note:__ This configuration option uses the [address common syntax](../prologue/common.md#address). Please
|
||||||
see the [documentation](../prologue/common.md#address) on this format for more information.*
|
see the [documentation](../prologue/common.md#address) on this format for more information.*
|
||||||
|
|
||||||
Configures the listener address for the Main HTTP Server. The address itself is a listener and the scheme must either be
|
Configures the listener address for the Main HTTP Server. The address itself is a listener and the scheme must either be
|
||||||
the `unix` scheme or one of the `tcp` schemes.
|
the `unix` scheme or one of the `tcp` schemes. It can configure the host, port, and path the listener responds to. If
|
||||||
|
the path is configured to anything other than `/` Authelia will handle requests for both `/` and the configured path.
|
||||||
|
|
||||||
__Examples:__
|
__Examples:__
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
server:
|
server:
|
||||||
address: tcp://127.0.0.1:9091
|
address: tcp://127.0.0.1:9091/
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
server:
|
||||||
|
address: tcp://127.0.0.1:9091/subpath
|
||||||
```
|
```
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
|
@ -79,34 +84,6 @@ server:
|
||||||
address: unix:///var/run/authelia.sock
|
address: unix:///var/run/authelia.sock
|
||||||
```
|
```
|
||||||
|
|
||||||
### path
|
|
||||||
|
|
||||||
{{< confkey type="string " required="no" >}}
|
|
||||||
|
|
||||||
Authelia by default is served from the root `/` location, either via its own domain or subdomain.
|
|
||||||
|
|
||||||
Modifying this setting will allow you to serve Authelia out from a specified base path. Please note
|
|
||||||
that currently only a single level path is supported meaning slashes are not allowed, and only
|
|
||||||
alphanumeric characters are supported.
|
|
||||||
|
|
||||||
__Example:__
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
server:
|
|
||||||
path: ""
|
|
||||||
```
|
|
||||||
|
|
||||||
*Works for https://auth.example.com/, https://example.com/, etc*.
|
|
||||||
|
|
||||||
__Example:__
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
server:
|
|
||||||
path: authelia
|
|
||||||
```
|
|
||||||
|
|
||||||
*Works for https://auth.example.com/authelia/, https://example.com/authelia/, etc*.
|
|
||||||
|
|
||||||
### asset_path
|
### asset_path
|
||||||
|
|
||||||
{{< confkey type="string " required="no" >}}
|
{{< confkey type="string " required="no" >}}
|
||||||
|
|
|
@ -88,7 +88,7 @@ Refer to the individual documentation for an option for clarity. In this format
|
||||||
are optional. The default for these when not provided varies.
|
are optional. The default for these when not provided varies.
|
||||||
|
|
||||||
```text
|
```text
|
||||||
[<scheme>://]<hostname>[:<port>]
|
[<scheme>://]<hostname>[:<port>][/<path>]
|
||||||
```
|
```
|
||||||
|
|
||||||
##### Port
|
##### Port
|
||||||
|
@ -99,7 +99,7 @@ hostname are optional. The default for the scheme when not provided varies, and
|
||||||
available addresses when not provided.
|
available addresses when not provided.
|
||||||
|
|
||||||
```text
|
```text
|
||||||
[<scheme>://][hostname]:<port>
|
[<scheme>://][hostname]:<port>[/<path>]
|
||||||
```
|
```
|
||||||
|
|
||||||
##### Unix Domain Socket
|
##### Unix Domain Socket
|
||||||
|
@ -131,8 +131,11 @@ Various examples for these formats.
|
||||||
```text
|
```text
|
||||||
0.0.0.0
|
0.0.0.0
|
||||||
tcp://0.0.0.0
|
tcp://0.0.0.0
|
||||||
|
tcp://0.0.0.0/subpath
|
||||||
tcp://0.0.0.0:9091
|
tcp://0.0.0.0:9091
|
||||||
|
tcp://0.0.0.0:9091/subpath
|
||||||
tcp://:9091
|
tcp://:9091
|
||||||
|
tcp://:9091/subpath
|
||||||
0.0.0.0:9091
|
0.0.0.0:9091
|
||||||
|
|
||||||
udp://0.0.0.0:123
|
udp://0.0.0.0:123
|
||||||
|
|
|
@ -38,4 +38,3 @@ this instance if you wanted to downgrade to pre1 you would need to use an Authel
|
||||||
| 7 | 4.37.3 | Fixed some schema inconsistencies most notably the MySQL/MariaDB Engine and Collation |
|
| 7 | 4.37.3 | Fixed some schema inconsistencies most notably the MySQL/MariaDB Engine and Collation |
|
||||||
| 8 | 4.38.0 | OpenID Connect 1.0 Pushed Authorization Requests |
|
| 8 | 4.38.0 | OpenID Connect 1.0 Pushed Authorization Requests |
|
||||||
| 9 | 4.38.0 | Fix a PostgreSQL NOT NULL constraint issue on the `aaguid` column of the `webauthn_devices` table |
|
| 9 | 4.38.0 | Fix a PostgreSQL NOT NULL constraint issue on the `aaguid` column of the `webauthn_devices` table |
|
||||||
| 10 | 4.38.0 | WebAuthn adjustments for multi-cookie domain changes |
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ toc: true
|
||||||
telemetry:
|
telemetry:
|
||||||
metrics:
|
metrics:
|
||||||
enabled: false
|
enabled: false
|
||||||
address: 'tcp://:9959'
|
address: 'tcp://:9959/'
|
||||||
buffers:
|
buffers:
|
||||||
read: 4096
|
read: 4096
|
||||||
write: 4096
|
write: 4096
|
||||||
|
@ -44,7 +44,7 @@ Determines if the [Prometheus] HTTP Metrics Exporter is enabled.
|
||||||
|
|
||||||
### address
|
### address
|
||||||
|
|
||||||
{{< confkey type="address" default="tcp://:9959" required="no" >}}
|
{{< confkey type="address" default="tcp://:9959/" required="no" >}}
|
||||||
|
|
||||||
*__Reference Note:__ This configuration option uses the [address common syntax](../prologue/common.md#address). Please
|
*__Reference Note:__ This configuration option uses the [address common syntax](../prologue/common.md#address). Please
|
||||||
see the [documentation](../prologue/common.md#address) on this format for more information.*
|
see the [documentation](../prologue/common.md#address) on this format for more information.*
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
title: "Database Schema"
|
title: "Database Schema"
|
||||||
description: "Authelia Development Database Schema Guidelines"
|
description: "Authelia Development Database Schema Guidelines"
|
||||||
lead: "This section covers the database schema guidelines we use for development."
|
lead: "This section covers the database schema guidelines we use for development."
|
||||||
date: 2022-11-19T17:42:03+11:00
|
date: 2022-11-19T16:47:09+11:00
|
||||||
draft: false
|
draft: false
|
||||||
images: []
|
images: []
|
||||||
menu:
|
menu:
|
||||||
|
|
|
@ -92,35 +92,35 @@ Use this [Standalone Example](#standalone-example) if you want to use
|
||||||
version: "3.8"
|
version: "3.8"
|
||||||
secrets:
|
secrets:
|
||||||
JWT_SECRET:
|
JWT_SECRET:
|
||||||
file: ${PWD}/data/authelia/secrets/JWT_SECRET
|
file: '${PWD}/data/authelia/secrets/JWT_SECRET'
|
||||||
SESSION_SECRET:
|
SESSION_SECRET:
|
||||||
file: ${PWD}/data/authelia/secrets/SESSION_SECRET
|
file: '${PWD}/data/authelia/secrets/SESSION_SECRET'
|
||||||
STORAGE_PASSWORD:
|
STORAGE_PASSWORD:
|
||||||
file: ${PWD}/data/authelia/secrets/STORAGE_PASSWORD
|
file: '${PWD}/data/authelia/secrets/STORAGE_PASSWORD'
|
||||||
STORAGE_ENCRYPTION_KEY:
|
STORAGE_ENCRYPTION_KEY:
|
||||||
file: ${PWD}/data/authelia/secrets/STORAGE_ENCRYPTION_KEY
|
file: '${PWD}/data/authelia/secrets/STORAGE_ENCRYPTION_KEY'
|
||||||
services:
|
services:
|
||||||
authelia:
|
authelia:
|
||||||
container_name: authelia
|
container_name: 'authelia'
|
||||||
image: docker.io/authelia/authelia:latest
|
image: 'docker.io/authelia/authelia:latest'
|
||||||
restart: unless-stopped
|
restart: 'unless-stopped'
|
||||||
networks:
|
networks:
|
||||||
net:
|
net:
|
||||||
aliases: []
|
aliases: []
|
||||||
expose:
|
expose:
|
||||||
- 9091
|
- 9091
|
||||||
secrets: [JWT_SECRET, SESSION_SECRET, STORAGE_PASSWORD, STORAGE_ENCRYPTION_KEY]
|
secrets: ['JWT_SECRET', 'SESSION_SECRET', 'STORAGE_PASSWORD', 'STORAGE_ENCRYPTION_KEY']
|
||||||
environment:
|
environment:
|
||||||
AUTHELIA_JWT_SECRET_FILE: /run/secrets/JWT_SECRET
|
AUTHELIA_JWT_SECRET_FILE: '/run/secrets/JWT_SECRET'
|
||||||
AUTHELIA_SESSION_SECRET_FILE: /run/secrets/SESSION_SECRET
|
AUTHELIA_SESSION_SECRET_FILE: '/run/secrets/SESSION_SECRET'
|
||||||
AUTHELIA_STORAGE_POSTGRES_PASSWORD_FILE: /run/secrets/STORAGE_PASSWORD
|
AUTHELIA_STORAGE_POSTGRES_PASSWORD_FILE: '/run/secrets/STORAGE_PASSWORD'
|
||||||
AUTHELIA_STORAGE_ENCRYPTION_KEY_FILE: /run/secrets/STORAGE_ENCRYPTION_KEY
|
AUTHELIA_STORAGE_ENCRYPTION_KEY_FILE: '/run/secrets/STORAGE_ENCRYPTION_KEY'
|
||||||
volumes:
|
volumes:
|
||||||
- ${PWD}/data/authelia/config:/config
|
- '${PWD}/data/authelia/config:/config'
|
||||||
networks:
|
networks:
|
||||||
net:
|
net:
|
||||||
external: true
|
external: true
|
||||||
name: net
|
name: 'net'
|
||||||
...
|
...
|
||||||
```
|
```
|
||||||
{{< /details >}}
|
{{< /details >}}
|
||||||
|
@ -136,26 +136,26 @@ Use this [Standalone Example](#standalone-example) if you want to use a standard
|
||||||
version: "3.8"
|
version: "3.8"
|
||||||
services:
|
services:
|
||||||
authelia:
|
authelia:
|
||||||
container_name: authelia
|
container_name: 'authelia'
|
||||||
image: docker.io/authelia/authelia:latest
|
image: 'docker.io/authelia/authelia:latest'
|
||||||
restart: unless-stopped
|
restart: 'unless-stopped'
|
||||||
networks:
|
networks:
|
||||||
net:
|
net:
|
||||||
aliases: []
|
aliases: []
|
||||||
expose:
|
expose:
|
||||||
- 9091
|
- 9091
|
||||||
environment:
|
environment:
|
||||||
AUTHELIA_JWT_SECRET_FILE: /secrets/JWT_SECRET
|
AUTHELIA_JWT_SECRET_FILE: '/secrets/JWT_SECRET'
|
||||||
AUTHELIA_SESSION_SECRET_FILE: /secrets/SESSION_SECRET
|
AUTHELIA_SESSION_SECRET_FILE: '/secrets/SESSION_SECRET'
|
||||||
AUTHELIA_STORAGE_POSTGRES_PASSWORD_FILE: /secrets/STORAGE_PASSWORD
|
AUTHELIA_STORAGE_POSTGRES_PASSWORD_FILE: '/secrets/STORAGE_PASSWORD'
|
||||||
AUTHELIA_STORAGE_ENCRYPTION_KEY_FILE: /secrets/STORAGE_ENCRYPTION_KEY
|
AUTHELIA_STORAGE_ENCRYPTION_KEY_FILE: '/secrets/STORAGE_ENCRYPTION_KEY'
|
||||||
volumes:
|
volumes:
|
||||||
- ${PWD}/data/authelia/config:/config
|
- '${PWD}/data/authelia/config:/config'
|
||||||
- ${PWD}/data/authelia/secrets:/secrets
|
- '${PWD}/data/authelia/secrets:/secrets'
|
||||||
networks:
|
networks:
|
||||||
net:
|
net:
|
||||||
external: true
|
external: true
|
||||||
name: net
|
name: 'net'
|
||||||
```
|
```
|
||||||
...
|
...
|
||||||
{{< /details >}}
|
{{< /details >}}
|
||||||
|
|
|
@ -55,14 +55,16 @@ In your Authelia configuration you will need to enter and update the following v
|
||||||
base_dn: DC=example,DC=com
|
base_dn: DC=example,DC=com
|
||||||
additional_users_dn: OU=users
|
additional_users_dn: OU=users
|
||||||
users_filter: (&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=person))
|
users_filter: (&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=person))
|
||||||
username_attribute: uid
|
|
||||||
mail_attribute: mail
|
|
||||||
display_name_attribute: displayName
|
|
||||||
additional_groups_dn: OU=groups
|
additional_groups_dn: OU=groups
|
||||||
groups_filter: (&(member=UID={input},OU=users,DC=example,DC=com)(objectClass=groupOfNames))
|
groups_filter: (&(member=UID={input},OU=users,DC=example,DC=com)(objectClass=groupOfNames))
|
||||||
group_name_attribute: cn
|
|
||||||
user: UID=authelia,OU=service accounts,DC=example,DC=com
|
user: UID=authelia,OU=service accounts,DC=example,DC=com
|
||||||
password: "SUPER_COMPLEX_PASSWORD"
|
password: "SUPER_COMPLEX_PASSWORD"
|
||||||
|
attributes:
|
||||||
|
distinguished_name: 'distinguishedName'
|
||||||
|
username: 'uid'
|
||||||
|
mail: 'mail'
|
||||||
|
member_of: 'memberOf'
|
||||||
|
group_name: 'cn'
|
||||||
```
|
```
|
||||||
Following this, restart Authelia, and you should be able to begin using LDAP integration for your user logins, with
|
Following this, restart Authelia, and you should be able to begin using LDAP integration for your user logins, with
|
||||||
Authelia taking the email attribute for users straight from the 'mail' attribute within the LDAP object.
|
Authelia taking the email attribute for users straight from the 'mail' attribute within the LDAP object.
|
||||||
|
@ -100,16 +102,18 @@ In your Authelia configuration you will need to enter and update the following v
|
||||||
skip_verify: true
|
skip_verify: true
|
||||||
minimum_version: TLS1.2
|
minimum_version: TLS1.2
|
||||||
base_dn: dc=example,DC=com
|
base_dn: dc=example,DC=com
|
||||||
username_attribute: uid
|
|
||||||
additional_users_dn: CN=users,CN=accounts
|
additional_users_dn: CN=users,CN=accounts
|
||||||
users_filter: (&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=person))
|
users_filter: (&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=person))
|
||||||
additional_groups_dn: OU=groups
|
additional_groups_dn: OU=groups
|
||||||
groups_filter: (&(member=UID={input},CN=users,CN=accounts,DC=example,DC=com)(objectClass=groupOfNames))
|
groups_filter: (&(member=UID={input},CN=users,CN=accounts,DC=example,DC=com)(objectClass=groupOfNames))
|
||||||
group_name_attribute: cn
|
|
||||||
mail_attribute: mail
|
|
||||||
display_name_attribute: displayName
|
|
||||||
user: UID=authelia,CN=users,CN=accounts,DC=example,DC=com
|
user: UID=authelia,CN=users,CN=accounts,DC=example,DC=com
|
||||||
password: "SUPER_COMPLEX_PASSWORD"
|
password: "SUPER_COMPLEX_PASSWORD"
|
||||||
|
attributes:
|
||||||
|
distinguished_name: 'distinguishedName'
|
||||||
|
username: 'uid'
|
||||||
|
mail: 'mail'
|
||||||
|
member_of: 'memberOf'
|
||||||
|
group_name: 'cn'
|
||||||
```
|
```
|
||||||
Following this, restart Authelia, and you should be able to begin using LDAP integration for your user logins, with
|
Following this, restart Authelia, and you should be able to begin using LDAP integration for your user logins, with
|
||||||
Authelia taking the email attribute for users straight from the 'mail' attribute within the LDAP object.
|
Authelia taking the email attribute for users straight from the 'mail' attribute within the LDAP object.
|
||||||
|
@ -139,19 +143,21 @@ ldap:
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
start_tls: false
|
start_tls: false
|
||||||
base_dn: dc=example,DC=com
|
base_dn: dc=example,DC=com
|
||||||
username_attribute: uid
|
|
||||||
additional_users_dn: OU=people
|
additional_users_dn: OU=people
|
||||||
# To allow sign in both with username and email, one can use a filter like
|
# To allow sign in both with username and email, one can use a filter like
|
||||||
# (&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=person))
|
# (&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=person))
|
||||||
users_filter: (&({username_attribute}={input})(objectClass=person))
|
users_filter: (&({username_attribute}={input})(objectClass=person))
|
||||||
additional_groups_dn: OU=groups
|
additional_groups_dn: OU=groups
|
||||||
groups_filter: (member={dn})
|
groups_filter: (member={dn})
|
||||||
group_name_attribute: cn
|
|
||||||
mail_attribute: mail
|
|
||||||
display_name_attribute: displayName
|
|
||||||
# The username and password of the admin or service user.
|
# The username and password of the admin or service user.
|
||||||
user: UID=authelia,OU=people,DC=example,DC=com
|
user: UID=authelia,OU=people,DC=example,DC=com
|
||||||
password: "SUPER_COMPLEX_PASSWORD"
|
password: "SUPER_COMPLEX_PASSWORD"
|
||||||
|
attributes:
|
||||||
|
distinguished_name: 'distinguishedName'
|
||||||
|
username: 'uid'
|
||||||
|
mail: 'mail'
|
||||||
|
member_of: 'memberOf'
|
||||||
|
group_name: 'cn'
|
||||||
```
|
```
|
||||||
Following this, restart Authelia, and you should be able to begin using lldap integration for your user logins, with
|
Following this, restart Authelia, and you should be able to begin using lldap integration for your user logins, with
|
||||||
Authelia taking the email attribute for users straight from the 'mail' attribute within the LDAP object.
|
Authelia taking the email attribute for users straight from the 'mail' attribute within the LDAP object.
|
||||||
|
|
|
@ -45,14 +45,14 @@ Easy, right?!
|
||||||
|
|
||||||
## Frequently Asked Questions
|
## Frequently Asked Questions
|
||||||
|
|
||||||
### Can I register multiple FIDO2 WebAuthn credentials?
|
### Can I register multiple FIDO2 WebAuthn devices?
|
||||||
|
|
||||||
Yes, as of v4.38.0 and above Authelia supprots registering multiple WebAuthn credentials as per the
|
At present this is not possible in the frontend. However the backend technically supports it. We plan to add this to the
|
||||||
[roadmap](../../../roadmap/active/webauthn.md#multi-device-registration).
|
frontend in the near future. Subscribe to [this issue](https://github.com/authelia/authelia/issues/275) for updates.
|
||||||
|
|
||||||
### Can I perform a passwordless login?
|
### Can I perform a passwordless login?
|
||||||
|
|
||||||
Not at this time. We will tackle this at a later date as per the [roadmap](../../../roadmap/active/webauthn.md#passwordless-login).
|
Not at this time. We will tackle this at a later date.
|
||||||
|
|
||||||
### Why don't I have access to the *Security Key* option?
|
### Why don't I have access to the *Security Key* option?
|
||||||
|
|
||||||
|
|
|
@ -75,6 +75,32 @@ The following implementations exist:
|
||||||
[GLAuth]: https://glauth.github.io/
|
[GLAuth]: https://glauth.github.io/
|
||||||
[RFC2307bis]: https://datatracker.ietf.org/doc/html/draft-howard-rfc2307bis-02
|
[RFC2307bis]: https://datatracker.ietf.org/doc/html/draft-howard-rfc2307bis-02
|
||||||
|
|
||||||
|
### Group Search Modes
|
||||||
|
|
||||||
|
There are currently two group search modes that exist.
|
||||||
|
|
||||||
|
#### Search Mode: filter
|
||||||
|
|
||||||
|
The `filter` search mode is the default search mode. Generally this is recommended.
|
||||||
|
|
||||||
|
#### Search Mode: memberof
|
||||||
|
|
||||||
|
The `memberof` search mode is a special search mode. Generally this is discouraged and is currently experimental.
|
||||||
|
|
||||||
|
Some systems provide a `memberOf` attribute which may include additional groups that the user is a member of. This
|
||||||
|
search mode allows using this attribute as a method to determine their groups. How it works is the search is performed
|
||||||
|
against the base with the subtree scope and the groups filter must include one of the `{memberof:*}` replacements, and
|
||||||
|
the distinguished names of the results from the search are compared (case-insensitive) against the users `memberOf`
|
||||||
|
attribute to determine if they are members.
|
||||||
|
|
||||||
|
This means:
|
||||||
|
|
||||||
|
1. The groups still must be in the search base that you have configured.
|
||||||
|
2. The `memberOf` attribute *__MUST__* include the distinguished name of the group.
|
||||||
|
3. If the `{memberof:dn}` replacement is used:
|
||||||
|
1. The distinguished name *__MUST__* be searchable by your directory server.
|
||||||
|
3. The first relative distinguished name of the distinguished name *__MUST__* be search
|
||||||
|
|
||||||
### Filter replacements
|
### Filter replacements
|
||||||
|
|
||||||
Various replacements occur in the user and groups filter. The replacements either occur at startup or upon an LDAP
|
Various replacements occur in the user and groups filter. The replacements either occur at startup or upon an LDAP
|
||||||
|
@ -85,14 +111,21 @@ is ever established. In addition to this, during the startup phase we purposeful
|
||||||
phase replacements exist so we only have to check if the replacement is necessary once, and we don't needlessly perform
|
phase replacements exist so we only have to check if the replacement is necessary once, and we don't needlessly perform
|
||||||
every possible replacement on every search regardless of if it's needed or not.
|
every possible replacement on every search regardless of if it's needed or not.
|
||||||
|
|
||||||
#### Users filter replacements
|
#### General filter replacements
|
||||||
|
|
||||||
| Placeholder | Phase | Replacement |
|
| Placeholder | Phase | Replacement |
|
||||||
|:------------------------:|:-------:|:----------------------------------------------------------------------------------------------------------------:|
|
|:------------------------------:|:-------:|:-------------------------------------------:|
|
||||||
|
| {distinguished_name_attribute} | startup | The configured distinguished name attribute |
|
||||||
| {username_attribute} | startup | The configured username attribute |
|
| {username_attribute} | startup | The configured username attribute |
|
||||||
| {mail_attribute} | startup | The configured mail attribute |
|
| {mail_attribute} | startup | The configured mail attribute |
|
||||||
| {display_name_attribute} | startup | The configured display name attribute |
|
| {display_name_attribute} | startup | The configured display name attribute |
|
||||||
|
| {member_of_attribute} | startup | The configured member of attribute |
|
||||||
| {input} | search | The input into the username field |
|
| {input} | search | The input into the username field |
|
||||||
|
|
||||||
|
#### Users filter replacements
|
||||||
|
|
||||||
|
| Placeholder | Phase | Replacement |
|
||||||
|
|:------------------------------:|:-------:|:----------------------------------------------------------------------------------------------------------------:|
|
||||||
| {date-time:generalized} | search | The current UTC time formatted as a LDAP generalized time in the format of `20060102150405.0Z` |
|
| {date-time:generalized} | search | The current UTC time formatted as a LDAP generalized time in the format of `20060102150405.0Z` |
|
||||||
| {date-time:unix} | search | The current time formatted as a Unix epoch |
|
| {date-time:unix} | search | The current time formatted as a Unix epoch |
|
||||||
| {date-time:microsoft-nt} | search | The current time formatted as a Microsoft NT epoch which is used by some Microsoft [Active Directory] attributes |
|
| {date-time:microsoft-nt} | search | The current time formatted as a Microsoft NT epoch which is used by some Microsoft [Active Directory] attributes |
|
||||||
|
@ -100,10 +133,43 @@ every possible replacement on every search regardless of if it's needed or not.
|
||||||
#### Groups filter replacements
|
#### Groups filter replacements
|
||||||
|
|
||||||
| Placeholder | Phase | Replacement |
|
| Placeholder | Phase | Replacement |
|
||||||
|:-----------:|:------:|:-------------------------------------------------------------------------:|
|
|:--------------:|:------:|:----------------------------------------------------------------------------------------------------------------------------------------------------:|
|
||||||
| {input} | search | The input into the username field |
|
|
||||||
| {username} | search | The username from the profile lookup obtained from the username attribute |
|
| {username} | search | The username from the profile lookup obtained from the username attribute |
|
||||||
| {dn} | search | The distinguished name from the profile lookup |
|
| {dn} | search | The distinguished name from the profile lookup |
|
||||||
|
| {memberof:dn} | search | See the detailed section below |
|
||||||
|
| {memberof:rdn} | search | Only allowed with the `memberof` search method and contains the first relative distinguished name of every `memberOf` entry a use has in parenthesis |
|
||||||
|
|
||||||
|
##### memberof:dn
|
||||||
|
|
||||||
|
Requirements:
|
||||||
|
|
||||||
|
1. Must be using the `memberof` search mode.
|
||||||
|
2. Must have the distinguished name attribute configured in Authelia.
|
||||||
|
3. Directory server must support searching by the distinguished name attribute (many directory services *__DO NOT__*
|
||||||
|
have a distinguished name attribute).
|
||||||
|
|
||||||
|
##### memberof:rdn
|
||||||
|
|
||||||
|
Requirements:
|
||||||
|
|
||||||
|
1. Must be using the `memberof` search mode.
|
||||||
|
2. Directory server must support searching by the first relative distinguished name as an attribute.
|
||||||
|
|
||||||
|
Splits every `memberOf` value to obtain th e first relative distinguished name and joins all of those after surrounding
|
||||||
|
them in parenthesis. This makes the general suggested filter pattern for this particular replacement
|
||||||
|
`(|{memberof:rdn})`. The format of this value is as follows:
|
||||||
|
|
||||||
|
```text
|
||||||
|
(<RDN>)
|
||||||
|
```
|
||||||
|
|
||||||
|
For example if the user has the following distinguished names in their object:
|
||||||
|
|
||||||
|
- CN=abc,OU=groups,DC=example,DC=com
|
||||||
|
- CN=xyz,OU=groups,DC=example,DC=com
|
||||||
|
|
||||||
|
The value will be replaced with `(CN=abc)(CN=xyz)` which using the suggested pattern for the filter becomes
|
||||||
|
`(|(CN=abc)(CN=xyz))` which will then return any user that as a `CN` of `abc` or `xyz`.
|
||||||
|
|
||||||
### Defaults
|
### Defaults
|
||||||
|
|
||||||
|
@ -122,14 +188,14 @@ The following set defaults for the `additional_users_dn` and `additional_groups_
|
||||||
This table describes the attribute defaults for each implementation. i.e. the username_attribute is described by the
|
This table describes the attribute defaults for each implementation. i.e. the username_attribute is described by the
|
||||||
Username column.
|
Username column.
|
||||||
|
|
||||||
| Implementation | Username | Display Name | Mail | Group Name |
|
| Implementation | Username | Display Name | Mail | Group Name | Distinguished Name | Member Of |
|
||||||
|:---------------:|:--------------:|:------------:|:----:|:----------:|
|
|:---------------:|:--------------:|:------------:|:----:|:----------:|:------------------:|:---------:|
|
||||||
| custom | N/A | displayName | mail | cn |
|
| custom | N/A | displayName | mail | cn | N/A | N/A |
|
||||||
| activedirectory | sAMAccountName | displayName | mail | cn |
|
| activedirectory | sAMAccountName | displayName | mail | cn | distinguishedName | memberOf |
|
||||||
| rfc2307bis | uid | displayName | mail | cn |
|
| rfc2307bis | uid | displayName | mail | cn | N/A | memberOf |
|
||||||
| freeipa | uid | displayName | mail | cn |
|
| freeipa | uid | displayName | mail | cn | N/A | memberOf |
|
||||||
| lldap | uid | cn | mail | cn |
|
| lldap | uid | cn | mail | cn | N/A | memberOf |
|
||||||
| glauth | cn | description | mail | cn |
|
| glauth | cn | description | mail | cn | N/A | memberOf |
|
||||||
|
|
||||||
#### Filter defaults
|
#### Filter defaults
|
||||||
|
|
||||||
|
@ -146,8 +212,8 @@ the following conditions:
|
||||||
- Their password is expired:
|
- Their password is expired:
|
||||||
- The [Active Directory] implementation achieves this via the `(!(pwdLastSet=0))` filter.
|
- The [Active Directory] implementation achieves this via the `(!(pwdLastSet=0))` filter.
|
||||||
- The [FreeIPA] implementation achieves this via the `(krbPasswordExpiration>={date-time:generalized})` filter.
|
- The [FreeIPA] implementation achieves this via the `(krbPasswordExpiration>={date-time:generalized})` filter.
|
||||||
|
- The [RFC2307bis] implementation achieves this via the `(!(pwdReset=TRUE))` filter.
|
||||||
- The following implementations have no suitable attribute for this as far as we're aware:
|
- The following implementations have no suitable attribute for this as far as we're aware:
|
||||||
- [RFC2307bis]
|
|
||||||
- [GLAuth]
|
- [GLAuth]
|
||||||
- [lldap]
|
- [lldap]
|
||||||
- Their account is expired:
|
- Their account is expired:
|
||||||
|
@ -162,7 +228,7 @@ the following conditions:
|
||||||
|:---------------:|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|:-----------------------------------------------------------------------------------------------------------------------------------------:|
|
|:---------------:|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|:-----------------------------------------------------------------------------------------------------------------------------------------:|
|
||||||
| custom | N/A | N/A |
|
| custom | N/A | N/A |
|
||||||
| activedirectory | (&(|({username_attribute}={input})({mail_attribute}={input}))(sAMAccountType=805306368)(!(userAccountControl:1.2.840.113556.1.4.803:=2))(!(pwdLastSet=0))(|(!(accountExpires=*))(accountExpires=0)(accountExpires>={date-time:microsoft-nt}))) | (&(member={dn})(|(sAMAccountType=268435456)(sAMAccountType=536870912))) |
|
| activedirectory | (&(|({username_attribute}={input})({mail_attribute}={input}))(sAMAccountType=805306368)(!(userAccountControl:1.2.840.113556.1.4.803:=2))(!(pwdLastSet=0))(|(!(accountExpires=*))(accountExpires=0)(accountExpires>={date-time:microsoft-nt}))) | (&(member={dn})(|(sAMAccountType=268435456)(sAMAccountType=536870912))) |
|
||||||
| rfc2307bis | (&(|({username_attribute}={input})({mail_attribute}={input}))(|(objectClass=inetOrgPerson)(objectClass=organizationalPerson))) | (&(|(member={dn})(uniqueMember={dn}))(|(objectClass=groupOfNames)(objectClass=groupOfUniqueNames)(objectClass=groupOfMembers))) |
|
| rfc2307bis | (&(|({username_attribute}={input})({mail_attribute}={input}))(|(objectClass=inetOrgPerson)(objectClass=organizationalPerson))(!(pwdReset=TRUE))) | (&(|(member={dn})(uniqueMember={dn}))(|(objectClass=groupOfNames)(objectClass=groupOfUniqueNames)(objectClass=groupOfMembers))) |
|
||||||
| freeipa | (&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=person)(!(nsAccountLock=TRUE))(krbPasswordExpiration>={date-time:generalized})(|(!(krbPrincipalExpiration=*))(krbPrincipalExpiration>={date-time:generalized}))) | (&(member={dn})(objectClass=groupOfNames)) |
|
| freeipa | (&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=person)(!(nsAccountLock=TRUE))(krbPasswordExpiration>={date-time:generalized})(|(!(krbPrincipalExpiration=*))(krbPrincipalExpiration>={date-time:generalized}))) | (&(member={dn})(objectClass=groupOfNames)) |
|
||||||
| lldap | (&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=person)) | (&(member={dn})(objectClass=groupOfNames)) |
|
| lldap | (&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=person)) | (&(member={dn})(objectClass=groupOfNames)) |
|
||||||
| glauth | (&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=posixAccount)(!(accountStatus=inactive))) | (&(uniqueMember={dn})(objectClass=posixGroup)) |
|
| glauth | (&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=posixAccount)(!(accountStatus=inactive))) | (&(uniqueMember={dn})(objectClass=posixGroup)) |
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
title: "Database Integrations"
|
title: "Database Integrations"
|
||||||
description: "A database integration reference guide"
|
description: "A database integration reference guide"
|
||||||
lead: "This section contains a database integration reference guide for Authelia."
|
lead: "This section contains a database integration reference guide for Authelia."
|
||||||
date: 2022-11-19T17:42:03+11:00
|
date: 2022-11-19T16:47:09+11:00
|
||||||
draft: false
|
draft: false
|
||||||
images: []
|
images: []
|
||||||
menu:
|
menu:
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
title: "Integrations"
|
title: "Integrations"
|
||||||
description: "A collection of integration reference guides"
|
description: "A collection of integration reference guides"
|
||||||
lead: "This section contains integration reference guides for Authelia."
|
lead: "This section contains integration reference guides for Authelia."
|
||||||
date: 2022-11-19T17:42:03+11:00
|
date: 2022-11-19T16:47:09+11:00
|
||||||
draft: false
|
draft: false
|
||||||
images: []
|
images: []
|
||||||
menu:
|
menu:
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1 +1 @@
|
||||||
{"defaults":{"language":{"display":"English","locale":"en"},"namespace":"portal"},"namespaces":["portal","settings"],"languages":[{"display":"English","locale":"en","namespaces":["portal","settings"],"fallbacks":["en"]},{"display":"Arabic","locale":"ar","namespaces":["portal"],"fallbacks":["en"]},{"display":"Arabic (Saudi Arabia)","locale":"ar-SA","namespaces":["portal"],"fallbacks":["ar","en"]},{"display":"Czech","locale":"cs","namespaces":["portal"],"fallbacks":["en"]},{"display":"Czech (Czechia)","locale":"cs-CZ","namespaces":["portal"],"fallbacks":["cs","en"]},{"display":"Danish","locale":"da","namespaces":["portal"],"fallbacks":["en"]},{"display":"Danish (Denmark)","locale":"da-DK","namespaces":["portal"],"fallbacks":["da","en"]},{"display":"German","locale":"de","namespaces":["portal"],"fallbacks":["en"]},{"display":"Greek","locale":"el","namespaces":["portal"],"fallbacks":["en"]},{"display":"Greek (Greece)","locale":"el-GR","namespaces":["portal"],"fallbacks":["el","en"]},{"display":"Spanish","locale":"es","namespaces":["portal"],"fallbacks":["en"]},{"display":"Finnish","locale":"fi","namespaces":["portal"],"fallbacks":["en"]},{"display":"French","locale":"fr","namespaces":["portal"],"fallbacks":["en"]},{"display":"Hungarian","locale":"hu","namespaces":["portal"],"fallbacks":["en"]},{"display":"Italian","locale":"it","namespaces":["portal"],"fallbacks":["en"]},{"display":"Japanese","locale":"ja","namespaces":["portal"],"fallbacks":["en"]},{"display":"Japanese (Japan)","locale":"ja-JP","namespaces":["portal"],"fallbacks":["ja","en"]},{"display":"Norwegian Bokmål","locale":"nb","namespaces":["portal"],"fallbacks":["en"]},{"display":"Norwegian Bokmål (Norway)","locale":"nb-NO","namespaces":["portal"],"fallbacks":["nb","en"]},{"display":"Dutch","locale":"nl","namespaces":["portal"],"fallbacks":["en"]},{"display":"Norwegian Bokmål","locale":"no","namespaces":["portal"],"fallbacks":["en"]},{"display":"Polish","locale":"pl","namespaces":["portal"],"fallbacks":["en"]},{"display":"Portuguese","locale":"pt","namespaces":["portal"],"fallbacks":["en"]},{"display":"Brazilian Portuguese","locale":"pt-BR","namespaces":["portal"],"fallbacks":["en"]},{"display":"Romanian","locale":"ro","namespaces":["portal"],"fallbacks":["en"]},{"display":"Russian","locale":"ru","namespaces":["portal"],"fallbacks":["en"]},{"display":"Slovenian","locale":"sl","namespaces":["portal"],"fallbacks":["en"]},{"display":"Slovenian (Slovenia)","locale":"sl-SI","namespaces":["portal"],"fallbacks":["sl","en"]},{"display":"Swedish","locale":"sv","namespaces":["portal"],"fallbacks":["en"]},{"display":"Swedish (Sweden)","locale":"sv-SE","namespaces":["portal"],"fallbacks":["sv","en"]},{"display":"Ukrainian","locale":"uk","namespaces":["portal"],"fallbacks":["en"]},{"display":"Ukrainian (Ukraine)","locale":"uk-UA","namespaces":["portal"],"fallbacks":["uk","en"]},{"display":"Chinese","locale":"zh","namespaces":["portal"],"fallbacks":["en"]},{"display":"Chinese (China)","locale":"zh-CN","namespaces":["portal"],"fallbacks":["zh","en"]},{"display":"Chinese (Taiwan)","locale":"zh-TW","namespaces":["portal"],"fallbacks":["en"]}]}
|
{"defaults":{"language":{"display":"English","locale":"en"},"namespace":"portal"},"namespaces":["portal"],"languages":[{"display":"English","locale":"en","namespaces":["portal"],"fallbacks":["en"]},{"display":"Arabic","locale":"ar","namespaces":["portal"],"fallbacks":["en"]},{"display":"Arabic (Saudi Arabia)","locale":"ar-SA","namespaces":["portal"],"fallbacks":["ar","en"]},{"display":"Czech","locale":"cs","namespaces":["portal"],"fallbacks":["en"]},{"display":"Czech (Czechia)","locale":"cs-CZ","namespaces":["portal"],"fallbacks":["cs","en"]},{"display":"Danish","locale":"da","namespaces":["portal"],"fallbacks":["en"]},{"display":"Danish (Denmark)","locale":"da-DK","namespaces":["portal"],"fallbacks":["da","en"]},{"display":"German","locale":"de","namespaces":["portal"],"fallbacks":["en"]},{"display":"Greek","locale":"el","namespaces":["portal"],"fallbacks":["en"]},{"display":"Greek (Greece)","locale":"el-GR","namespaces":["portal"],"fallbacks":["el","en"]},{"display":"Spanish","locale":"es","namespaces":["portal"],"fallbacks":["en"]},{"display":"Finnish","locale":"fi","namespaces":["portal"],"fallbacks":["en"]},{"display":"French","locale":"fr","namespaces":["portal"],"fallbacks":["en"]},{"display":"Hungarian","locale":"hu","namespaces":["portal"],"fallbacks":["en"]},{"display":"Italian","locale":"it","namespaces":["portal"],"fallbacks":["en"]},{"display":"Japanese","locale":"ja","namespaces":["portal"],"fallbacks":["en"]},{"display":"Japanese (Japan)","locale":"ja-JP","namespaces":["portal"],"fallbacks":["ja","en"]},{"display":"Norwegian Bokmål","locale":"nb","namespaces":["portal"],"fallbacks":["en"]},{"display":"Norwegian Bokmål (Norway)","locale":"nb-NO","namespaces":["portal"],"fallbacks":["nb","en"]},{"display":"Dutch","locale":"nl","namespaces":["portal"],"fallbacks":["en"]},{"display":"Norwegian Bokmål","locale":"no","namespaces":["portal"],"fallbacks":["en"]},{"display":"Polish","locale":"pl","namespaces":["portal"],"fallbacks":["en"]},{"display":"Portuguese","locale":"pt","namespaces":["portal"],"fallbacks":["en"]},{"display":"Brazilian Portuguese","locale":"pt-BR","namespaces":["portal"],"fallbacks":["en"]},{"display":"Romanian","locale":"ro","namespaces":["portal"],"fallbacks":["en"]},{"display":"Russian","locale":"ru","namespaces":["portal"],"fallbacks":["en"]},{"display":"Slovenian","locale":"sl","namespaces":["portal"],"fallbacks":["en"]},{"display":"Slovenian (Slovenia)","locale":"sl-SI","namespaces":["portal"],"fallbacks":["sl","en"]},{"display":"Swedish","locale":"sv","namespaces":["portal"],"fallbacks":["en"]},{"display":"Swedish (Sweden)","locale":"sv-SE","namespaces":["portal"],"fallbacks":["sv","en"]},{"display":"Ukrainian","locale":"uk","namespaces":["portal"],"fallbacks":["en"]},{"display":"Ukrainian (Ukraine)","locale":"uk-UA","namespaces":["portal"],"fallbacks":["uk","en"]},{"display":"Chinese","locale":"zh","namespaces":["portal"],"fallbacks":["en"]},{"display":"Chinese (China)","locale":"zh-CN","namespaces":["portal"],"fallbacks":["zh","en"]},{"display":"Chinese (Taiwan)","locale":"zh-TW","namespaces":["portal"],"fallbacks":["en"]}]}
|
|
@ -0,0 +1,144 @@
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package envoy.service.auth.v3;
|
||||||
|
|
||||||
|
import "envoy/config/core/v3/base.proto";
|
||||||
|
import "envoy/service/auth/v3/attribute_context.proto";
|
||||||
|
import "envoy/type/v3/http_status.proto";
|
||||||
|
|
||||||
|
import "google/protobuf/struct.proto";
|
||||||
|
import "google/rpc/status.proto";
|
||||||
|
|
||||||
|
import "envoy/annotations/deprecation.proto";
|
||||||
|
import "udpa/annotations/status.proto";
|
||||||
|
import "udpa/annotations/versioning.proto";
|
||||||
|
|
||||||
|
option java_package = "io.envoyproxy.envoy.service.auth.v3";
|
||||||
|
option java_outer_classname = "ExternalAuthProto";
|
||||||
|
option java_multiple_files = true;
|
||||||
|
option go_package = "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3;authv3";
|
||||||
|
option (udpa.annotations.file_status).package_version_status = ACTIVE;
|
||||||
|
|
||||||
|
// [#protodoc-title: Authorization service]
|
||||||
|
|
||||||
|
// The authorization service request messages used by external authorization :ref:`network filter
|
||||||
|
// <config_network_filters_ext_authz>` and :ref:`HTTP filter <config_http_filters_ext_authz>`.
|
||||||
|
|
||||||
|
// A generic interface for performing authorization check on incoming
|
||||||
|
// requests to a networked service.
|
||||||
|
service Authorization {
|
||||||
|
// Performs authorization check based on the attributes associated with the
|
||||||
|
// incoming request, and returns status `OK` or not `OK`.
|
||||||
|
rpc Check(CheckRequest) returns (CheckResponse) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message CheckRequest {
|
||||||
|
option (udpa.annotations.versioning).previous_message_type = "envoy.service.auth.v2.CheckRequest";
|
||||||
|
|
||||||
|
// The request attributes.
|
||||||
|
AttributeContext attributes = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTP attributes for a denied response.
|
||||||
|
message DeniedHttpResponse {
|
||||||
|
option (udpa.annotations.versioning).previous_message_type =
|
||||||
|
"envoy.service.auth.v2.DeniedHttpResponse";
|
||||||
|
|
||||||
|
// This field allows the authorization service to send an HTTP response status code to the
|
||||||
|
// downstream client. If not set, Envoy sends ``403 Forbidden`` HTTP status code by default.
|
||||||
|
type.v3.HttpStatus status = 1;
|
||||||
|
|
||||||
|
// This field allows the authorization service to send HTTP response headers
|
||||||
|
// to the downstream client. Note that the :ref:`append field in HeaderValueOption <envoy_v3_api_field_config.core.v3.HeaderValueOption.append>` defaults to
|
||||||
|
// false when used in this message.
|
||||||
|
repeated config.core.v3.HeaderValueOption headers = 2;
|
||||||
|
|
||||||
|
// This field allows the authorization service to send a response body data
|
||||||
|
// to the downstream client.
|
||||||
|
string body = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTP attributes for an OK response.
|
||||||
|
// [#next-free-field: 9]
|
||||||
|
message OkHttpResponse {
|
||||||
|
option (udpa.annotations.versioning).previous_message_type =
|
||||||
|
"envoy.service.auth.v2.OkHttpResponse";
|
||||||
|
|
||||||
|
// HTTP entity headers in addition to the original request headers. This allows the authorization
|
||||||
|
// service to append, to add or to override headers from the original request before
|
||||||
|
// dispatching it to the upstream. Note that the :ref:`append field in HeaderValueOption <envoy_v3_api_field_config.core.v3.HeaderValueOption.append>` defaults to
|
||||||
|
// false when used in this message. By setting the ``append`` field to ``true``,
|
||||||
|
// the filter will append the correspondent header value to the matched request header.
|
||||||
|
// By leaving ``append`` as false, the filter will either add a new header, or override an existing
|
||||||
|
// one if there is a match.
|
||||||
|
repeated config.core.v3.HeaderValueOption headers = 2;
|
||||||
|
|
||||||
|
// HTTP entity headers to remove from the original request before dispatching
|
||||||
|
// it to the upstream. This allows the authorization service to act on auth
|
||||||
|
// related headers (like ``Authorization``), process them, and consume them.
|
||||||
|
// Under this model, the upstream will either receive the request (if it's
|
||||||
|
// authorized) or not receive it (if it's not), but will not see headers
|
||||||
|
// containing authorization credentials.
|
||||||
|
//
|
||||||
|
// Pseudo headers (such as ``:authority``, ``:method``, ``:path`` etc), as well as
|
||||||
|
// the header ``Host``, may not be removed as that would make the request
|
||||||
|
// malformed. If mentioned in ``headers_to_remove`` these special headers will
|
||||||
|
// be ignored.
|
||||||
|
//
|
||||||
|
// When using the HTTP service this must instead be set by the HTTP
|
||||||
|
// authorization service as a comma separated list like so:
|
||||||
|
// ``x-envoy-auth-headers-to-remove: one-auth-header, another-auth-header``.
|
||||||
|
repeated string headers_to_remove = 5;
|
||||||
|
|
||||||
|
// This field has been deprecated in favor of :ref:`CheckResponse.dynamic_metadata
|
||||||
|
// <envoy_v3_api_field_service.auth.v3.CheckResponse.dynamic_metadata>`. Until it is removed,
|
||||||
|
// setting this field overrides :ref:`CheckResponse.dynamic_metadata
|
||||||
|
// <envoy_v3_api_field_service.auth.v3.CheckResponse.dynamic_metadata>`.
|
||||||
|
google.protobuf.Struct dynamic_metadata = 3
|
||||||
|
[deprecated = true, (envoy.annotations.deprecated_at_minor_version) = "3.0"];
|
||||||
|
|
||||||
|
// This field allows the authorization service to send HTTP response headers
|
||||||
|
// to the downstream client on success. Note that the :ref:`append field in HeaderValueOption <envoy_v3_api_field_config.core.v3.HeaderValueOption.append>`
|
||||||
|
// defaults to false when used in this message.
|
||||||
|
repeated config.core.v3.HeaderValueOption response_headers_to_add = 6;
|
||||||
|
|
||||||
|
// This field allows the authorization service to set (and overwrite) query
|
||||||
|
// string parameters on the original request before it is sent upstream.
|
||||||
|
repeated config.core.v3.QueryParameter query_parameters_to_set = 7;
|
||||||
|
|
||||||
|
// This field allows the authorization service to specify which query parameters
|
||||||
|
// should be removed from the original request before it is sent upstream. Each
|
||||||
|
// element in this list is a case-sensitive query parameter name to be removed.
|
||||||
|
repeated string query_parameters_to_remove = 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Intended for gRPC and Network Authorization servers ``only``.
|
||||||
|
message CheckResponse {
|
||||||
|
option (udpa.annotations.versioning).previous_message_type =
|
||||||
|
"envoy.service.auth.v2.CheckResponse";
|
||||||
|
|
||||||
|
// Status ``OK`` allows the request. Any other status indicates the request should be denied, and
|
||||||
|
// for HTTP filter, if not overridden by :ref:`denied HTTP response status <envoy_v3_api_field_service.auth.v3.DeniedHttpResponse.status>`
|
||||||
|
// Envoy sends ``403 Forbidden`` HTTP status code by default.
|
||||||
|
google.rpc.Status status = 1;
|
||||||
|
|
||||||
|
// An message that contains HTTP response attributes. This message is
|
||||||
|
// used when the authorization service needs to send custom responses to the
|
||||||
|
// downstream client or, to modify/add request headers being dispatched to the upstream.
|
||||||
|
oneof http_response {
|
||||||
|
// Supplies http attributes for a denied response.
|
||||||
|
DeniedHttpResponse denied_response = 2;
|
||||||
|
|
||||||
|
// Supplies http attributes for an ok response.
|
||||||
|
OkHttpResponse ok_response = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional response metadata that will be emitted as dynamic metadata to be consumed by the next
|
||||||
|
// filter. This metadata lives in a namespace specified by the canonical name of extension filter
|
||||||
|
// that requires it:
|
||||||
|
//
|
||||||
|
// - :ref:`envoy.filters.http.ext_authz <config_http_filters_ext_authz_dynamic_metadata>` for HTTP filter.
|
||||||
|
// - :ref:`envoy.filters.network.ext_authz <config_network_filters_ext_authz_dynamic_metadata>` for network filter.
|
||||||
|
google.protobuf.Struct dynamic_metadata = 4;
|
||||||
|
}
|
|
@ -45,7 +45,7 @@ services:
|
||||||
- TZ=Australia/Melbourne
|
- TZ=Australia/Melbourne
|
||||||
|
|
||||||
traefik:
|
traefik:
|
||||||
image: traefik:v2.10.1
|
image: traefik:v2.10.3
|
||||||
container_name: traefik
|
container_name: traefik
|
||||||
volumes:
|
volumes:
|
||||||
- ./traefik:/etc/traefik
|
- ./traefik:/etc/traefik
|
||||||
|
|
|
@ -32,7 +32,7 @@ services:
|
||||||
- TZ=Australia/Melbourne
|
- TZ=Australia/Melbourne
|
||||||
|
|
||||||
traefik:
|
traefik:
|
||||||
image: traefik:v2.10.1
|
image: traefik:v2.10.3
|
||||||
container_name: traefik
|
container_name: traefik
|
||||||
volumes:
|
volumes:
|
||||||
- ./traefik:/etc/traefik
|
- ./traefik:/etc/traefik
|
||||||
|
|
56
go.mod
56
go.mod
|
@ -12,15 +12,15 @@ require (
|
||||||
github.com/fsnotify/fsnotify v1.6.0
|
github.com/fsnotify/fsnotify v1.6.0
|
||||||
github.com/go-asn1-ber/asn1-ber v1.5.4
|
github.com/go-asn1-ber/asn1-ber v1.5.4
|
||||||
github.com/go-crypt/crypt v0.2.9
|
github.com/go-crypt/crypt v0.2.9
|
||||||
github.com/go-ldap/ldap/v3 v3.4.5-0.20230521105649-cdb0754f6668
|
github.com/go-ldap/ldap/v3 v3.4.5
|
||||||
github.com/go-rod/rod v0.113.1
|
github.com/go-rod/rod v0.113.3
|
||||||
github.com/go-sql-driver/mysql v1.7.1
|
github.com/go-sql-driver/mysql v1.7.1
|
||||||
github.com/go-webauthn/webauthn v0.8.2
|
github.com/go-webauthn/webauthn v0.5.0
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.0
|
github.com/golang-jwt/jwt/v4 v4.5.0
|
||||||
github.com/golang/mock v1.6.0
|
github.com/golang/mock v1.6.0
|
||||||
github.com/google/uuid v1.3.0
|
github.com/google/uuid v1.3.0
|
||||||
github.com/hashicorp/go-retryablehttp v0.7.2
|
github.com/hashicorp/go-retryablehttp v0.7.4
|
||||||
github.com/jackc/pgx/v5 v5.3.1
|
github.com/jackc/pgx/v5 v5.4.1
|
||||||
github.com/jmoiron/sqlx v1.3.5
|
github.com/jmoiron/sqlx v1.3.5
|
||||||
github.com/knadh/koanf/parsers/yaml v0.1.0
|
github.com/knadh/koanf/parsers/yaml v0.1.0
|
||||||
github.com/knadh/koanf/providers/confmap v0.1.0
|
github.com/knadh/koanf/providers/confmap v0.1.0
|
||||||
|
@ -28,27 +28,27 @@ require (
|
||||||
github.com/knadh/koanf/providers/posflag v0.1.0
|
github.com/knadh/koanf/providers/posflag v0.1.0
|
||||||
github.com/knadh/koanf/providers/rawbytes v0.1.0
|
github.com/knadh/koanf/providers/rawbytes v0.1.0
|
||||||
github.com/knadh/koanf/v2 v2.0.1
|
github.com/knadh/koanf/v2 v2.0.1
|
||||||
github.com/mattn/go-sqlite3 v1.14.16
|
github.com/mattn/go-sqlite3 v1.14.17
|
||||||
github.com/mitchellh/mapstructure v1.5.0
|
github.com/mitchellh/mapstructure v1.5.0
|
||||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826
|
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826
|
||||||
github.com/ory/fosite v0.44.0
|
github.com/ory/fosite v0.44.0
|
||||||
github.com/ory/herodot v0.10.2
|
github.com/ory/herodot v0.10.2
|
||||||
github.com/ory/x v0.0.555
|
github.com/ory/x v0.0.563
|
||||||
github.com/otiai10/copy v1.11.0
|
github.com/otiai10/copy v1.12.0
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/pquerna/otp v1.4.0
|
github.com/pquerna/otp v1.4.0
|
||||||
github.com/prometheus/client_golang v1.15.1
|
github.com/prometheus/client_golang v1.16.0
|
||||||
github.com/sirupsen/logrus v1.9.2
|
github.com/sirupsen/logrus v1.9.3
|
||||||
github.com/spf13/cobra v1.7.0
|
github.com/spf13/cobra v1.7.0
|
||||||
github.com/spf13/pflag v1.0.5
|
github.com/spf13/pflag v1.0.5
|
||||||
github.com/stretchr/testify v1.8.3
|
github.com/stretchr/testify v1.8.4
|
||||||
github.com/trustelem/zxcvbn v1.0.1
|
github.com/trustelem/zxcvbn v1.0.1
|
||||||
github.com/valyala/fasthttp v1.47.0
|
github.com/valyala/fasthttp v1.48.0
|
||||||
github.com/wneessen/go-mail v0.3.9
|
github.com/wneessen/go-mail v0.3.9
|
||||||
golang.org/x/net v0.10.0
|
golang.org/x/net v0.11.0
|
||||||
golang.org/x/sync v0.2.0
|
golang.org/x/sync v0.3.0
|
||||||
golang.org/x/term v0.8.0
|
golang.org/x/term v0.9.0
|
||||||
golang.org/x/text v0.9.0
|
golang.org/x/text v0.10.0
|
||||||
gopkg.in/square/go-jose.v2 v2.6.0
|
gopkg.in/square/go-jose.v2 v2.6.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
)
|
)
|
||||||
|
@ -59,6 +59,7 @@ require (
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
|
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||||
|
github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 // indirect
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||||
github.com/cristalhq/jwt/v4 v4.0.2 // indirect
|
github.com/cristalhq/jwt/v4 v4.0.2 // indirect
|
||||||
github.com/dave/jennifer v1.6.0 // indirect
|
github.com/dave/jennifer v1.6.0 // indirect
|
||||||
|
@ -68,12 +69,16 @@ require (
|
||||||
github.com/dlclark/regexp2 v1.4.0 // indirect
|
github.com/dlclark/regexp2 v1.4.0 // indirect
|
||||||
github.com/dustin/go-humanize v1.0.0 // indirect
|
github.com/dustin/go-humanize v1.0.0 // indirect
|
||||||
github.com/ecordell/optgen v0.0.6 // indirect
|
github.com/ecordell/optgen v0.0.6 // indirect
|
||||||
|
github.com/envoyproxy/go-control-plane v0.11.1 // indirect
|
||||||
|
github.com/envoyproxy/protoc-gen-validate v1.0.1 // indirect
|
||||||
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect
|
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect
|
||||||
github.com/fxamacker/cbor/v2 v2.4.0 // indirect
|
github.com/fxamacker/cbor/v2 v2.4.0 // indirect
|
||||||
github.com/go-crypt/x v0.2.1 // indirect
|
github.com/go-crypt/x v0.2.1 // indirect
|
||||||
github.com/go-redis/redis/v8 v8.11.5 // indirect
|
github.com/go-redis/redis/v8 v8.11.5 // indirect
|
||||||
github.com/go-webauthn/revoke v0.1.9 // indirect
|
github.com/go-webauthn/revoke v0.1.9 // indirect
|
||||||
github.com/golang/glog v1.0.0 // indirect
|
github.com/gogo/googleapis v1.4.1 // indirect
|
||||||
|
github.com/gogo/protobuf v1.3.2 // indirect
|
||||||
|
github.com/golang/glog v1.1.0 // indirect
|
||||||
github.com/golang/protobuf v1.5.3 // indirect
|
github.com/golang/protobuf v1.5.3 // indirect
|
||||||
github.com/google/go-tpm v0.3.3 // indirect
|
github.com/google/go-tpm v0.3.3 // indirect
|
||||||
github.com/gorilla/websocket v1.5.0 // indirect
|
github.com/gorilla/websocket v1.5.0 // indirect
|
||||||
|
@ -97,9 +102,9 @@ require (
|
||||||
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
|
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
|
||||||
github.com/philhofer/fwd v1.1.2 // indirect
|
github.com/philhofer/fwd v1.1.2 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/prometheus/client_model v0.3.0 // indirect
|
github.com/prometheus/client_model v0.4.0 // indirect
|
||||||
github.com/prometheus/common v0.42.0 // indirect
|
github.com/prometheus/common v0.42.0 // indirect
|
||||||
github.com/prometheus/procfs v0.9.0 // indirect
|
github.com/prometheus/procfs v0.10.1 // indirect
|
||||||
github.com/redis/go-redis/v9 v9.0.4 // indirect
|
github.com/redis/go-redis/v9 v9.0.4 // indirect
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect
|
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect
|
||||||
|
@ -117,14 +122,15 @@ require (
|
||||||
github.com/ysmood/got v0.34.1 // indirect
|
github.com/ysmood/got v0.34.1 // indirect
|
||||||
github.com/ysmood/gson v0.7.3 // indirect
|
github.com/ysmood/gson v0.7.3 // indirect
|
||||||
github.com/ysmood/leakless v0.8.0 // indirect
|
github.com/ysmood/leakless v0.8.0 // indirect
|
||||||
golang.org/x/crypto v0.7.0 // indirect
|
golang.org/x/crypto v0.10.0 // indirect
|
||||||
golang.org/x/mod v0.10.0 // indirect
|
golang.org/x/mod v0.10.0 // indirect
|
||||||
golang.org/x/oauth2 v0.5.0 // indirect
|
golang.org/x/oauth2 v0.7.0 // indirect
|
||||||
golang.org/x/sys v0.8.0 // indirect
|
golang.org/x/sys v0.9.0 // indirect
|
||||||
golang.org/x/tools v0.7.0 // indirect
|
golang.org/x/tools v0.8.0 // indirect
|
||||||
google.golang.org/appengine v1.6.7 // indirect
|
google.golang.org/appengine v1.6.7 // indirect
|
||||||
google.golang.org/genproto v0.0.0-20230403163135-c38d8f061ccd // indirect
|
google.golang.org/genproto v0.0.0-20230526203410-71b5a4ffd15e // indirect
|
||||||
google.golang.org/grpc v1.54.0 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20230526203410-71b5a4ffd15e // indirect
|
||||||
|
google.golang.org/grpc v1.56.0 // indirect
|
||||||
google.golang.org/protobuf v1.30.0 // indirect
|
google.golang.org/protobuf v1.30.0 // indirect
|
||||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
|
|
105
go.sum
105
go.sum
|
@ -72,6 +72,8 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
|
||||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||||
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||||
|
github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+gqO04wryn5h75LSazbRlnya1k=
|
||||||
|
github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||||
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
|
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
|
||||||
|
@ -113,7 +115,11 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m
|
||||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
|
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.11.1 h1:wSUXTlLfiAQRWs2F+p+EKOY9rUyis1MyGqJ2DIk5HpM=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.11.1/go.mod h1:uhMcXKCQMEJHiAb0w+YGefQLaTEw+YhGluxZkrTmD0g=
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||||
|
github.com/envoyproxy/protoc-gen-validate v1.0.1 h1:kt9FtLiooDc0vbwTLhdg3dyNX1K9Qwa1EK9LcD4jVUQ=
|
||||||
|
github.com/envoyproxy/protoc-gen-validate v1.0.1/go.mod h1:0vj8bNkYbSTNS2PIyH87KZaeN4x9zpL9Qt8fQC7d+vs=
|
||||||
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A=
|
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A=
|
||||||
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg=
|
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg=
|
||||||
github.com/fasthttp/router v1.4.19 h1:RLE539IU/S4kfb4MP56zgP0TIBU9kEg0ID9GpWO0vqk=
|
github.com/fasthttp/router v1.4.19 h1:RLE539IU/S4kfb4MP56zgP0TIBU9kEg0ID9GpWO0vqk=
|
||||||
|
@ -138,29 +144,35 @@ github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
github.com/go-ldap/ldap/v3 v3.4.5-0.20230521105649-cdb0754f6668 h1:qbWHOCDBT8m2I1tDGP7S58dgi/xaDDCKuR5dbarLGOU=
|
github.com/go-ldap/ldap/v3 v3.4.5 h1:ekEKmaDrpvR2yf5Nc/DClsGG9lAmdDixe44mLzlW5r8=
|
||||||
github.com/go-ldap/ldap/v3 v3.4.5-0.20230521105649-cdb0754f6668/go.mod h1:bMGIq3AGbytbaMwf8wdv5Phdxz0FWHTIYMSzyrYgnQs=
|
github.com/go-ldap/ldap/v3 v3.4.5/go.mod h1:bMGIq3AGbytbaMwf8wdv5Phdxz0FWHTIYMSzyrYgnQs=
|
||||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||||
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
|
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
|
||||||
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
|
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
|
||||||
github.com/go-rod/rod v0.113.1 h1:+Qb4K/vkR7BOhW6FhfhtLzUD3l11+0XlF4do+27sOQk=
|
github.com/go-rod/rod v0.113.3 h1:oLiKZW721CCMwA5g7977cWfcAKQ+FuosP47Zf1QiDrA=
|
||||||
github.com/go-rod/rod v0.113.1/go.mod h1:aiedSEFg5DwG/fnNbUOTPMTTWX3MRj6vIs/a684Mthw=
|
github.com/go-rod/rod v0.113.3/go.mod h1:aiedSEFg5DwG/fnNbUOTPMTTWX3MRj6vIs/a684Mthw=
|
||||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||||
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
|
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
|
||||||
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
github.com/go-webauthn/revoke v0.1.9 h1:gSJ1ckA9VaKA2GN4Ukp+kiGTk1/EXtaDb1YE8RknbS0=
|
github.com/go-webauthn/revoke v0.1.9 h1:gSJ1ckA9VaKA2GN4Ukp+kiGTk1/EXtaDb1YE8RknbS0=
|
||||||
github.com/go-webauthn/revoke v0.1.9/go.mod h1:j6WKPnv0HovtEs++paan9g3ar46gm1NarktkXBaPR+w=
|
github.com/go-webauthn/revoke v0.1.9/go.mod h1:j6WKPnv0HovtEs++paan9g3ar46gm1NarktkXBaPR+w=
|
||||||
github.com/go-webauthn/webauthn v0.8.2 h1:8KLIbpldjz9KVGHfqEgJNbkhd7bbRXhNw4QWFJE15oA=
|
github.com/go-webauthn/webauthn v0.5.0 h1:Tbmp37AGIhYbQmcy2hEffo3U3cgPClqvxJ7cLUnF7Rc=
|
||||||
github.com/go-webauthn/webauthn v0.8.2/go.mod h1:d+ezx/jMCNDiqSMzOchuynKb9CVU1NM9BumOnokfcVQ=
|
github.com/go-webauthn/webauthn v0.5.0/go.mod h1:0CBq/jNfPS9l033j4AxMk8K8MluiMsde9uGNSPFLEVE=
|
||||||
|
github.com/gogo/googleapis v1.4.1 h1:1Yx4Myt7BxzvUr5ldGSbwYiZG6t9wGBZ+8/fX3Wvtq0=
|
||||||
|
github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4=
|
||||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||||
|
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||||
|
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
|
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ=
|
github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ=
|
||||||
github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4=
|
github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4=
|
||||||
|
github.com/golang/glog v1.1.0 h1:/d3pCKDPWNnvIWe0vVUpNP32qc8U3PDVxySP/y360qE=
|
||||||
|
github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ=
|
||||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
|
@ -241,8 +253,8 @@ github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9n
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
|
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
|
||||||
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
|
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
|
||||||
github.com/hashicorp/go-hclog v1.2.0 h1:La19f8d7WIlm4ogzNHB0JGqs5AUDAZ2UfCY4sJXcJdM=
|
github.com/hashicorp/go-hclog v1.2.0 h1:La19f8d7WIlm4ogzNHB0JGqs5AUDAZ2UfCY4sJXcJdM=
|
||||||
github.com/hashicorp/go-retryablehttp v0.7.2 h1:AcYqCvkpalPnPF2pn0KamgwamS42TqUDDYFRKq/RAd0=
|
github.com/hashicorp/go-retryablehttp v0.7.4 h1:ZQgVdpTdAL7WpMIwLzCfbalOcSUdkDZnpUv3/+BxzFA=
|
||||||
github.com/hashicorp/go-retryablehttp v0.7.2/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8=
|
github.com/hashicorp/go-retryablehttp v0.7.4/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8=
|
||||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||||
|
@ -256,8 +268,8 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI
|
||||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
|
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
|
||||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||||
github.com/jackc/pgx/v5 v5.3.1 h1:Fcr8QJ1ZeLi5zsPZqQeUZhNhxfkkKBOgJuYkJHoBOtU=
|
github.com/jackc/pgx/v5 v5.4.1 h1:oKfB/FhuVtit1bBM3zNRRsZ925ZkMN3HXL+LgLUM9lE=
|
||||||
github.com/jackc/pgx/v5 v5.3.1/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8=
|
github.com/jackc/pgx/v5 v5.4.1/go.mod h1:q6iHT8uDNXWiFNOlRqJzBTaSH3+2xCXkokxHZC5qWFY=
|
||||||
github.com/jandelgado/gcov2lcov v1.0.5 h1:rkBt40h0CVK4oCb8Dps950gvfd1rYvQ8+cWa346lVU0=
|
github.com/jandelgado/gcov2lcov v1.0.5 h1:rkBt40h0CVK4oCb8Dps950gvfd1rYvQ8+cWa346lVU0=
|
||||||
github.com/jandelgado/gcov2lcov v1.0.5/go.mod h1:NnSxK6TMlg1oGDBfGelGbjgorT5/L3cchlbtgFYZSss=
|
github.com/jandelgado/gcov2lcov v1.0.5/go.mod h1:NnSxK6TMlg1oGDBfGelGbjgorT5/L3cchlbtgFYZSss=
|
||||||
github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
|
github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
|
||||||
|
@ -267,6 +279,7 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1
|
||||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||||
|
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI=
|
github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI=
|
||||||
github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||||
|
@ -300,8 +313,8 @@ github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3v
|
||||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
|
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
|
||||||
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||||
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
|
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
|
||||||
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||||
github.com/mattn/goveralls v0.0.11 h1:eJXea6R6IFlL1QMKNMzDvvHv/hwGrnvyig4N+0+XiMM=
|
github.com/mattn/goveralls v0.0.11 h1:eJXea6R6IFlL1QMKNMzDvvHv/hwGrnvyig4N+0+XiMM=
|
||||||
github.com/mattn/goveralls v0.0.11/go.mod h1:gU8SyhNswsJKchEV93xRQxX6X3Ei4PJdQk/6ZHvrvRk=
|
github.com/mattn/goveralls v0.0.11/go.mod h1:gU8SyhNswsJKchEV93xRQxX6X3Ei4PJdQk/6ZHvrvRk=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||||
|
@ -331,10 +344,10 @@ github.com/ory/go-convenience v0.1.0 h1:zouLKfF2GoSGnJwGq+PE/nJAE6dj2Zj5QlTgmMTs
|
||||||
github.com/ory/go-convenience v0.1.0/go.mod h1:uEY/a60PL5c12nYz4V5cHY03IBmwIAEm8TWB0yn9KNs=
|
github.com/ory/go-convenience v0.1.0/go.mod h1:uEY/a60PL5c12nYz4V5cHY03IBmwIAEm8TWB0yn9KNs=
|
||||||
github.com/ory/herodot v0.10.2 h1:gGvNMHgAwWzdP/eo+roSiT5CGssygHSjDU7MSQNlJ4E=
|
github.com/ory/herodot v0.10.2 h1:gGvNMHgAwWzdP/eo+roSiT5CGssygHSjDU7MSQNlJ4E=
|
||||||
github.com/ory/herodot v0.10.2/go.mod h1:MMNmY6MG1uB6fnXYFaHoqdV23DTWctlPsmRCeq/2+wc=
|
github.com/ory/herodot v0.10.2/go.mod h1:MMNmY6MG1uB6fnXYFaHoqdV23DTWctlPsmRCeq/2+wc=
|
||||||
github.com/ory/x v0.0.555 h1:Etg6yUnrXP5YVTlpgipRh+hDLMavldbTURj+Mb0kdIY=
|
github.com/ory/x v0.0.563 h1:T77Bjt6ALMZmUJIsQ5UEkzDBCD+8vxfQlBCU1Y39uDk=
|
||||||
github.com/ory/x v0.0.555/go.mod h1:oRVemI3SQQOLvOCJWIRinHQKlgmay/NbwSyRUIsS/Yk=
|
github.com/ory/x v0.0.563/go.mod h1:kup4ebSC4SzwU6KPZJ4G60UR3EEsHxJ0apQVflVw5yQ=
|
||||||
github.com/otiai10/copy v1.11.0 h1:OKBD80J/mLBrwnzXqGtFCzprFSGioo30JcmR4APsNwc=
|
github.com/otiai10/copy v1.12.0 h1:cLMgSQnXBs1eehF0Wy/FAGsgDTDmAqFR7rQylBb1nDY=
|
||||||
github.com/otiai10/copy v1.11.0/go.mod h1:rSaLseMUsZFFbsFGc7wCJnnkTAvdc5L6VWxPE4308Ww=
|
github.com/otiai10/copy v1.12.0/go.mod h1:rSaLseMUsZFFbsFGc7wCJnnkTAvdc5L6VWxPE4308Ww=
|
||||||
github.com/otiai10/mint v1.5.1 h1:XaPLeE+9vGbuyEHem1JNk3bYc7KKqyI/na0/mLd/Kks=
|
github.com/otiai10/mint v1.5.1 h1:XaPLeE+9vGbuyEHem1JNk3bYc7KKqyI/na0/mLd/Kks=
|
||||||
github.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw=
|
github.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw=
|
||||||
github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
|
github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
|
||||||
|
@ -356,21 +369,23 @@ github.com/pquerna/otp v1.4.0 h1:wZvl1TIVxKRThZIBiwOOHOGP/1+nZyWBil9Y2XNEDzg=
|
||||||
github.com/pquerna/otp v1.4.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
|
github.com/pquerna/otp v1.4.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
|
||||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||||
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
|
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
|
||||||
github.com/prometheus/client_golang v1.15.1 h1:8tXpTmJbyH5lydzFPoxSIJ0J46jdh3tylbvM1xCv0LI=
|
github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8=
|
||||||
github.com/prometheus/client_golang v1.15.1/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk=
|
github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=
|
||||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4=
|
github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4=
|
||||||
github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
|
github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
|
||||||
|
github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
|
||||||
|
github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
|
||||||
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||||
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||||
github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
|
github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
|
||||||
github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
|
github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
|
||||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||||
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||||
github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI=
|
github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg=
|
||||||
github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY=
|
github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
|
||||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||||
github.com/redis/go-redis/v9 v9.0.4 h1:FC82T+CHJ/Q/PdyLW++GeCO+Ol59Y4T7R4jbgjvktgc=
|
github.com/redis/go-redis/v9 v9.0.4 h1:FC82T+CHJ/Q/PdyLW++GeCO+Ol59Y4T7R4jbgjvktgc=
|
||||||
github.com/redis/go-redis/v9 v9.0.4/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk=
|
github.com/redis/go-redis/v9 v9.0.4/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk=
|
||||||
|
@ -385,8 +400,8 @@ github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1Avp
|
||||||
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g=
|
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g=
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||||
github.com/sirupsen/logrus v1.9.2 h1:oxx1eChJGI6Uks2ZC4W1zpLlVgqB8ner4EuQwV4Ik1Y=
|
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||||
github.com/sirupsen/logrus v1.9.2/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||||
|
@ -421,8 +436,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8=
|
github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8=
|
||||||
github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
|
github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
|
||||||
github.com/test-go/testify v1.1.4 h1:Tf9lntrKUMHiXQ07qBScBTSA0dhYQlu83hswqelv1iE=
|
github.com/test-go/testify v1.1.4 h1:Tf9lntrKUMHiXQ07qBScBTSA0dhYQlu83hswqelv1iE=
|
||||||
|
@ -436,8 +451,8 @@ github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGr
|
||||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||||
github.com/valyala/fasthttp v1.47.0 h1:y7moDoxYzMooFpT5aHgNgVOQDrS3qlkfiP9mDtGGK9c=
|
github.com/valyala/fasthttp v1.48.0 h1:oJWvHb9BIZToTQS3MuQ2R3bJZiNSa2KiNdeI8A+79Tc=
|
||||||
github.com/valyala/fasthttp v1.47.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA=
|
github.com/valyala/fasthttp v1.48.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA=
|
||||||
github.com/wneessen/go-mail v0.3.9 h1:Q4DbCk3htT5DtDWKeMgNXCiHc4bBY/vv/XQPT6XDXzc=
|
github.com/wneessen/go-mail v0.3.9 h1:Q4DbCk3htT5DtDWKeMgNXCiHc4bBY/vv/XQPT6XDXzc=
|
||||||
github.com/wneessen/go-mail v0.3.9/go.mod h1:zxOlafWCP/r6FEhAaRgH4IC1vg2YXxO0Nar9u0IScZ8=
|
github.com/wneessen/go-mail v0.3.9/go.mod h1:zxOlafWCP/r6FEhAaRgH4IC1vg2YXxO0Nar9u0IScZ8=
|
||||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||||
|
@ -484,8 +499,9 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
|
||||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
|
|
||||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||||
|
golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM=
|
||||||
|
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||||
|
@ -565,8 +581,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
|
||||||
golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
|
golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
|
||||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||||
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
|
golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU=
|
||||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
@ -578,6 +594,10 @@ golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ
|
||||||
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||||
golang.org/x/oauth2 v0.5.0 h1:HuArIo48skDwlrvM3sEdHXElYslAMsf3KwRkkW4MC4s=
|
golang.org/x/oauth2 v0.5.0 h1:HuArIo48skDwlrvM3sEdHXElYslAMsf3KwRkkW4MC4s=
|
||||||
golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I=
|
golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I=
|
||||||
|
golang.org/x/oauth2 v0.6.0 h1:Lh8GPgSKBfWSwFvtuWOfeI3aAAnbXTSutYxJiOJFgIw=
|
||||||
|
golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw=
|
||||||
|
golang.org/x/oauth2 v0.7.0 h1:qe6s0zUXlPX80/dITx3440hWZ7GwMwgDDyrSGTPJG/g=
|
||||||
|
golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
@ -591,8 +611,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI=
|
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
||||||
golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
@ -643,15 +663,15 @@ golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
|
||||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
|
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||||
golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols=
|
golang.org/x/term v0.9.0 h1:GRRCnKYhdQrD8kfRAdQ6Zcw1P0OcELxGLKJvtjVMZ28=
|
||||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo=
|
||||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
@ -663,8 +683,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
|
||||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
@ -706,6 +726,7 @@ golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roY
|
||||||
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
|
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
golang.org/x/tools v0.0.0-20200717024301-6ddee64345a6/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
golang.org/x/tools v0.0.0-20200717024301-6ddee64345a6/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||||
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||||
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||||
|
@ -715,6 +736,7 @@ golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4f
|
||||||
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
|
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
|
@ -723,6 +745,7 @@ golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ=
|
||||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4=
|
golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4=
|
||||||
golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
|
golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
|
||||||
|
golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
@ -792,6 +815,10 @@ google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6D
|
||||||
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
google.golang.org/genproto v0.0.0-20230403163135-c38d8f061ccd h1:sLpv7bNL1AsX3fdnWh9WVh7ejIzXdOc1RRHGeAmeStU=
|
google.golang.org/genproto v0.0.0-20230403163135-c38d8f061ccd h1:sLpv7bNL1AsX3fdnWh9WVh7ejIzXdOc1RRHGeAmeStU=
|
||||||
google.golang.org/genproto v0.0.0-20230403163135-c38d8f061ccd/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak=
|
google.golang.org/genproto v0.0.0-20230403163135-c38d8f061ccd/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak=
|
||||||
|
google.golang.org/genproto v0.0.0-20230526203410-71b5a4ffd15e h1:Ao9GzfUMPH3zjVfzXG5rlWlk+Q8MXWKwWpwVQE1MXfw=
|
||||||
|
google.golang.org/genproto v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:zqTuNwFlFRsw5zIts5VnzLQxSRqh+CGOTVMlYbY0Eyk=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20230526203410-71b5a4ffd15e h1:NumxXLPfHSndr3wBBdeKiVHjGVFzi9RX2HwwQke94iY=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA=
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||||
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||||
|
@ -811,6 +838,10 @@ google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA5
|
||||||
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||||
google.golang.org/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag=
|
google.golang.org/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag=
|
||||||
google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g=
|
google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g=
|
||||||
|
google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag=
|
||||||
|
google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8=
|
||||||
|
google.golang.org/grpc v1.56.0 h1:+y7Bs8rtMd07LeXmL3NxcTLn7mUkbKZqEpPhMNkwJEE=
|
||||||
|
google.golang.org/grpc v1.56.0/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
|
||||||
google.golang.org/grpc/examples v0.0.0-20210304020650-930c79186c99 h1:qA8rMbz1wQ4DOFfM2ouD29DG9aHWBm6ZOy9BGxiUMmY=
|
google.golang.org/grpc/examples v0.0.0-20210304020650-930c79186c99 h1:qA8rMbz1wQ4DOFfM2ouD29DG9aHWBm6ZOy9BGxiUMmY=
|
||||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||||
|
|
|
@ -62,10 +62,17 @@ const (
|
||||||
const (
|
const (
|
||||||
ldapPlaceholderInput = "{input}"
|
ldapPlaceholderInput = "{input}"
|
||||||
ldapPlaceholderDistinguishedName = "{dn}"
|
ldapPlaceholderDistinguishedName = "{dn}"
|
||||||
|
ldapPlaceholderMemberOfDistinguishedName = "{memberof:dn}"
|
||||||
|
ldapPlaceholderMemberOfRelativeDistinguishedName = "{memberof:rdn}"
|
||||||
ldapPlaceholderUsername = "{username}"
|
ldapPlaceholderUsername = "{username}"
|
||||||
ldapPlaceholderDateTimeGeneralized = "{date-time:generalized}"
|
ldapPlaceholderDateTimeGeneralized = "{date-time:generalized}"
|
||||||
ldapPlaceholderDateTimeMicrosoftNTTimeEpoch = "{date-time:microsoft-nt}"
|
ldapPlaceholderDateTimeMicrosoftNTTimeEpoch = "{date-time:microsoft-nt}"
|
||||||
ldapPlaceholderDateTimeUnixEpoch = "{date-time:unix}"
|
ldapPlaceholderDateTimeUnixEpoch = "{date-time:unix}"
|
||||||
|
ldapPlaceholderDistinguishedNameAttribute = "{distinguished_name_attribute}"
|
||||||
|
ldapPlaceholderUsernameAttribute = "{username_attribute}"
|
||||||
|
ldapPlaceholderDisplayNameAttribute = "{display_name_attribute}"
|
||||||
|
ldapPlaceholderMailAttribute = "{mail_attribute}"
|
||||||
|
ldapPlaceholderMemberOfAttribute = "{member_of_attribute}"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
|
@ -3,7 +3,7 @@ package authentication
|
||||||
// This file is used to generate mocks. You can generate all mocks using the
|
// This file is used to generate mocks. You can generate all mocks using the
|
||||||
// command `go generate github.com/authelia/authelia/v4/internal/authentication`.
|
// command `go generate github.com/authelia/authelia/v4/internal/authentication`.
|
||||||
|
|
||||||
//go:generate mockgen -package authentication -destination ldap_client_mock.go -mock_names LDAPClient=MockLDAPClient github.com/authelia/authelia/v4/internal/authentication LDAPClient
|
//go:generate mockgen -package authentication -destination ldap_client_mock_test.go -mock_names LDAPClient=MockLDAPClient github.com/authelia/authelia/v4/internal/authentication LDAPClient
|
||||||
//go:generate mockgen -package authentication -destination ldap_client_factory_mock.go -mock_names LDAPClientFactory=MockLDAPClientFactory github.com/authelia/authelia/v4/internal/authentication LDAPClientFactory
|
//go:generate mockgen -package authentication -destination ldap_client_factory_mock_test.go -mock_names LDAPClientFactory=MockLDAPClientFactory github.com/authelia/authelia/v4/internal/authentication LDAPClientFactory
|
||||||
//go:generate mockgen -package authentication -destination file_user_provider_database_mock_test.go -mock_names FileUserDatabase=MockFileUserDatabase github.com/authelia/authelia/v4/internal/authentication FileUserDatabase
|
//go:generate mockgen -package authentication -destination file_user_provider_database_mock_test.go -mock_names FileUserDatabase=MockFileUserDatabase github.com/authelia/authelia/v4/internal/authentication FileUserDatabase
|
||||||
//go:generate mockgen -package authentication -destination file_user_provider_hash_mock_test.go -mock_names Hash=MockHash github.com/go-crypt/crypt/algorithm Hash
|
//go:generate mockgen -package authentication -destination file_user_provider_hash_mock_test.go -mock_names Hash=MockHash github.com/go-crypt/crypt/algorithm Hash
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
package authentication
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestControlMsftServerPolicyHints(t *testing.T) {
|
||||||
|
ct := &controlMsftServerPolicyHints{
|
||||||
|
oid: ldapOIDControlMsftServerPolicyHints,
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, ldapOIDControlMsftServerPolicyHints, ct.GetControlType())
|
||||||
|
assert.Equal(t, "Enforce the password history length constraint (MS-SAMR section 3.1.1.7.1) during password set: 1.2.840.113556.1.4.2239", ct.String())
|
||||||
|
assert.NotNil(t, ct.Encode())
|
||||||
|
}
|
|
@ -8,7 +8,7 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/go-ldap/ldap/v3"
|
ldap "github.com/go-ldap/ldap/v3"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
||||||
|
@ -45,6 +45,8 @@ type LDAPUserProvider struct {
|
||||||
groupsFilterReplacementInput bool
|
groupsFilterReplacementInput bool
|
||||||
groupsFilterReplacementUsername bool
|
groupsFilterReplacementUsername bool
|
||||||
groupsFilterReplacementDN bool
|
groupsFilterReplacementDN bool
|
||||||
|
groupsFilterReplacementsMemberOfDN bool
|
||||||
|
groupsFilterReplacementsMemberOfRDN bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewLDAPUserProvider creates a new instance of LDAPUserProvider with the ProductionLDAPClientFactory.
|
// NewLDAPUserProvider creates a new instance of LDAPUserProvider with the ProductionLDAPClientFactory.
|
||||||
|
@ -86,6 +88,7 @@ func NewLDAPUserProviderWithFactory(config schema.LDAPAuthenticationBackend, dis
|
||||||
|
|
||||||
provider.parseDynamicUsersConfiguration()
|
provider.parseDynamicUsersConfiguration()
|
||||||
provider.parseDynamicGroupsConfiguration()
|
provider.parseDynamicGroupsConfiguration()
|
||||||
|
provider.parseDynamicConfiguration()
|
||||||
|
|
||||||
return provider
|
return provider
|
||||||
}
|
}
|
||||||
|
@ -134,38 +137,11 @@ func (p *LDAPUserProvider) GetDetails(username string) (details *UserDetails, er
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
request *ldap.SearchRequest
|
groups []string
|
||||||
result *ldap.SearchResult
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Search for the users groups.
|
if groups, err = p.getUserGroups(client, username, profile); err != nil {
|
||||||
request = ldap.NewSearchRequest(
|
return nil, err
|
||||||
p.groupsBaseDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases,
|
|
||||||
0, 0, false, p.resolveGroupsFilter(username, profile), p.groupsAttributes, nil,
|
|
||||||
)
|
|
||||||
|
|
||||||
p.log.
|
|
||||||
WithField("base_dn", request.BaseDN).
|
|
||||||
WithField("filter", request.Filter).
|
|
||||||
WithField("attr", request.Attributes).
|
|
||||||
WithField("scope", request.Scope).
|
|
||||||
WithField("deref", request.DerefAliases).
|
|
||||||
Trace("Performing group search")
|
|
||||||
|
|
||||||
if result, err = p.search(client, request); err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to retrieve groups of user '%s'. Cause: %w", username, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
groups := make([]string, 0)
|
|
||||||
|
|
||||||
for _, res := range result.Entries {
|
|
||||||
if len(res.Attributes) == 0 {
|
|
||||||
p.log.Warningf("No groups retrieved from LDAP for user %s", username)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// Append all values of the document. Normally there should be only one per document.
|
|
||||||
groups = append(groups, res.Attributes[0].Values...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return &UserDetails{
|
return &UserDetails{
|
||||||
|
@ -275,14 +251,12 @@ func (p *LDAPUserProvider) search(client LDAPClient, request *ldap.SearchRequest
|
||||||
} else {
|
} else {
|
||||||
result.Referrals = append(result.Referrals, referral)
|
result.Referrals = append(result.Referrals, referral)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !p.config.PermitReferrals || len(result.Referrals) == 0 {
|
if !p.config.PermitReferrals || len(result.Referrals) == 0 {
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -357,6 +331,11 @@ func (p *LDAPUserProvider) getUserProfile(client LDAPClient, username string) (p
|
||||||
return nil, fmt.Errorf("there were %d users found when searching for '%s' but there should only be 1", len(result.Entries), username)
|
return nil, fmt.Errorf("there were %d users found when searching for '%s' but there should only be 1", len(result.Entries), username)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return p.getUserProfileResultToProfile(username, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
//nolint:gocyclo // Not overly complex.
|
||||||
|
func (p *LDAPUserProvider) getUserProfileResultToProfile(username string, result *ldap.SearchResult) (profile *ldapUserProfile, err error) {
|
||||||
userProfile := ldapUserProfile{
|
userProfile := ldapUserProfile{
|
||||||
DN: result.Entries[0].DN,
|
DN: result.Entries[0].DN,
|
||||||
}
|
}
|
||||||
|
@ -364,35 +343,50 @@ func (p *LDAPUserProvider) getUserProfile(client LDAPClient, username string) (p
|
||||||
for _, attr := range result.Entries[0].Attributes {
|
for _, attr := range result.Entries[0].Attributes {
|
||||||
attrs := len(attr.Values)
|
attrs := len(attr.Values)
|
||||||
|
|
||||||
if attr.Name == p.config.UsernameAttribute {
|
switch attr.Name {
|
||||||
|
case p.config.Attributes.Username:
|
||||||
switch attrs {
|
switch attrs {
|
||||||
case 1:
|
case 1:
|
||||||
userProfile.Username = attr.Values[0]
|
userProfile.Username = attr.Values[0]
|
||||||
case 0:
|
|
||||||
return nil, fmt.Errorf("user '%s' must have value for attribute '%s'",
|
if attr.Name == p.config.Attributes.DisplayName && userProfile.DisplayName == "" {
|
||||||
username, p.config.UsernameAttribute)
|
userProfile.DisplayName = attr.Values[0]
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("user '%s' has %d values for for attribute '%s' but the attribute must be a single value attribute",
|
|
||||||
username, attrs, p.config.UsernameAttribute)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if attr.Name == p.config.Attributes.Mail && len(userProfile.Emails) == 0 {
|
||||||
|
userProfile.Emails = []string{attr.Values[0]}
|
||||||
|
}
|
||||||
|
case 0:
|
||||||
|
return nil, fmt.Errorf("user '%s' must have value for attribute '%s'",
|
||||||
|
username, p.config.Attributes.Username)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("user '%s' has %d values for for attribute '%s' but the attribute must be a single value attribute",
|
||||||
|
username, attrs, p.config.Attributes.Username)
|
||||||
|
}
|
||||||
|
case p.config.Attributes.Mail:
|
||||||
if attrs == 0 {
|
if attrs == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if attr.Name == p.config.MailAttribute {
|
|
||||||
userProfile.Emails = attr.Values
|
userProfile.Emails = attr.Values
|
||||||
|
case p.config.Attributes.DisplayName:
|
||||||
|
if attrs == 0 {
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if attr.Name == p.config.DisplayNameAttribute {
|
|
||||||
userProfile.DisplayName = attr.Values[0]
|
userProfile.DisplayName = attr.Values[0]
|
||||||
|
case p.config.Attributes.MemberOf:
|
||||||
|
if attrs == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
userProfile.MemberOf = attr.Values
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if userProfile.Username == "" {
|
if userProfile.Username == "" {
|
||||||
return nil, fmt.Errorf("user '%s' must have value for attribute '%s'",
|
return nil, fmt.Errorf("user '%s' must have value for attribute '%s'",
|
||||||
username, p.config.UsernameAttribute)
|
username, p.config.Attributes.Username)
|
||||||
}
|
}
|
||||||
|
|
||||||
if userProfile.DN == "" {
|
if userProfile.DN == "" {
|
||||||
|
@ -402,6 +396,118 @@ func (p *LDAPUserProvider) getUserProfile(client LDAPClient, username string) (p
|
||||||
return &userProfile, nil
|
return &userProfile, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *LDAPUserProvider) getUserGroups(client LDAPClient, username string, profile *ldapUserProfile) (groups []string, err error) {
|
||||||
|
request := ldap.NewSearchRequest(
|
||||||
|
p.groupsBaseDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases,
|
||||||
|
0, 0, false, p.resolveGroupsFilter(username, profile), p.groupsAttributes, nil,
|
||||||
|
)
|
||||||
|
|
||||||
|
p.log.
|
||||||
|
WithField("base_dn", request.BaseDN).
|
||||||
|
WithField("filter", request.Filter).
|
||||||
|
WithField("attributes", request.Attributes).
|
||||||
|
WithField("scope", request.Scope).
|
||||||
|
WithField("deref", request.DerefAliases).
|
||||||
|
WithField("mode", p.config.GroupSearchMode).
|
||||||
|
Trace("Performing group search")
|
||||||
|
|
||||||
|
switch p.config.GroupSearchMode {
|
||||||
|
case "", "filter":
|
||||||
|
return p.getUserGroupsRequestFilter(client, username, profile, request)
|
||||||
|
case "memberof":
|
||||||
|
return p.getUserGroupsRequestMemberOf(client, username, profile, request)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("could not perform group search with mode '%s' as it's unknown", p.config.GroupSearchMode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *LDAPUserProvider) getUserGroupsRequestFilter(client LDAPClient, username string, _ *ldapUserProfile, request *ldap.SearchRequest) (groups []string, err error) {
|
||||||
|
var result *ldap.SearchResult
|
||||||
|
|
||||||
|
if result, err = p.search(client, request); err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to retrieve groups of user '%s'. Cause: %w", username, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, entry := range result.Entries {
|
||||||
|
if group := p.getUserGroupFromEntry(entry); len(group) != 0 {
|
||||||
|
groups = append(groups, group)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return groups, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *LDAPUserProvider) getUserGroupsRequestMemberOf(client LDAPClient, username string, profile *ldapUserProfile, request *ldap.SearchRequest) (groups []string, err error) {
|
||||||
|
var result *ldap.SearchResult
|
||||||
|
|
||||||
|
if result, err = p.search(client, request); err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to retrieve groups of user '%s'. Cause: %w", username, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, entry := range result.Entries {
|
||||||
|
if len(entry.Attributes) == 0 {
|
||||||
|
p.log.
|
||||||
|
WithField("dn", entry.DN).
|
||||||
|
WithField("attributes", request.Attributes).
|
||||||
|
WithField("mode", "memberof").
|
||||||
|
Trace("Skipping Group as the server did not return any requested attributes")
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !utils.IsStringInSliceFold(entry.DN, profile.MemberOf) {
|
||||||
|
p.log.
|
||||||
|
WithField("dn", entry.DN).
|
||||||
|
WithField("mode", "memberof").
|
||||||
|
Trace("Skipping Group as it doesn't match the users memberof entries")
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if group := p.getUserGroupFromEntry(entry); len(group) != 0 {
|
||||||
|
groups = append(groups, group)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return groups, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *LDAPUserProvider) getUserGroupFromEntry(entry *ldap.Entry) string {
|
||||||
|
attributes:
|
||||||
|
for _, attr := range entry.Attributes {
|
||||||
|
switch attr.Name {
|
||||||
|
case p.config.Attributes.GroupName:
|
||||||
|
switch len(attr.Values) {
|
||||||
|
case 0:
|
||||||
|
p.log.
|
||||||
|
WithField("dn", entry.DN).
|
||||||
|
WithField("attribute", attr.Name).
|
||||||
|
Trace("Group skipped as the server returned a null attribute")
|
||||||
|
case 1:
|
||||||
|
switch len(attr.Values[0]) {
|
||||||
|
case 0:
|
||||||
|
p.log.
|
||||||
|
WithField("dn", entry.DN).
|
||||||
|
WithField("attribute", attr.Name).
|
||||||
|
Trace("Skipping group as the configured group name attribute had no value")
|
||||||
|
|
||||||
|
default:
|
||||||
|
return attr.Values[0]
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
p.log.
|
||||||
|
WithField("dn", entry.DN).
|
||||||
|
WithField("attribute", attr.Name).
|
||||||
|
Trace("Group skipped as the server returned a multi-valued attribute but it should be a single-valued attribute")
|
||||||
|
}
|
||||||
|
|
||||||
|
break attributes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
func (p *LDAPUserProvider) resolveUsersFilter(input string) (filter string) {
|
func (p *LDAPUserProvider) resolveUsersFilter(input string) (filter string) {
|
||||||
filter = p.config.UsersFilter
|
filter = p.config.UsersFilter
|
||||||
|
|
||||||
|
@ -445,6 +551,27 @@ func (p *LDAPUserProvider) resolveGroupsFilter(input string, profile *ldapUserPr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if p.groupsFilterReplacementsMemberOfDN {
|
||||||
|
sep := fmt.Sprintf(")(%s=", p.config.Attributes.DistinguishedName)
|
||||||
|
values := make([]string, len(profile.MemberOf))
|
||||||
|
|
||||||
|
for i, memberof := range profile.MemberOf {
|
||||||
|
values[i] = ldap.EscapeFilter(memberof)
|
||||||
|
}
|
||||||
|
|
||||||
|
filter = strings.ReplaceAll(filter, ldapPlaceholderMemberOfDistinguishedName, fmt.Sprintf("(%s=%s)", p.config.Attributes.DistinguishedName, strings.Join(values, sep)))
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.groupsFilterReplacementsMemberOfRDN {
|
||||||
|
values := make([]string, len(profile.MemberOf))
|
||||||
|
|
||||||
|
for i, memberof := range profile.MemberOf {
|
||||||
|
values[i] = ldap.EscapeFilter(strings.SplitN(memberof, ",", 2)[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
filter = strings.ReplaceAll(filter, ldapPlaceholderMemberOfRelativeDistinguishedName, fmt.Sprintf("(%s)", strings.Join(values, ")(")))
|
||||||
|
}
|
||||||
|
|
||||||
p.log.Tracef("Computed groups filter is %s", filter)
|
p.log.Tracef("Computed groups filter is %s", filter)
|
||||||
|
|
||||||
return filter
|
return filter
|
||||||
|
|
|
@ -37,11 +37,6 @@ func (p *LDAPUserProvider) StartupCheck() (err error) {
|
||||||
"LDAP Server.")
|
"LDAP Server.")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !p.features.Extensions.TLS && p.config.StartTLS {
|
|
||||||
p.log.Info("Your LDAP Server does not appear to support TLS but you enabled StartTLS which may result " +
|
|
||||||
"in an error.")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,22 +85,24 @@ func (p *LDAPUserProvider) getServerSupportedFeatures(client LDAPClient) (featur
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *LDAPUserProvider) parseDynamicUsersConfiguration() {
|
func (p *LDAPUserProvider) parseDynamicUsersConfiguration() {
|
||||||
p.config.UsersFilter = strings.ReplaceAll(p.config.UsersFilter, "{username_attribute}", p.config.UsernameAttribute)
|
p.config.UsersFilter = strings.ReplaceAll(p.config.UsersFilter, ldapPlaceholderDistinguishedNameAttribute, p.config.Attributes.DistinguishedName)
|
||||||
p.config.UsersFilter = strings.ReplaceAll(p.config.UsersFilter, "{mail_attribute}", p.config.MailAttribute)
|
p.config.UsersFilter = strings.ReplaceAll(p.config.UsersFilter, ldapPlaceholderUsernameAttribute, p.config.Attributes.Username)
|
||||||
p.config.UsersFilter = strings.ReplaceAll(p.config.UsersFilter, "{display_name_attribute}", p.config.DisplayNameAttribute)
|
p.config.UsersFilter = strings.ReplaceAll(p.config.UsersFilter, ldapPlaceholderDisplayNameAttribute, p.config.Attributes.DisplayName)
|
||||||
|
p.config.UsersFilter = strings.ReplaceAll(p.config.UsersFilter, ldapPlaceholderMailAttribute, p.config.Attributes.Mail)
|
||||||
|
p.config.UsersFilter = strings.ReplaceAll(p.config.UsersFilter, ldapPlaceholderMemberOfAttribute, p.config.Attributes.MemberOf)
|
||||||
|
|
||||||
p.log.Tracef("Dynamically generated users filter is %s", p.config.UsersFilter)
|
p.log.Tracef("Dynamically generated users filter is %s", p.config.UsersFilter)
|
||||||
|
|
||||||
if !utils.IsStringInSlice(p.config.UsernameAttribute, p.usersAttributes) {
|
if len(p.config.Attributes.Username) != 0 && !utils.IsStringInSlice(p.config.Attributes.Username, p.usersAttributes) {
|
||||||
p.usersAttributes = append(p.usersAttributes, p.config.UsernameAttribute)
|
p.usersAttributes = append(p.usersAttributes, p.config.Attributes.Username)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !utils.IsStringInSlice(p.config.MailAttribute, p.usersAttributes) {
|
if len(p.config.Attributes.Mail) != 0 && !utils.IsStringInSlice(p.config.Attributes.Mail, p.usersAttributes) {
|
||||||
p.usersAttributes = append(p.usersAttributes, p.config.MailAttribute)
|
p.usersAttributes = append(p.usersAttributes, p.config.Attributes.Mail)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !utils.IsStringInSlice(p.config.DisplayNameAttribute, p.usersAttributes) {
|
if len(p.config.Attributes.DisplayName) != 0 && !utils.IsStringInSlice(p.config.Attributes.DisplayName, p.usersAttributes) {
|
||||||
p.usersAttributes = append(p.usersAttributes, p.config.DisplayNameAttribute)
|
p.usersAttributes = append(p.usersAttributes, p.config.Attributes.DisplayName)
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.config.AdditionalUsersDN != "" {
|
if p.config.AdditionalUsersDN != "" {
|
||||||
|
@ -137,8 +134,14 @@ func (p *LDAPUserProvider) parseDynamicUsersConfiguration() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *LDAPUserProvider) parseDynamicGroupsConfiguration() {
|
func (p *LDAPUserProvider) parseDynamicGroupsConfiguration() {
|
||||||
p.groupsAttributes = []string{
|
p.config.GroupsFilter = strings.ReplaceAll(p.config.GroupsFilter, ldapPlaceholderDistinguishedNameAttribute, p.config.Attributes.DistinguishedName)
|
||||||
p.config.GroupNameAttribute,
|
p.config.GroupsFilter = strings.ReplaceAll(p.config.GroupsFilter, ldapPlaceholderUsernameAttribute, p.config.Attributes.Username)
|
||||||
|
p.config.GroupsFilter = strings.ReplaceAll(p.config.GroupsFilter, ldapPlaceholderDisplayNameAttribute, p.config.Attributes.DisplayName)
|
||||||
|
p.config.GroupsFilter = strings.ReplaceAll(p.config.GroupsFilter, ldapPlaceholderMailAttribute, p.config.Attributes.Mail)
|
||||||
|
p.config.GroupsFilter = strings.ReplaceAll(p.config.GroupsFilter, ldapPlaceholderMemberOfAttribute, p.config.Attributes.MemberOf)
|
||||||
|
|
||||||
|
if len(p.config.Attributes.GroupName) != 0 && !utils.IsStringInSlice(p.config.Attributes.GroupName, p.groupsAttributes) {
|
||||||
|
p.groupsAttributes = append(p.groupsAttributes, p.config.Attributes.GroupName)
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.config.AdditionalGroupsDN != "" {
|
if p.config.AdditionalGroupsDN != "" {
|
||||||
|
@ -161,5 +164,25 @@ func (p *LDAPUserProvider) parseDynamicGroupsConfiguration() {
|
||||||
p.groupsFilterReplacementDN = true
|
p.groupsFilterReplacementDN = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if strings.Contains(p.config.GroupsFilter, ldapPlaceholderMemberOfDistinguishedName) {
|
||||||
|
p.groupsFilterReplacementsMemberOfDN = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.Contains(p.config.GroupsFilter, ldapPlaceholderMemberOfRelativeDistinguishedName) {
|
||||||
|
p.groupsFilterReplacementsMemberOfRDN = true
|
||||||
|
}
|
||||||
|
|
||||||
p.log.Tracef("Detected group filter replacements that need to be resolved per lookup are: input=%v, username=%v, dn=%v", p.groupsFilterReplacementInput, p.groupsFilterReplacementUsername, p.groupsFilterReplacementDN)
|
p.log.Tracef("Detected group filter replacements that need to be resolved per lookup are: input=%v, username=%v, dn=%v", p.groupsFilterReplacementInput, p.groupsFilterReplacementUsername, p.groupsFilterReplacementDN)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *LDAPUserProvider) parseDynamicConfiguration() {
|
||||||
|
if len(p.config.Attributes.MemberOf) != 0 {
|
||||||
|
if !utils.IsStringInSlice(p.config.Attributes.MemberOf, p.usersAttributes) {
|
||||||
|
p.usersAttributes = append(p.usersAttributes, p.config.Attributes.MemberOf)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !utils.IsStringInSlice(p.config.Attributes.MemberOf, p.groupsAttributes) {
|
||||||
|
p.groupsAttributes = append(p.groupsAttributes, p.config.Attributes.MemberOf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -5,7 +5,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
ber "github.com/go-asn1-ber/asn1-ber"
|
ber "github.com/go-asn1-ber/asn1-ber"
|
||||||
"github.com/go-ldap/ldap/v3"
|
ldap "github.com/go-ldap/ldap/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ldapEntriesContainsEntry(needle *ldap.Entry, haystack []*ldap.Entry) bool {
|
func ldapEntriesContainsEntry(needle *ldap.Entry, haystack []*ldap.Entry) bool {
|
||||||
|
@ -67,12 +67,12 @@ func ldapEscape(inputUsername string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func ldapGetReferral(err error) (referral string, ok bool) {
|
func ldapGetReferral(err error) (referral string, ok bool) {
|
||||||
if !ldap.IsErrorWithCode(err, ldap.LDAPResultReferral) {
|
switch e := err.(type) {
|
||||||
|
case *ldap.Error:
|
||||||
|
if e.ResultCode != ldap.LDAPResultReferral {
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
|
|
||||||
switch e := err.(type) {
|
|
||||||
case *ldap.Error:
|
|
||||||
if e.Packet == nil {
|
if e.Packet == nil {
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
|
|
|
@ -193,6 +193,12 @@ func TestLDAPGetReferral(t *testing.T) {
|
||||||
expectedReferral: "",
|
expectedReferral: "",
|
||||||
expectedOK: false,
|
expectedOK: false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
description: "ShouldNotGetInvalidErrType",
|
||||||
|
have: errors.New("not an err"),
|
||||||
|
expectedReferral: "",
|
||||||
|
expectedOK: false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
|
|
@ -82,6 +82,7 @@ type ldapUserProfile struct {
|
||||||
Emails []string
|
Emails []string
|
||||||
DisplayName string
|
DisplayName string
|
||||||
Username string
|
Username string
|
||||||
|
MemberOf []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// LDAPSupportedFeatures represents features which a server may support which are implemented in code.
|
// LDAPSupportedFeatures represents features which a server may support which are implemented in code.
|
||||||
|
|
|
@ -16,6 +16,7 @@ import (
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/valyala/fasthttp"
|
"github.com/valyala/fasthttp"
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
|
||||||
"github.com/authelia/authelia/v4/internal/authentication"
|
"github.com/authelia/authelia/v4/internal/authentication"
|
||||||
"github.com/authelia/authelia/v4/internal/server"
|
"github.com/authelia/authelia/v4/internal/server"
|
||||||
|
@ -33,6 +34,17 @@ func NewServerService(name string, server *fasthttp.Server, listener net.Listene
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewGRCPServerService creates a new ServerService with the appropriate logger etc.
|
||||||
|
func NewGRCPServerService(name string, server *grpc.Server, listener net.Listener, isTLS bool, log *logrus.Logger) (service *GRCPServerService) {
|
||||||
|
return &GRCPServerService{
|
||||||
|
name: name,
|
||||||
|
server: server,
|
||||||
|
listener: listener,
|
||||||
|
isTLS: isTLS,
|
||||||
|
log: log.WithFields(map[string]any{logFieldService: serviceTypeServer, serviceTypeServer: name}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// NewFileWatcherService creates a new FileWatcherService with the appropriate logger etc.
|
// NewFileWatcherService creates a new FileWatcherService with the appropriate logger etc.
|
||||||
func NewFileWatcherService(name, path string, reload ProviderReload, log *logrus.Logger) (service *FileWatcherService, err error) {
|
func NewFileWatcherService(name, path string, reload ProviderReload, log *logrus.Logger) (service *FileWatcherService, err error) {
|
||||||
if path == "" {
|
if path == "" {
|
||||||
|
@ -161,6 +173,54 @@ func (service *ServerService) Log() *logrus.Entry {
|
||||||
return service.log
|
return service.log
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GRCPServerService is a Service which runs a gRCP server.
|
||||||
|
type GRCPServerService struct {
|
||||||
|
name string
|
||||||
|
server *grpc.Server
|
||||||
|
isTLS bool
|
||||||
|
listener net.Listener
|
||||||
|
log *logrus.Entry
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServiceType returns the service type for this service, which is always 'server'.
|
||||||
|
func (service *GRCPServerService) ServiceType() string {
|
||||||
|
return serviceTypeServer
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServiceName returns the individual name for this service.
|
||||||
|
func (service *GRCPServerService) ServiceName() string {
|
||||||
|
return service.name
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the ServerService.
|
||||||
|
func (service *GRCPServerService) Run() (err error) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
service.log.WithError(recoverErr(r)).Error("Critical error caught (recovered)")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
service.log.Infof(fmtLogServerListening, connectionType(service.isTLS), service.listener.Addr().String())
|
||||||
|
|
||||||
|
if err = service.server.Serve(service.listener); err != nil {
|
||||||
|
service.log.WithError(err).Error("Error returned attempting to serve requests")
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shutdown the ServerService.
|
||||||
|
func (service *GRCPServerService) Shutdown() {
|
||||||
|
service.server.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log returns the *logrus.Entry of the ServerService.
|
||||||
|
func (service *GRCPServerService) Log() *logrus.Entry {
|
||||||
|
return service.log
|
||||||
|
}
|
||||||
|
|
||||||
// FileWatcherService is a Service that watches files for changes.
|
// FileWatcherService is a Service that watches files for changes.
|
||||||
type FileWatcherService struct {
|
type FileWatcherService struct {
|
||||||
name string
|
name string
|
||||||
|
@ -272,6 +332,19 @@ func svcSvrMetricsFunc(ctx *CmdCtx) (service Service) {
|
||||||
return service
|
return service
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func svcSvrGRPCFunc(ctx *CmdCtx) (service Service) {
|
||||||
|
switch svr, listener, isTLS, err := server.CreateGRPCServer(ctx.config, ctx.providers); {
|
||||||
|
case err != nil:
|
||||||
|
ctx.log.WithError(err).Fatal("Create Server Service (gRPC) returned error")
|
||||||
|
case svr != nil && listener != nil:
|
||||||
|
service = NewGRCPServerService("gRCP", svr, listener, isTLS, ctx.log)
|
||||||
|
default:
|
||||||
|
ctx.log.Debug("Create Server Service (gRPC) skipped")
|
||||||
|
}
|
||||||
|
|
||||||
|
return service
|
||||||
|
}
|
||||||
|
|
||||||
func svcWatcherUsersFunc(ctx *CmdCtx) (service Service) {
|
func svcWatcherUsersFunc(ctx *CmdCtx) (service Service) {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
|
@ -312,7 +385,7 @@ func servicesRun(ctx *CmdCtx) {
|
||||||
)
|
)
|
||||||
|
|
||||||
for _, serviceFunc := range []func(ctx *CmdCtx) Service{
|
for _, serviceFunc := range []func(ctx *CmdCtx) Service{
|
||||||
svcSvrMainFunc, svcSvrMetricsFunc,
|
svcSvrMainFunc, svcSvrGRPCFunc, svcSvrMetricsFunc,
|
||||||
svcWatcherUsersFunc,
|
svcWatcherUsersFunc,
|
||||||
} {
|
} {
|
||||||
if service := serviceFunc(ctx); service != nil {
|
if service := serviceFunc(ctx); service != nil {
|
||||||
|
|
|
@ -550,7 +550,7 @@ func (ctx *CmdCtx) StorageUserWebAuthnListRunE(cmd *cobra.Command, args []string
|
||||||
|
|
||||||
user := args[0]
|
user := args[0]
|
||||||
|
|
||||||
devices, err = ctx.providers.StorageProvider.LoadWebAuthnDevicesByUsername(ctx, "", user)
|
devices, err = ctx.providers.StorageProvider.LoadWebAuthnDevicesByUsername(ctx, user)
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case len(devices) == 0 || (err != nil && errors.Is(err, storage.ErrNoWebAuthnDevice)):
|
case len(devices) == 0 || (err != nil && errors.Is(err, storage.ErrNoWebAuthnDevice)):
|
||||||
|
|
|
@ -39,7 +39,7 @@ default_redirection_url: 'https://home.example.com/'
|
||||||
## Set the default 2FA method for new users and for when a user has a preferred method configured that has been
|
## Set the default 2FA method for new users and for when a user has a preferred method configured that has been
|
||||||
## disabled. This setting must be a method that is enabled.
|
## disabled. This setting must be a method that is enabled.
|
||||||
## Options are totp, webauthn, mobile_push.
|
## Options are totp, webauthn, mobile_push.
|
||||||
default_2fa_method: ''
|
# default_2fa_method: ''
|
||||||
|
|
||||||
##
|
##
|
||||||
## Server Configuration
|
## Server Configuration
|
||||||
|
@ -47,15 +47,12 @@ default_2fa_method: ''
|
||||||
server:
|
server:
|
||||||
## The address for the Main server to listen on in the address common syntax.
|
## The address for the Main server to listen on in the address common syntax.
|
||||||
## Formats:
|
## Formats:
|
||||||
## - [<scheme>://]<hostname>[:<port>]
|
## - [<scheme>://]<hostname>[:<port>][/<path>]
|
||||||
## - [<scheme>://][hostname]:<port>
|
## - [<scheme>://][hostname]:<port>[/<path>]
|
||||||
## Square brackets indicate optional portions of the format. Scheme must be 'tcp', 'tcp4', 'tcp6', or 'unix'.
|
## Square brackets indicate optional portions of the format. Scheme must be 'tcp', 'tcp4', 'tcp6', or 'unix'.
|
||||||
## The default scheme is 'unix' if the address is an absolute path otherwise it's 'tcp'. The default port is '9091'.
|
## The default scheme is 'unix' if the address is an absolute path otherwise it's 'tcp'. The default port is '9091'.
|
||||||
address: 'tcp://:9091'
|
## If the path is specified this configures the router to handle both the `/` path and the configured path.
|
||||||
|
address: 'tcp://:9091/'
|
||||||
## Set the single level path Authelia listens on.
|
|
||||||
## Must be alphanumeric chars and should not contain any slashes.
|
|
||||||
path: ''
|
|
||||||
|
|
||||||
## Set the path on disk to Authelia assets.
|
## Set the path on disk to Authelia assets.
|
||||||
## Useful to allow overriding of specific static assets.
|
## Useful to allow overriding of specific static assets.
|
||||||
|
@ -162,11 +159,12 @@ telemetry:
|
||||||
|
|
||||||
## The address for the Metrics server to listen on in the address common syntax.
|
## The address for the Metrics server to listen on in the address common syntax.
|
||||||
## Formats:
|
## Formats:
|
||||||
## - [<scheme>://]<hostname>[:<port>]
|
## - [<scheme>://]<hostname>[:<port>][/<path>]
|
||||||
## - [<scheme>://][hostname]:<port>
|
## - [<scheme>://][hostname]:<port>[/<path>]
|
||||||
## Square brackets indicate optional portions of the format. Scheme must be 'tcp', 'tcp4', 'tcp6', or 'unix'.
|
## Square brackets indicate optional portions of the format. Scheme must be 'tcp', 'tcp4', 'tcp6', or 'unix'.
|
||||||
## The default scheme is 'unix' if the address is an absolute path otherwise it's 'tcp'. The default port is '9959'.
|
## The default scheme is 'unix' if the address is an absolute path otherwise it's 'tcp'. The default port is '9959'.
|
||||||
address: 'tcp://:9959'
|
## If the path is not specified it defaults to `/metrics`.
|
||||||
|
address: 'tcp://:9959/metrics'
|
||||||
|
|
||||||
## Metrics Server Buffers configuration.
|
## Metrics Server Buffers configuration.
|
||||||
# buffers:
|
# buffers:
|
||||||
|
@ -203,7 +201,7 @@ totp:
|
||||||
## The TOTP algorithm to use.
|
## The TOTP algorithm to use.
|
||||||
## It is CRITICAL you read the documentation before changing this option:
|
## It is CRITICAL you read the documentation before changing this option:
|
||||||
## https://www.authelia.com/c/totp#algorithm
|
## https://www.authelia.com/c/totp#algorithm
|
||||||
algorithm: 'sha1'
|
algorithm: 'SHA1'
|
||||||
|
|
||||||
## The number of digits a user has to input. Must either be 6 or 8.
|
## The number of digits a user has to input. Must either be 6 or 8.
|
||||||
## Changing this option only affects newly generated TOTP configurations.
|
## Changing this option only affects newly generated TOTP configurations.
|
||||||
|
@ -300,7 +298,7 @@ authentication_backend:
|
||||||
|
|
||||||
## External reset password url that redirects the user to an external reset portal. This disables the internal reset
|
## External reset password url that redirects the user to an external reset portal. This disables the internal reset
|
||||||
## functionality.
|
## functionality.
|
||||||
custom_url: ''
|
# custom_url: ''
|
||||||
|
|
||||||
## The amount of time to wait before we refresh data from the authentication backend in the duration common syntax.
|
## The amount of time to wait before we refresh data from the authentication backend in the duration common syntax.
|
||||||
## To disable this feature set it to 'disable', this will slightly reduce security because for Authelia, users will
|
## To disable this feature set it to 'disable', this will slightly reduce security because for Authelia, users will
|
||||||
|
@ -317,7 +315,7 @@ authentication_backend:
|
||||||
## because it allows Authelia to offload the stateful operations
|
## because it allows Authelia to offload the stateful operations
|
||||||
## onto the LDAP service.
|
## onto the LDAP service.
|
||||||
# ldap:
|
# ldap:
|
||||||
## The address of the LDAP server to connect to in the address common syntax.
|
## The address of the directory server to connect to in the address common syntax.
|
||||||
## Format: [<scheme>://]<hostname>[:<port>].
|
## Format: [<scheme>://]<hostname>[:<port>].
|
||||||
## Square brackets indicate optional portions of the format. Scheme must be 'ldap', 'ldaps', or 'ldapi`.
|
## Square brackets indicate optional portions of the format. Scheme must be 'ldap', 'ldaps', or 'ldapi`.
|
||||||
## The default scheme is 'ldapi' if the address is an absolute path otherwise it's 'ldaps'.
|
## The default scheme is 'ldapi' if the address is an absolute path otherwise it's 'ldaps'.
|
||||||
|
@ -396,23 +394,13 @@ authentication_backend:
|
||||||
# SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH
|
# SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH
|
||||||
# ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg
|
# ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg
|
||||||
# 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg
|
# 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg
|
||||||
# HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRsDO_NOT_USE=
|
# HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRs_DO_NOT_USE=
|
||||||
# -----END RSA PRIVATE KEY-----
|
# -----END RSA PRIVATE KEY-----
|
||||||
|
|
||||||
## The distinguished name of the container searched for objects in the directory information tree.
|
## The distinguished name of the container searched for objects in the directory information tree.
|
||||||
## See also: additional_users_dn, additional_groups_dn.
|
## See also: additional_users_dn, additional_groups_dn.
|
||||||
# base_dn: 'dc=example,dc=com'
|
# base_dn: 'dc=example,dc=com'
|
||||||
|
|
||||||
## The attribute holding the username of the user. This attribute is used to populate the username in the session
|
|
||||||
## information. For your information, Microsoft Active Directory usually uses 'sAMAccountName' and OpenLDAP usually
|
|
||||||
## uses 'uid'. Beware that this attribute holds the unique identifiers for the users binding the user and the
|
|
||||||
## configuration stored in database. Therefore only single value attributes are allowed and the value must never be
|
|
||||||
## changed once attributed to a user otherwise it would break the configuration for that user. Technically,
|
|
||||||
## non-unique attributes like 'mail' can also be used but we don't recommend using them, we instead advise to use
|
|
||||||
## a filter to perform alternative lookups and the attributes mentioned above (sAMAccountName and uid) to
|
|
||||||
## follow https://datatracker.ietf.org/doc/html/rfc2307.
|
|
||||||
# username_attribute: 'uid'
|
|
||||||
|
|
||||||
## The additional_users_dn is prefixed to base_dn and delimited by a comma when searching for users.
|
## The additional_users_dn is prefixed to base_dn and delimited by a comma when searching for users.
|
||||||
## i.e. with this set to OU=Users and base_dn set to DC=a,DC=com; OU=Users,DC=a,DC=com is searched for users.
|
## i.e. with this set to OU=Users and base_dn set to DC=a,DC=com; OU=Users,DC=a,DC=com is searched for users.
|
||||||
# additional_users_dn: 'ou=users'
|
# additional_users_dn: 'ou=users'
|
||||||
|
@ -443,15 +431,9 @@ authentication_backend:
|
||||||
## (&(uniqueMember={dn})(objectClass=groupOfUniqueNames))
|
## (&(uniqueMember={dn})(objectClass=groupOfUniqueNames))
|
||||||
# groups_filter: '(&(member={dn})(objectClass=groupOfNames))'
|
# groups_filter: '(&(member={dn})(objectClass=groupOfNames))'
|
||||||
|
|
||||||
## The attribute holding the name of the group.
|
## The group search mode to use. Options are 'filter' or 'memberof'. It's essential to read the docs if you wish to
|
||||||
# group_name_attribute: 'cn'
|
## use 'memberof'. Also 'filter' is the best choice for most use cases.
|
||||||
|
# group_search_mode: 'filter'
|
||||||
## The attribute holding the mail address of the user. If multiple email addresses are defined for a user, only the
|
|
||||||
## first one returned by the LDAP server is used.
|
|
||||||
# mail_attribute: 'mail'
|
|
||||||
|
|
||||||
## The attribute holding the display name of the user. This will be used to greet an authenticated user.
|
|
||||||
# display_name_attribute: 'displayName'
|
|
||||||
|
|
||||||
## Follow referrals returned by the server.
|
## Follow referrals returned by the server.
|
||||||
## This is especially useful for environments where read-only servers exist. Only implemented for write operations.
|
## This is especially useful for environments where read-only servers exist. Only implemented for write operations.
|
||||||
|
@ -462,6 +444,37 @@ authentication_backend:
|
||||||
## Password can also be set using a secret: https://www.authelia.com/c/secrets
|
## Password can also be set using a secret: https://www.authelia.com/c/secrets
|
||||||
# password: 'password'
|
# password: 'password'
|
||||||
|
|
||||||
|
## The attributes for users and objects from the directory server.
|
||||||
|
# attributes:
|
||||||
|
|
||||||
|
## The distinguished name attribute if your directory server supports it. Users should read the docs before
|
||||||
|
## configuring. Only used for the 'memberof' group search mode.
|
||||||
|
# distinguished_name: ''
|
||||||
|
|
||||||
|
## The attribute holding the username of the user. This attribute is used to populate the username in the session
|
||||||
|
## information. For your information, Microsoft Active Directory usually uses 'sAMAccountName' and OpenLDAP
|
||||||
|
## usually uses 'uid'. Beware that this attribute holds the unique identifiers for the users binding the user and
|
||||||
|
## the configuration stored in database; therefore only single value attributes are allowed and the value must
|
||||||
|
## never be changed once attributed to a user otherwise it would break the configuration for that user.
|
||||||
|
## Technically non-unique attributes like 'mail' can also be used but we don't recommend using them, we instead
|
||||||
|
## advise to use a filter to perform alternative lookups and the attributes mentioned above
|
||||||
|
## (sAMAccountName and uid) to follow https://datatracker.ietf.org/doc/html/rfc2307.
|
||||||
|
# username: 'uid'
|
||||||
|
|
||||||
|
## The attribute holding the display name of the user. This will be used to greet an authenticated user.
|
||||||
|
# display_name: 'displayName'
|
||||||
|
|
||||||
|
## The attribute holding the mail address of the user. If multiple email addresses are defined for a user, only
|
||||||
|
## the first one returned by the directory server is used.
|
||||||
|
# mail: 'mail'
|
||||||
|
|
||||||
|
## The attribute which provides distinguished names of groups an object is a member of.
|
||||||
|
## Only used for the 'memberof' group search mode.
|
||||||
|
# member_of: 'memberOf'
|
||||||
|
|
||||||
|
## The attribute holding the name of the group.
|
||||||
|
# group_name: 'cn'
|
||||||
|
|
||||||
##
|
##
|
||||||
## File (Authentication Provider)
|
## File (Authentication Provider)
|
||||||
##
|
##
|
||||||
|
@ -820,7 +833,7 @@ session:
|
||||||
# SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH
|
# SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH
|
||||||
# ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg
|
# ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg
|
||||||
# 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg
|
# 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg
|
||||||
# HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRsDO_NOT_USE=
|
# HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRs_DO_NOT_USE=
|
||||||
# -----END RSA PRIVATE KEY-----
|
# -----END RSA PRIVATE KEY-----
|
||||||
|
|
||||||
## The Redis HA configuration options.
|
## The Redis HA configuration options.
|
||||||
|
@ -965,7 +978,7 @@ regulation:
|
||||||
# SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH
|
# SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH
|
||||||
# ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg
|
# ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg
|
||||||
# 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg
|
# 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg
|
||||||
# HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRsDO_NOT_USE=
|
# HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRs_DO_NOT_USE=
|
||||||
# -----END RSA PRIVATE KEY-----
|
# -----END RSA PRIVATE KEY-----
|
||||||
|
|
||||||
##
|
##
|
||||||
|
@ -1048,7 +1061,7 @@ regulation:
|
||||||
# SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH
|
# SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH
|
||||||
# ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg
|
# ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg
|
||||||
# 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg
|
# 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg
|
||||||
# HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRsDO_NOT_USE=
|
# HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRs_DO_NOT_USE=
|
||||||
# -----END RSA PRIVATE KEY-----
|
# -----END RSA PRIVATE KEY-----
|
||||||
|
|
||||||
##
|
##
|
||||||
|
@ -1166,7 +1179,7 @@ notifier:
|
||||||
# SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH
|
# SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH
|
||||||
# ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg
|
# ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg
|
||||||
# 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg
|
# 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg
|
||||||
# HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRsDO_NOT_USE=
|
# HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRs_DO_NOT_USE=
|
||||||
# -----END RSA PRIVATE KEY-----
|
# -----END RSA PRIVATE KEY-----
|
||||||
|
|
||||||
##
|
##
|
||||||
|
@ -1208,7 +1221,7 @@ notifier:
|
||||||
# SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH
|
# SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH
|
||||||
# ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg
|
# ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg
|
||||||
# 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg
|
# 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg
|
||||||
# HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRsDO_NOT_USE=
|
# HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRs_DO_NOT_USE=
|
||||||
# -----END RSA PRIVATE KEY-----
|
# -----END RSA PRIVATE KEY-----
|
||||||
|
|
||||||
|
|
||||||
|
@ -1238,7 +1251,7 @@ notifier:
|
||||||
# SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH
|
# SKECIQDY6G8gvsYJdXCE9UJ7ukoLrRHxt/frhAtmSY5lVAPuMwIhAMzuDrJo73LH
|
||||||
# ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg
|
# ZyEaqIXc5pIiX3Sag43csPDHfuXdtT2NAiEAhyRKGJzDxiDlefFU+sGWYK/z/iYg
|
||||||
# 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg
|
# 0Rvz/kbV8UvnJwECIQDAYN6VJ6NZmc27qv33JIejOfdoTEEhZMMKVg1PlxE0ZQIg
|
||||||
# HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRsDO_NOT_USE=
|
# HFpJiFxZES3QvVPr8deBXORPurqD5uU85NKsf61AdRs_DO_NOT_USE=
|
||||||
# -----END RSA PRIVATE KEY-----
|
# -----END RSA PRIVATE KEY-----
|
||||||
|
|
||||||
## Optional matching certificate chain in PEM DER form that matches the issuer_private_key. All certificates within
|
## Optional matching certificate chain in PEM DER form that matches the issuer_private_key. All certificates within
|
||||||
|
@ -1287,6 +1300,7 @@ notifier:
|
||||||
## List of endpoints in addition to the metadata endpoints to permit cross-origin requests on.
|
## List of endpoints in addition to the metadata endpoints to permit cross-origin requests on.
|
||||||
# endpoints:
|
# endpoints:
|
||||||
# - 'authorization'
|
# - 'authorization'
|
||||||
|
# - 'pushed-authorization-request'
|
||||||
# - 'token'
|
# - 'token'
|
||||||
# - 'revocation'
|
# - 'revocation'
|
||||||
# - 'introspection'
|
# - 'introspection'
|
||||||
|
@ -1412,7 +1426,7 @@ notifier:
|
||||||
# key: |
|
# key: |
|
||||||
# -----BEGIN RSA PUBLIC KEY-----
|
# -----BEGIN RSA PUBLIC KEY-----
|
||||||
# MEgCQQDAwV26ZA1lodtOQxNrJ491gWT+VzFum9IeZ+WTmMypYWyW1CzXKwsvTHDz
|
# MEgCQQDAwV26ZA1lodtOQxNrJ491gWT+VzFum9IeZ+WTmMypYWyW1CzXKwsvTHDz
|
||||||
# 9ec+jserR3EMQ0Rr24lj13FL1ib5AgMBAAE=
|
# 9ec+jserR3EMQ0Rr24lj13FL1ib5AgMBAAE_DO_NOT_USE=
|
||||||
# -----END RSA PUBLIC KEY----
|
# -----END RSA PUBLIC KEY----
|
||||||
|
|
||||||
## The matching certificate chain in PEM DER form that matches the key if available.
|
## The matching certificate chain in PEM DER form that matches the key if available.
|
||||||
|
|
|
@ -38,6 +38,8 @@ const (
|
||||||
errFmtDecodeHookCouldNotParse = "could not decode '%s' to a %s%s: %w"
|
errFmtDecodeHookCouldNotParse = "could not decode '%s' to a %s%s: %w"
|
||||||
errFmtDecodeHookCouldNotParseBasic = "could not decode to a %s%s: %w"
|
errFmtDecodeHookCouldNotParseBasic = "could not decode to a %s%s: %w"
|
||||||
errFmtDecodeHookCouldNotParseEmptyValue = "could not decode an empty value to a %s%s: %w"
|
errFmtDecodeHookCouldNotParseEmptyValue = "could not decode an empty value to a %s%s: %w"
|
||||||
|
|
||||||
|
errFmtSpecialRemappedKey = "configuration key '%s' is deprecated in %s and has been replaced by '%s' when combined with the '%s' in the format of '%s': this should be automatically mapped for you but you will need to adjust your configuration to remove this message"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
|
@ -634,11 +634,11 @@ func StringToPasswordDigestHookFunc() mapstructure.DecodeHookFuncType {
|
||||||
|
|
||||||
var result *schema.PasswordDigest
|
var result *schema.PasswordDigest
|
||||||
|
|
||||||
|
if dataStr != "" {
|
||||||
if !strings.HasPrefix(dataStr, "$") {
|
if !strings.HasPrefix(dataStr, "$") {
|
||||||
dataStr = fmt.Sprintf(plaintext.EncodingFmt, plaintext.AlgIdentifierPlainText, dataStr)
|
dataStr = fmt.Sprintf(plaintext.EncodingFmt, plaintext.AlgIdentifierPlainText, dataStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
if dataStr != "" {
|
|
||||||
if result, err = schema.DecodePasswordDigest(dataStr); err != nil {
|
if result, err = schema.DecodePasswordDigest(dataStr); err != nil {
|
||||||
return nil, fmt.Errorf(errFmtDecodeHookCouldNotParse, dataStr, prefixType, expectedType.String(), err)
|
return nil, fmt.Errorf(errFmtDecodeHookCouldNotParse, dataStr, prefixType, expectedType.String(), err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,8 +4,10 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"net/mail"
|
"net/mail"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
@ -786,24 +788,6 @@ func TestStringToRegexpFuncPointers(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStringToAddressHookFunc(t *testing.T) {
|
func TestStringToAddressHookFunc(t *testing.T) {
|
||||||
mustParseAddress := func(a string) (addr schema.Address) {
|
|
||||||
addrs, err := schema.NewAddress(a)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return *addrs
|
|
||||||
}
|
|
||||||
|
|
||||||
mustParseAddressPtr := func(a string) (addr *schema.Address) {
|
|
||||||
addr, err := schema.NewAddress(a)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return addr
|
|
||||||
}
|
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
have any
|
have any
|
||||||
|
@ -814,13 +798,13 @@ func TestStringToAddressHookFunc(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "ShouldDecodeNonPtr",
|
name: "ShouldDecodeNonPtr",
|
||||||
have: "tcp://0.0.0.0:2020",
|
have: "tcp://0.0.0.0:2020",
|
||||||
expected: mustParseAddress("tcp://0.0.0.0:2020"),
|
expected: MustParseAddress("tcp://0.0.0.0:2020"),
|
||||||
decode: true,
|
decode: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "ShouldDecodePtr",
|
name: "ShouldDecodePtr",
|
||||||
have: "tcp://0.0.0.0:2020",
|
have: "tcp://0.0.0.0:2020",
|
||||||
expected: mustParseAddressPtr("tcp://0.0.0.0:2020"),
|
expected: MustParseAddressPtr("tcp://0.0.0.0:2020"),
|
||||||
decode: true,
|
decode: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -872,6 +856,90 @@ func TestStringToAddressHookFunc(t *testing.T) {
|
||||||
err: "could not decode 'tcp://&!@^#*&!@#&*@!:2020' to a schema.Address: could not parse string 'tcp://&!@^#*&!@#&*@!:2020' as address: expected format is [<scheme>://]<hostname>[:<port>]: parse \"tcp://&!@^\": invalid character \"^\" in host name",
|
err: "could not decode 'tcp://&!@^#*&!@#&*@!:2020' to a schema.Address: could not parse string 'tcp://&!@^#*&!@#&*@!:2020' as address: expected format is [<scheme>://]<hostname>[:<port>]: parse \"tcp://&!@^\": invalid character \"^\" in host name",
|
||||||
decode: false,
|
decode: false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "ShouldDecodeTCP",
|
||||||
|
have: "tcp://127.0.0.1",
|
||||||
|
expected: schema.AddressTCP{Address: MustParseAddress("tcp://127.0.0.1")},
|
||||||
|
err: "",
|
||||||
|
decode: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ShouldDecodeTCPPtr",
|
||||||
|
have: "tcp://127.0.0.1",
|
||||||
|
expected: &schema.AddressTCP{Address: MustParseAddress("tcp://127.0.0.1")},
|
||||||
|
err: "",
|
||||||
|
decode: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ShouldDecodeUDP",
|
||||||
|
have: "udp://127.0.0.1",
|
||||||
|
expected: schema.AddressUDP{Address: MustParseAddress("udp://127.0.0.1")},
|
||||||
|
err: "",
|
||||||
|
decode: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ShouldDecodeUDPPtr",
|
||||||
|
have: "udp://127.0.0.1",
|
||||||
|
expected: &schema.AddressUDP{Address: MustParseAddress("udp://127.0.0.1")},
|
||||||
|
err: "",
|
||||||
|
decode: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ShouldDecodeLDAP",
|
||||||
|
have: "ldap://127.0.0.1",
|
||||||
|
expected: schema.AddressLDAP{Address: MustParseAddress("ldap://127.0.0.1")},
|
||||||
|
err: "",
|
||||||
|
decode: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ShouldDecodeLDAPPtr",
|
||||||
|
have: "ldap://127.0.0.1",
|
||||||
|
expected: &schema.AddressLDAP{Address: MustParseAddress("ldap://127.0.0.1")},
|
||||||
|
err: "",
|
||||||
|
decode: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ShouldDecodeSMTP",
|
||||||
|
have: "smtp://127.0.0.1",
|
||||||
|
expected: schema.AddressSMTP{Address: MustParseAddress("smtp://127.0.0.1")},
|
||||||
|
err: "",
|
||||||
|
decode: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ShouldDecodeSMTPPtr",
|
||||||
|
have: "smtp://127.0.0.1",
|
||||||
|
expected: &schema.AddressSMTP{Address: MustParseAddress("smtp://127.0.0.1")},
|
||||||
|
err: "",
|
||||||
|
decode: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ShouldFailDecodeTCP",
|
||||||
|
have: "@@@@@@@",
|
||||||
|
expected: schema.AddressTCP{Address: MustParseAddress("tcp://127.0.0.1")},
|
||||||
|
err: "could not decode '@@@@@@@' to a schema.AddressTCP: error validating the address: the url 'tcp://%40%40%40%40%40%40@' appears to have user info but this is not valid for addresses",
|
||||||
|
decode: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ShouldFailDecodeUDP",
|
||||||
|
have: "@@@@@@@",
|
||||||
|
expected: schema.AddressUDP{Address: MustParseAddress("udp://127.0.0.1")},
|
||||||
|
err: "could not decode '@@@@@@@' to a schema.AddressUDP: error validating the address: the url 'udp://%40%40%40%40%40%40@' appears to have user info but this is not valid for addresses",
|
||||||
|
decode: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ShouldFailDecodeLDAP",
|
||||||
|
have: "@@@@@@@",
|
||||||
|
expected: schema.AddressLDAP{Address: MustParseAddress("ldap://127.0.0.1")},
|
||||||
|
err: "could not decode '@@@@@@@' to a schema.AddressLDAP: error validating the address: the url 'ldaps://%40%40%40%40%40%40@' appears to have user info but this is not valid for addresses",
|
||||||
|
decode: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ShouldFailDecodeSMTP",
|
||||||
|
have: "@@@@@@@",
|
||||||
|
expected: schema.AddressSMTP{Address: MustParseAddress("smtp://127.0.0.1")},
|
||||||
|
err: "could not decode '@@@@@@@' to a schema.AddressSMTP: error validating the address: the url 'smtp://%40%40%40%40%40%40@' appears to have user info but this is not valid for addresses",
|
||||||
|
decode: false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
hook := configuration.StringToAddressHookFunc()
|
hook := configuration.StringToAddressHookFunc()
|
||||||
|
@ -1095,6 +1163,160 @@ func TestStringToX509CertificateHookFunc(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestStringToPasswordDigestHookFunc(t *testing.T) {
|
||||||
|
var nilvalue *schema.PasswordDigest
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
have any
|
||||||
|
expected any
|
||||||
|
err string
|
||||||
|
decode bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"ShouldParse",
|
||||||
|
"$plaintext$example",
|
||||||
|
MustParsePasswordDigest("$plaintext$example"),
|
||||||
|
"",
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ShouldParsePtr",
|
||||||
|
"$plaintext$example",
|
||||||
|
MustParsePasswordDigestPtr("$plaintext$example"),
|
||||||
|
"",
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ShouldNotParseUnknown",
|
||||||
|
"$abc$example",
|
||||||
|
schema.PasswordDigest{},
|
||||||
|
"could not decode '$abc$example' to a schema.PasswordDigest: provided encoded hash has an invalid identifier: the identifier 'abc' is unknown to the decoder",
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ShouldNotParseWrongType",
|
||||||
|
"$abc$example",
|
||||||
|
schema.TLSVersion{},
|
||||||
|
"",
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ShouldNotParseWrongTypePtr",
|
||||||
|
"$abc$example",
|
||||||
|
&schema.TLSVersion{},
|
||||||
|
"",
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ShouldNotParseEmptyString",
|
||||||
|
"",
|
||||||
|
schema.PasswordDigest{},
|
||||||
|
"could not decode an empty value to a schema.PasswordDigest: must have a non-empty value",
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ShouldParseEmptyStringPtr",
|
||||||
|
"",
|
||||||
|
nilvalue,
|
||||||
|
"",
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
hook := configuration.StringToPasswordDigestHookFunc()
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
actual, err := hook(reflect.TypeOf(tc.have), reflect.TypeOf(tc.expected), tc.have)
|
||||||
|
if tc.err != "" {
|
||||||
|
assert.EqualError(t, err, tc.err)
|
||||||
|
|
||||||
|
if !tc.decode {
|
||||||
|
assert.Nil(t, actual)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
if tc.decode {
|
||||||
|
assert.Equal(t, tc.expected, actual)
|
||||||
|
} else {
|
||||||
|
assert.Equal(t, tc.have, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStringToTLSVersionHookFunc(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
have any
|
||||||
|
expected any
|
||||||
|
err string
|
||||||
|
decode bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"ShouldParseTLS1.3",
|
||||||
|
"TLS1.3",
|
||||||
|
schema.TLSVersion{Value: tls.VersionTLS13},
|
||||||
|
"",
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ShouldParseTLS1.3PTR",
|
||||||
|
"TLS1.3",
|
||||||
|
&schema.TLSVersion{Value: tls.VersionTLS13},
|
||||||
|
"",
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ShouldParseTLS1.2",
|
||||||
|
"TLS1.2",
|
||||||
|
schema.TLSVersion{Value: tls.VersionTLS12},
|
||||||
|
"",
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ShouldNotParseInt",
|
||||||
|
1,
|
||||||
|
&schema.TLSVersion{},
|
||||||
|
"",
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ShouldNotParseNonVersion",
|
||||||
|
"1",
|
||||||
|
&schema.TLSVersion{},
|
||||||
|
"could not decode '1' to a *schema.TLSVersion: supplied tls version isn't supported",
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
hook := configuration.StringToTLSVersionHookFunc()
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
actual, err := hook(reflect.TypeOf(tc.have), reflect.TypeOf(tc.expected), tc.have)
|
||||||
|
if tc.err != "" {
|
||||||
|
assert.EqualError(t, err, tc.err)
|
||||||
|
|
||||||
|
if !tc.decode {
|
||||||
|
assert.Nil(t, actual)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
if tc.decode {
|
||||||
|
assert.Equal(t, tc.expected, actual)
|
||||||
|
} else {
|
||||||
|
assert.Equal(t, tc.have, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestStringToX509CertificateChainHookFunc(t *testing.T) {
|
func TestStringToX509CertificateChainHookFunc(t *testing.T) {
|
||||||
var nilkey *schema.X509CertificateChain
|
var nilkey *schema.X509CertificateChain
|
||||||
|
|
||||||
|
@ -1411,3 +1633,42 @@ var (
|
||||||
testZero int32
|
testZero int32
|
||||||
testString = ""
|
testString = ""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func MustParseAddress(input string) schema.Address {
|
||||||
|
address, err := schema.NewAddress(input)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(address.String())
|
||||||
|
addr := *address
|
||||||
|
|
||||||
|
return addr
|
||||||
|
}
|
||||||
|
|
||||||
|
func MustParseAddressPtr(input string) *schema.Address {
|
||||||
|
address, err := schema.NewAddress(input)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return address
|
||||||
|
}
|
||||||
|
|
||||||
|
func MustParsePasswordDigest(input string) schema.PasswordDigest {
|
||||||
|
digest, err := schema.DecodePasswordDigest(input)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return *digest
|
||||||
|
}
|
||||||
|
|
||||||
|
func MustParsePasswordDigestPtr(input string) *schema.PasswordDigest {
|
||||||
|
digest, err := schema.DecodePasswordDigest(input)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return digest
|
||||||
|
}
|
||||||
|
|
|
@ -187,7 +187,7 @@ var deprecations = map[string]Deprecation{
|
||||||
Keep: true,
|
Keep: true,
|
||||||
MapFunc: nil,
|
MapFunc: nil,
|
||||||
ErrFunc: func(d Deprecation, _ map[string]any, _ any, val *schema.StructValidator) {
|
ErrFunc: func(d Deprecation, _ map[string]any, _ any, val *schema.StructValidator) {
|
||||||
val.PushWarning(fmt.Errorf("configuration key 'server.host' is deprecated in %s and has been replaced by 'server.address' when combined with the 'server.port' in the format of '[tcp://]<hostname>[:<port>]': this should be automatically mapped for you but you will need to adjust your configuration to remove this message", d.Version.String()))
|
val.PushWarning(fmt.Errorf("configuration key '%s' is deprecated in %s and has been replaced by '%s' when combined with the 'server.port' and 'server.path' in the format of %s: this should be automatically mapped for you but you will need to adjust your configuration to remove this message", d.Key, d.Version.String(), d.NewKey, "'[tcp[(4|6)]://]<hostname>[:<port>][/<path>]' or 'tcp[(4|6)://][hostname]:<port>[/<path>]'"))
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"server.port": {
|
"server.port": {
|
||||||
|
@ -198,7 +198,18 @@ var deprecations = map[string]Deprecation{
|
||||||
Keep: true,
|
Keep: true,
|
||||||
MapFunc: nil,
|
MapFunc: nil,
|
||||||
ErrFunc: func(d Deprecation, _ map[string]any, _ any, val *schema.StructValidator) {
|
ErrFunc: func(d Deprecation, _ map[string]any, _ any, val *schema.StructValidator) {
|
||||||
val.PushWarning(fmt.Errorf("configuration key 'server.port' is deprecated in %s and has been replaced by 'server.address' when combined with the 'server.host' in the format of '[tcp://]<hostname>[:<port>]': this should be automatically mapped for you but you will need to adjust your configuration to remove this message", d.Version.String()))
|
val.PushWarning(fmt.Errorf("configuration key '%s' is deprecated in %s and has been replaced by '%s' when combined with the 'server.host' and 'server.path' in the format of %s: this should be automatically mapped for you but you will need to adjust your configuration to remove this message", d.Key, d.Version.String(), d.NewKey, "'[tcp[(4|6)]://]<hostname>[:<port>][/<path>]' or 'tcp[(4|6)://][hostname]:<port>[/<path>]'"))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"server.path": {
|
||||||
|
Version: model.SemanticVersion{Major: 4, Minor: 38},
|
||||||
|
Key: "server.path",
|
||||||
|
NewKey: "server.address",
|
||||||
|
AutoMap: false,
|
||||||
|
Keep: true,
|
||||||
|
MapFunc: nil,
|
||||||
|
ErrFunc: func(d Deprecation, _ map[string]any, _ any, val *schema.StructValidator) {
|
||||||
|
val.PushWarning(fmt.Errorf("configuration key '%s' is deprecated in %s and has been replaced by '%s' when combined with the 'server.host' and 'server.port' in the format of %s: this should be automatically mapped for you but you will need to adjust your configuration to remove this message", d.Key, d.Version.String(), d.NewKey, "'[tcp[(4|6)]://]<hostname>[:<port>][/<path>]' or 'tcp[(4|6)://][hostname]:<port>[/<path>]'"))
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"storage.mysql.host": {
|
"storage.mysql.host": {
|
||||||
|
@ -209,7 +220,7 @@ var deprecations = map[string]Deprecation{
|
||||||
Keep: true,
|
Keep: true,
|
||||||
MapFunc: nil,
|
MapFunc: nil,
|
||||||
ErrFunc: func(d Deprecation, _ map[string]any, _ any, val *schema.StructValidator) {
|
ErrFunc: func(d Deprecation, _ map[string]any, _ any, val *schema.StructValidator) {
|
||||||
val.PushWarning(fmt.Errorf("configuration key 'storage.mysql.host' is deprecated in %s and has been replaced by 'storage.mysql.address' when combined with the 'storage.mysql.port' in the format of '[tcp://]<hostname>[:<port>]': this should be automatically mapped for you but you will need to adjust your configuration to remove this message", d.Version.String()))
|
val.PushWarning(fmt.Errorf(errFmtSpecialRemappedKey, d.Key, d.Version.String(), d.NewKey, "storage.mysql.port", "[tcp://]<hostname>[:<port>]"))
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"storage.mysql.port": {
|
"storage.mysql.port": {
|
||||||
|
@ -220,7 +231,7 @@ var deprecations = map[string]Deprecation{
|
||||||
Keep: true,
|
Keep: true,
|
||||||
MapFunc: nil,
|
MapFunc: nil,
|
||||||
ErrFunc: func(d Deprecation, _ map[string]any, _ any, val *schema.StructValidator) {
|
ErrFunc: func(d Deprecation, _ map[string]any, _ any, val *schema.StructValidator) {
|
||||||
val.PushWarning(fmt.Errorf("configuration key 'storage.mysql.port' is deprecated in %s and has been replaced by 'storage.mysql.address' when combined with the 'storage.mysql.host' in the format of '[tcp://]<hostname>[:<port>]': this should be automatically mapped for you but you will need to adjust your configuration to remove this message", d.Version.String()))
|
val.PushWarning(fmt.Errorf(errFmtSpecialRemappedKey, d.Key, d.Version.String(), d.NewKey, "storage.mysql.host", "[tcp://]<hostname>[:<port>]"))
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"storage.postgres.host": {
|
"storage.postgres.host": {
|
||||||
|
@ -231,7 +242,7 @@ var deprecations = map[string]Deprecation{
|
||||||
Keep: true,
|
Keep: true,
|
||||||
MapFunc: nil,
|
MapFunc: nil,
|
||||||
ErrFunc: func(d Deprecation, _ map[string]any, _ any, val *schema.StructValidator) {
|
ErrFunc: func(d Deprecation, _ map[string]any, _ any, val *schema.StructValidator) {
|
||||||
val.PushWarning(fmt.Errorf("configuration key 'storage.postgres.host' is deprecated in %s and has been replaced by 'storage.postgres.address' when combined with the 'storage.postgres.port' in the format of '[tcp://]<hostname>[:<port>]': this should be automatically mapped for you but you will need to adjust your configuration to remove this message", d.Version.String()))
|
val.PushWarning(fmt.Errorf(errFmtSpecialRemappedKey, d.Key, d.Version.String(), d.NewKey, "storage.postgres.port", "[tcp://]<hostname>[:<port>]"))
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"storage.postgres.port": {
|
"storage.postgres.port": {
|
||||||
|
@ -242,7 +253,29 @@ var deprecations = map[string]Deprecation{
|
||||||
Keep: true,
|
Keep: true,
|
||||||
MapFunc: nil,
|
MapFunc: nil,
|
||||||
ErrFunc: func(d Deprecation, _ map[string]any, _ any, val *schema.StructValidator) {
|
ErrFunc: func(d Deprecation, _ map[string]any, _ any, val *schema.StructValidator) {
|
||||||
val.PushWarning(fmt.Errorf("configuration key 'storage.postgres.port' is deprecated in %s and has been replaced by 'storage.postgres.address' when combined with the 'storage.postgres.host' in the format of '[tcp://]<hostname>[:<port>]': this should be automatically mapped for you but you will need to adjust your configuration to remove this message", d.Version.String()))
|
val.PushWarning(fmt.Errorf(errFmtSpecialRemappedKey, d.Key, d.Version.String(), d.NewKey, "storage.postgres.host", "[tcp://]<hostname>[:<port>]"))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"notifier.smtp.host": {
|
||||||
|
Version: model.SemanticVersion{Major: 4, Minor: 38},
|
||||||
|
Key: "notifier.smtp.host",
|
||||||
|
NewKey: "notifier.smtp.address",
|
||||||
|
AutoMap: false,
|
||||||
|
Keep: true,
|
||||||
|
MapFunc: nil,
|
||||||
|
ErrFunc: func(d Deprecation, _ map[string]any, _ any, val *schema.StructValidator) {
|
||||||
|
val.PushWarning(fmt.Errorf(errFmtSpecialRemappedKey, d.Key, d.Version.String(), d.NewKey, "notifier.smtp.port", "[tcp://]<hostname>[:<port>]"))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"notifier.smtp.port": {
|
||||||
|
Version: model.SemanticVersion{Major: 4, Minor: 38},
|
||||||
|
Key: "notifier.smtp.port",
|
||||||
|
NewKey: "notifier.smtp.address",
|
||||||
|
AutoMap: false,
|
||||||
|
Keep: true,
|
||||||
|
MapFunc: nil,
|
||||||
|
ErrFunc: func(d Deprecation, _ map[string]any, _ any, val *schema.StructValidator) {
|
||||||
|
val.PushWarning(fmt.Errorf(errFmtSpecialRemappedKey, d.Key, d.Version.String(), d.NewKey, "notifier.smtp.host", "[tcp://]<hostname>[:<port>]"))
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"authentication_backend.ldap.url": {
|
"authentication_backend.ldap.url": {
|
||||||
|
@ -261,4 +294,36 @@ var deprecations = map[string]Deprecation{
|
||||||
MapFunc: nil,
|
MapFunc: nil,
|
||||||
ErrFunc: nil,
|
ErrFunc: nil,
|
||||||
},
|
},
|
||||||
|
"authentication_backend.ldap.username_attribute": {
|
||||||
|
Version: model.SemanticVersion{Major: 4, Minor: 38},
|
||||||
|
Key: "authentication_backend.ldap.username_attribute",
|
||||||
|
NewKey: "authentication_backend.ldap.attributes.username",
|
||||||
|
AutoMap: true,
|
||||||
|
MapFunc: nil,
|
||||||
|
ErrFunc: nil,
|
||||||
|
},
|
||||||
|
"authentication_backend.ldap.mail_attribute": {
|
||||||
|
Version: model.SemanticVersion{Major: 4, Minor: 38},
|
||||||
|
Key: "authentication_backend.ldap.mail_attribute",
|
||||||
|
NewKey: "authentication_backend.ldap.attributes.mail",
|
||||||
|
AutoMap: true,
|
||||||
|
MapFunc: nil,
|
||||||
|
ErrFunc: nil,
|
||||||
|
},
|
||||||
|
"authentication_backend.ldap.display_name_attribute": {
|
||||||
|
Version: model.SemanticVersion{Major: 4, Minor: 38},
|
||||||
|
Key: "authentication_backend.ldap.display_name_attribute",
|
||||||
|
NewKey: "authentication_backend.ldap.attributes.display_name",
|
||||||
|
AutoMap: true,
|
||||||
|
MapFunc: nil,
|
||||||
|
ErrFunc: nil,
|
||||||
|
},
|
||||||
|
"authentication_backend.ldap.group_name_attribute": {
|
||||||
|
Version: model.SemanticVersion{Major: 4, Minor: 38},
|
||||||
|
Key: "authentication_backend.ldap.group_name_attribute",
|
||||||
|
NewKey: "authentication_backend.ldap.attributes.group_name",
|
||||||
|
AutoMap: true,
|
||||||
|
MapFunc: nil,
|
||||||
|
ErrFunc: nil,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"runtime"
|
"runtime"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/spf13/pflag"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
@ -125,3 +126,85 @@ func TestKoanfSecretCallbackShouldErrorOnFSError(t *testing.T) {
|
||||||
assert.Len(t, val.Warnings(), 0)
|
assert.Len(t, val.Warnings(), 0)
|
||||||
assert.EqualError(t, val.Errors()[0], fmt.Sprintf("secrets: error loading secret path %s into key 'theme': file permission error occurred: open %s: permission denied", secret, secret))
|
assert.EqualError(t, val.Errors()[0], fmt.Sprintf("secrets: error loading secret path %s into key 'theme': file permission error occurred: open %s: permission denied", secret, secret))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestKoanfCommandLineWithMappingCallback(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
have []string
|
||||||
|
flagName string
|
||||||
|
flagValue string
|
||||||
|
mapped string
|
||||||
|
valid bool
|
||||||
|
unchanged bool
|
||||||
|
expectedName string
|
||||||
|
expectedValue any
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"ShouldDecodeStandard",
|
||||||
|
[]string{"--commands", "abc"},
|
||||||
|
"commands",
|
||||||
|
"",
|
||||||
|
"command.another",
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
"command.another",
|
||||||
|
"abc",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ShouldSkipUnchangedKey",
|
||||||
|
[]string{},
|
||||||
|
"commands",
|
||||||
|
"abc",
|
||||||
|
"command.another",
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
"",
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ShouldLookupNormalizedKey",
|
||||||
|
[]string{"--log.file-path", "abc"},
|
||||||
|
"log.file-path",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
"log.file_path",
|
||||||
|
"abc",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ShouldReturnUnmodified",
|
||||||
|
[]string{"--commands", "abc"},
|
||||||
|
"commands",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
"",
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
flagset := pflag.NewFlagSet("test", pflag.ContinueOnError)
|
||||||
|
|
||||||
|
flagset.String(tc.flagName, tc.flagValue, "")
|
||||||
|
|
||||||
|
assert.NoError(t, flagset.Parse(tc.have))
|
||||||
|
|
||||||
|
mapper := map[string]string{}
|
||||||
|
|
||||||
|
if tc.mapped != "" {
|
||||||
|
mapper[tc.flagName] = tc.mapped
|
||||||
|
}
|
||||||
|
|
||||||
|
callback := koanfCommandLineWithMappingCallback(mapper, tc.valid, tc.unchanged)
|
||||||
|
|
||||||
|
actualName, actualValue := callback(flagset.Lookup(tc.flagName))
|
||||||
|
|
||||||
|
assert.Equal(t, tc.expectedName, actualName)
|
||||||
|
assert.Equal(t, tc.expectedValue, actualValue)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -245,6 +245,28 @@ func TestShouldLoadURLList(t *testing.T) {
|
||||||
assert.Equal(t, "https://example.com", config.IdentityProviders.OIDC.CORS.AllowedOrigins[1].String())
|
assert.Equal(t, "https://example.com", config.IdentityProviders.OIDC.CORS.AllowedOrigins[1].String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestShouldDisableOIDCEntropy(t *testing.T) {
|
||||||
|
val := schema.NewStructValidator()
|
||||||
|
keys, config, err := Load(val, NewDefaultSources([]string{"./test_resources/config_oidc_disable_entropy.yml"}, DefaultEnvPrefix, DefaultEnvDelimiter)...)
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
validator.ValidateKeys(keys, DefaultEnvPrefix, val)
|
||||||
|
|
||||||
|
assert.Len(t, val.Errors(), 0)
|
||||||
|
assert.Len(t, val.Warnings(), 0)
|
||||||
|
|
||||||
|
assert.Equal(t, -1, config.IdentityProviders.OIDC.MinimumParameterEntropy)
|
||||||
|
|
||||||
|
validator.ValidateIdentityProviders(&config.IdentityProviders, val)
|
||||||
|
|
||||||
|
assert.Len(t, val.Errors(), 1)
|
||||||
|
require.Len(t, val.Warnings(), 2)
|
||||||
|
|
||||||
|
assert.EqualError(t, val.Warnings()[0], "identity_providers: oidc: option 'minimum_parameter_entropy' is disabled which is considered unsafe and insecure")
|
||||||
|
assert.Equal(t, -1, config.IdentityProviders.OIDC.MinimumParameterEntropy)
|
||||||
|
}
|
||||||
|
|
||||||
func TestShouldConfigureConsent(t *testing.T) {
|
func TestShouldConfigureConsent(t *testing.T) {
|
||||||
val := schema.NewStructValidator()
|
val := schema.NewStructValidator()
|
||||||
keys, config, err := Load(val, NewDefaultSources([]string{"./test_resources/config_oidc.yml"}, DefaultEnvPrefix, DefaultEnvDelimiter)...)
|
keys, config, err := Load(val, NewDefaultSources([]string{"./test_resources/config_oidc.yml"}, DefaultEnvPrefix, DefaultEnvDelimiter)...)
|
||||||
|
@ -314,44 +336,24 @@ func TestShouldValidateDeprecatedEnvNamesWithDeprecatedKeys(t *testing.T) {
|
||||||
assert.Len(t, val.Errors(), 0)
|
assert.Len(t, val.Errors(), 0)
|
||||||
|
|
||||||
warnings := val.Warnings()
|
warnings := val.Warnings()
|
||||||
require.Len(t, warnings, 3)
|
require.Len(t, warnings, 10)
|
||||||
|
|
||||||
sort.Sort(utils.ErrSliceSortAlphabetical(warnings))
|
sort.Sort(utils.ErrSliceSortAlphabetical(warnings))
|
||||||
|
|
||||||
assert.EqualError(t, warnings[0], "configuration key 'authentication_backend.ldap.url' is deprecated in 4.38.0 and has been replaced by 'authentication_backend.ldap.address': this has been automatically mapped for you but you will need to adjust your configuration to remove this message")
|
assert.EqualError(t, warnings[0], "configuration key 'authentication_backend.ldap.url' is deprecated in 4.38.0 and has been replaced by 'authentication_backend.ldap.address': this has been automatically mapped for you but you will need to adjust your configuration to remove this message")
|
||||||
assert.EqualError(t, warnings[1], "configuration key 'storage.mysql.host' is deprecated in 4.38.0 and has been replaced by 'storage.mysql.address' when combined with the 'storage.mysql.port' in the format of '[tcp://]<hostname>[:<port>]': this should be automatically mapped for you but you will need to adjust your configuration to remove this message")
|
assert.EqualError(t, warnings[1], "configuration key 'notifier.smtp.host' is deprecated in 4.38.0 and has been replaced by 'notifier.smtp.address' when combined with the 'notifier.smtp.port' in the format of '[tcp://]<hostname>[:<port>]': this should be automatically mapped for you but you will need to adjust your configuration to remove this message")
|
||||||
assert.EqualError(t, warnings[2], "configuration key 'storage.mysql.port' is deprecated in 4.38.0 and has been replaced by 'storage.mysql.address' when combined with the 'storage.mysql.host' in the format of '[tcp://]<hostname>[:<port>]': this should be automatically mapped for you but you will need to adjust your configuration to remove this message")
|
assert.EqualError(t, warnings[2], "configuration key 'notifier.smtp.port' is deprecated in 4.38.0 and has been replaced by 'notifier.smtp.address' when combined with the 'notifier.smtp.host' in the format of '[tcp://]<hostname>[:<port>]': this should be automatically mapped for you but you will need to adjust your configuration to remove this message")
|
||||||
|
assert.EqualError(t, warnings[3], "configuration key 'server.host' is deprecated in 4.38.0 and has been replaced by 'server.address' when combined with the 'server.port' and 'server.path' in the format of '[tcp[(4|6)]://]<hostname>[:<port>][/<path>]' or 'tcp[(4|6)://][hostname]:<port>[/<path>]': this should be automatically mapped for you but you will need to adjust your configuration to remove this message")
|
||||||
|
assert.EqualError(t, warnings[4], "configuration key 'server.path' is deprecated in 4.38.0 and has been replaced by 'server.address' when combined with the 'server.host' and 'server.port' in the format of '[tcp[(4|6)]://]<hostname>[:<port>][/<path>]' or 'tcp[(4|6)://][hostname]:<port>[/<path>]': this should be automatically mapped for you but you will need to adjust your configuration to remove this message")
|
||||||
|
assert.EqualError(t, warnings[5], "configuration key 'server.port' is deprecated in 4.38.0 and has been replaced by 'server.address' when combined with the 'server.host' and 'server.path' in the format of '[tcp[(4|6)]://]<hostname>[:<port>][/<path>]' or 'tcp[(4|6)://][hostname]:<port>[/<path>]': this should be automatically mapped for you but you will need to adjust your configuration to remove this message")
|
||||||
|
assert.EqualError(t, warnings[6], "configuration key 'storage.mysql.host' is deprecated in 4.38.0 and has been replaced by 'storage.mysql.address' when combined with the 'storage.mysql.port' in the format of '[tcp://]<hostname>[:<port>]': this should be automatically mapped for you but you will need to adjust your configuration to remove this message")
|
||||||
|
assert.EqualError(t, warnings[7], "configuration key 'storage.mysql.port' is deprecated in 4.38.0 and has been replaced by 'storage.mysql.address' when combined with the 'storage.mysql.host' in the format of '[tcp://]<hostname>[:<port>]': this should be automatically mapped for you but you will need to adjust your configuration to remove this message")
|
||||||
|
assert.EqualError(t, warnings[8], "configuration key 'storage.postgres.host' is deprecated in 4.38.0 and has been replaced by 'storage.postgres.address' when combined with the 'storage.postgres.port' in the format of '[tcp://]<hostname>[:<port>]': this should be automatically mapped for you but you will need to adjust your configuration to remove this message")
|
||||||
|
assert.EqualError(t, warnings[9], "configuration key 'storage.postgres.port' is deprecated in 4.38.0 and has been replaced by 'storage.postgres.address' when combined with the 'storage.postgres.host' in the format of '[tcp://]<hostname>[:<port>]': this should be automatically mapped for you but you will need to adjust your configuration to remove this message")
|
||||||
|
|
||||||
assert.Equal(t, "ldap://from-env:389", c.AuthenticationBackend.LDAP.Address.String())
|
assert.Equal(t, "ldap://from-env:389", c.AuthenticationBackend.LDAP.Address.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldValidateDeprecatedEnvNamesWithDeprecatedKeysAlt(t *testing.T) {
|
|
||||||
val := schema.NewStructValidator()
|
|
||||||
keys, c, err := Load(val, NewDefaultSources([]string{"./test_resources/config.deprecated.alt.yml"}, DefaultEnvPrefix, DefaultEnvDelimiter)...)
|
|
||||||
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
validator.ValidateKeys(keys, DefaultEnvPrefix, val)
|
|
||||||
|
|
||||||
assert.Len(t, val.Errors(), 0)
|
|
||||||
|
|
||||||
warnings := val.Warnings()
|
|
||||||
require.Len(t, warnings, 3)
|
|
||||||
|
|
||||||
sort.Sort(utils.ErrSliceSortAlphabetical(warnings))
|
|
||||||
|
|
||||||
assert.EqualError(t, warnings[0], "configuration key 'authentication_backend.ldap.url' is deprecated in 4.38.0 and has been replaced by 'authentication_backend.ldap.address': this has been automatically mapped for you but you will need to adjust your configuration to remove this message")
|
|
||||||
assert.EqualError(t, warnings[1], "configuration key 'storage.postgres.host' is deprecated in 4.38.0 and has been replaced by 'storage.postgres.address' when combined with the 'storage.postgres.port' in the format of '[tcp://]<hostname>[:<port>]': this should be automatically mapped for you but you will need to adjust your configuration to remove this message")
|
|
||||||
assert.EqualError(t, warnings[2], "configuration key 'storage.postgres.port' is deprecated in 4.38.0 and has been replaced by 'storage.postgres.address' when combined with the 'storage.postgres.host' in the format of '[tcp://]<hostname>[:<port>]': this should be automatically mapped for you but you will need to adjust your configuration to remove this message")
|
|
||||||
|
|
||||||
val.Clear()
|
|
||||||
|
|
||||||
validator.ValidateConfiguration(c, val)
|
|
||||||
|
|
||||||
require.NotNil(t, c.Storage.PostgreSQL.Address)
|
|
||||||
assert.Equal(t, "tcp://127.0.0.1:5432", c.Storage.PostgreSQL.Address.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestShouldRaiseErrOnInvalidNotifierSMTPSender(t *testing.T) {
|
func TestShouldRaiseErrOnInvalidNotifierSMTPSender(t *testing.T) {
|
||||||
val := schema.NewStructValidator()
|
val := schema.NewStructValidator()
|
||||||
keys, _, err := Load(val, NewDefaultSources([]string{"./test_resources/config_smtp_sender_invalid.yml"}, DefaultEnvPrefix, DefaultEnvDelimiter)...)
|
keys, _, err := Load(val, NewDefaultSources([]string{"./test_resources/config_smtp_sender_invalid.yml"}, DefaultEnvPrefix, DefaultEnvDelimiter)...)
|
||||||
|
@ -495,7 +497,7 @@ func TestShouldLoadDirectoryConfiguration(t *testing.T) {
|
||||||
assert.Len(t, val.Errors(), 0)
|
assert.Len(t, val.Errors(), 0)
|
||||||
require.Len(t, val.Warnings(), 1)
|
require.Len(t, val.Warnings(), 1)
|
||||||
|
|
||||||
assert.EqualError(t, val.Warnings()[0], "configuration key 'server.port' is deprecated in 4.38.0 and has been replaced by 'server.address' when combined with the 'server.host' in the format of '[tcp://]<hostname>[:<port>]': this should be automatically mapped for you but you will need to adjust your configuration to remove this message")
|
assert.EqualError(t, val.Warnings()[0], "configuration key 'server.port' is deprecated in 4.38.0 and has been replaced by 'server.address' when combined with the 'server.host' and 'server.path' in the format of '[tcp[(4|6)]://]<hostname>[:<port>][/<path>]' or 'tcp[(4|6)://][hostname]:<port>[/<path>]': this should be automatically mapped for you but you will need to adjust your configuration to remove this message")
|
||||||
}
|
}
|
||||||
|
|
||||||
func testSetEnv(t *testing.T, key, value string) {
|
func testSetEnv(t *testing.T, key, value string) {
|
||||||
|
|
|
@ -108,11 +108,9 @@ type LDAPAuthenticationBackend struct {
|
||||||
|
|
||||||
AdditionalGroupsDN string `koanf:"additional_groups_dn"`
|
AdditionalGroupsDN string `koanf:"additional_groups_dn"`
|
||||||
GroupsFilter string `koanf:"groups_filter"`
|
GroupsFilter string `koanf:"groups_filter"`
|
||||||
|
GroupSearchMode string `koanf:"group_search_mode"`
|
||||||
|
|
||||||
GroupNameAttribute string `koanf:"group_name_attribute"`
|
Attributes LDAPAuthenticationAttributes `koanf:"attributes"`
|
||||||
UsernameAttribute string `koanf:"username_attribute"`
|
|
||||||
MailAttribute string `koanf:"mail_attribute"`
|
|
||||||
DisplayNameAttribute string `koanf:"display_name_attribute"`
|
|
||||||
|
|
||||||
PermitReferrals bool `koanf:"permit_referrals"`
|
PermitReferrals bool `koanf:"permit_referrals"`
|
||||||
PermitUnauthenticatedBind bool `koanf:"permit_unauthenticated_bind"`
|
PermitUnauthenticatedBind bool `koanf:"permit_unauthenticated_bind"`
|
||||||
|
@ -122,6 +120,16 @@ type LDAPAuthenticationBackend struct {
|
||||||
Password string `koanf:"password"`
|
Password string `koanf:"password"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LDAPAuthenticationAttributes represents the configuration related to LDAP server attributes.
|
||||||
|
type LDAPAuthenticationAttributes struct {
|
||||||
|
DistinguishedName string `koanf:"distinguished_name"`
|
||||||
|
Username string `koanf:"username"`
|
||||||
|
DisplayName string `koanf:"display_name"`
|
||||||
|
Mail string `koanf:"mail"`
|
||||||
|
MemberOf string `koanf:"member_of"`
|
||||||
|
GroupName string `koanf:"group_name"`
|
||||||
|
}
|
||||||
|
|
||||||
// DefaultPasswordConfig represents the default configuration related to Argon2id hashing.
|
// DefaultPasswordConfig represents the default configuration related to Argon2id hashing.
|
||||||
var DefaultPasswordConfig = Password{
|
var DefaultPasswordConfig = Password{
|
||||||
Algorithm: argon2,
|
Algorithm: argon2,
|
||||||
|
@ -175,10 +183,13 @@ var DefaultCIPasswordConfig = Password{
|
||||||
|
|
||||||
// DefaultLDAPAuthenticationBackendConfigurationImplementationCustom represents the default LDAP config.
|
// DefaultLDAPAuthenticationBackendConfigurationImplementationCustom represents the default LDAP config.
|
||||||
var DefaultLDAPAuthenticationBackendConfigurationImplementationCustom = LDAPAuthenticationBackend{
|
var DefaultLDAPAuthenticationBackendConfigurationImplementationCustom = LDAPAuthenticationBackend{
|
||||||
UsernameAttribute: ldapAttrUserID,
|
GroupSearchMode: ldapGroupSearchModeFilter,
|
||||||
MailAttribute: ldapAttrMail,
|
Attributes: LDAPAuthenticationAttributes{
|
||||||
DisplayNameAttribute: ldapAttrDisplayName,
|
Username: ldapAttrUserID,
|
||||||
GroupNameAttribute: ldapAttrCommonName,
|
DisplayName: ldapAttrDisplayName,
|
||||||
|
Mail: ldapAttrMail,
|
||||||
|
GroupName: ldapAttrCommonName,
|
||||||
|
},
|
||||||
Timeout: time.Second * 5,
|
Timeout: time.Second * 5,
|
||||||
TLS: &TLSConfig{
|
TLS: &TLSConfig{
|
||||||
MinimumVersion: TLSVersion{tls.VersionTLS12},
|
MinimumVersion: TLSVersion{tls.VersionTLS12},
|
||||||
|
@ -188,11 +199,16 @@ var DefaultLDAPAuthenticationBackendConfigurationImplementationCustom = LDAPAuth
|
||||||
// DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory represents the default LDAP config for the LDAPImplementationActiveDirectory Implementation.
|
// DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory represents the default LDAP config for the LDAPImplementationActiveDirectory Implementation.
|
||||||
var DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory = LDAPAuthenticationBackend{
|
var DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory = LDAPAuthenticationBackend{
|
||||||
UsersFilter: "(&(|({username_attribute}={input})({mail_attribute}={input}))(sAMAccountType=805306368)(!(userAccountControl:1.2.840.113556.1.4.803:=2))(!(pwdLastSet=0))(|(!(accountExpires=*))(accountExpires=0)(accountExpires>={date-time:microsoft-nt})))",
|
UsersFilter: "(&(|({username_attribute}={input})({mail_attribute}={input}))(sAMAccountType=805306368)(!(userAccountControl:1.2.840.113556.1.4.803:=2))(!(pwdLastSet=0))(|(!(accountExpires=*))(accountExpires=0)(accountExpires>={date-time:microsoft-nt})))",
|
||||||
UsernameAttribute: "sAMAccountName",
|
|
||||||
MailAttribute: ldapAttrMail,
|
|
||||||
DisplayNameAttribute: ldapAttrDisplayName,
|
|
||||||
GroupsFilter: "(&(member={dn})(|(sAMAccountType=268435456)(sAMAccountType=536870912)))",
|
GroupsFilter: "(&(member={dn})(|(sAMAccountType=268435456)(sAMAccountType=536870912)))",
|
||||||
GroupNameAttribute: ldapAttrCommonName,
|
GroupSearchMode: ldapGroupSearchModeFilter,
|
||||||
|
Attributes: LDAPAuthenticationAttributes{
|
||||||
|
DistinguishedName: ldapAttrDistinguishedName,
|
||||||
|
Username: ldapAttrSAMAccountName,
|
||||||
|
DisplayName: ldapAttrDisplayName,
|
||||||
|
Mail: ldapAttrMail,
|
||||||
|
MemberOf: ldapAttrMemberOf,
|
||||||
|
GroupName: ldapAttrCommonName,
|
||||||
|
},
|
||||||
Timeout: time.Second * 5,
|
Timeout: time.Second * 5,
|
||||||
TLS: &TLSConfig{
|
TLS: &TLSConfig{
|
||||||
MinimumVersion: TLSVersion{tls.VersionTLS12},
|
MinimumVersion: TLSVersion{tls.VersionTLS12},
|
||||||
|
@ -202,11 +218,15 @@ var DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory =
|
||||||
// DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis represents the default LDAP config for the LDAPImplementationRFC2307bis Implementation.
|
// DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis represents the default LDAP config for the LDAPImplementationRFC2307bis Implementation.
|
||||||
var DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis = LDAPAuthenticationBackend{
|
var DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis = LDAPAuthenticationBackend{
|
||||||
UsersFilter: "(&(|({username_attribute}={input})({mail_attribute}={input}))(|(objectClass=inetOrgPerson)(objectClass=organizationalPerson)))",
|
UsersFilter: "(&(|({username_attribute}={input})({mail_attribute}={input}))(|(objectClass=inetOrgPerson)(objectClass=organizationalPerson)))",
|
||||||
UsernameAttribute: ldapAttrUserID,
|
GroupsFilter: "(&(|(member={dn})(uniqueMember={dn}))(|(objectClass=groupOfNames)(objectClass=groupOfUniqueNames)(objectClass=groupOfMembers))(!(pwdReset=TRUE)))",
|
||||||
MailAttribute: ldapAttrMail,
|
GroupSearchMode: ldapGroupSearchModeFilter,
|
||||||
DisplayNameAttribute: ldapAttrDisplayName,
|
Attributes: LDAPAuthenticationAttributes{
|
||||||
GroupsFilter: "(&(|(member={dn})(uniqueMember={dn}))(|(objectClass=groupOfNames)(objectClass=groupOfUniqueNames)(objectClass=groupOfMembers)))",
|
Username: ldapAttrUserID,
|
||||||
GroupNameAttribute: ldapAttrCommonName,
|
DisplayName: ldapAttrDisplayName,
|
||||||
|
Mail: ldapAttrMail,
|
||||||
|
MemberOf: ldapAttrMemberOf,
|
||||||
|
GroupName: ldapAttrCommonName,
|
||||||
|
},
|
||||||
Timeout: time.Second * 5,
|
Timeout: time.Second * 5,
|
||||||
TLS: &TLSConfig{
|
TLS: &TLSConfig{
|
||||||
MinimumVersion: TLSVersion{tls.VersionTLS12},
|
MinimumVersion: TLSVersion{tls.VersionTLS12},
|
||||||
|
@ -216,11 +236,15 @@ var DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis = LDAP
|
||||||
// DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA represents the default LDAP config for the LDAPImplementationFreeIPA Implementation.
|
// DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA represents the default LDAP config for the LDAPImplementationFreeIPA Implementation.
|
||||||
var DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA = LDAPAuthenticationBackend{
|
var DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA = LDAPAuthenticationBackend{
|
||||||
UsersFilter: "(&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=person)(!(nsAccountLock=TRUE))(krbPasswordExpiration>={date-time:generalized})(|(!(krbPrincipalExpiration=*))(krbPrincipalExpiration>={date-time:generalized})))",
|
UsersFilter: "(&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=person)(!(nsAccountLock=TRUE))(krbPasswordExpiration>={date-time:generalized})(|(!(krbPrincipalExpiration=*))(krbPrincipalExpiration>={date-time:generalized})))",
|
||||||
UsernameAttribute: ldapAttrUserID,
|
|
||||||
MailAttribute: ldapAttrMail,
|
|
||||||
DisplayNameAttribute: ldapAttrDisplayName,
|
|
||||||
GroupsFilter: "(&(member={dn})(objectClass=groupOfNames))",
|
GroupsFilter: "(&(member={dn})(objectClass=groupOfNames))",
|
||||||
GroupNameAttribute: ldapAttrCommonName,
|
GroupSearchMode: ldapGroupSearchModeFilter,
|
||||||
|
Attributes: LDAPAuthenticationAttributes{
|
||||||
|
Username: ldapAttrUserID,
|
||||||
|
DisplayName: ldapAttrDisplayName,
|
||||||
|
Mail: ldapAttrMail,
|
||||||
|
MemberOf: ldapAttrMemberOf,
|
||||||
|
GroupName: ldapAttrCommonName,
|
||||||
|
},
|
||||||
Timeout: time.Second * 5,
|
Timeout: time.Second * 5,
|
||||||
TLS: &TLSConfig{
|
TLS: &TLSConfig{
|
||||||
MinimumVersion: TLSVersion{tls.VersionTLS12},
|
MinimumVersion: TLSVersion{tls.VersionTLS12},
|
||||||
|
@ -232,11 +256,15 @@ var DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP = LDAPAuthe
|
||||||
AdditionalUsersDN: "OU=people",
|
AdditionalUsersDN: "OU=people",
|
||||||
AdditionalGroupsDN: "OU=groups",
|
AdditionalGroupsDN: "OU=groups",
|
||||||
UsersFilter: "(&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=person))",
|
UsersFilter: "(&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=person))",
|
||||||
UsernameAttribute: ldapAttrUserID,
|
|
||||||
MailAttribute: ldapAttrMail,
|
|
||||||
DisplayNameAttribute: ldapAttrCommonName,
|
|
||||||
GroupsFilter: "(&(member={dn})(objectClass=groupOfUniqueNames))",
|
GroupsFilter: "(&(member={dn})(objectClass=groupOfUniqueNames))",
|
||||||
GroupNameAttribute: ldapAttrCommonName,
|
GroupSearchMode: ldapGroupSearchModeFilter,
|
||||||
|
Attributes: LDAPAuthenticationAttributes{
|
||||||
|
Username: ldapAttrUserID,
|
||||||
|
DisplayName: ldapAttrCommonName,
|
||||||
|
Mail: ldapAttrMail,
|
||||||
|
MemberOf: ldapAttrMemberOf,
|
||||||
|
GroupName: ldapAttrCommonName,
|
||||||
|
},
|
||||||
Timeout: time.Second * 5,
|
Timeout: time.Second * 5,
|
||||||
TLS: &TLSConfig{
|
TLS: &TLSConfig{
|
||||||
MinimumVersion: TLSVersion{tls.VersionTLS12},
|
MinimumVersion: TLSVersion{tls.VersionTLS12},
|
||||||
|
@ -246,11 +274,15 @@ var DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP = LDAPAuthe
|
||||||
// DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth represents the default LDAP config for the LDAPImplementationGLAuth Implementation.
|
// DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth represents the default LDAP config for the LDAPImplementationGLAuth Implementation.
|
||||||
var DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth = LDAPAuthenticationBackend{
|
var DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth = LDAPAuthenticationBackend{
|
||||||
UsersFilter: "(&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=posixAccount)(!(accountStatus=inactive)))",
|
UsersFilter: "(&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=posixAccount)(!(accountStatus=inactive)))",
|
||||||
UsernameAttribute: ldapAttrCommonName,
|
|
||||||
MailAttribute: ldapAttrMail,
|
|
||||||
DisplayNameAttribute: ldapAttrDescription,
|
|
||||||
GroupsFilter: "(&(uniqueMember={dn})(objectClass=posixGroup))",
|
GroupsFilter: "(&(uniqueMember={dn})(objectClass=posixGroup))",
|
||||||
GroupNameAttribute: ldapAttrCommonName,
|
GroupSearchMode: ldapGroupSearchModeFilter,
|
||||||
|
Attributes: LDAPAuthenticationAttributes{
|
||||||
|
Username: ldapAttrCommonName,
|
||||||
|
DisplayName: ldapAttrDescription,
|
||||||
|
Mail: ldapAttrMail,
|
||||||
|
MemberOf: ldapAttrMemberOf,
|
||||||
|
GroupName: ldapAttrCommonName,
|
||||||
|
},
|
||||||
Timeout: time.Second * 5,
|
Timeout: time.Second * 5,
|
||||||
TLS: &TLSConfig{
|
TLS: &TLSConfig{
|
||||||
MinimumVersion: TLSVersion{tls.VersionTLS12},
|
MinimumVersion: TLSVersion{tls.VersionTLS12},
|
||||||
|
|
|
@ -78,6 +78,14 @@ const (
|
||||||
LDAPImplementationGLAuth = "glauth"
|
LDAPImplementationGLAuth = "glauth"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// LDAPGroupSearchModeFilter is the string for the filter group search mode.
|
||||||
|
LDAPGroupSearchModeFilter = "filter"
|
||||||
|
|
||||||
|
// LDAPGroupSearchModeMemberOf is the string for the memberOf group search mode.
|
||||||
|
LDAPGroupSearchModeMemberOf = "memberof"
|
||||||
|
)
|
||||||
|
|
||||||
// TOTP Algorithm.
|
// TOTP Algorithm.
|
||||||
const (
|
const (
|
||||||
TOTPAlgorithmSHA1 = "SHA1"
|
TOTPAlgorithmSHA1 = "SHA1"
|
||||||
|
@ -121,11 +129,18 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
ldapGroupSearchModeFilter = "filter"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ldapAttrDistinguishedName = "distinguishedName"
|
||||||
ldapAttrMail = "mail"
|
ldapAttrMail = "mail"
|
||||||
ldapAttrUserID = "uid"
|
ldapAttrUserID = "uid"
|
||||||
|
ldapAttrSAMAccountName = "sAMAccountName"
|
||||||
ldapAttrDisplayName = "displayName"
|
ldapAttrDisplayName = "displayName"
|
||||||
ldapAttrDescription = "description"
|
ldapAttrDescription = "description"
|
||||||
ldapAttrCommonName = "cn"
|
ldapAttrCommonName = "cn"
|
||||||
|
ldapAttrMemberOf = "memberOf"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Address Schemes.
|
// Address Schemes.
|
||||||
|
|
|
@ -120,10 +120,13 @@ var Keys = []string{
|
||||||
"authentication_backend.ldap.users_filter",
|
"authentication_backend.ldap.users_filter",
|
||||||
"authentication_backend.ldap.additional_groups_dn",
|
"authentication_backend.ldap.additional_groups_dn",
|
||||||
"authentication_backend.ldap.groups_filter",
|
"authentication_backend.ldap.groups_filter",
|
||||||
"authentication_backend.ldap.group_name_attribute",
|
"authentication_backend.ldap.group_search_mode",
|
||||||
"authentication_backend.ldap.username_attribute",
|
"authentication_backend.ldap.attributes.distinguished_name",
|
||||||
"authentication_backend.ldap.mail_attribute",
|
"authentication_backend.ldap.attributes.username",
|
||||||
"authentication_backend.ldap.display_name_attribute",
|
"authentication_backend.ldap.attributes.display_name",
|
||||||
|
"authentication_backend.ldap.attributes.mail",
|
||||||
|
"authentication_backend.ldap.attributes.member_of",
|
||||||
|
"authentication_backend.ldap.attributes.group_name",
|
||||||
"authentication_backend.ldap.permit_referrals",
|
"authentication_backend.ldap.permit_referrals",
|
||||||
"authentication_backend.ldap.permit_unauthenticated_bind",
|
"authentication_backend.ldap.permit_unauthenticated_bind",
|
||||||
"authentication_backend.ldap.permit_feature_detection_failure",
|
"authentication_backend.ldap.permit_feature_detection_failure",
|
||||||
|
@ -259,9 +262,10 @@ var Keys = []string{
|
||||||
"notifier.smtp.port",
|
"notifier.smtp.port",
|
||||||
"notifier.template_path",
|
"notifier.template_path",
|
||||||
"server.address",
|
"server.address",
|
||||||
"server.path",
|
|
||||||
"server.asset_path",
|
"server.asset_path",
|
||||||
"server.disable_healthcheck",
|
"server.disable_healthcheck",
|
||||||
|
"server.disable_autho_https_redirect",
|
||||||
|
"server.use_ip_for_ban",
|
||||||
"server.tls.certificate",
|
"server.tls.certificate",
|
||||||
"server.tls.key",
|
"server.tls.key",
|
||||||
"server.tls.client_certificates",
|
"server.tls.client_certificates",
|
||||||
|
@ -272,6 +276,8 @@ var Keys = []string{
|
||||||
"server.endpoints.authz.*.implementation",
|
"server.endpoints.authz.*.implementation",
|
||||||
"server.endpoints.authz.*.authn_strategies",
|
"server.endpoints.authz.*.authn_strategies",
|
||||||
"server.endpoints.authz.*.authn_strategies[].name",
|
"server.endpoints.authz.*.authn_strategies[].name",
|
||||||
|
"server.grpc.address",
|
||||||
|
"server.grpc.disableTLS",
|
||||||
"server.buffers.read",
|
"server.buffers.read",
|
||||||
"server.buffers.write",
|
"server.buffers.write",
|
||||||
"server.timeouts.read",
|
"server.timeouts.read",
|
||||||
|
@ -279,6 +285,7 @@ var Keys = []string{
|
||||||
"server.timeouts.idle",
|
"server.timeouts.idle",
|
||||||
"server.host",
|
"server.host",
|
||||||
"server.port",
|
"server.port",
|
||||||
|
"server.path",
|
||||||
"telemetry.metrics.enabled",
|
"telemetry.metrics.enabled",
|
||||||
"telemetry.metrics.address",
|
"telemetry.metrics.address",
|
||||||
"telemetry.metrics.buffers.read",
|
"telemetry.metrics.buffers.read",
|
||||||
|
|
|
@ -8,13 +8,15 @@ import (
|
||||||
// ServerConfiguration represents the configuration of the http server.
|
// ServerConfiguration represents the configuration of the http server.
|
||||||
type ServerConfiguration struct {
|
type ServerConfiguration struct {
|
||||||
Address *AddressTCP `koanf:"address"`
|
Address *AddressTCP `koanf:"address"`
|
||||||
Path string `koanf:"path"`
|
|
||||||
AssetPath string `koanf:"asset_path"`
|
AssetPath string `koanf:"asset_path"`
|
||||||
DisableHealthcheck bool `koanf:"disable_healthcheck"`
|
DisableHealthcheck bool `koanf:"disable_healthcheck"`
|
||||||
|
DisableAutoHttpsRedirect bool `koanf:"disable_autho_https_redirect"`
|
||||||
|
UseIPInsteadOfUserForBan bool `koanf:"use_ip_for_ban"`
|
||||||
|
|
||||||
TLS ServerTLS `koanf:"tls"`
|
TLS ServerTLS `koanf:"tls"`
|
||||||
Headers ServerHeaders `koanf:"headers"`
|
Headers ServerHeaders `koanf:"headers"`
|
||||||
Endpoints ServerEndpoints `koanf:"endpoints"`
|
Endpoints ServerEndpoints `koanf:"endpoints"`
|
||||||
|
GRPC ServerGRPC `koanf:"grpc"`
|
||||||
|
|
||||||
Buffers ServerBuffers `koanf:"buffers"`
|
Buffers ServerBuffers `koanf:"buffers"`
|
||||||
Timeouts ServerTimeouts `koanf:"timeouts"`
|
Timeouts ServerTimeouts `koanf:"timeouts"`
|
||||||
|
@ -24,6 +26,9 @@ type ServerConfiguration struct {
|
||||||
|
|
||||||
// Deprecated: use address instead.
|
// Deprecated: use address instead.
|
||||||
Port int `koanf:"port"`
|
Port int `koanf:"port"`
|
||||||
|
|
||||||
|
// Deprecated: use address instead.
|
||||||
|
Path string `koanf:"path"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServerEndpoints is the endpoints configuration for the HTTP server.
|
// ServerEndpoints is the endpoints configuration for the HTTP server.
|
||||||
|
@ -58,9 +63,18 @@ type ServerHeaders struct {
|
||||||
CSPTemplate string `koanf:"csp_template"`
|
CSPTemplate string `koanf:"csp_template"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ServerGRCP contains configuration options for the gRCP server.
|
||||||
|
type ServerGRPC struct {
|
||||||
|
// Address with port to listen on. If this field is empty, no grcp server
|
||||||
|
// will be spawned.
|
||||||
|
Address *AddressTCP `koanf:"address"`
|
||||||
|
|
||||||
|
DisableTLS bool `koanf:"disableTLS"`
|
||||||
|
}
|
||||||
|
|
||||||
// DefaultServerConfiguration represents the default values of the ServerConfiguration.
|
// DefaultServerConfiguration represents the default values of the ServerConfiguration.
|
||||||
var DefaultServerConfiguration = ServerConfiguration{
|
var DefaultServerConfiguration = ServerConfiguration{
|
||||||
Address: &AddressTCP{Address{true, false, -1, 9091, &url.URL{Scheme: AddressSchemeTCP, Host: ":9091"}}},
|
Address: &AddressTCP{Address{true, false, -1, 9091, &url.URL{Scheme: AddressSchemeTCP, Host: ":9091", Path: "/"}}},
|
||||||
Buffers: ServerBuffers{
|
Buffers: ServerBuffers{
|
||||||
Read: 4096,
|
Read: 4096,
|
||||||
Write: 4096,
|
Write: 4096,
|
||||||
|
|
|
@ -21,7 +21,7 @@ type TelemetryMetricsConfig struct {
|
||||||
// DefaultTelemetryConfig is the default telemetry configuration.
|
// DefaultTelemetryConfig is the default telemetry configuration.
|
||||||
var DefaultTelemetryConfig = TelemetryConfig{
|
var DefaultTelemetryConfig = TelemetryConfig{
|
||||||
Metrics: TelemetryMetricsConfig{
|
Metrics: TelemetryMetricsConfig{
|
||||||
Address: &AddressTCP{Address{true, false, -1, 9959, &url.URL{Scheme: AddressSchemeTCP, Host: ":9959"}}},
|
Address: &AddressTCP{Address{true, false, -1, 9959, &url.URL{Scheme: AddressSchemeTCP, Host: ":9959", Path: "/metrics"}}},
|
||||||
Buffers: ServerBuffers{
|
Buffers: ServerBuffers{
|
||||||
Read: 4096,
|
Read: 4096,
|
||||||
Write: 4096,
|
Write: 4096,
|
||||||
|
|
|
@ -6,7 +6,6 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewAddress returns an *Address and error depending on the ability to parse the string as an Address.
|
// NewAddress returns an *Address and error depending on the ability to parse the string as an Address.
|
||||||
|
@ -336,6 +335,15 @@ func (a *Address) Path() string {
|
||||||
return a.url.Path
|
return a.url.Path
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetPath sets the path.
|
||||||
|
func (a *Address) SetPath(path string) {
|
||||||
|
if !a.valid || a.url == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
a.url.Path = path
|
||||||
|
}
|
||||||
|
|
||||||
// SocketHostname returns the correct hostname for a socket connection.
|
// SocketHostname returns the correct hostname for a socket connection.
|
||||||
func (a *Address) SocketHostname() string {
|
func (a *Address) SocketHostname() string {
|
||||||
if !a.valid || a.url == nil {
|
if !a.valid || a.url == nil {
|
||||||
|
@ -372,25 +380,6 @@ func (a *Address) NetworkAddress() string {
|
||||||
return a.url.Host
|
return a.url.Host
|
||||||
}
|
}
|
||||||
|
|
||||||
// Listener creates and returns a net.Listener.
|
|
||||||
func (a *Address) Listener() (ln net.Listener, err error) {
|
|
||||||
if a.url == nil {
|
|
||||||
return nil, fmt.Errorf("address url is nil")
|
|
||||||
}
|
|
||||||
|
|
||||||
if a.socket && a.umask != -1 {
|
|
||||||
umask := syscall.Umask(a.umask)
|
|
||||||
|
|
||||||
ln, err = net.Listen(a.Network(), a.NetworkAddress())
|
|
||||||
|
|
||||||
_ = syscall.Umask(umask)
|
|
||||||
|
|
||||||
return ln, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return net.Listen(a.Network(), a.NetworkAddress())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dial creates and returns a dialed net.Conn.
|
// Dial creates and returns a dialed net.Conn.
|
||||||
func (a *Address) Dial() (net.Conn, error) {
|
func (a *Address) Dial() (net.Conn, error) {
|
||||||
if !a.valid || a.url == nil {
|
if !a.valid || a.url == nil {
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
//go:build linux || freebsd || darwin || netbsd || solaris
|
||||||
|
|
||||||
|
package schema
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Listener creates and returns a net.Listener.
|
||||||
|
func (a *Address) Listener() (ln net.Listener, err error) {
|
||||||
|
if a.url == nil {
|
||||||
|
return nil, fmt.Errorf("address url is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if a.socket && a.umask != -1 {
|
||||||
|
umask := syscall.Umask(a.umask)
|
||||||
|
|
||||||
|
ln, err = net.Listen(a.Network(), a.NetworkAddress())
|
||||||
|
|
||||||
|
_ = syscall.Umask(umask)
|
||||||
|
|
||||||
|
return ln, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return net.Listen(a.Network(), a.NetworkAddress())
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
//go:build !linux && !freebsd && !darwin && !netbsd && !solaris
|
||||||
|
|
||||||
|
package schema
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Listener creates and returns a net.Listener.
|
||||||
|
func (a *Address) Listener() (ln net.Listener, err error) {
|
||||||
|
if a.url == nil {
|
||||||
|
return nil, fmt.Errorf("address url is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
return net.Listen(a.Network(), a.NetworkAddress())
|
||||||
|
}
|
|
@ -268,6 +268,21 @@ func TestX509CertificateChain(t *testing.T) {
|
||||||
assert.Regexp(t, regexp.MustCompile(`^certificate #1 in chain is invalid before 13569465600 but the time is \d+$`), err.Error())
|
assert.Regexp(t, regexp.MustCompile(`^certificate #1 in chain is invalid before 13569465600 but the time is \d+$`), err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPasswordDigest_IsPlainText(t *testing.T) {
|
||||||
|
digest, err := DecodePasswordDigest("$plaintext$exam")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.True(t, digest.IsPlainText())
|
||||||
|
|
||||||
|
digest = &PasswordDigest{}
|
||||||
|
|
||||||
|
assert.False(t, digest.IsPlainText())
|
||||||
|
|
||||||
|
digest, err = DecodePasswordDigest("$pbkdf2-sha512$310000$c8p78n7pUMln0jzvd4aK4Q$JNRBzwAo0ek5qKn50cFzzvE9RXV88h1wJn5KGiHrD0YKtZaR/nCb2CJPOsKaPK0hjf.9yHxzQGZziziccp6Yng")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.False(t, digest.IsPlainText())
|
||||||
|
}
|
||||||
|
|
||||||
func MustParseX509CertificateChain(data string) *X509CertificateChain {
|
func MustParseX509CertificateChain(data string) *X509CertificateChain {
|
||||||
chain, err := NewX509CertificateChain(data)
|
chain, err := NewX509CertificateChain(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -1,173 +0,0 @@
|
||||||
---
|
|
||||||
default_redirection_url: https://home.example.com:8080/
|
|
||||||
|
|
||||||
server:
|
|
||||||
address: "tcp://127.0.0.1:9091"
|
|
||||||
endpoints:
|
|
||||||
authz:
|
|
||||||
forward-auth:
|
|
||||||
implementation: ForwardAuth
|
|
||||||
authn_strategies:
|
|
||||||
- name: HeaderProxyAuthorization
|
|
||||||
- name: CookieSession
|
|
||||||
ext-authz:
|
|
||||||
implementation: ExtAuthz
|
|
||||||
authn_strategies:
|
|
||||||
- name: HeaderProxyAuthorization
|
|
||||||
- name: CookieSession
|
|
||||||
auth-request:
|
|
||||||
implementation: AuthRequest
|
|
||||||
authn_strategies:
|
|
||||||
- name: HeaderAuthRequestProxyAuthorization
|
|
||||||
- name: CookieSession
|
|
||||||
legacy:
|
|
||||||
implementation: Legacy
|
|
||||||
|
|
||||||
log:
|
|
||||||
level: debug
|
|
||||||
|
|
||||||
totp:
|
|
||||||
issuer: authelia.com
|
|
||||||
|
|
||||||
duo_api:
|
|
||||||
hostname: api-123456789.example.com
|
|
||||||
integration_key: ABCDEF
|
|
||||||
|
|
||||||
authentication_backend:
|
|
||||||
ldap:
|
|
||||||
url: 'ldap://127.0.0.1'
|
|
||||||
tls:
|
|
||||||
private_key: |
|
|
||||||
-----BEGIN RSA PRIVATE KEY-----
|
|
||||||
MIIEpAIBAAKCAQEA6z1LOg1ZCqb0lytXWZ+MRBpMHEXOoTOLYgfZXt1IYyE3Z758
|
|
||||||
cyalk0NYQhY5cZDsXPYWPvAHiPMUxutWkoxFwby56S+AbIMa3/Is+ILrHRJs8Exn
|
|
||||||
ZkpyrYFxPX12app2kErdmAkHSx0Z5/kuXiz96PHs8S8/ZbyZolLHzdfLtSzjvRm5
|
|
||||||
Zue5iFzsf19NJz5CIBfv8g5lRwtE8wNJoRSpn1xq7fqfuA0weDNFPzjlNWRLy6aa
|
|
||||||
rK7qJexRkmkCs4sLgyl+9NODYJpvmN8E1yhyC27E0joI6rBFVW7Ihv+cSPCdDzGp
|
|
||||||
EWe81x3AeqAa3mjVqkiq4u4Z2i8JDgBaPboqJwIDAQABAoIBAAFdLZ58jVOefDSU
|
|
||||||
L8F5R1rtvBs93GDa56f926jNJ6pLewLC+/2+757W+SAI+PRLntM7Kg3bXm/Q2QH+
|
|
||||||
Q1Y+MflZmspbWCdI61L5GIGoYKyeers59i+FpvySj5GHtLQRiTZ0+Kv1AXHSDWBm
|
|
||||||
9XneUOqU3IbZe0ifu1RRno72/VtjkGXbW8Mkkw+ohyGbIeTx/0/JQ6sSNZTT3Vk7
|
|
||||||
8i4IXptq3HSF0/vqZuah8rShoeNq72pD1YLM9YPdL5by1QkDLnqATDiCpLBTCaNV
|
|
||||||
I8sqYEun+HYbQzBj8ZACG2JVZpEEidONWQHw5BPWO95DSZYrVnEkuCqeH+u5vYt7
|
|
||||||
CHuJ3AECgYEA+W3v5z+j91w1VPHS0VB3SCDMouycAMIUnJPAbt+0LPP0scUFsBGE
|
|
||||||
hPAKddC54pmMZRQ2KIwBKiyWfCrJ8Xz8Yogn7fJgmwTHidJBr2WQpIEkNGlK3Dzi
|
|
||||||
jXL2sh0yC7sHvn0DqiQ79l/e7yRbSnv2wrTJEczOOH2haD7/tBRyCYECgYEA8W+q
|
|
||||||
E9YyGvEltnPFaOxofNZ8LHVcZSsQI5b6fc0iE7fjxFqeXPXEwGSOTwqQLQRiHn9b
|
|
||||||
CfPmIG4Vhyq0otVmlPvUnfBZ2OK+tl5X2/mQFO3ROMdvpi0KYa994uqfJdSTaqLn
|
|
||||||
jjoKFB906UFHnDQDLZUNiV1WwnkTglgLc+xrd6cCgYEAqqthyv6NyBTM3Tm2gcio
|
|
||||||
Ra9Dtntl51LlXZnvwy3IkDXBCd6BHM9vuLKyxZiziGx+Vy90O1xI872cnot8sINQ
|
|
||||||
Am+dur/tAEVN72zxyv0Y8qb2yfH96iKy9gxi5s75TnOEQgAygLnYWaWR2lorKRUX
|
|
||||||
bHTdXBOiS58S0UzCFEslGIECgYBqkO4SKWYeTDhoKvuEj2yjRYyzlu28XeCWxOo1
|
|
||||||
otiauX0YSyNBRt2cSgYiTzhKFng0m+QUJYp63/wymB/5C5Zmxi0XtWIDADpLhqLj
|
|
||||||
HmmBQ2Mo26alQ5YkffBju0mZyhVzaQop1eZi8WuKFV1FThPlB7hc3E0SM5zv2Grd
|
|
||||||
tQnOWwKBgQC40yZY0PcjuILhy+sIc0Wvh7LUA7taSdTye149kRvbvsCDN7Jh75lM
|
|
||||||
USjhLXY0Nld2zBm9r8wMb81mXH29uvD+tDqqsICvyuKlA/tyzXR+QTr7dCVKVwu0
|
|
||||||
1YjCJ36UpTsLre2f8nOSLtNmRfDPtbOE2mkOoO9dD9UU0XZwnvn9xw==
|
|
||||||
-----END RSA PRIVATE KEY-----
|
|
||||||
base_dn: dc=example,dc=com
|
|
||||||
username_attribute: uid
|
|
||||||
additional_users_dn: ou=users
|
|
||||||
users_filter: (&({username_attribute}={input})(objectCategory=person)(objectClass=user))
|
|
||||||
additional_groups_dn: ou=groups
|
|
||||||
groups_filter: (&(member={dn})(objectClass=groupOfNames))
|
|
||||||
group_name_attribute: cn
|
|
||||||
mail_attribute: mail
|
|
||||||
user: cn=admin,dc=example,dc=com
|
|
||||||
|
|
||||||
access_control:
|
|
||||||
default_policy: deny
|
|
||||||
|
|
||||||
rules:
|
|
||||||
# Rules applied to everyone
|
|
||||||
- domain: public.example.com
|
|
||||||
policy: bypass
|
|
||||||
|
|
||||||
- domain: secure.example.com
|
|
||||||
policy: one_factor
|
|
||||||
# Network based rule, if not provided any network matches.
|
|
||||||
networks:
|
|
||||||
- 192.168.1.0/24
|
|
||||||
- domain: secure.example.com
|
|
||||||
policy: two_factor
|
|
||||||
|
|
||||||
- domain: [singlefactor.example.com, onefactor.example.com]
|
|
||||||
policy: one_factor
|
|
||||||
|
|
||||||
# Rules applied to 'admins' group
|
|
||||||
- domain: "mx2.mail.example.com"
|
|
||||||
subject: "group:admins"
|
|
||||||
policy: deny
|
|
||||||
- domain: "*.example.com"
|
|
||||||
subject: "group:admins"
|
|
||||||
policy: two_factor
|
|
||||||
|
|
||||||
# Rules applied to 'dev' group
|
|
||||||
- domain: dev.example.com
|
|
||||||
resources:
|
|
||||||
- "^/groups/dev/.*$"
|
|
||||||
subject: "group:dev"
|
|
||||||
policy: two_factor
|
|
||||||
|
|
||||||
# Rules applied to user 'john'
|
|
||||||
- domain: dev.example.com
|
|
||||||
resources:
|
|
||||||
- "^/users/john/.*$"
|
|
||||||
subject: "user:john"
|
|
||||||
policy: two_factor
|
|
||||||
|
|
||||||
# Rules applied to 'dev' group and user 'john'
|
|
||||||
- domain: dev.example.com
|
|
||||||
resources:
|
|
||||||
- "^/deny-all.*$"
|
|
||||||
subject: ["group:dev", "user:john"]
|
|
||||||
policy: deny
|
|
||||||
|
|
||||||
# Rules applied to user 'harry'
|
|
||||||
- domain: dev.example.com
|
|
||||||
resources:
|
|
||||||
- "^/users/harry/.*$"
|
|
||||||
subject: "user:harry"
|
|
||||||
policy: two_factor
|
|
||||||
|
|
||||||
# Rules applied to user 'bob'
|
|
||||||
- domain: "*.mail.example.com"
|
|
||||||
subject: "user:bob"
|
|
||||||
policy: two_factor
|
|
||||||
- domain: "dev.example.com"
|
|
||||||
resources:
|
|
||||||
- "^/users/bob/.*$"
|
|
||||||
subject: "user:bob"
|
|
||||||
policy: two_factor
|
|
||||||
|
|
||||||
session:
|
|
||||||
name: authelia_session
|
|
||||||
expiration: 3600000 # 1 hour
|
|
||||||
inactivity: 300000 # 5 minutes
|
|
||||||
domain: example.com
|
|
||||||
redis:
|
|
||||||
host: 127.0.0.1
|
|
||||||
port: 6379
|
|
||||||
high_availability:
|
|
||||||
sentinel_name: test
|
|
||||||
|
|
||||||
regulation:
|
|
||||||
max_retries: 3
|
|
||||||
find_time: 120
|
|
||||||
ban_time: 300
|
|
||||||
|
|
||||||
storage:
|
|
||||||
postgres:
|
|
||||||
host: 127.0.0.1
|
|
||||||
port: 5432
|
|
||||||
database: authelia
|
|
||||||
username: authelia
|
|
||||||
|
|
||||||
notifier:
|
|
||||||
smtp:
|
|
||||||
username: test
|
|
||||||
host: 127.0.0.1
|
|
||||||
port: 1025
|
|
||||||
sender: admin@example.com
|
|
||||||
disable_require_tls: true
|
|
||||||
...
|
|
|
@ -1,173 +1,23 @@
|
||||||
---
|
---
|
||||||
default_redirection_url: https://home.example.com:8080/
|
|
||||||
|
|
||||||
server:
|
server:
|
||||||
address: "tcp://127.0.0.1:9091"
|
host: '0.0.0.0'
|
||||||
endpoints:
|
port: 9091
|
||||||
authz:
|
path: "auth"
|
||||||
forward-auth:
|
|
||||||
implementation: ForwardAuth
|
|
||||||
authn_strategies:
|
|
||||||
- name: HeaderProxyAuthorization
|
|
||||||
- name: CookieSession
|
|
||||||
ext-authz:
|
|
||||||
implementation: ExtAuthz
|
|
||||||
authn_strategies:
|
|
||||||
- name: HeaderProxyAuthorization
|
|
||||||
- name: CookieSession
|
|
||||||
auth-request:
|
|
||||||
implementation: AuthRequest
|
|
||||||
authn_strategies:
|
|
||||||
- name: HeaderAuthRequestProxyAuthorization
|
|
||||||
- name: CookieSession
|
|
||||||
legacy:
|
|
||||||
implementation: Legacy
|
|
||||||
|
|
||||||
log:
|
|
||||||
level: debug
|
|
||||||
|
|
||||||
totp:
|
|
||||||
issuer: authelia.com
|
|
||||||
|
|
||||||
duo_api:
|
|
||||||
hostname: api-123456789.example.com
|
|
||||||
integration_key: ABCDEF
|
|
||||||
|
|
||||||
authentication_backend:
|
authentication_backend:
|
||||||
ldap:
|
ldap:
|
||||||
url: 'ldap://127.0.0.1'
|
url: 'ldap://127.0.0.1'
|
||||||
tls:
|
|
||||||
private_key: |
|
|
||||||
-----BEGIN RSA PRIVATE KEY-----
|
|
||||||
MIIEpAIBAAKCAQEA6z1LOg1ZCqb0lytXWZ+MRBpMHEXOoTOLYgfZXt1IYyE3Z758
|
|
||||||
cyalk0NYQhY5cZDsXPYWPvAHiPMUxutWkoxFwby56S+AbIMa3/Is+ILrHRJs8Exn
|
|
||||||
ZkpyrYFxPX12app2kErdmAkHSx0Z5/kuXiz96PHs8S8/ZbyZolLHzdfLtSzjvRm5
|
|
||||||
Zue5iFzsf19NJz5CIBfv8g5lRwtE8wNJoRSpn1xq7fqfuA0weDNFPzjlNWRLy6aa
|
|
||||||
rK7qJexRkmkCs4sLgyl+9NODYJpvmN8E1yhyC27E0joI6rBFVW7Ihv+cSPCdDzGp
|
|
||||||
EWe81x3AeqAa3mjVqkiq4u4Z2i8JDgBaPboqJwIDAQABAoIBAAFdLZ58jVOefDSU
|
|
||||||
L8F5R1rtvBs93GDa56f926jNJ6pLewLC+/2+757W+SAI+PRLntM7Kg3bXm/Q2QH+
|
|
||||||
Q1Y+MflZmspbWCdI61L5GIGoYKyeers59i+FpvySj5GHtLQRiTZ0+Kv1AXHSDWBm
|
|
||||||
9XneUOqU3IbZe0ifu1RRno72/VtjkGXbW8Mkkw+ohyGbIeTx/0/JQ6sSNZTT3Vk7
|
|
||||||
8i4IXptq3HSF0/vqZuah8rShoeNq72pD1YLM9YPdL5by1QkDLnqATDiCpLBTCaNV
|
|
||||||
I8sqYEun+HYbQzBj8ZACG2JVZpEEidONWQHw5BPWO95DSZYrVnEkuCqeH+u5vYt7
|
|
||||||
CHuJ3AECgYEA+W3v5z+j91w1VPHS0VB3SCDMouycAMIUnJPAbt+0LPP0scUFsBGE
|
|
||||||
hPAKddC54pmMZRQ2KIwBKiyWfCrJ8Xz8Yogn7fJgmwTHidJBr2WQpIEkNGlK3Dzi
|
|
||||||
jXL2sh0yC7sHvn0DqiQ79l/e7yRbSnv2wrTJEczOOH2haD7/tBRyCYECgYEA8W+q
|
|
||||||
E9YyGvEltnPFaOxofNZ8LHVcZSsQI5b6fc0iE7fjxFqeXPXEwGSOTwqQLQRiHn9b
|
|
||||||
CfPmIG4Vhyq0otVmlPvUnfBZ2OK+tl5X2/mQFO3ROMdvpi0KYa994uqfJdSTaqLn
|
|
||||||
jjoKFB906UFHnDQDLZUNiV1WwnkTglgLc+xrd6cCgYEAqqthyv6NyBTM3Tm2gcio
|
|
||||||
Ra9Dtntl51LlXZnvwy3IkDXBCd6BHM9vuLKyxZiziGx+Vy90O1xI872cnot8sINQ
|
|
||||||
Am+dur/tAEVN72zxyv0Y8qb2yfH96iKy9gxi5s75TnOEQgAygLnYWaWR2lorKRUX
|
|
||||||
bHTdXBOiS58S0UzCFEslGIECgYBqkO4SKWYeTDhoKvuEj2yjRYyzlu28XeCWxOo1
|
|
||||||
otiauX0YSyNBRt2cSgYiTzhKFng0m+QUJYp63/wymB/5C5Zmxi0XtWIDADpLhqLj
|
|
||||||
HmmBQ2Mo26alQ5YkffBju0mZyhVzaQop1eZi8WuKFV1FThPlB7hc3E0SM5zv2Grd
|
|
||||||
tQnOWwKBgQC40yZY0PcjuILhy+sIc0Wvh7LUA7taSdTye149kRvbvsCDN7Jh75lM
|
|
||||||
USjhLXY0Nld2zBm9r8wMb81mXH29uvD+tDqqsICvyuKlA/tyzXR+QTr7dCVKVwu0
|
|
||||||
1YjCJ36UpTsLre2f8nOSLtNmRfDPtbOE2mkOoO9dD9UU0XZwnvn9xw==
|
|
||||||
-----END RSA PRIVATE KEY-----
|
|
||||||
base_dn: dc=example,dc=com
|
|
||||||
username_attribute: uid
|
|
||||||
additional_users_dn: ou=users
|
|
||||||
users_filter: (&({username_attribute}={input})(objectCategory=person)(objectClass=user))
|
|
||||||
additional_groups_dn: ou=groups
|
|
||||||
groups_filter: (&(member={dn})(objectClass=groupOfNames))
|
|
||||||
group_name_attribute: cn
|
|
||||||
mail_attribute: mail
|
|
||||||
user: cn=admin,dc=example,dc=com
|
|
||||||
|
|
||||||
access_control:
|
|
||||||
default_policy: deny
|
|
||||||
|
|
||||||
rules:
|
|
||||||
# Rules applied to everyone
|
|
||||||
- domain: public.example.com
|
|
||||||
policy: bypass
|
|
||||||
|
|
||||||
- domain: secure.example.com
|
|
||||||
policy: one_factor
|
|
||||||
# Network based rule, if not provided any network matches.
|
|
||||||
networks:
|
|
||||||
- 192.168.1.0/24
|
|
||||||
- domain: secure.example.com
|
|
||||||
policy: two_factor
|
|
||||||
|
|
||||||
- domain: [singlefactor.example.com, onefactor.example.com]
|
|
||||||
policy: one_factor
|
|
||||||
|
|
||||||
# Rules applied to 'admins' group
|
|
||||||
- domain: "mx2.mail.example.com"
|
|
||||||
subject: "group:admins"
|
|
||||||
policy: deny
|
|
||||||
- domain: "*.example.com"
|
|
||||||
subject: "group:admins"
|
|
||||||
policy: two_factor
|
|
||||||
|
|
||||||
# Rules applied to 'dev' group
|
|
||||||
- domain: dev.example.com
|
|
||||||
resources:
|
|
||||||
- "^/groups/dev/.*$"
|
|
||||||
subject: "group:dev"
|
|
||||||
policy: two_factor
|
|
||||||
|
|
||||||
# Rules applied to user 'john'
|
|
||||||
- domain: dev.example.com
|
|
||||||
resources:
|
|
||||||
- "^/users/john/.*$"
|
|
||||||
subject: "user:john"
|
|
||||||
policy: two_factor
|
|
||||||
|
|
||||||
# Rules applied to 'dev' group and user 'john'
|
|
||||||
- domain: dev.example.com
|
|
||||||
resources:
|
|
||||||
- "^/deny-all.*$"
|
|
||||||
subject: ["group:dev", "user:john"]
|
|
||||||
policy: deny
|
|
||||||
|
|
||||||
# Rules applied to user 'harry'
|
|
||||||
- domain: dev.example.com
|
|
||||||
resources:
|
|
||||||
- "^/users/harry/.*$"
|
|
||||||
subject: "user:harry"
|
|
||||||
policy: two_factor
|
|
||||||
|
|
||||||
# Rules applied to user 'bob'
|
|
||||||
- domain: "*.mail.example.com"
|
|
||||||
subject: "user:bob"
|
|
||||||
policy: two_factor
|
|
||||||
- domain: "dev.example.com"
|
|
||||||
resources:
|
|
||||||
- "^/users/bob/.*$"
|
|
||||||
subject: "user:bob"
|
|
||||||
policy: two_factor
|
|
||||||
|
|
||||||
session:
|
|
||||||
name: authelia_session
|
|
||||||
expiration: 3600000 # 1 hour
|
|
||||||
inactivity: 300000 # 5 minutes
|
|
||||||
domain: example.com
|
|
||||||
redis:
|
|
||||||
host: 127.0.0.1
|
|
||||||
port: 6379
|
|
||||||
high_availability:
|
|
||||||
sentinel_name: test
|
|
||||||
|
|
||||||
regulation:
|
|
||||||
max_retries: 3
|
|
||||||
find_time: 120
|
|
||||||
ban_time: 300
|
|
||||||
|
|
||||||
storage:
|
storage:
|
||||||
mysql:
|
mysql:
|
||||||
host: 127.0.0.1
|
host: 127.0.0.1
|
||||||
port: 3306
|
port: 3306
|
||||||
database: authelia
|
postgres:
|
||||||
username: authelia
|
host: 127.0.0.1
|
||||||
|
port: 5432
|
||||||
|
|
||||||
notifier:
|
notifier:
|
||||||
smtp:
|
smtp:
|
||||||
username: test
|
|
||||||
host: 127.0.0.1
|
host: 127.0.0.1
|
||||||
port: 1025
|
port: 1025
|
||||||
sender: admin@example.com
|
|
||||||
disable_require_tls: true
|
|
||||||
...
|
...
|
||||||
|
|
|
@ -66,14 +66,15 @@ authentication_backend:
|
||||||
1YjCJ36UpTsLre2f8nOSLtNmRfDPtbOE2mkOoO9dD9UU0XZwnvn9xw==
|
1YjCJ36UpTsLre2f8nOSLtNmRfDPtbOE2mkOoO9dD9UU0XZwnvn9xw==
|
||||||
-----END RSA PRIVATE KEY-----
|
-----END RSA PRIVATE KEY-----
|
||||||
base_dn: 'dc=example,dc=com'
|
base_dn: 'dc=example,dc=com'
|
||||||
username_attribute: 'uid'
|
|
||||||
additional_users_dn: 'ou=users'
|
additional_users_dn: 'ou=users'
|
||||||
users_filter: '(&({username_attribute}={input})(objectCategory=person)(objectClass=user))'
|
users_filter: '(&({username_attribute}={input})(objectCategory=person)(objectClass=user))'
|
||||||
additional_groups_dn: 'ou=groups'
|
additional_groups_dn: 'ou=groups'
|
||||||
groups_filter: '(&(member={dn})(objectClass=groupOfNames))'
|
groups_filter: '(&(member={dn})(objectClass=groupOfNames))'
|
||||||
group_name_attribute: 'cn'
|
|
||||||
mail_attribute: 'mail'
|
|
||||||
user: 'cn=admin,dc=example,dc=com'
|
user: 'cn=admin,dc=example,dc=com'
|
||||||
|
attributes:
|
||||||
|
username: 'uid'
|
||||||
|
group_name: 'cn'
|
||||||
|
mail: 'mail'
|
||||||
|
|
||||||
access_control:
|
access_control:
|
||||||
default_policy: 'deny'
|
default_policy: 'deny'
|
||||||
|
|
|
@ -47,14 +47,15 @@ authentication_backend:
|
||||||
1YjCJ36UpTsLre2f8nOSLtNmRfDPtbOE2mkOoO9dD9UU0XZwnvn9xw==
|
1YjCJ36UpTsLre2f8nOSLtNmRfDPtbOE2mkOoO9dD9UU0XZwnvn9xw==
|
||||||
-----END RSA PRIVATE KEY-----
|
-----END RSA PRIVATE KEY-----
|
||||||
base_dn: dc=example,dc=com
|
base_dn: dc=example,dc=com
|
||||||
username_attribute: uid
|
|
||||||
additional_users_dn: ou=users
|
additional_users_dn: ou=users
|
||||||
users_filter: (&({username_attribute}={input})(objectCategory=person)(objectClass=user))
|
users_filter: (&({username_attribute}={input})(objectCategory=person)(objectClass=user))
|
||||||
additional_groups_dn: ou=groups
|
additional_groups_dn: ou=groups
|
||||||
groups_filter: (&(member={dn})(objectClass=groupOfNames))
|
groups_filter: (&(member={dn})(objectClass=groupOfNames))
|
||||||
group_name_attribute: cn
|
|
||||||
mail_attribute: mail
|
|
||||||
user: cn=admin,dc=example,dc=com
|
user: cn=admin,dc=example,dc=com
|
||||||
|
attributes:
|
||||||
|
username: 'uid'
|
||||||
|
group_name: 'cn'
|
||||||
|
mail: 'mail'
|
||||||
|
|
||||||
access_control:
|
access_control:
|
||||||
default_policy: deny
|
default_policy: deny
|
||||||
|
|
|
@ -66,14 +66,15 @@ authentication_backend:
|
||||||
1YjCJ36UpTsLre2f8nOSLtNmRfDPtbOE2mkOoO9dD9UU0XZwnvn9xw==
|
1YjCJ36UpTsLre2f8nOSLtNmRfDPtbOE2mkOoO9dD9UU0XZwnvn9xw==
|
||||||
-----END RSA PRIVATE KEY-----
|
-----END RSA PRIVATE KEY-----
|
||||||
base_dn: 'dc=example,dc=com'
|
base_dn: 'dc=example,dc=com'
|
||||||
username_attribute: 'uid'
|
|
||||||
additional_users_dn: 'ou=users'
|
additional_users_dn: 'ou=users'
|
||||||
users_filter: '(&({username_attribute}={input})(objectCategory=person)(objectClass=user))'
|
users_filter: '(&({username_attribute}={input})(objectCategory=person)(objectClass=user))'
|
||||||
additional_groups_dn: 'ou=groups'
|
additional_groups_dn: 'ou=groups'
|
||||||
groups_filter: '(&(member={dn})(objectClass=groupOfNames))'
|
groups_filter: '(&(member={dn})(objectClass=groupOfNames))'
|
||||||
group_name_attribute: 'cn'
|
|
||||||
mail_attribute: 'mail'
|
|
||||||
user: 'cn=admin,dc=example,dc=com'
|
user: 'cn=admin,dc=example,dc=com'
|
||||||
|
attributes:
|
||||||
|
username: 'uid'
|
||||||
|
group_name: 'cn'
|
||||||
|
mail: 'mail'
|
||||||
|
|
||||||
access_control:
|
access_control:
|
||||||
default_policy: 'deny'
|
default_policy: 'deny'
|
||||||
|
|
|
@ -18,14 +18,16 @@ authentication_backend:
|
||||||
ldap:
|
ldap:
|
||||||
address: 'ldap://127.0.0.1'
|
address: 'ldap://127.0.0.1'
|
||||||
base_dn: 'dc=example,dc=com'
|
base_dn: 'dc=example,dc=com'
|
||||||
username_attribute: 'uid'
|
|
||||||
additional_users_dn: 'ou=users'
|
additional_users_dn: 'ou=users'
|
||||||
users_filter: '(&({username_attribute}={input})(objectCategory=person)(objectClass=user))'
|
users_filter: '(&({username_attribute}={input})(objectCategory=person)(objectClass=user))'
|
||||||
additional_groups_dn: 'ou=groups'
|
additional_groups_dn: 'ou=groups'
|
||||||
groups_filter: '(&(member={dn})(objectClass=groupOfNames))'
|
groups_filter: '(&(member={dn})(objectClass=groupOfNames))'
|
||||||
group_name_attribute: 'cn'
|
|
||||||
mail_attribute: 'mail'
|
|
||||||
user: 'cn=admin,dc=example,dc=com'
|
user: 'cn=admin,dc=example,dc=com'
|
||||||
|
attributes:
|
||||||
|
mail: 'mail'
|
||||||
|
group_name: 'cn'
|
||||||
|
username: 'uid'
|
||||||
|
|
||||||
access_control:
|
access_control:
|
||||||
default_policy: 'deny'
|
default_policy: 'deny'
|
||||||
|
|
|
@ -19,14 +19,15 @@ authentication_backend:
|
||||||
ldap:
|
ldap:
|
||||||
address: 'ldap://127.0.0.1'
|
address: 'ldap://127.0.0.1'
|
||||||
base_dn: 'dc=example,dc=com'
|
base_dn: 'dc=example,dc=com'
|
||||||
username_attribute: 'uid'
|
|
||||||
additional_users_dn: 'ou=users'
|
additional_users_dn: 'ou=users'
|
||||||
users_filter: '(&({username_attribute}={input})(objectCategory=person)(objectClass=user))'
|
users_filter: '(&({username_attribute}={input})(objectCategory=person)(objectClass=user))'
|
||||||
additional_groups_dn: 'ou=groups'
|
additional_groups_dn: 'ou=groups'
|
||||||
groups_filter: '(&(member={dn})(objectClass=groupOfNames))'
|
groups_filter: '(&(member={dn})(objectClass=groupOfNames))'
|
||||||
group_name_attribute: 'cn'
|
|
||||||
mail_attribute: 'mail'
|
|
||||||
user: 'cn=admin,dc=example,dc=com'
|
user: 'cn=admin,dc=example,dc=com'
|
||||||
|
attributes:
|
||||||
|
username: 'uid'
|
||||||
|
group_name: 'cn'
|
||||||
|
mail: 'mail'
|
||||||
|
|
||||||
access_control:
|
access_control:
|
||||||
default_policy: 'deny'
|
default_policy: 'deny'
|
||||||
|
|
|
@ -18,14 +18,15 @@ authentication_backend:
|
||||||
ldap:
|
ldap:
|
||||||
address: 'ldap://127.0.0.1'
|
address: 'ldap://127.0.0.1'
|
||||||
base_dn: 'dc=example,dc=com'
|
base_dn: 'dc=example,dc=com'
|
||||||
username_attribute: 'uid'
|
|
||||||
additional_users_dn: 'ou=users'
|
additional_users_dn: 'ou=users'
|
||||||
users_filter: '(&({username_attribute}={input})(objectCategory=person)(objectClass=user))'
|
users_filter: '(&({username_attribute}={input})(objectCategory=person)(objectClass=user))'
|
||||||
additional_groups_dn: 'ou=groups'
|
additional_groups_dn: 'ou=groups'
|
||||||
groups_filter: '(&(member={dn})(objectClass=groupOfNames))'
|
groups_filter: '(&(member={dn})(objectClass=groupOfNames))'
|
||||||
group_name_attribute: 'cn'
|
|
||||||
mail_attribute: 'mail'
|
|
||||||
user: 'cn=admin,dc=example,dc=com'
|
user: 'cn=admin,dc=example,dc=com'
|
||||||
|
attributes:
|
||||||
|
username: 'uid'
|
||||||
|
group_name: 'cn'
|
||||||
|
mail: 'mail'
|
||||||
|
|
||||||
access_control:
|
access_control:
|
||||||
default_policy: 'deny'
|
default_policy: 'deny'
|
||||||
|
|
|
@ -18,13 +18,15 @@ authentication_backend:
|
||||||
ldap:
|
ldap:
|
||||||
address: 'ldap://127.0.0.1'
|
address: 'ldap://127.0.0.1'
|
||||||
base_dn: 'dc=example,dc=com'
|
base_dn: 'dc=example,dc=com'
|
||||||
username_attribute: 'uid'
|
|
||||||
additional_users_dn: 'ou=users'
|
additional_users_dn: 'ou=users'
|
||||||
users_filter: '(&({username_attribute}={input})(objectCategory=person)(objectClass=user))'
|
users_filter: '(&({username_attribute}={input})(objectCategory=person)(objectClass=user))'
|
||||||
additional_groups_dn: 'ou=groups'
|
additional_groups_dn: 'ou=groups'
|
||||||
groups_filter: '(&(member={dn})(objectClass=groupOfNames))'
|
groups_filter: '(&(member={dn})(objectClass=groupOfNames))'
|
||||||
group_name_attribute: 'cn'
|
attributes:
|
||||||
mail_attribute: 'mail'
|
group_name: 'cn'
|
||||||
|
mail: 'mail'
|
||||||
|
username: 'uid'
|
||||||
|
|
||||||
user: 'cn=admin,dc=example,dc=com'
|
user: 'cn=admin,dc=example,dc=com'
|
||||||
|
|
||||||
access_control:
|
access_control:
|
||||||
|
|
|
@ -18,14 +18,15 @@ authentication_backend:
|
||||||
ldap:
|
ldap:
|
||||||
address: 'ldap://127.0.0.1'
|
address: 'ldap://127.0.0.1'
|
||||||
base_dn: 'dc=example,dc=com'
|
base_dn: 'dc=example,dc=com'
|
||||||
username_attribute: 'uid'
|
|
||||||
additional_users_dn: 'ou=users'
|
additional_users_dn: 'ou=users'
|
||||||
users_filter: '(&({username_attribute}={input})(objectCategory=person)(objectClass=user))'
|
users_filter: '(&({username_attribute}={input})(objectCategory=person)(objectClass=user))'
|
||||||
additional_groups_dn: 'ou=groups'
|
additional_groups_dn: 'ou=groups'
|
||||||
groups_filter: '(&(member={dn})(objectClass=groupOfNames))'
|
groups_filter: '(&(member={dn})(objectClass=groupOfNames))'
|
||||||
group_name_attribute: 'cn'
|
|
||||||
mail_attribute: 'mail'
|
|
||||||
user: 'cn=admin,dc=example,dc=com'
|
user: 'cn=admin,dc=example,dc=com'
|
||||||
|
attributes:
|
||||||
|
username: 'uid'
|
||||||
|
group_name: 'cn'
|
||||||
|
mail: 'mail'
|
||||||
|
|
||||||
access_control:
|
access_control:
|
||||||
default_policy: 'deny'
|
default_policy: 'deny'
|
||||||
|
|
|
@ -0,0 +1,137 @@
|
||||||
|
---
|
||||||
|
default_redirection_url: 'https://home.example.com:8080/'
|
||||||
|
|
||||||
|
server:
|
||||||
|
address: 'tcp://127.0.0.1:9091'
|
||||||
|
|
||||||
|
log:
|
||||||
|
level: 'debug'
|
||||||
|
|
||||||
|
totp:
|
||||||
|
issuer: 'authelia.com'
|
||||||
|
|
||||||
|
duo_api:
|
||||||
|
hostname: 'api-123456789.example.com'
|
||||||
|
integration_key: 'ABCDEF'
|
||||||
|
|
||||||
|
authentication_backend:
|
||||||
|
ldap:
|
||||||
|
address: 'ldap://127.0.0.1'
|
||||||
|
base_dn: 'dc=example,dc=com'
|
||||||
|
additional_users_dn: 'ou=users'
|
||||||
|
users_filter: '(&({username_attribute}={input})(objectCategory=person)(objectClass=user))'
|
||||||
|
additional_groups_dn: 'ou=groups'
|
||||||
|
groups_filter: '(&(member={dn})(objectClass=groupOfNames))'
|
||||||
|
user: 'cn=admin,dc=example,dc=com'
|
||||||
|
attributes:
|
||||||
|
username: 'uid'
|
||||||
|
group_name: 'cn'
|
||||||
|
mail: 'mail'
|
||||||
|
|
||||||
|
access_control:
|
||||||
|
default_policy: 'deny'
|
||||||
|
|
||||||
|
rules:
|
||||||
|
# Rules applied to everyone
|
||||||
|
- domain: 'public.example.com'
|
||||||
|
policy: 'bypass'
|
||||||
|
|
||||||
|
- domain: 'secure.example.com'
|
||||||
|
policy: 'one_factor'
|
||||||
|
# Network based rule, if not provided any network matches.
|
||||||
|
networks:
|
||||||
|
- '192.168.1.0/24'
|
||||||
|
- domain: 'secure.example.com'
|
||||||
|
policy: 'two_factor'
|
||||||
|
|
||||||
|
- domain: ['singlefactor.example.com', 'onefactor.example.com']
|
||||||
|
policy: 'one_factor'
|
||||||
|
|
||||||
|
# Rules applied to 'admins' group
|
||||||
|
- domain: 'mx2.mail.example.com'
|
||||||
|
subject: 'group:admins'
|
||||||
|
policy: 'deny'
|
||||||
|
- domain: '*.example.com'
|
||||||
|
subject: 'group:admins'
|
||||||
|
policy: 'two_factor'
|
||||||
|
|
||||||
|
# Rules applied to 'dev' group
|
||||||
|
- domain: 'dev.example.com'
|
||||||
|
resources:
|
||||||
|
- '^/groups/dev/.*$'
|
||||||
|
subject: 'group:dev'
|
||||||
|
policy: 'two_factor'
|
||||||
|
|
||||||
|
# Rules applied to user 'john'
|
||||||
|
- domain: 'dev.example.com'
|
||||||
|
resources:
|
||||||
|
- '^/users/john/.*$'
|
||||||
|
subject: 'user:john'
|
||||||
|
policy: 'two_factor'
|
||||||
|
|
||||||
|
# Rules applied to 'dev' group and user 'john'
|
||||||
|
- domain: 'dev.example.com'
|
||||||
|
resources:
|
||||||
|
- '^/deny-all.*$'
|
||||||
|
subject: ['group:dev', 'user:john']
|
||||||
|
policy: 'deny'
|
||||||
|
|
||||||
|
# Rules applied to user 'harry'
|
||||||
|
- domain: 'dev.example.com'
|
||||||
|
resources:
|
||||||
|
- '^/users/harry/.*$'
|
||||||
|
subject: 'user:harry'
|
||||||
|
policy: 'two_factor'
|
||||||
|
|
||||||
|
# Rules applied to user 'bob'
|
||||||
|
- domain: '*.mail.example.com'
|
||||||
|
subject: 'user:bob'
|
||||||
|
policy: 'two_factor'
|
||||||
|
- domain: 'dev.example.com'
|
||||||
|
resources:
|
||||||
|
- '^/users/bob/.*$'
|
||||||
|
subject: 'user:bob'
|
||||||
|
policy: 'two_factor'
|
||||||
|
|
||||||
|
session:
|
||||||
|
name: 'authelia_session'
|
||||||
|
expiration: '1h' # 1 hour
|
||||||
|
inactivity: '5m' # 5 minutes
|
||||||
|
domain: 'example.com'
|
||||||
|
redis:
|
||||||
|
host: '127.0.0.1'
|
||||||
|
port: 6379
|
||||||
|
high_availability:
|
||||||
|
sentinel_name: 'test'
|
||||||
|
|
||||||
|
regulation:
|
||||||
|
max_retries: 3
|
||||||
|
find_time: '2m'
|
||||||
|
ban_time: '5m'
|
||||||
|
|
||||||
|
storage:
|
||||||
|
mysql:
|
||||||
|
address: 'tcp://127.0.0.1:3306'
|
||||||
|
database: 'authelia'
|
||||||
|
username: 'authelia'
|
||||||
|
|
||||||
|
notifier:
|
||||||
|
smtp:
|
||||||
|
address: 'smtp://127.0.0.1:1025'
|
||||||
|
username: 'test'
|
||||||
|
sender: 'admin@example.com'
|
||||||
|
disable_require_tls: true
|
||||||
|
|
||||||
|
identity_providers:
|
||||||
|
oidc:
|
||||||
|
cors:
|
||||||
|
allowed_origins:
|
||||||
|
- 'https://google.com'
|
||||||
|
- 'https://example.com'
|
||||||
|
minimum_parameter_entropy: -1
|
||||||
|
clients:
|
||||||
|
- id: 'abc'
|
||||||
|
secret: '123'
|
||||||
|
consent_mode: 'explicit'
|
||||||
|
userinfo_signing_alg: 'none'
|
||||||
|
...
|
|
@ -1,108 +1,111 @@
|
||||||
---
|
---
|
||||||
default_redirection_url: https://home.example.com:8080/
|
default_redirection_url: 'https://home.example.com:8080/'
|
||||||
|
|
||||||
server:
|
server:
|
||||||
host: 127.0.0.1
|
host: 127.0.0.1
|
||||||
port: 9091
|
port: 9091
|
||||||
|
|
||||||
log:
|
log:
|
||||||
level: debug
|
level: 'debug'
|
||||||
|
|
||||||
totp:
|
totp:
|
||||||
issuer: authelia.com
|
issuer: 'authelia.com'
|
||||||
|
|
||||||
duo_api:
|
duo_api:
|
||||||
hostname: api-123456789.example.com
|
hostname: 'api-123456789.example.com'
|
||||||
integration_key: ABCDEF
|
integration_key: 'ABCDEF'
|
||||||
|
|
||||||
authentication_backend:
|
authentication_backend:
|
||||||
ldap:
|
ldap:
|
||||||
url: ldap://127.0.0.1
|
url: 'ldap://127.0.0.1'
|
||||||
base_dn: dc=example,dc=com
|
base_dn: 'dc=example,dc=com'
|
||||||
username_attribute: uid
|
additional_users_dn: 'ou=users'
|
||||||
additional_users_dn: ou=users
|
users_filter: '(&({username_attribute}={input})(objectCategory=person)(objectClass=user))'
|
||||||
users_filter: (&({username_attribute}={input})(objectCategory=person)(objectClass=user))
|
additional_groups_dn: 'ou=groups'
|
||||||
additional_groups_dn: ou=groups
|
groups_filter: '(&(member={dn})(objectClass=groupOfNames))'
|
||||||
groups_filter: (&(member={dn})(objectClass=groupOfNames))
|
user: 'cn=admin,dc=example,dc=com'
|
||||||
group_name_attribute: cn
|
attributes:
|
||||||
mail_attribute: mail
|
mail: 'mail'
|
||||||
user: cn=admin,dc=example,dc=com
|
username: 'uid'
|
||||||
|
group_name: 'cn'
|
||||||
|
|
||||||
access_control:
|
access_control:
|
||||||
default_policy: deny
|
default_policy: 'deny'
|
||||||
|
|
||||||
rules:
|
rules:
|
||||||
# Rules applied to everyone
|
# Rules applied to everyone
|
||||||
- domain: public.example.com
|
- domain: 'public.example.com'
|
||||||
policy: bypass
|
policy: 'bypass'
|
||||||
|
|
||||||
- domain: secure.example.com
|
- domain: 'secure.example.com'
|
||||||
policy: one_factor
|
policy: 'one_factor'
|
||||||
# Network based rule, if not provided any network matches.
|
# Network based rule, if not provided any network matches.
|
||||||
networks:
|
networks:
|
||||||
- 192.168.1.0/24
|
- '192.168.1.0/24'
|
||||||
- domain: secure.example.com
|
- domain: 'secure.example.com'
|
||||||
policy: two_factor
|
policy: 'two_factor'
|
||||||
|
|
||||||
- domain: [singlefactor.example.com, onefactor.example.com]
|
- domain:
|
||||||
policy: one_factor
|
- 'singlefactor.example.com'
|
||||||
|
- 'onefactor.example.com'
|
||||||
|
policy: 'one_factor'
|
||||||
|
|
||||||
# Rules applied to 'admins' group
|
# Rules applied to 'admins' group
|
||||||
- domain: "mx2.mail.example.com"
|
- domain: 'mx2.mail.example.com'
|
||||||
subject: "group:admins"
|
subject: 'group:admins'
|
||||||
policy: deny
|
policy: 'deny'
|
||||||
- domain: "*.example.com"
|
- domain: '*.example.com'
|
||||||
subject: "group:admins"
|
subject: 'group:admins'
|
||||||
policy: two_factor
|
policy: 'two_factor'
|
||||||
|
|
||||||
# Rules applied to 'dev' group
|
# Rules applied to 'dev' group
|
||||||
- domain: dev.example.com
|
- domain: 'dev.example.com'
|
||||||
resources:
|
resources:
|
||||||
- "^/groups/dev/.*$"
|
- '^/groups/dev/.*$'
|
||||||
subject: "group:dev"
|
subject: 'group:dev'
|
||||||
policy: two_factor
|
policy: 'two_factor'
|
||||||
|
|
||||||
# Rules applied to user 'john'
|
# Rules applied to user 'john'
|
||||||
- domain: dev.example.com
|
- domain: 'dev.example.com'
|
||||||
resources:
|
resources:
|
||||||
- "^/users/john/.*$"
|
- '^/users/john/.*$'
|
||||||
subject: "user:john"
|
subject: 'user:john'
|
||||||
policy: two_factor
|
policy: 'two_factor'
|
||||||
|
|
||||||
# Rules applied to 'dev' group and user 'john'
|
# Rules applied to 'dev' group and user 'john'
|
||||||
- domain: dev.example.com
|
- domain: 'dev.example.com'
|
||||||
resources:
|
resources:
|
||||||
- "^/deny-all.*$"
|
- '^/deny-all.*$'
|
||||||
subject: ["group:dev", "user:john"]
|
subject: ['group:dev', 'user:john']
|
||||||
policy: deny
|
policy: 'deny'
|
||||||
|
|
||||||
# Rules applied to user 'harry'
|
# Rules applied to user 'harry'
|
||||||
- domain: dev.example.com
|
- domain: 'dev.example.com'
|
||||||
resources:
|
resources:
|
||||||
- "^/users/harry/.*$"
|
- '^/users/harry/.*$'
|
||||||
subject: "user:harry"
|
subject: 'user:harry'
|
||||||
policy: two_factor
|
policy: 'two_factor'
|
||||||
|
|
||||||
# Rules applied to user 'bob'
|
# Rules applied to user 'bob'
|
||||||
- domain: "*.mail.example.com"
|
- domain: '*.mail.example.com'
|
||||||
subject: "user:bob"
|
subject: 'user:bob'
|
||||||
policy: two_factor
|
policy: 'two_factor'
|
||||||
- domain: "dev.example.com"
|
- domain: 'dev.example.com'
|
||||||
resources:
|
resources:
|
||||||
- "^/users/bob/.*$"
|
- '^/users/bob/.*$'
|
||||||
subject: "user:bob"
|
subject: 'user:bob'
|
||||||
policy: two_factor
|
policy: 'two_factor'
|
||||||
|
|
||||||
session:
|
session:
|
||||||
name: authelia_session
|
name: 'authelia_session'
|
||||||
expiration: 3600000 # 1 hour
|
expiration: 3600000 # 1 hour
|
||||||
inactivity: 300000 # 5 minutes
|
inactivity: 300000 # 5 minutes
|
||||||
domain: example.com
|
domain: 'example.com'
|
||||||
redis:
|
redis:
|
||||||
host: 127.0.0.1
|
host: 127.0.0.1
|
||||||
port: 6379
|
port: 6379
|
||||||
high_availability:
|
high_availability:
|
||||||
sentinel_name: test
|
sentinel_name: 'test'
|
||||||
|
|
||||||
regulation:
|
regulation:
|
||||||
max_retries: 3
|
max_retries: 3
|
||||||
|
@ -113,12 +116,12 @@ storage:
|
||||||
mysql:
|
mysql:
|
||||||
host: 127.0.0.1
|
host: 127.0.0.1
|
||||||
port: 3306
|
port: 3306
|
||||||
database: authelia
|
database: 'authelia'
|
||||||
username: authelia
|
username: 'authelia'
|
||||||
|
|
||||||
notifier:
|
notifier:
|
||||||
smtp:
|
smtp:
|
||||||
username: test
|
username: 'test'
|
||||||
host: 127.0.0.1
|
host: 127.0.0.1
|
||||||
port: 1025
|
port: 1025
|
||||||
sender: admin@example.com
|
sender: admin@example.com
|
||||||
|
@ -126,7 +129,7 @@ notifier:
|
||||||
|
|
||||||
identity_providers:
|
identity_providers:
|
||||||
oidc:
|
oidc:
|
||||||
hmac_secret: 1nb2j3kh1b23kjh1b23jh1b23j1h2b3
|
hmac_secret: '1nb2j3kh1b23kjh1b23jh1b23j1h2b3'
|
||||||
issuer_private_keys:
|
issuer_private_keys:
|
||||||
keys:
|
keys:
|
||||||
keya:
|
keya:
|
||||||
|
@ -202,10 +205,10 @@ identity_providers:
|
||||||
-----END CERTIFICATE-----
|
-----END CERTIFICATE-----
|
||||||
cors:
|
cors:
|
||||||
allowed_origins:
|
allowed_origins:
|
||||||
- https://google.com
|
- 'https://google.com'
|
||||||
- https://example.com
|
- 'https://example.com'
|
||||||
clients:
|
clients:
|
||||||
- id: abc
|
- id: 'abc'
|
||||||
secret: '123'
|
secret: '123'
|
||||||
consent_mode: explicit
|
consent_mode: 'explicit'
|
||||||
...
|
...
|
||||||
|
|
|
@ -18,14 +18,15 @@ authentication_backend:
|
||||||
ldap:
|
ldap:
|
||||||
address: 'ldap://127.0.0.1'
|
address: 'ldap://127.0.0.1'
|
||||||
base_dn: 'dc=example,dc=com'
|
base_dn: 'dc=example,dc=com'
|
||||||
username_attribute: 'uid'
|
|
||||||
additional_users_dn: 'ou=users'
|
additional_users_dn: 'ou=users'
|
||||||
users_filter: '(&({username_attribute}={input})(objectCategory=person)(objectClass=user))'
|
users_filter: '(&({username_attribute}={input})(objectCategory=person)(objectClass=user))'
|
||||||
additional_groups_dn: 'ou=groups'
|
additional_groups_dn: 'ou=groups'
|
||||||
groups_filter: '(&(member={dn})(objectClass=groupOfNames))'
|
groups_filter: '(&(member={dn})(objectClass=groupOfNames))'
|
||||||
group_name_attribute: 'cn'
|
|
||||||
mail_attribute: 'mail'
|
|
||||||
user: 'cn=admin,dc=example,dc=com'
|
user: 'cn=admin,dc=example,dc=com'
|
||||||
|
attributes:
|
||||||
|
username: 'uid'
|
||||||
|
group_name: 'cn'
|
||||||
|
mail: 'mail'
|
||||||
|
|
||||||
access_control:
|
access_control:
|
||||||
default_policy: 'deny'
|
default_policy: 'deny'
|
||||||
|
|
|
@ -18,14 +18,15 @@ authentication_backend:
|
||||||
ldap:
|
ldap:
|
||||||
address: 'ldap://127.0.0.1'
|
address: 'ldap://127.0.0.1'
|
||||||
base_dn: 'dc=example,dc=com'
|
base_dn: 'dc=example,dc=com'
|
||||||
username_attribute: 'uid'
|
|
||||||
additional_users_dn: 'ou=users'
|
additional_users_dn: 'ou=users'
|
||||||
users_filter: '(&({username_attribute}={input})(objectCategory=person)(objectClass=user))'
|
users_filter: '(&({username_attribute}={input})(objectCategory=person)(objectClass=user))'
|
||||||
additional_groups_dn: 'ou=groups'
|
additional_groups_dn: 'ou=groups'
|
||||||
groups_filter: '(&(member={dn})(objectClass=groupOfNames))'
|
groups_filter: '(&(member={dn})(objectClass=groupOfNames))'
|
||||||
group_name_attribute: 'cn'
|
|
||||||
mail_attribute: 'mail'
|
|
||||||
user: 'cn=admin,dc=example,dc=com'
|
user: 'cn=admin,dc=example,dc=com'
|
||||||
|
attributes:
|
||||||
|
username: 'uid'
|
||||||
|
group_name: 'cn'
|
||||||
|
mail: 'mail'
|
||||||
|
|
||||||
access_control:
|
access_control:
|
||||||
default_policy: 'deny'
|
default_policy: 'deny'
|
||||||
|
|
|
@ -20,14 +20,16 @@ authentication_backend:
|
||||||
ldap:
|
ldap:
|
||||||
address: 'ldap://127.0.0.1'
|
address: 'ldap://127.0.0.1'
|
||||||
base_dn: 'dc=example,dc=com'
|
base_dn: 'dc=example,dc=com'
|
||||||
username_attribute: 'uid'
|
|
||||||
additional_users_dn: 'ou=users'
|
additional_users_dn: 'ou=users'
|
||||||
users_filter: '(&({username_attribute}={input})(objectCategory=person)(objectClass=user))'
|
users_filter: '(&({username_attribute}={input})(objectCategory=person)(objectClass=user))'
|
||||||
additional_groups_dn: 'ou=groups'
|
additional_groups_dn: 'ou=groups'
|
||||||
groups_filter: '(&(member={dn})(objectClass=groupOfNames))'
|
groups_filter: '(&(member={dn})(objectClass=groupOfNames))'
|
||||||
group_name_attribute: 'cn'
|
|
||||||
mail_attribute: 'mail'
|
|
||||||
user: 'cn=admin,dc=example,dc=com'
|
user: 'cn=admin,dc=example,dc=com'
|
||||||
|
attributes:
|
||||||
|
username: 'uid'
|
||||||
|
group_name: 'cn'
|
||||||
|
mail: 'mail'
|
||||||
|
|
||||||
|
|
||||||
access_control:
|
access_control:
|
||||||
default_policy: 'deny'
|
default_policy: 'deny'
|
||||||
|
|
|
@ -21,7 +21,7 @@ type FileSource struct {
|
||||||
filters []FileFilter
|
filters []FileFilter
|
||||||
}
|
}
|
||||||
|
|
||||||
// EnvironmentSource is a configuration configuration.Source which loads values from the environment.
|
// EnvironmentSource is a configuration.Source which loads values from the environment.
|
||||||
type EnvironmentSource struct {
|
type EnvironmentSource struct {
|
||||||
koanf *koanf.Koanf
|
koanf *koanf.Koanf
|
||||||
prefix string
|
prefix string
|
||||||
|
|
|
@ -364,7 +364,7 @@ func validateLDAPAuthenticationBackendImplementation(config *schema.Authenticati
|
||||||
case schema.LDAPImplementationGLAuth:
|
case schema.LDAPImplementationGLAuth:
|
||||||
implementation = &schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth
|
implementation = &schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth
|
||||||
default:
|
default:
|
||||||
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendImplementation, strJoinOr(validLDAPImplementations), config.LDAP.Implementation))
|
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendOptionMustBeOneOf, "implementation", strJoinOr(validLDAPImplementations), config.LDAP.Implementation))
|
||||||
}
|
}
|
||||||
|
|
||||||
tlsconfig := &schema.TLSConfig{}
|
tlsconfig := &schema.TLSConfig{}
|
||||||
|
@ -394,32 +394,44 @@ func setDefaultImplementationLDAPAuthenticationBackendProfileAttributes(config *
|
||||||
config.AdditionalUsersDN = implementation.AdditionalUsersDN
|
config.AdditionalUsersDN = implementation.AdditionalUsersDN
|
||||||
}
|
}
|
||||||
|
|
||||||
if ldapImplementationShouldSetStr(config.AdditionalGroupsDN, implementation.AdditionalGroupsDN) {
|
|
||||||
config.AdditionalGroupsDN = implementation.AdditionalGroupsDN
|
|
||||||
}
|
|
||||||
|
|
||||||
if ldapImplementationShouldSetStr(config.UsersFilter, implementation.UsersFilter) {
|
if ldapImplementationShouldSetStr(config.UsersFilter, implementation.UsersFilter) {
|
||||||
config.UsersFilter = implementation.UsersFilter
|
config.UsersFilter = implementation.UsersFilter
|
||||||
}
|
}
|
||||||
|
|
||||||
if ldapImplementationShouldSetStr(config.UsernameAttribute, implementation.UsernameAttribute) {
|
if ldapImplementationShouldSetStr(config.AdditionalGroupsDN, implementation.AdditionalGroupsDN) {
|
||||||
config.UsernameAttribute = implementation.UsernameAttribute
|
config.AdditionalGroupsDN = implementation.AdditionalGroupsDN
|
||||||
}
|
|
||||||
|
|
||||||
if ldapImplementationShouldSetStr(config.DisplayNameAttribute, implementation.DisplayNameAttribute) {
|
|
||||||
config.DisplayNameAttribute = implementation.DisplayNameAttribute
|
|
||||||
}
|
|
||||||
|
|
||||||
if ldapImplementationShouldSetStr(config.MailAttribute, implementation.MailAttribute) {
|
|
||||||
config.MailAttribute = implementation.MailAttribute
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ldapImplementationShouldSetStr(config.GroupsFilter, implementation.GroupsFilter) {
|
if ldapImplementationShouldSetStr(config.GroupsFilter, implementation.GroupsFilter) {
|
||||||
config.GroupsFilter = implementation.GroupsFilter
|
config.GroupsFilter = implementation.GroupsFilter
|
||||||
}
|
}
|
||||||
|
|
||||||
if ldapImplementationShouldSetStr(config.GroupNameAttribute, implementation.GroupNameAttribute) {
|
if ldapImplementationShouldSetStr(config.GroupSearchMode, implementation.GroupSearchMode) {
|
||||||
config.GroupNameAttribute = implementation.GroupNameAttribute
|
config.GroupSearchMode = implementation.GroupSearchMode
|
||||||
|
}
|
||||||
|
|
||||||
|
if ldapImplementationShouldSetStr(config.Attributes.DistinguishedName, implementation.Attributes.DistinguishedName) {
|
||||||
|
config.Attributes.DistinguishedName = implementation.Attributes.DistinguishedName
|
||||||
|
}
|
||||||
|
|
||||||
|
if ldapImplementationShouldSetStr(config.Attributes.Username, implementation.Attributes.Username) {
|
||||||
|
config.Attributes.Username = implementation.Attributes.Username
|
||||||
|
}
|
||||||
|
|
||||||
|
if ldapImplementationShouldSetStr(config.Attributes.DisplayName, implementation.Attributes.DisplayName) {
|
||||||
|
config.Attributes.DisplayName = implementation.Attributes.DisplayName
|
||||||
|
}
|
||||||
|
|
||||||
|
if ldapImplementationShouldSetStr(config.Attributes.Mail, implementation.Attributes.Mail) {
|
||||||
|
config.Attributes.Mail = implementation.Attributes.Mail
|
||||||
|
}
|
||||||
|
|
||||||
|
if ldapImplementationShouldSetStr(config.Attributes.MemberOf, implementation.Attributes.MemberOf) {
|
||||||
|
config.Attributes.MemberOf = implementation.Attributes.MemberOf
|
||||||
|
}
|
||||||
|
|
||||||
|
if ldapImplementationShouldSetStr(config.Attributes.GroupName, implementation.Attributes.GroupName) {
|
||||||
|
config.Attributes.GroupName = implementation.Attributes.GroupName
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -486,4 +498,32 @@ func validateLDAPRequiredParameters(config *schema.AuthenticationBackend, valida
|
||||||
} else if !strings.HasPrefix(config.LDAP.GroupsFilter, "(") || !strings.HasSuffix(config.LDAP.GroupsFilter, ")") {
|
} else if !strings.HasPrefix(config.LDAP.GroupsFilter, "(") || !strings.HasSuffix(config.LDAP.GroupsFilter, ")") {
|
||||||
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendFilterEnclosingParenthesis, "groups_filter", config.LDAP.GroupsFilter, config.LDAP.GroupsFilter))
|
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendFilterEnclosingParenthesis, "groups_filter", config.LDAP.GroupsFilter, config.LDAP.GroupsFilter))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
validateLDAPGroupFilter(config, validator)
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateLDAPGroupFilter(config *schema.AuthenticationBackend, validator *schema.StructValidator) {
|
||||||
|
if config.LDAP.GroupSearchMode == "" {
|
||||||
|
config.LDAP.GroupSearchMode = schema.LDAPGroupSearchModeFilter
|
||||||
|
}
|
||||||
|
|
||||||
|
if !utils.IsStringInSlice(config.LDAP.GroupSearchMode, validLDAPGroupSearchModes) {
|
||||||
|
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendOptionMustBeOneOf, "group_search_mode", strJoinOr(validLDAPGroupSearchModes), config.LDAP.GroupSearchMode))
|
||||||
|
}
|
||||||
|
|
||||||
|
pMemberOfDN, pMemberOfRDN := strings.Contains(config.LDAP.GroupsFilter, "{memberof:dn}"), strings.Contains(config.LDAP.GroupsFilter, "{memberof:rdn}")
|
||||||
|
|
||||||
|
if config.LDAP.GroupSearchMode == schema.LDAPGroupSearchModeMemberOf {
|
||||||
|
if !pMemberOfDN && !pMemberOfRDN {
|
||||||
|
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendFilterMissingPlaceholderGroupSearchMode, "groups_filter", strJoinOr([]string{"{memberof:rdn}", "{memberof:dn}"}), config.LDAP.GroupSearchMode))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if pMemberOfDN && config.LDAP.Attributes.DistinguishedName == "" {
|
||||||
|
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendFilterMissingAttribute, "distinguished_name", strJoinOr([]string{"{memberof:dn}"})))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pMemberOfDN || pMemberOfRDN) && config.LDAP.Attributes.MemberOf == "" {
|
||||||
|
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendFilterMissingAttribute, "member_of", strJoinOr([]string{"{memberof:rdn}", "{memberof:dn}"})))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -577,7 +577,7 @@ func (suite *LDAPAuthenticationBackendSuite) SetupTest() {
|
||||||
suite.config.LDAP.User = testLDAPUser
|
suite.config.LDAP.User = testLDAPUser
|
||||||
suite.config.LDAP.Password = testLDAPPassword
|
suite.config.LDAP.Password = testLDAPPassword
|
||||||
suite.config.LDAP.BaseDN = testLDAPBaseDN
|
suite.config.LDAP.BaseDN = testLDAPBaseDN
|
||||||
suite.config.LDAP.UsernameAttribute = "uid"
|
suite.config.LDAP.Attributes.Username = "uid"
|
||||||
suite.config.LDAP.UsersFilter = "({username_attribute}={input})"
|
suite.config.LDAP.UsersFilter = "({username_attribute}={input})"
|
||||||
suite.config.LDAP.GroupsFilter = "(cn={input})"
|
suite.config.LDAP.GroupsFilter = "(cn={input})"
|
||||||
}
|
}
|
||||||
|
@ -591,12 +591,12 @@ func (suite *LDAPAuthenticationBackendSuite) TestShouldValidateCompleteConfigura
|
||||||
|
|
||||||
func (suite *LDAPAuthenticationBackendSuite) TestShouldValidateDefaultImplementationAndUsernameAttribute() {
|
func (suite *LDAPAuthenticationBackendSuite) TestShouldValidateDefaultImplementationAndUsernameAttribute() {
|
||||||
suite.config.LDAP.Implementation = ""
|
suite.config.LDAP.Implementation = ""
|
||||||
suite.config.LDAP.UsernameAttribute = ""
|
suite.config.LDAP.Attributes.Username = ""
|
||||||
ValidateAuthenticationBackend(&suite.config, suite.validator)
|
ValidateAuthenticationBackend(&suite.config, suite.validator)
|
||||||
|
|
||||||
suite.Equal(schema.LDAPImplementationCustom, suite.config.LDAP.Implementation)
|
suite.Equal(schema.LDAPImplementationCustom, suite.config.LDAP.Implementation)
|
||||||
|
|
||||||
suite.Equal(suite.config.LDAP.UsernameAttribute, schema.DefaultLDAPAuthenticationBackendConfigurationImplementationCustom.UsernameAttribute)
|
suite.Equal(suite.config.LDAP.Attributes.Username, schema.DefaultLDAPAuthenticationBackendConfigurationImplementationCustom.Attributes.Username)
|
||||||
suite.Len(suite.validator.Warnings(), 0)
|
suite.Len(suite.validator.Warnings(), 0)
|
||||||
suite.Len(suite.validator.Errors(), 0)
|
suite.Len(suite.validator.Errors(), 0)
|
||||||
}
|
}
|
||||||
|
@ -743,7 +743,7 @@ func (suite *LDAPAuthenticationBackendSuite) TestShouldRaiseOnEmptyUsersFilter()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *LDAPAuthenticationBackendSuite) TestShouldNotRaiseOnEmptyUsernameAttribute() {
|
func (suite *LDAPAuthenticationBackendSuite) TestShouldNotRaiseOnEmptyUsernameAttribute() {
|
||||||
suite.config.LDAP.UsernameAttribute = ""
|
suite.config.LDAP.Attributes.Username = ""
|
||||||
|
|
||||||
ValidateAuthenticationBackend(&suite.config, suite.validator)
|
ValidateAuthenticationBackend(&suite.config, suite.validator)
|
||||||
|
|
||||||
|
@ -793,7 +793,7 @@ func (suite *LDAPAuthenticationBackendSuite) TestShouldSetDefaultGroupNameAttrib
|
||||||
suite.Len(suite.validator.Warnings(), 0)
|
suite.Len(suite.validator.Warnings(), 0)
|
||||||
suite.Len(suite.validator.Errors(), 0)
|
suite.Len(suite.validator.Errors(), 0)
|
||||||
|
|
||||||
suite.Equal("cn", suite.config.LDAP.GroupNameAttribute)
|
suite.Equal("cn", suite.config.LDAP.Attributes.GroupName)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *LDAPAuthenticationBackendSuite) TestShouldSetDefaultMailAttribute() {
|
func (suite *LDAPAuthenticationBackendSuite) TestShouldSetDefaultMailAttribute() {
|
||||||
|
@ -802,7 +802,7 @@ func (suite *LDAPAuthenticationBackendSuite) TestShouldSetDefaultMailAttribute()
|
||||||
suite.Len(suite.validator.Warnings(), 0)
|
suite.Len(suite.validator.Warnings(), 0)
|
||||||
suite.Len(suite.validator.Errors(), 0)
|
suite.Len(suite.validator.Errors(), 0)
|
||||||
|
|
||||||
suite.Equal("mail", suite.config.LDAP.MailAttribute)
|
suite.Equal("mail", suite.config.LDAP.Attributes.Mail)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *LDAPAuthenticationBackendSuite) TestShouldSetDefaultDisplayNameAttribute() {
|
func (suite *LDAPAuthenticationBackendSuite) TestShouldSetDefaultDisplayNameAttribute() {
|
||||||
|
@ -811,7 +811,7 @@ func (suite *LDAPAuthenticationBackendSuite) TestShouldSetDefaultDisplayNameAttr
|
||||||
suite.Len(suite.validator.Warnings(), 0)
|
suite.Len(suite.validator.Warnings(), 0)
|
||||||
suite.Len(suite.validator.Errors(), 0)
|
suite.Len(suite.validator.Errors(), 0)
|
||||||
|
|
||||||
suite.Equal("displayName", suite.config.LDAP.DisplayNameAttribute)
|
suite.Equal("displayName", suite.config.LDAP.Attributes.DisplayName)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *LDAPAuthenticationBackendSuite) TestShouldSetDefaultRefreshInterval() {
|
func (suite *LDAPAuthenticationBackendSuite) TestShouldSetDefaultRefreshInterval() {
|
||||||
|
@ -890,6 +890,64 @@ func (suite *LDAPAuthenticationBackendSuite) TestShouldNotAllowSSL30() {
|
||||||
suite.EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: tls: option 'minimum_version' is invalid: minimum version is TLS1.0 but SSL3.0 was configured")
|
suite.EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: tls: option 'minimum_version' is invalid: minimum version is TLS1.0 but SSL3.0 was configured")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (suite *LDAPAuthenticationBackendSuite) TestShouldErrorOnBadSearchMode() {
|
||||||
|
suite.config.LDAP.GroupSearchMode = "memberOF"
|
||||||
|
|
||||||
|
ValidateAuthenticationBackend(&suite.config, suite.validator)
|
||||||
|
|
||||||
|
suite.Len(suite.validator.Warnings(), 0)
|
||||||
|
suite.Require().Len(suite.validator.Errors(), 1)
|
||||||
|
|
||||||
|
suite.EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: option 'group_search_mode' must be one of 'filter' or 'memberof' but it's configured as 'memberOF'")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *LDAPAuthenticationBackendSuite) TestShouldNoErrorOnPlaceholderSearchMode() {
|
||||||
|
suite.config.LDAP.GroupSearchMode = memberof
|
||||||
|
suite.config.LDAP.GroupsFilter = filterMemberOfRDN
|
||||||
|
suite.config.LDAP.Attributes.MemberOf = memberOf
|
||||||
|
|
||||||
|
ValidateAuthenticationBackend(&suite.config, suite.validator)
|
||||||
|
|
||||||
|
suite.Len(suite.validator.Warnings(), 0)
|
||||||
|
suite.Len(suite.validator.Errors(), 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *LDAPAuthenticationBackendSuite) TestShouldErrorOnMissingPlaceholderSearchMode() {
|
||||||
|
suite.config.LDAP.GroupSearchMode = memberof
|
||||||
|
|
||||||
|
ValidateAuthenticationBackend(&suite.config, suite.validator)
|
||||||
|
|
||||||
|
suite.Len(suite.validator.Warnings(), 0)
|
||||||
|
suite.Require().Len(suite.validator.Errors(), 1)
|
||||||
|
|
||||||
|
suite.EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: option 'groups_filter' must contain one of the '{memberof:rdn}' or '{memberof:dn}' placeholders when using a group_search_mode of 'memberof' but they're absent")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *LDAPAuthenticationBackendSuite) TestShouldErrorOnMissingDistinguishedNameDN() {
|
||||||
|
suite.config.LDAP.Attributes.DistinguishedName = ""
|
||||||
|
suite.config.LDAP.GroupsFilter = "(|({memberof:dn}))"
|
||||||
|
|
||||||
|
ValidateAuthenticationBackend(&suite.config, suite.validator)
|
||||||
|
|
||||||
|
suite.Len(suite.validator.Warnings(), 0)
|
||||||
|
suite.Require().Len(suite.validator.Errors(), 2)
|
||||||
|
|
||||||
|
suite.EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: attributes: option 'distinguished_name' must be provided when using the '{memberof:dn}' placeholder but it's absent")
|
||||||
|
suite.EqualError(suite.validator.Errors()[1], "authentication_backend: ldap: attributes: option 'member_of' must be provided when using the '{memberof:rdn}' or '{memberof:dn}' placeholder but it's absent")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *LDAPAuthenticationBackendSuite) TestShouldErrorOnMissingMemberOfRDN() {
|
||||||
|
suite.config.LDAP.Attributes.DistinguishedName = ""
|
||||||
|
suite.config.LDAP.GroupsFilter = filterMemberOfRDN
|
||||||
|
|
||||||
|
ValidateAuthenticationBackend(&suite.config, suite.validator)
|
||||||
|
|
||||||
|
suite.Len(suite.validator.Warnings(), 0)
|
||||||
|
suite.Require().Len(suite.validator.Errors(), 1)
|
||||||
|
|
||||||
|
suite.EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: attributes: option 'member_of' must be provided when using the '{memberof:rdn}' or '{memberof:dn}' placeholder but it's absent")
|
||||||
|
}
|
||||||
|
|
||||||
func (suite *LDAPAuthenticationBackendSuite) TestShouldNotAllowTLSVerMinGreaterThanVerMax() {
|
func (suite *LDAPAuthenticationBackendSuite) TestShouldNotAllowTLSVerMinGreaterThanVerMax() {
|
||||||
suite.config.LDAP.TLS = &schema.TLSConfig{
|
suite.config.LDAP.TLS = &schema.TLSConfig{
|
||||||
MinimumVersion: schema.TLSVersion{Value: tls.VersionTLS13},
|
MinimumVersion: schema.TLSVersion{Value: tls.VersionTLS13},
|
||||||
|
@ -909,9 +967,7 @@ func TestLDAPAuthenticationBackend(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
type ActiveDirectoryAuthenticationBackendSuite struct {
|
type ActiveDirectoryAuthenticationBackendSuite struct {
|
||||||
suite.Suite
|
LDAPImplementationSuite
|
||||||
config schema.AuthenticationBackend
|
|
||||||
validator *schema.StructValidator
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *ActiveDirectoryAuthenticationBackendSuite) SetupTest() {
|
func (suite *ActiveDirectoryAuthenticationBackendSuite) SetupTest() {
|
||||||
|
@ -932,81 +988,30 @@ func (suite *ActiveDirectoryAuthenticationBackendSuite) TestShouldSetActiveDirec
|
||||||
suite.Len(suite.validator.Warnings(), 0)
|
suite.Len(suite.validator.Warnings(), 0)
|
||||||
suite.Len(suite.validator.Errors(), 0)
|
suite.Len(suite.validator.Errors(), 0)
|
||||||
|
|
||||||
suite.Equal(
|
suite.EqualImplementationDefaults(schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory)
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.Timeout,
|
|
||||||
suite.config.LDAP.Timeout)
|
|
||||||
suite.Equal(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.AdditionalUsersDN,
|
|
||||||
suite.config.LDAP.AdditionalUsersDN)
|
|
||||||
suite.Equal(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.AdditionalGroupsDN,
|
|
||||||
suite.config.LDAP.AdditionalGroupsDN)
|
|
||||||
suite.Equal(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.AdditionalUsersDN,
|
|
||||||
suite.config.LDAP.AdditionalUsersDN)
|
|
||||||
suite.Equal(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.AdditionalGroupsDN,
|
|
||||||
suite.config.LDAP.AdditionalGroupsDN)
|
|
||||||
suite.Equal(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.UsersFilter,
|
|
||||||
suite.config.LDAP.UsersFilter)
|
|
||||||
suite.Equal(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.UsernameAttribute,
|
|
||||||
suite.config.LDAP.UsernameAttribute)
|
|
||||||
suite.Equal(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.DisplayNameAttribute,
|
|
||||||
suite.config.LDAP.DisplayNameAttribute)
|
|
||||||
suite.Equal(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.MailAttribute,
|
|
||||||
suite.config.LDAP.MailAttribute)
|
|
||||||
suite.Equal(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.GroupsFilter,
|
|
||||||
suite.config.LDAP.GroupsFilter)
|
|
||||||
suite.Equal(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.GroupNameAttribute,
|
|
||||||
suite.config.LDAP.GroupNameAttribute)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *ActiveDirectoryAuthenticationBackendSuite) TestShouldOnlySetDefaultsIfNotManuallyConfigured() {
|
func (suite *ActiveDirectoryAuthenticationBackendSuite) TestShouldOnlySetDefaultsIfNotManuallyConfigured() {
|
||||||
suite.config.LDAP.Timeout = time.Second * 2
|
suite.config.LDAP.Timeout = time.Second * 2
|
||||||
suite.config.LDAP.UsersFilter = "(&({username_attribute}={input})(objectCategory=person)(objectClass=user)(!userAccountControl:1.2.840.113556.1.4.803:=2))"
|
suite.config.LDAP.UsersFilter = "(&({username_attribute}={input})(objectCategory=person)(objectClass=user)(!userAccountControl:1.2.840.113556.1.4.803:=2))"
|
||||||
suite.config.LDAP.UsernameAttribute = "cn"
|
suite.config.LDAP.Attributes.Username = "cn"
|
||||||
suite.config.LDAP.MailAttribute = "userPrincipalName"
|
suite.config.LDAP.Attributes.Mail = "userPrincipalName"
|
||||||
suite.config.LDAP.DisplayNameAttribute = "name"
|
suite.config.LDAP.Attributes.DisplayName = "name"
|
||||||
suite.config.LDAP.GroupsFilter = "(&(member={dn})(objectClass=group)(objectCategory=group))"
|
suite.config.LDAP.GroupsFilter = "(&(member={dn})(objectClass=group)(objectCategory=group))"
|
||||||
suite.config.LDAP.GroupNameAttribute = "distinguishedName"
|
suite.config.LDAP.Attributes.GroupName = "distinguishedName"
|
||||||
suite.config.LDAP.AdditionalUsersDN = "OU=test"
|
suite.config.LDAP.AdditionalUsersDN = "OU=test"
|
||||||
suite.config.LDAP.AdditionalGroupsDN = "OU=grps"
|
suite.config.LDAP.AdditionalGroupsDN = "OU=grps"
|
||||||
|
suite.config.LDAP.Attributes.MemberOf = member
|
||||||
|
suite.config.LDAP.GroupSearchMode = memberof
|
||||||
|
suite.config.LDAP.Attributes.DistinguishedName = "objectGUID"
|
||||||
|
|
||||||
ValidateAuthenticationBackend(&suite.config, suite.validator)
|
ValidateAuthenticationBackend(&suite.config, suite.validator)
|
||||||
|
|
||||||
suite.NotEqual(
|
suite.NotEqualImplementationDefaults(schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory)
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.Timeout,
|
|
||||||
suite.config.LDAP.Timeout)
|
suite.Equal(member, suite.config.LDAP.Attributes.MemberOf)
|
||||||
suite.NotEqual(
|
suite.Equal("objectGUID", suite.config.LDAP.Attributes.DistinguishedName)
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.AdditionalUsersDN,
|
suite.Equal(memberof, suite.config.LDAP.GroupSearchMode)
|
||||||
suite.config.LDAP.AdditionalUsersDN)
|
|
||||||
suite.NotEqual(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.AdditionalGroupsDN,
|
|
||||||
suite.config.LDAP.AdditionalGroupsDN)
|
|
||||||
suite.NotEqual(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.UsersFilter,
|
|
||||||
suite.config.LDAP.UsersFilter)
|
|
||||||
suite.NotEqual(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.UsernameAttribute,
|
|
||||||
suite.config.LDAP.UsernameAttribute)
|
|
||||||
suite.NotEqual(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.DisplayNameAttribute,
|
|
||||||
suite.config.LDAP.DisplayNameAttribute)
|
|
||||||
suite.NotEqual(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.MailAttribute,
|
|
||||||
suite.config.LDAP.MailAttribute)
|
|
||||||
suite.NotEqual(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.GroupsFilter,
|
|
||||||
suite.config.LDAP.GroupsFilter)
|
|
||||||
suite.NotEqual(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.GroupNameAttribute,
|
|
||||||
suite.config.LDAP.GroupNameAttribute)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *ActiveDirectoryAuthenticationBackendSuite) TestShouldRaiseErrorOnInvalidURLWithHTTP() {
|
func (suite *ActiveDirectoryAuthenticationBackendSuite) TestShouldRaiseErrorOnInvalidURLWithHTTP() {
|
||||||
|
@ -1023,9 +1028,7 @@ func TestActiveDirectoryAuthenticationBackend(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
type RFC2307bisAuthenticationBackendSuite struct {
|
type RFC2307bisAuthenticationBackendSuite struct {
|
||||||
suite.Suite
|
LDAPImplementationSuite
|
||||||
config schema.AuthenticationBackend
|
|
||||||
validator *schema.StructValidator
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *RFC2307bisAuthenticationBackendSuite) SetupTest() {
|
func (suite *RFC2307bisAuthenticationBackendSuite) SetupTest() {
|
||||||
|
@ -1046,78 +1049,29 @@ func (suite *RFC2307bisAuthenticationBackendSuite) TestShouldSetDefaults() {
|
||||||
suite.Len(suite.validator.Warnings(), 0)
|
suite.Len(suite.validator.Warnings(), 0)
|
||||||
suite.Len(suite.validator.Errors(), 0)
|
suite.Len(suite.validator.Errors(), 0)
|
||||||
|
|
||||||
suite.Equal(
|
suite.EqualImplementationDefaults(schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis)
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.Timeout,
|
|
||||||
suite.config.LDAP.Timeout)
|
|
||||||
suite.Equal(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.AdditionalUsersDN,
|
|
||||||
suite.config.LDAP.AdditionalUsersDN)
|
|
||||||
suite.Equal(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.AdditionalGroupsDN,
|
|
||||||
suite.config.LDAP.AdditionalGroupsDN)
|
|
||||||
suite.Equal(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.UsersFilter,
|
|
||||||
suite.config.LDAP.UsersFilter)
|
|
||||||
suite.Equal(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.UsernameAttribute,
|
|
||||||
suite.config.LDAP.UsernameAttribute)
|
|
||||||
suite.Equal(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.DisplayNameAttribute,
|
|
||||||
suite.config.LDAP.DisplayNameAttribute)
|
|
||||||
suite.Equal(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.MailAttribute,
|
|
||||||
suite.config.LDAP.MailAttribute)
|
|
||||||
suite.Equal(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.GroupsFilter,
|
|
||||||
suite.config.LDAP.GroupsFilter)
|
|
||||||
suite.Equal(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.GroupNameAttribute,
|
|
||||||
suite.config.LDAP.GroupNameAttribute)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *RFC2307bisAuthenticationBackendSuite) TestShouldOnlySetDefaultsIfNotManuallyConfigured() {
|
func (suite *RFC2307bisAuthenticationBackendSuite) TestShouldOnlySetDefaultsIfNotManuallyConfigured() {
|
||||||
suite.config.LDAP.Timeout = time.Second * 2
|
suite.config.LDAP.Timeout = time.Second * 2
|
||||||
suite.config.LDAP.UsersFilter = "(&({username_attribute}={input})(objectClass=Person))"
|
suite.config.LDAP.UsersFilter = "(&({username_attribute}={input})(objectClass=Person))"
|
||||||
suite.config.LDAP.UsernameAttribute = "o"
|
suite.config.LDAP.Attributes.Username = "o"
|
||||||
suite.config.LDAP.MailAttribute = "Email"
|
suite.config.LDAP.Attributes.Mail = "Email"
|
||||||
suite.config.LDAP.DisplayNameAttribute = "Given"
|
suite.config.LDAP.Attributes.DisplayName = "Given"
|
||||||
suite.config.LDAP.GroupsFilter = "(&(member={dn})(objectClass=posixGroup)(objectClass=top))"
|
suite.config.LDAP.GroupsFilter = "(&(member={dn})(objectClass=posixGroup)(objectClass=top))"
|
||||||
suite.config.LDAP.GroupNameAttribute = "gid"
|
suite.config.LDAP.Attributes.GroupName = "gid"
|
||||||
|
suite.config.LDAP.Attributes.MemberOf = member
|
||||||
suite.config.LDAP.AdditionalUsersDN = "OU=users,OU=OpenLDAP"
|
suite.config.LDAP.AdditionalUsersDN = "OU=users,OU=OpenLDAP"
|
||||||
suite.config.LDAP.AdditionalGroupsDN = "OU=groups,OU=OpenLDAP"
|
suite.config.LDAP.AdditionalGroupsDN = "OU=groups,OU=OpenLDAP"
|
||||||
|
suite.config.LDAP.GroupSearchMode = memberof
|
||||||
|
|
||||||
ValidateAuthenticationBackend(&suite.config, suite.validator)
|
ValidateAuthenticationBackend(&suite.config, suite.validator)
|
||||||
|
|
||||||
suite.NotEqual(
|
suite.NotEqualImplementationDefaults(schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis)
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.Timeout,
|
|
||||||
suite.config.LDAP.Timeout)
|
suite.Equal(member, suite.config.LDAP.Attributes.MemberOf)
|
||||||
suite.NotEqual(
|
suite.Equal("", suite.config.LDAP.Attributes.DistinguishedName)
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.AdditionalUsersDN,
|
suite.Equal(schema.LDAPGroupSearchModeMemberOf, suite.config.LDAP.GroupSearchMode)
|
||||||
suite.config.LDAP.AdditionalUsersDN)
|
|
||||||
suite.NotEqual(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.AdditionalGroupsDN,
|
|
||||||
suite.config.LDAP.AdditionalGroupsDN)
|
|
||||||
suite.NotEqual(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.Timeout,
|
|
||||||
suite.config.LDAP.Timeout)
|
|
||||||
suite.NotEqual(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.UsersFilter,
|
|
||||||
suite.config.LDAP.UsersFilter)
|
|
||||||
suite.NotEqual(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.UsernameAttribute,
|
|
||||||
suite.config.LDAP.UsernameAttribute)
|
|
||||||
suite.NotEqual(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.DisplayNameAttribute,
|
|
||||||
suite.config.LDAP.DisplayNameAttribute)
|
|
||||||
suite.NotEqual(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.MailAttribute,
|
|
||||||
suite.config.LDAP.MailAttribute)
|
|
||||||
suite.NotEqual(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.GroupsFilter,
|
|
||||||
suite.config.LDAP.GroupsFilter)
|
|
||||||
suite.NotEqual(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.GroupNameAttribute,
|
|
||||||
suite.config.LDAP.GroupNameAttribute)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRFC2307bisAuthenticationBackend(t *testing.T) {
|
func TestRFC2307bisAuthenticationBackend(t *testing.T) {
|
||||||
|
@ -1125,9 +1079,7 @@ func TestRFC2307bisAuthenticationBackend(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
type FreeIPAAuthenticationBackendSuite struct {
|
type FreeIPAAuthenticationBackendSuite struct {
|
||||||
suite.Suite
|
LDAPImplementationSuite
|
||||||
config schema.AuthenticationBackend
|
|
||||||
validator *schema.StructValidator
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *FreeIPAAuthenticationBackendSuite) SetupTest() {
|
func (suite *FreeIPAAuthenticationBackendSuite) SetupTest() {
|
||||||
|
@ -1148,75 +1100,29 @@ func (suite *FreeIPAAuthenticationBackendSuite) TestShouldSetDefaults() {
|
||||||
suite.Len(suite.validator.Warnings(), 0)
|
suite.Len(suite.validator.Warnings(), 0)
|
||||||
suite.Len(suite.validator.Errors(), 0)
|
suite.Len(suite.validator.Errors(), 0)
|
||||||
|
|
||||||
suite.Equal(
|
suite.EqualImplementationDefaults(schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA)
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA.Timeout,
|
|
||||||
suite.config.LDAP.Timeout)
|
|
||||||
suite.Equal(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA.AdditionalUsersDN,
|
|
||||||
suite.config.LDAP.AdditionalUsersDN)
|
|
||||||
suite.Equal(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA.AdditionalGroupsDN,
|
|
||||||
suite.config.LDAP.AdditionalGroupsDN)
|
|
||||||
suite.Equal(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA.UsersFilter,
|
|
||||||
suite.config.LDAP.UsersFilter)
|
|
||||||
suite.Equal(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA.UsernameAttribute,
|
|
||||||
suite.config.LDAP.UsernameAttribute)
|
|
||||||
suite.Equal(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA.DisplayNameAttribute,
|
|
||||||
suite.config.LDAP.DisplayNameAttribute)
|
|
||||||
suite.Equal(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA.MailAttribute,
|
|
||||||
suite.config.LDAP.MailAttribute)
|
|
||||||
suite.Equal(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA.GroupsFilter,
|
|
||||||
suite.config.LDAP.GroupsFilter)
|
|
||||||
suite.Equal(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA.GroupNameAttribute,
|
|
||||||
suite.config.LDAP.GroupNameAttribute)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *FreeIPAAuthenticationBackendSuite) TestShouldOnlySetDefaultsIfNotManuallyConfigured() {
|
func (suite *FreeIPAAuthenticationBackendSuite) TestShouldOnlySetDefaultsIfNotManuallyConfigured() {
|
||||||
suite.config.LDAP.Timeout = time.Second * 2
|
suite.config.LDAP.Timeout = time.Second * 2
|
||||||
suite.config.LDAP.UsersFilter = "(&({username_attribute}={input})(objectClass=person)(!(nsAccountLock=TRUE)))"
|
suite.config.LDAP.UsersFilter = "(&({username_attribute}={input})(objectClass=person)(!(nsAccountLock=TRUE)))"
|
||||||
suite.config.LDAP.UsernameAttribute = "dn"
|
suite.config.LDAP.Attributes.Username = "dn"
|
||||||
suite.config.LDAP.MailAttribute = "email"
|
suite.config.LDAP.Attributes.Mail = "email"
|
||||||
suite.config.LDAP.DisplayNameAttribute = "gecos"
|
suite.config.LDAP.Attributes.DisplayName = "gecos"
|
||||||
suite.config.LDAP.GroupsFilter = "(&(member={dn})(objectClass=posixgroup))"
|
suite.config.LDAP.GroupsFilter = "(&(member={dn})(objectClass=posixgroup))"
|
||||||
suite.config.LDAP.GroupNameAttribute = "groupName"
|
suite.config.LDAP.GroupSearchMode = schema.LDAPGroupSearchModeMemberOf
|
||||||
|
suite.config.LDAP.Attributes.GroupName = "groupName"
|
||||||
|
suite.config.LDAP.Attributes.MemberOf = member
|
||||||
suite.config.LDAP.AdditionalUsersDN = "OU=people"
|
suite.config.LDAP.AdditionalUsersDN = "OU=people"
|
||||||
suite.config.LDAP.AdditionalGroupsDN = "OU=grp"
|
suite.config.LDAP.AdditionalGroupsDN = "OU=grp"
|
||||||
|
|
||||||
ValidateAuthenticationBackend(&suite.config, suite.validator)
|
ValidateAuthenticationBackend(&suite.config, suite.validator)
|
||||||
|
|
||||||
suite.NotEqual(
|
suite.NotEqualImplementationDefaults(schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA)
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA.Timeout,
|
|
||||||
suite.config.LDAP.Timeout)
|
suite.Equal(member, suite.config.LDAP.Attributes.MemberOf)
|
||||||
suite.NotEqual(
|
suite.Equal("", suite.config.LDAP.Attributes.DistinguishedName)
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA.AdditionalUsersDN,
|
suite.Equal(schema.LDAPGroupSearchModeMemberOf, suite.config.LDAP.GroupSearchMode)
|
||||||
suite.config.LDAP.AdditionalUsersDN)
|
|
||||||
suite.NotEqual(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA.AdditionalGroupsDN,
|
|
||||||
suite.config.LDAP.AdditionalGroupsDN)
|
|
||||||
suite.NotEqual(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA.UsersFilter,
|
|
||||||
suite.config.LDAP.UsersFilter)
|
|
||||||
suite.NotEqual(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA.UsernameAttribute,
|
|
||||||
suite.config.LDAP.UsernameAttribute)
|
|
||||||
suite.NotEqual(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA.DisplayNameAttribute,
|
|
||||||
suite.config.LDAP.DisplayNameAttribute)
|
|
||||||
suite.NotEqual(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA.MailAttribute,
|
|
||||||
suite.config.LDAP.MailAttribute)
|
|
||||||
suite.NotEqual(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA.GroupsFilter,
|
|
||||||
suite.config.LDAP.GroupsFilter)
|
|
||||||
suite.NotEqual(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA.GroupNameAttribute,
|
|
||||||
suite.config.LDAP.GroupNameAttribute)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFreeIPAAuthenticationBackend(t *testing.T) {
|
func TestFreeIPAAuthenticationBackend(t *testing.T) {
|
||||||
|
@ -1224,9 +1130,7 @@ func TestFreeIPAAuthenticationBackend(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
type LLDAPAuthenticationBackendSuite struct {
|
type LLDAPAuthenticationBackendSuite struct {
|
||||||
suite.Suite
|
LDAPImplementationSuite
|
||||||
config schema.AuthenticationBackend
|
|
||||||
validator *schema.StructValidator
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *LLDAPAuthenticationBackendSuite) SetupTest() {
|
func (suite *LLDAPAuthenticationBackendSuite) SetupTest() {
|
||||||
|
@ -1247,78 +1151,29 @@ func (suite *LLDAPAuthenticationBackendSuite) TestShouldSetDefaults() {
|
||||||
suite.Len(suite.validator.Warnings(), 0)
|
suite.Len(suite.validator.Warnings(), 0)
|
||||||
suite.Len(suite.validator.Errors(), 0)
|
suite.Len(suite.validator.Errors(), 0)
|
||||||
|
|
||||||
suite.Equal(
|
suite.EqualImplementationDefaults(schema.DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP)
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP.Timeout,
|
|
||||||
suite.config.LDAP.Timeout)
|
|
||||||
suite.Equal(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP.AdditionalUsersDN,
|
|
||||||
suite.config.LDAP.AdditionalUsersDN)
|
|
||||||
suite.Equal(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP.AdditionalGroupsDN,
|
|
||||||
suite.config.LDAP.AdditionalGroupsDN)
|
|
||||||
suite.Equal(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP.UsersFilter,
|
|
||||||
suite.config.LDAP.UsersFilter)
|
|
||||||
suite.Equal(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP.UsernameAttribute,
|
|
||||||
suite.config.LDAP.UsernameAttribute)
|
|
||||||
suite.Equal(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP.DisplayNameAttribute,
|
|
||||||
suite.config.LDAP.DisplayNameAttribute)
|
|
||||||
suite.Equal(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP.MailAttribute,
|
|
||||||
suite.config.LDAP.MailAttribute)
|
|
||||||
suite.Equal(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP.GroupsFilter,
|
|
||||||
suite.config.LDAP.GroupsFilter)
|
|
||||||
suite.Equal(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP.GroupNameAttribute,
|
|
||||||
suite.config.LDAP.GroupNameAttribute)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *LLDAPAuthenticationBackendSuite) TestShouldOnlySetDefaultsIfNotManuallyConfigured() {
|
func (suite *LLDAPAuthenticationBackendSuite) TestShouldOnlySetDefaultsIfNotManuallyConfigured() {
|
||||||
suite.config.LDAP.Timeout = time.Second * 2
|
suite.config.LDAP.Timeout = time.Second * 2
|
||||||
suite.config.LDAP.UsersFilter = "(&({username_attribute}={input})(objectClass=Person)(!(nsAccountLock=TRUE)))"
|
suite.config.LDAP.UsersFilter = "(&({username_attribute}={input})(objectClass=Person)(!(nsAccountLock=TRUE)))"
|
||||||
suite.config.LDAP.UsernameAttribute = "username"
|
suite.config.LDAP.Attributes.Username = "username"
|
||||||
suite.config.LDAP.MailAttribute = "m"
|
suite.config.LDAP.Attributes.Mail = "m"
|
||||||
suite.config.LDAP.DisplayNameAttribute = "fn"
|
suite.config.LDAP.Attributes.DisplayName = "fn"
|
||||||
|
suite.config.LDAP.Attributes.MemberOf = member
|
||||||
suite.config.LDAP.GroupsFilter = "(&(member={dn})(!(objectClass=posixGroup)))"
|
suite.config.LDAP.GroupsFilter = "(&(member={dn})(!(objectClass=posixGroup)))"
|
||||||
suite.config.LDAP.GroupNameAttribute = "grpz"
|
suite.config.LDAP.Attributes.GroupName = "grpz"
|
||||||
suite.config.LDAP.AdditionalUsersDN = "OU=no"
|
suite.config.LDAP.AdditionalUsersDN = "OU=no"
|
||||||
suite.config.LDAP.AdditionalGroupsDN = "OU=yes"
|
suite.config.LDAP.AdditionalGroupsDN = "OU=yes"
|
||||||
|
suite.config.LDAP.GroupSearchMode = memberof
|
||||||
|
|
||||||
ValidateAuthenticationBackend(&suite.config, suite.validator)
|
ValidateAuthenticationBackend(&suite.config, suite.validator)
|
||||||
|
|
||||||
suite.NotEqual(
|
suite.NotEqualImplementationDefaults(schema.DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP)
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP.Timeout,
|
|
||||||
suite.config.LDAP.Timeout)
|
suite.Equal(member, suite.config.LDAP.Attributes.MemberOf)
|
||||||
suite.NotEqual(
|
suite.Equal("", suite.config.LDAP.Attributes.DistinguishedName)
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP.AdditionalUsersDN,
|
suite.Equal(schema.LDAPGroupSearchModeMemberOf, suite.config.LDAP.GroupSearchMode)
|
||||||
suite.config.LDAP.AdditionalUsersDN)
|
|
||||||
suite.NotEqual(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP.AdditionalGroupsDN,
|
|
||||||
suite.config.LDAP.AdditionalGroupsDN)
|
|
||||||
suite.NotEqual(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP.Timeout,
|
|
||||||
suite.config.LDAP.Timeout)
|
|
||||||
suite.NotEqual(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP.UsersFilter,
|
|
||||||
suite.config.LDAP.UsersFilter)
|
|
||||||
suite.NotEqual(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP.UsernameAttribute,
|
|
||||||
suite.config.LDAP.UsernameAttribute)
|
|
||||||
suite.NotEqual(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP.DisplayNameAttribute,
|
|
||||||
suite.config.LDAP.DisplayNameAttribute)
|
|
||||||
suite.NotEqual(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP.MailAttribute,
|
|
||||||
suite.config.LDAP.MailAttribute)
|
|
||||||
suite.NotEqual(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP.GroupsFilter,
|
|
||||||
suite.config.LDAP.GroupsFilter)
|
|
||||||
suite.NotEqual(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP.GroupNameAttribute,
|
|
||||||
suite.config.LDAP.GroupNameAttribute)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLLDAPAuthenticationBackend(t *testing.T) {
|
func TestLLDAPAuthenticationBackend(t *testing.T) {
|
||||||
|
@ -1326,9 +1181,7 @@ func TestLLDAPAuthenticationBackend(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
type GLAuthAuthenticationBackendSuite struct {
|
type GLAuthAuthenticationBackendSuite struct {
|
||||||
suite.Suite
|
LDAPImplementationSuite
|
||||||
config schema.AuthenticationBackend
|
|
||||||
validator *schema.StructValidator
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *GLAuthAuthenticationBackendSuite) SetupTest() {
|
func (suite *GLAuthAuthenticationBackendSuite) SetupTest() {
|
||||||
|
@ -1349,80 +1202,80 @@ func (suite *GLAuthAuthenticationBackendSuite) TestShouldSetDefaults() {
|
||||||
suite.Len(suite.validator.Warnings(), 0)
|
suite.Len(suite.validator.Warnings(), 0)
|
||||||
suite.Len(suite.validator.Errors(), 0)
|
suite.Len(suite.validator.Errors(), 0)
|
||||||
|
|
||||||
suite.Equal(
|
suite.EqualImplementationDefaults(schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth)
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth.Timeout,
|
|
||||||
suite.config.LDAP.Timeout)
|
|
||||||
suite.Equal(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth.AdditionalUsersDN,
|
|
||||||
suite.config.LDAP.AdditionalUsersDN)
|
|
||||||
suite.Equal(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth.AdditionalGroupsDN,
|
|
||||||
suite.config.LDAP.AdditionalGroupsDN)
|
|
||||||
suite.Equal(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth.UsersFilter,
|
|
||||||
suite.config.LDAP.UsersFilter)
|
|
||||||
suite.Equal(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth.UsernameAttribute,
|
|
||||||
suite.config.LDAP.UsernameAttribute)
|
|
||||||
suite.Equal(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth.DisplayNameAttribute,
|
|
||||||
suite.config.LDAP.DisplayNameAttribute)
|
|
||||||
suite.Equal(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth.MailAttribute,
|
|
||||||
suite.config.LDAP.MailAttribute)
|
|
||||||
suite.Equal(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth.GroupsFilter,
|
|
||||||
suite.config.LDAP.GroupsFilter)
|
|
||||||
suite.Equal(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth.GroupNameAttribute,
|
|
||||||
suite.config.LDAP.GroupNameAttribute)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *GLAuthAuthenticationBackendSuite) TestShouldOnlySetDefaultsIfNotManuallyConfigured() {
|
func (suite *GLAuthAuthenticationBackendSuite) TestShouldOnlySetDefaultsIfNotManuallyConfigured() {
|
||||||
suite.config.LDAP.Timeout = time.Second * 2
|
suite.config.LDAP.Timeout = time.Second * 2
|
||||||
suite.config.LDAP.UsersFilter = "(&({username_attribute}={input})(objectClass=Person)(!(accountStatus=inactive)))"
|
suite.config.LDAP.UsersFilter = "(&({username_attribute}={input})(objectClass=Person)(!(accountStatus=inactive)))"
|
||||||
suite.config.LDAP.UsernameAttribute = "description"
|
suite.config.LDAP.Attributes.Username = "description"
|
||||||
suite.config.LDAP.MailAttribute = "sender"
|
suite.config.LDAP.Attributes.Mail = "sender"
|
||||||
suite.config.LDAP.DisplayNameAttribute = "given"
|
suite.config.LDAP.Attributes.DisplayName = "given"
|
||||||
suite.config.LDAP.GroupsFilter = "(&(member={dn})(objectClass=posixGroup))"
|
suite.config.LDAP.GroupsFilter = "(&(member={dn})(objectClass=posixGroup))"
|
||||||
suite.config.LDAP.GroupNameAttribute = "grp"
|
suite.config.LDAP.Attributes.GroupName = "grp"
|
||||||
suite.config.LDAP.AdditionalUsersDN = "OU=users,OU=GlAuth"
|
suite.config.LDAP.AdditionalUsersDN = "OU=users,OU=GlAuth"
|
||||||
suite.config.LDAP.AdditionalGroupsDN = "OU=groups,OU=GLAuth"
|
suite.config.LDAP.AdditionalGroupsDN = "OU=groups,OU=GLAuth"
|
||||||
|
suite.config.LDAP.Attributes.MemberOf = member
|
||||||
|
suite.config.LDAP.GroupSearchMode = memberof
|
||||||
|
|
||||||
ValidateAuthenticationBackend(&suite.config, suite.validator)
|
ValidateAuthenticationBackend(&suite.config, suite.validator)
|
||||||
|
|
||||||
suite.NotEqual(
|
suite.NotEqualImplementationDefaults(schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth)
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth.Timeout,
|
|
||||||
suite.config.LDAP.Timeout)
|
suite.Equal(member, suite.config.LDAP.Attributes.MemberOf)
|
||||||
suite.NotEqual(
|
suite.Equal("", suite.config.LDAP.Attributes.DistinguishedName)
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth.AdditionalUsersDN,
|
suite.Equal(schema.LDAPGroupSearchModeMemberOf, suite.config.LDAP.GroupSearchMode)
|
||||||
suite.config.LDAP.AdditionalUsersDN)
|
|
||||||
suite.NotEqual(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth.AdditionalGroupsDN,
|
|
||||||
suite.config.LDAP.AdditionalGroupsDN)
|
|
||||||
suite.NotEqual(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth.Timeout,
|
|
||||||
suite.config.LDAP.Timeout)
|
|
||||||
suite.NotEqual(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth.UsersFilter,
|
|
||||||
suite.config.LDAP.UsersFilter)
|
|
||||||
suite.NotEqual(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth.UsernameAttribute,
|
|
||||||
suite.config.LDAP.UsernameAttribute)
|
|
||||||
suite.NotEqual(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth.DisplayNameAttribute,
|
|
||||||
suite.config.LDAP.DisplayNameAttribute)
|
|
||||||
suite.NotEqual(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth.MailAttribute,
|
|
||||||
suite.config.LDAP.MailAttribute)
|
|
||||||
suite.NotEqual(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth.GroupsFilter,
|
|
||||||
suite.config.LDAP.GroupsFilter)
|
|
||||||
suite.NotEqual(
|
|
||||||
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth.GroupNameAttribute,
|
|
||||||
suite.config.LDAP.GroupNameAttribute)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGLAuthAuthenticationBackend(t *testing.T) {
|
func TestGLAuthAuthenticationBackend(t *testing.T) {
|
||||||
suite.Run(t, new(GLAuthAuthenticationBackendSuite))
|
suite.Run(t, new(GLAuthAuthenticationBackendSuite))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type LDAPImplementationSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
config schema.AuthenticationBackend
|
||||||
|
validator *schema.StructValidator
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *LDAPImplementationSuite) EqualImplementationDefaults(expected schema.LDAPAuthenticationBackend) {
|
||||||
|
suite.Equal(expected.Timeout, suite.config.LDAP.Timeout)
|
||||||
|
suite.Equal(expected.AdditionalUsersDN, suite.config.LDAP.AdditionalUsersDN)
|
||||||
|
suite.Equal(expected.AdditionalGroupsDN, suite.config.LDAP.AdditionalGroupsDN)
|
||||||
|
suite.Equal(expected.UsersFilter, suite.config.LDAP.UsersFilter)
|
||||||
|
suite.Equal(expected.GroupsFilter, suite.config.LDAP.GroupsFilter)
|
||||||
|
suite.Equal(expected.GroupSearchMode, suite.config.LDAP.GroupSearchMode)
|
||||||
|
|
||||||
|
suite.Equal(expected.Attributes.DistinguishedName, suite.config.LDAP.Attributes.DistinguishedName)
|
||||||
|
suite.Equal(expected.Attributes.Username, suite.config.LDAP.Attributes.Username)
|
||||||
|
suite.Equal(expected.Attributes.DisplayName, suite.config.LDAP.Attributes.DisplayName)
|
||||||
|
suite.Equal(expected.Attributes.Mail, suite.config.LDAP.Attributes.Mail)
|
||||||
|
suite.Equal(expected.Attributes.MemberOf, suite.config.LDAP.Attributes.MemberOf)
|
||||||
|
suite.Equal(expected.Attributes.GroupName, suite.config.LDAP.Attributes.GroupName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *LDAPImplementationSuite) NotEqualImplementationDefaults(expected schema.LDAPAuthenticationBackend) {
|
||||||
|
suite.NotEqual(expected.Timeout, suite.config.LDAP.Timeout)
|
||||||
|
suite.NotEqual(expected.UsersFilter, suite.config.LDAP.UsersFilter)
|
||||||
|
suite.NotEqual(expected.GroupsFilter, suite.config.LDAP.GroupsFilter)
|
||||||
|
suite.NotEqual(expected.GroupSearchMode, suite.config.LDAP.GroupSearchMode)
|
||||||
|
suite.NotEqual(expected.Attributes.Username, suite.config.LDAP.Attributes.Username)
|
||||||
|
suite.NotEqual(expected.Attributes.DisplayName, suite.config.LDAP.Attributes.DisplayName)
|
||||||
|
suite.NotEqual(expected.Attributes.Mail, suite.config.LDAP.Attributes.Mail)
|
||||||
|
suite.NotEqual(expected.Attributes.GroupName, suite.config.LDAP.Attributes.GroupName)
|
||||||
|
|
||||||
|
if expected.Attributes.DistinguishedName != "" {
|
||||||
|
suite.NotEqual(expected.Attributes.DistinguishedName, suite.config.LDAP.Attributes.DistinguishedName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if expected.AdditionalUsersDN != "" {
|
||||||
|
suite.NotEqual(expected.AdditionalUsersDN, suite.config.LDAP.AdditionalUsersDN)
|
||||||
|
}
|
||||||
|
|
||||||
|
if expected.AdditionalGroupsDN != "" {
|
||||||
|
suite.NotEqual(expected.AdditionalGroupsDN, suite.config.LDAP.AdditionalGroupsDN)
|
||||||
|
}
|
||||||
|
|
||||||
|
if expected.Attributes.MemberOf != "" {
|
||||||
|
suite.NotEqual(expected.Attributes.MemberOf, suite.config.LDAP.Attributes.MemberOf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -100,7 +100,7 @@ const (
|
||||||
|
|
||||||
errFmtLDAPAuthBackendMissingOption = "authentication_backend: ldap: option '%s' is required"
|
errFmtLDAPAuthBackendMissingOption = "authentication_backend: ldap: option '%s' is required"
|
||||||
errFmtLDAPAuthBackendTLSConfigInvalid = "authentication_backend: ldap: tls: %w"
|
errFmtLDAPAuthBackendTLSConfigInvalid = "authentication_backend: ldap: tls: %w"
|
||||||
errFmtLDAPAuthBackendImplementation = "authentication_backend: ldap: option 'implementation' " +
|
errFmtLDAPAuthBackendOptionMustBeOneOf = "authentication_backend: ldap: option '%s' " +
|
||||||
errSuffixMustBeOneOf
|
errSuffixMustBeOneOf
|
||||||
errFmtLDAPAuthBackendFilterReplacedPlaceholders = "authentication_backend: ldap: option " +
|
errFmtLDAPAuthBackendFilterReplacedPlaceholders = "authentication_backend: ldap: option " +
|
||||||
"'%s' has an invalid placeholder: '%s' has been removed, please use '%s' instead"
|
"'%s' has an invalid placeholder: '%s' has been removed, please use '%s' instead"
|
||||||
|
@ -109,6 +109,10 @@ const (
|
||||||
"'%s' must contain enclosing parenthesis: '%s' should probably be '(%s)'"
|
"'%s' must contain enclosing parenthesis: '%s' should probably be '(%s)'"
|
||||||
errFmtLDAPAuthBackendFilterMissingPlaceholder = "authentication_backend: ldap: option " +
|
errFmtLDAPAuthBackendFilterMissingPlaceholder = "authentication_backend: ldap: option " +
|
||||||
"'%s' must contain the placeholder '{%s}' but it's absent"
|
"'%s' must contain the placeholder '{%s}' but it's absent"
|
||||||
|
errFmtLDAPAuthBackendFilterMissingPlaceholderGroupSearchMode = "authentication_backend: ldap: option " +
|
||||||
|
"'%s' must contain one of the %s placeholders when using a group_search_mode of '%s' but they're absent"
|
||||||
|
errFmtLDAPAuthBackendFilterMissingAttribute = "authentication_backend: ldap: attributes: option " +
|
||||||
|
"'%s' must be provided when using the %s placeholder but it's absent"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TOTP Error constants.
|
// TOTP Error constants.
|
||||||
|
@ -324,6 +328,7 @@ const (
|
||||||
errFmtServerAddress = "server: option 'address' with value '%s' is invalid: %w"
|
errFmtServerAddress = "server: option 'address' with value '%s' is invalid: %w"
|
||||||
|
|
||||||
errFmtServerPathNoForwardSlashes = "server: option 'path' must not contain any forward slashes"
|
errFmtServerPathNoForwardSlashes = "server: option 'path' must not contain any forward slashes"
|
||||||
|
errFmtServerPathNotEndForwardSlash = "server: option 'address' must not and with a forward slash but it's configured as '%s'"
|
||||||
errFmtServerPathAlphaNum = "server: option 'path' must only contain alpha numeric characters"
|
errFmtServerPathAlphaNum = "server: option 'path' must only contain alpha numeric characters"
|
||||||
|
|
||||||
errFmtServerEndpointsAuthzImplementation = "server: endpoints: authz: %s: option 'implementation' must be one of %s but it's configured as '%s'"
|
errFmtServerEndpointsAuthzImplementation = "server: endpoints: authz: %s: option 'implementation' must be one of %s but it's configured as '%s'"
|
||||||
|
@ -373,17 +378,6 @@ const (
|
||||||
operatorNotPattern = "not pattern"
|
operatorNotPattern = "not pattern"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
validLDAPImplementations = []string{
|
|
||||||
schema.LDAPImplementationCustom,
|
|
||||||
schema.LDAPImplementationActiveDirectory,
|
|
||||||
schema.LDAPImplementationRFC2307bis,
|
|
||||||
schema.LDAPImplementationFreeIPA,
|
|
||||||
schema.LDAPImplementationLLDAP,
|
|
||||||
schema.LDAPImplementationGLAuth,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
legacy = "legacy"
|
legacy = "legacy"
|
||||||
authzImplementationLegacy = "Legacy"
|
authzImplementationLegacy = "Legacy"
|
||||||
|
@ -399,6 +393,22 @@ var (
|
||||||
validAuthzAuthnStrategies = []string{"CookieSession", "HeaderAuthorization", "HeaderProxyAuthorization", "HeaderAuthRequestProxyAuthorization", "HeaderLegacy"}
|
validAuthzAuthnStrategies = []string{"CookieSession", "HeaderAuthorization", "HeaderProxyAuthorization", "HeaderAuthRequestProxyAuthorization", "HeaderLegacy"}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
validLDAPImplementations = []string{
|
||||||
|
schema.LDAPImplementationCustom,
|
||||||
|
schema.LDAPImplementationActiveDirectory,
|
||||||
|
schema.LDAPImplementationRFC2307bis,
|
||||||
|
schema.LDAPImplementationFreeIPA,
|
||||||
|
schema.LDAPImplementationLLDAP,
|
||||||
|
schema.LDAPImplementationGLAuth,
|
||||||
|
}
|
||||||
|
|
||||||
|
validLDAPGroupSearchModes = []string{
|
||||||
|
schema.LDAPGroupSearchModeFilter,
|
||||||
|
schema.LDAPGroupSearchModeMemberOf,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
validArgon2Variants = []string{"argon2id", "id", "argon2i", "i", "argon2d", "d"}
|
validArgon2Variants = []string{"argon2id", "id", "argon2i", "i", "argon2d", "d"}
|
||||||
validSHA2CryptVariants = []string{digestSHA256, digestSHA512}
|
validSHA2CryptVariants = []string{digestSHA256, digestSHA512}
|
||||||
|
|
|
@ -13,6 +13,11 @@ const (
|
||||||
testLDAPURL = "ldap://ldap"
|
testLDAPURL = "ldap://ldap"
|
||||||
testLDAPUser = "user"
|
testLDAPUser = "user"
|
||||||
testEncryptionKey = "a_not_so_secure_encryption_key"
|
testEncryptionKey = "a_not_so_secure_encryption_key"
|
||||||
|
|
||||||
|
member = "member"
|
||||||
|
memberof = "memberof"
|
||||||
|
memberOf = "memberOf"
|
||||||
|
filterMemberOfRDN = "(|({memberof:rdn}))"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
|
@ -59,17 +59,6 @@ func ValidateServer(config *schema.Configuration, validator *schema.StructValida
|
||||||
ValidateServerAddress(config, validator)
|
ValidateServerAddress(config, validator)
|
||||||
ValidateServerTLS(config, validator)
|
ValidateServerTLS(config, validator)
|
||||||
|
|
||||||
switch {
|
|
||||||
case strings.Contains(config.Server.Path, "/"):
|
|
||||||
validator.Push(fmt.Errorf(errFmtServerPathNoForwardSlashes))
|
|
||||||
case !utils.IsStringAlphaNumeric(config.Server.Path):
|
|
||||||
validator.Push(fmt.Errorf(errFmtServerPathAlphaNum))
|
|
||||||
case config.Server.Path == "": // Don't do anything if it's blank.
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
config.Server.Path = path.Clean("/" + config.Server.Path)
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.Server.Buffers.Read <= 0 {
|
if config.Server.Buffers.Read <= 0 {
|
||||||
config.Server.Buffers.Read = schema.DefaultServerConfiguration.Buffers.Read
|
config.Server.Buffers.Read = schema.DefaultServerConfiguration.Buffers.Read
|
||||||
}
|
}
|
||||||
|
@ -96,11 +85,12 @@ func ValidateServer(config *schema.Configuration, validator *schema.StructValida
|
||||||
// ValidateServerAddress checks the configured server address is correct.
|
// ValidateServerAddress checks the configured server address is correct.
|
||||||
func ValidateServerAddress(config *schema.Configuration, validator *schema.StructValidator) {
|
func ValidateServerAddress(config *schema.Configuration, validator *schema.StructValidator) {
|
||||||
if config.Server.Address == nil {
|
if config.Server.Address == nil {
|
||||||
if config.Server.Host == "" && config.Server.Port == 0 { //nolint:staticcheck
|
if config.Server.Host == "" && config.Server.Port == 0 && config.Server.Path == "" { //nolint:staticcheck
|
||||||
config.Server.Address = schema.DefaultServerConfiguration.Address
|
config.Server.Address = schema.DefaultServerConfiguration.Address
|
||||||
} else {
|
} else {
|
||||||
host := config.Server.Host //nolint:staticcheck
|
host := config.Server.Host //nolint:staticcheck
|
||||||
port := config.Server.Port //nolint:staticcheck
|
port := config.Server.Port //nolint:staticcheck
|
||||||
|
subpath := config.Server.Path //nolint:staticcheck
|
||||||
|
|
||||||
if host == "" {
|
if host == "" {
|
||||||
host = schema.DefaultServerConfiguration.Address.Hostname()
|
host = schema.DefaultServerConfiguration.Address.Hostname()
|
||||||
|
@ -110,7 +100,20 @@ func ValidateServerAddress(config *schema.Configuration, validator *schema.Struc
|
||||||
port = schema.DefaultServerConfiguration.Address.Port()
|
port = schema.DefaultServerConfiguration.Address.Port()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case strings.Contains(subpath, "/"):
|
||||||
|
validator.Push(fmt.Errorf(errFmtServerPathNoForwardSlashes))
|
||||||
|
case !utils.IsStringAlphaNumeric(config.Server.Path): //nolint:staticcheck
|
||||||
|
validator.Push(fmt.Errorf(errFmtServerPathAlphaNum))
|
||||||
|
case subpath == "":
|
||||||
|
subpath = schema.DefaultServerConfiguration.Address.Path()
|
||||||
|
default:
|
||||||
|
subpath = path.Clean("/" + config.Server.Path) //nolint:staticcheck
|
||||||
|
}
|
||||||
|
|
||||||
config.Server.Address = &schema.AddressTCP{Address: schema.NewAddressFromNetworkValues(schema.AddressSchemeTCP, host, port)}
|
config.Server.Address = &schema.AddressTCP{Address: schema.NewAddressFromNetworkValues(schema.AddressSchemeTCP, host, port)}
|
||||||
|
|
||||||
|
config.Server.Address.SetPath(subpath)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if config.Server.Host != "" || config.Server.Port != 0 { //nolint:staticcheck
|
if config.Server.Host != "" || config.Server.Port != 0 { //nolint:staticcheck
|
||||||
|
@ -123,6 +126,10 @@ func ValidateServerAddress(config *schema.Configuration, validator *schema.Struc
|
||||||
validator.Push(fmt.Errorf(errFmtServerAddress, config.Server.Address.String(), err))
|
validator.Push(fmt.Errorf(errFmtServerAddress, config.Server.Address.String(), err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if path := config.Server.Address.Path(); path != "/" && strings.HasSuffix(path, "/") {
|
||||||
|
validator.Push(fmt.Errorf(errFmtServerPathNotEndForwardSlash, path))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateServerEndpoints configures the default endpoints and checks the configuration of custom endpoints.
|
// ValidateServerEndpoints configures the default endpoints and checks the configuration of custom endpoints.
|
||||||
|
|
|
@ -23,26 +23,87 @@ func TestShouldSetDefaultServerValues(t *testing.T) {
|
||||||
assert.Len(t, validator.Errors(), 0)
|
assert.Len(t, validator.Errors(), 0)
|
||||||
assert.Len(t, validator.Warnings(), 0)
|
assert.Len(t, validator.Warnings(), 0)
|
||||||
|
|
||||||
assert.Equal(t, "", config.Server.Host) //nolint:staticcheck
|
|
||||||
assert.Equal(t, 0, config.Server.Port) //nolint:staticcheck
|
|
||||||
assert.Equal(t, schema.DefaultServerConfiguration.Address, config.Server.Address)
|
assert.Equal(t, schema.DefaultServerConfiguration.Address, config.Server.Address)
|
||||||
assert.Equal(t, schema.DefaultServerConfiguration.Buffers.Read, config.Server.Buffers.Read)
|
assert.Equal(t, schema.DefaultServerConfiguration.Buffers.Read, config.Server.Buffers.Read)
|
||||||
assert.Equal(t, schema.DefaultServerConfiguration.Buffers.Write, config.Server.Buffers.Write)
|
assert.Equal(t, schema.DefaultServerConfiguration.Buffers.Write, config.Server.Buffers.Write)
|
||||||
assert.Equal(t, schema.DefaultServerConfiguration.TLS.Key, config.Server.TLS.Key)
|
assert.Equal(t, schema.DefaultServerConfiguration.TLS.Key, config.Server.TLS.Key)
|
||||||
assert.Equal(t, schema.DefaultServerConfiguration.TLS.Certificate, config.Server.TLS.Certificate)
|
assert.Equal(t, schema.DefaultServerConfiguration.TLS.Certificate, config.Server.TLS.Certificate)
|
||||||
assert.Equal(t, schema.DefaultServerConfiguration.Path, config.Server.Path)
|
|
||||||
assert.Equal(t, schema.DefaultServerConfiguration.Endpoints.EnableExpvars, config.Server.Endpoints.EnableExpvars)
|
assert.Equal(t, schema.DefaultServerConfiguration.Endpoints.EnableExpvars, config.Server.Endpoints.EnableExpvars)
|
||||||
assert.Equal(t, schema.DefaultServerConfiguration.Endpoints.EnablePprof, config.Server.Endpoints.EnablePprof)
|
assert.Equal(t, schema.DefaultServerConfiguration.Endpoints.EnablePprof, config.Server.Endpoints.EnablePprof)
|
||||||
assert.Equal(t, schema.DefaultServerConfiguration.Endpoints.Authz, config.Server.Endpoints.Authz)
|
assert.Equal(t, schema.DefaultServerConfiguration.Endpoints.Authz, config.Server.Endpoints.Authz)
|
||||||
|
|
||||||
|
assert.Equal(t, "", config.Server.Host) //nolint:staticcheck
|
||||||
|
assert.Equal(t, 0, config.Server.Port) //nolint:staticcheck
|
||||||
|
assert.Equal(t, "", config.Server.Path) //nolint:staticcheck
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldSetDefaultServerValuesWithLegacyAddress(t *testing.T) {
|
func TestShouldSetDefaultServerValuesWithLegacyAddress(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
have schema.ServerConfiguration
|
||||||
|
expected schema.Address
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"ShouldParseAll",
|
||||||
|
schema.ServerConfiguration{
|
||||||
|
Host: "abc",
|
||||||
|
Port: 123,
|
||||||
|
Path: "subpath",
|
||||||
|
},
|
||||||
|
MustParseAddress("tcp://abc:123/subpath"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ShouldParseHostAndPort",
|
||||||
|
schema.ServerConfiguration{
|
||||||
|
Host: "abc",
|
||||||
|
Port: 123,
|
||||||
|
},
|
||||||
|
MustParseAddress("tcp://abc:123/"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ShouldParseHostAndPath",
|
||||||
|
schema.ServerConfiguration{
|
||||||
|
Host: "abc",
|
||||||
|
Path: "subpath",
|
||||||
|
},
|
||||||
|
MustParseAddress("tcp://abc:9091/subpath"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ShouldParsePortAndPath",
|
||||||
|
schema.ServerConfiguration{
|
||||||
|
Port: 123,
|
||||||
|
Path: "subpath",
|
||||||
|
},
|
||||||
|
MustParseAddress("tcp://:123/subpath"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ShouldParseHost",
|
||||||
|
schema.ServerConfiguration{
|
||||||
|
Host: "abc",
|
||||||
|
},
|
||||||
|
MustParseAddress("tcp://abc:9091/"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ShouldParsePort",
|
||||||
|
schema.ServerConfiguration{
|
||||||
|
Port: 123,
|
||||||
|
},
|
||||||
|
MustParseAddress("tcp://:123/"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ShouldParsePath",
|
||||||
|
schema.ServerConfiguration{
|
||||||
|
Path: "subpath",
|
||||||
|
},
|
||||||
|
MustParseAddress("tcp://:9091/subpath"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
validator := schema.NewStructValidator()
|
validator := schema.NewStructValidator()
|
||||||
config := &schema.Configuration{
|
config := &schema.Configuration{
|
||||||
Server: schema.ServerConfiguration{
|
Server: tc.have,
|
||||||
Host: "abc",
|
|
||||||
Port: 123,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ValidateServer(config, validator)
|
ValidateServer(config, validator)
|
||||||
|
@ -50,39 +111,9 @@ func TestShouldSetDefaultServerValuesWithLegacyAddress(t *testing.T) {
|
||||||
assert.Len(t, validator.Errors(), 0)
|
assert.Len(t, validator.Errors(), 0)
|
||||||
assert.Len(t, validator.Warnings(), 0)
|
assert.Len(t, validator.Warnings(), 0)
|
||||||
|
|
||||||
assert.Equal(t, "abc", config.Server.Host) //nolint:staticcheck
|
assert.Equal(t, &schema.AddressTCP{Address: tc.expected}, config.Server.Address)
|
||||||
assert.Equal(t, 123, config.Server.Port) //nolint:staticcheck
|
})
|
||||||
assert.Equal(t, &schema.AddressTCP{Address: MustParseAddress("tcp://abc:123")}, config.Server.Address)
|
|
||||||
|
|
||||||
config = &schema.Configuration{
|
|
||||||
Server: schema.ServerConfiguration{
|
|
||||||
Host: "abc",
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ValidateServer(config, validator)
|
|
||||||
|
|
||||||
assert.Len(t, validator.Errors(), 0)
|
|
||||||
assert.Len(t, validator.Warnings(), 0)
|
|
||||||
|
|
||||||
assert.Equal(t, "abc", config.Server.Host) //nolint:staticcheck
|
|
||||||
assert.Equal(t, 0, config.Server.Port) //nolint:staticcheck
|
|
||||||
assert.Equal(t, &schema.AddressTCP{Address: MustParseAddress("tcp://abc:9091")}, config.Server.Address)
|
|
||||||
|
|
||||||
config = &schema.Configuration{
|
|
||||||
Server: schema.ServerConfiguration{
|
|
||||||
Port: 123,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
ValidateServer(config, validator)
|
|
||||||
|
|
||||||
assert.Len(t, validator.Errors(), 0)
|
|
||||||
assert.Len(t, validator.Warnings(), 0)
|
|
||||||
|
|
||||||
assert.Equal(t, "", config.Server.Host) //nolint:staticcheck
|
|
||||||
assert.Equal(t, 123, config.Server.Port) //nolint:staticcheck
|
|
||||||
assert.Equal(t, &schema.AddressTCP{Address: MustParseAddress("tcp://:123")}, config.Server.Address)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldSetDefaultConfig(t *testing.T) {
|
func TestShouldSetDefaultConfig(t *testing.T) {
|
||||||
|
@ -98,20 +129,20 @@ func TestShouldSetDefaultConfig(t *testing.T) {
|
||||||
assert.Equal(t, schema.DefaultServerConfiguration.Buffers.Write, config.Server.Buffers.Write)
|
assert.Equal(t, schema.DefaultServerConfiguration.Buffers.Write, config.Server.Buffers.Write)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldParsePathCorrectly(t *testing.T) {
|
func TestValidateSeverAddress(t *testing.T) {
|
||||||
validator := schema.NewStructValidator()
|
|
||||||
config := &schema.Configuration{
|
config := &schema.Configuration{
|
||||||
Server: schema.ServerConfiguration{
|
Server: schema.ServerConfiguration{
|
||||||
Path: "apple",
|
Address: &schema.AddressTCP{Address: MustParseAddress("tcp://:9091/path/")},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
validator := schema.NewStructValidator()
|
||||||
ValidateServer(config, validator)
|
ValidateServer(config, validator)
|
||||||
|
|
||||||
assert.Len(t, validator.Errors(), 0)
|
require.Len(t, validator.Errors(), 1)
|
||||||
assert.Len(t, validator.Warnings(), 0)
|
assert.Len(t, validator.Warnings(), 0)
|
||||||
|
|
||||||
assert.Equal(t, "/apple", config.Server.Path)
|
assert.EqualError(t, validator.Errors()[0], "server: option 'address' must not and with a forward slash but it's configured as '/path/'")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestValidateServerShouldCorrectlyIdentifyValidAddressSchemes(t *testing.T) {
|
func TestValidateServerShouldCorrectlyIdentifyValidAddressSchemes(t *testing.T) {
|
||||||
|
@ -236,7 +267,7 @@ func TestShouldValidateAndUpdateAddress(t *testing.T) {
|
||||||
ValidateServer(&config, validator)
|
ValidateServer(&config, validator)
|
||||||
|
|
||||||
require.Len(t, validator.Errors(), 0)
|
require.Len(t, validator.Errors(), 0)
|
||||||
assert.Equal(t, "tcp://:9091", config.Server.Address.String())
|
assert.Equal(t, "tcp://:9091/", config.Server.Address.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldRaiseErrorOnLegacyAndModernValues(t *testing.T) {
|
func TestShouldRaiseErrorOnLegacyAndModernValues(t *testing.T) {
|
||||||
|
@ -251,22 +282,6 @@ func TestShouldRaiseErrorOnLegacyAndModernValues(t *testing.T) {
|
||||||
assert.EqualError(t, validator.Errors()[0], "server: option 'host' and 'port' can't be configured at the same time as 'address'")
|
assert.EqualError(t, validator.Errors()[0], "server: option 'host' and 'port' can't be configured at the same time as 'address'")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldValidateAndUpdateAddressWithOldValues(t *testing.T) {
|
|
||||||
validator := schema.NewStructValidator()
|
|
||||||
config := newDefaultConfig()
|
|
||||||
config.Server.Address = nil
|
|
||||||
config.Server.Host = local25 //nolint:staticcheck
|
|
||||||
config.Server.Port = 9999 //nolint:staticcheck
|
|
||||||
|
|
||||||
ValidateServer(&config, validator)
|
|
||||||
|
|
||||||
assert.Len(t, validator.Errors(), 0)
|
|
||||||
assert.Len(t, validator.Warnings(), 0)
|
|
||||||
|
|
||||||
require.NotNil(t, config.Server.Address)
|
|
||||||
assert.Equal(t, "tcp://127.0.0.25:9999", config.Server.Address.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestShouldRaiseErrorWhenTLSCertWithoutKeyIsProvided(t *testing.T) {
|
func TestShouldRaiseErrorWhenTLSCertWithoutKeyIsProvided(t *testing.T) {
|
||||||
validator := schema.NewStructValidator()
|
validator := schema.NewStructValidator()
|
||||||
config := newDefaultConfig()
|
config := newDefaultConfig()
|
||||||
|
|
|
@ -20,6 +20,10 @@ func ValidateTelemetry(config *schema.Configuration, validator *schema.StructVal
|
||||||
config.Telemetry.Metrics.Address.SetPort(schema.DefaultTelemetryConfig.Metrics.Address.Port())
|
config.Telemetry.Metrics.Address.SetPort(schema.DefaultTelemetryConfig.Metrics.Address.Port())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if config.Telemetry.Metrics.Address.Path() == "" {
|
||||||
|
config.Telemetry.Metrics.Address.SetPath(schema.DefaultTelemetryConfig.Metrics.Address.Path())
|
||||||
|
}
|
||||||
|
|
||||||
if config.Telemetry.Metrics.Buffers.Read <= 0 {
|
if config.Telemetry.Metrics.Buffers.Read <= 0 {
|
||||||
config.Telemetry.Metrics.Buffers.Read = schema.DefaultTelemetryConfig.Metrics.Buffers.Read
|
config.Telemetry.Metrics.Buffers.Read = schema.DefaultTelemetryConfig.Metrics.Buffers.Read
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,7 +38,7 @@ func TestValidateTelemetry(t *testing.T) {
|
||||||
&schema.Configuration{Telemetry: schema.TelemetryConfig{Metrics: schema.TelemetryMetricsConfig{Address: mustParseAddress("tcp://0.0.0.0")}}},
|
&schema.Configuration{Telemetry: schema.TelemetryConfig{Metrics: schema.TelemetryMetricsConfig{Address: mustParseAddress("tcp://0.0.0.0")}}},
|
||||||
&schema.Configuration{Telemetry: schema.TelemetryConfig{
|
&schema.Configuration{Telemetry: schema.TelemetryConfig{
|
||||||
Metrics: schema.TelemetryMetricsConfig{
|
Metrics: schema.TelemetryMetricsConfig{
|
||||||
Address: &schema.AddressTCP{Address: schema.NewAddressFromNetworkValues(schema.AddressSchemeTCP, "0.0.0.0", schema.DefaultTelemetryConfig.Metrics.Address.Port())},
|
Address: mustParseAddress("tcp://0.0.0.0:9959/metrics"),
|
||||||
Buffers: schema.ServerBuffers{
|
Buffers: schema.ServerBuffers{
|
||||||
Read: 4096,
|
Read: 4096,
|
||||||
Write: 4096,
|
Write: 4096,
|
||||||
|
@ -55,7 +55,7 @@ func TestValidateTelemetry(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ShouldSetDefaultPortAlt",
|
"ShouldSetDefaultPortAlt",
|
||||||
&schema.Configuration{Telemetry: schema.TelemetryConfig{Metrics: schema.TelemetryMetricsConfig{Address: mustParseAddress("tcp://:0")}}},
|
&schema.Configuration{Telemetry: schema.TelemetryConfig{Metrics: schema.TelemetryMetricsConfig{Address: mustParseAddress("tcp://:0/metrics")}}},
|
||||||
&schema.Configuration{Telemetry: schema.DefaultTelemetryConfig},
|
&schema.Configuration{Telemetry: schema.DefaultTelemetryConfig},
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
|
@ -63,14 +63,14 @@ func TestValidateTelemetry(t *testing.T) {
|
||||||
{
|
{
|
||||||
"ShouldSetDefaultPortWithCustomIP",
|
"ShouldSetDefaultPortWithCustomIP",
|
||||||
&schema.Configuration{Telemetry: schema.TelemetryConfig{Metrics: schema.TelemetryMetricsConfig{Address: mustParseAddress("tcp://127.0.0.1")}}},
|
&schema.Configuration{Telemetry: schema.TelemetryConfig{Metrics: schema.TelemetryMetricsConfig{Address: mustParseAddress("tcp://127.0.0.1")}}},
|
||||||
&schema.Configuration{Telemetry: schema.TelemetryConfig{Metrics: schema.TelemetryMetricsConfig{Address: mustParseAddress("tcp://127.0.0.1:9959")}}},
|
&schema.Configuration{Telemetry: schema.TelemetryConfig{Metrics: schema.TelemetryMetricsConfig{Address: mustParseAddress("tcp://127.0.0.1:9959/metrics")}}},
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ShouldNotValidateUDP",
|
"ShouldNotValidateUDP",
|
||||||
&schema.Configuration{Telemetry: schema.TelemetryConfig{Metrics: schema.TelemetryMetricsConfig{Address: mustParseAddress("udp://0.0.0.0")}}},
|
&schema.Configuration{Telemetry: schema.TelemetryConfig{Metrics: schema.TelemetryMetricsConfig{Address: mustParseAddress("udp://0.0.0.0")}}},
|
||||||
&schema.Configuration{Telemetry: schema.TelemetryConfig{Metrics: schema.TelemetryMetricsConfig{Address: mustParseAddress("udp://0.0.0.0:9959")}}},
|
&schema.Configuration{Telemetry: schema.TelemetryConfig{Metrics: schema.TelemetryMetricsConfig{Address: mustParseAddress("udp://0.0.0.0:9959/metrics")}}},
|
||||||
nil,
|
nil,
|
||||||
[]string{"telemetry: metrics: option 'address' with value 'udp://0.0.0.0:0' is invalid: scheme must be one of 'tcp', 'tcp4', 'tcp6', or 'unix' but is configured as 'udp'"},
|
[]string{"telemetry: metrics: option 'address' with value 'udp://0.0.0.0:0' is invalid: scheme must be one of 'tcp', 'tcp4', 'tcp6', or 'unix' but is configured as 'udp'"},
|
||||||
},
|
},
|
||||||
|
|
|
@ -68,7 +68,6 @@ const (
|
||||||
messageAuthenticationFailed = "Authentication failed. Check your credentials."
|
messageAuthenticationFailed = "Authentication failed. Check your credentials."
|
||||||
messageUnableToRegisterOneTimePassword = "Unable to set up one-time passwords." //nolint:gosec
|
messageUnableToRegisterOneTimePassword = "Unable to set up one-time passwords." //nolint:gosec
|
||||||
messageUnableToRegisterSecurityKey = "Unable to register your security key."
|
messageUnableToRegisterSecurityKey = "Unable to register your security key."
|
||||||
messageSecurityKeyDuplicateName = "Another one of your security keys is already registered with that display name."
|
|
||||||
messageUnableToResetPassword = "Unable to reset your password."
|
messageUnableToResetPassword = "Unable to reset your password."
|
||||||
messageMFAValidationFailed = "Authentication failed, please retry later."
|
messageMFAValidationFailed = "Authentication failed, please retry later."
|
||||||
messagePasswordWeak = "Your supplied password does not meet the password policy requirements"
|
messagePasswordWeak = "Your supplied password does not meet the password policy requirements"
|
||||||
|
|
|
@ -0,0 +1,410 @@
|
||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/authelia/authelia/v4/internal/authorization"
|
||||||
|
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
||||||
|
"github.com/authelia/authelia/v4/internal/middlewares"
|
||||||
|
"github.com/authelia/authelia/v4/internal/session"
|
||||||
|
"github.com/authelia/authelia/v4/internal/utils"
|
||||||
|
core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
|
||||||
|
autha "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3"
|
||||||
|
envoy_type "github.com/envoyproxy/go-control-plane/envoy/type/v3"
|
||||||
|
"github.com/gogo/googleapis/google/rpc"
|
||||||
|
"github.com/valyala/fasthttp"
|
||||||
|
rpcstatus "google.golang.org/genproto/googleapis/rpc/status"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
healthpb "google.golang.org/grpc/health/grpc_health_v1"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
|
||||||
|
"context"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AuthzGRCP struct {
|
||||||
|
Config *schema.Configuration
|
||||||
|
Providers middlewares.Providers
|
||||||
|
AuthStrategies []AuthnStrategy
|
||||||
|
Authz Authz
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAuthzGRCP(config *schema.Configuration, providers middlewares.Providers) *AuthzGRCP {
|
||||||
|
|
||||||
|
// Determine the refresh interval
|
||||||
|
authBuilder := NewAuthzBuilder().WithConfig(config)
|
||||||
|
|
||||||
|
// Only the following strategies are supported. These are hardcoded at the moment and won't be taken from the configuration
|
||||||
|
strategies := []AuthnStrategy{NewHeaderProxyAuthorizationAuthnStrategy() /* NewHeaderAuthorizationAuthnStrategy(), */, NewCookieSessionAuthnStrategy(authBuilder.config.RefreshInterval)}
|
||||||
|
|
||||||
|
return &AuthzGRCP{
|
||||||
|
Config: config,
|
||||||
|
Providers: providers,
|
||||||
|
AuthStrategies: strategies,
|
||||||
|
Authz: Authz{strategies: strategies},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type GRCPRequestData struct {
|
||||||
|
RemoteHost string
|
||||||
|
Domain string
|
||||||
|
Method string
|
||||||
|
Protocol string
|
||||||
|
Path string
|
||||||
|
AutheliaURL string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (data *GRCPRequestData) String() string {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
`Extracted data from headers:
|
||||||
|
Remote Host = %s
|
||||||
|
Domain = %s
|
||||||
|
Method = %s
|
||||||
|
Protocol = %s
|
||||||
|
Path = %s
|
||||||
|
AutheliaURL = %s
|
||||||
|
`, data.RemoteHost, data.Domain, data.Method, data.Protocol, data.Path, data.AutheliaURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRequestURI returns a request URI from the provided Data inside this struct.
|
||||||
|
// It returns an error if values are missing
|
||||||
|
func (data *GRCPRequestData) GetRequestURI() (*url.URL, error) {
|
||||||
|
if len(data.Protocol) == 0 || len(data.Domain) == 0 || len(data.Path) == 0 || len(data.Method) == 0 {
|
||||||
|
return nil, fmt.Errorf("missing required values for URI. One of 'Protocol', 'Domain', 'Path' or 'Method' was not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
return url.ParseRequestURI(fmt.Sprintf("%s://%s%s", data.Protocol, data.Domain, data.Path))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAutheliaURI returns a URI for the authelia protal from the provided Data inside this struct.
|
||||||
|
func (data *GRCPRequestData) GetAutheliaURI(provider *session.Session) (*url.URL, error) {
|
||||||
|
if len(data.AutheliaURL) == 0 {
|
||||||
|
return nil, fmt.Errorf("missing required 'AutheliaURL' for request")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the URL
|
||||||
|
uri, err := url.ParseRequestURI(data.AutheliaURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate URL
|
||||||
|
if !utils.HasURIDomainSuffix(uri, provider.Config.Domain) {
|
||||||
|
return nil, fmt.Errorf("authelia url '%s' is not valid for detected domain '%s' as the url does not have the domain as a suffix", data.AutheliaURL, provider.Config.Domain)
|
||||||
|
}
|
||||||
|
|
||||||
|
return uri, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleGRPC is the authentication handler for envoy proxies via the gRPC protocoll.
|
||||||
|
// It is oriented on handler_authz.go
|
||||||
|
func (authz *AuthzGRCP) Check(goCtx context.Context, req *autha.CheckRequest) (*autha.CheckResponse, error) {
|
||||||
|
request, data := authz.GetHttpCtxFromGRPC(req)
|
||||||
|
ctx := middlewares.NewAutheliaCtx(request, *authz.Config, authz.Providers)
|
||||||
|
|
||||||
|
// Log the parsed data and the inbound headers
|
||||||
|
ctx.Logger.Debug(data)
|
||||||
|
b, err := json.MarshalIndent(req.Attributes.Request.Http.Headers, "", " ")
|
||||||
|
if err == nil {
|
||||||
|
ctx.Logger.Trace("Inbound Headers: ")
|
||||||
|
ctx.Logger.Trace((string(b)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get request URI
|
||||||
|
uri, err := data.GetRequestURI()
|
||||||
|
if err != nil {
|
||||||
|
ctx.Logger.WithError(err).Error("Error getting Target URL and Request Method")
|
||||||
|
return authz.ErrAuthResponse(envoy_type.StatusCode_BadRequest, "Bad authentication request"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get authentication object
|
||||||
|
object := authorization.NewObject(uri, data.Method)
|
||||||
|
if !utils.IsURISecure(object.URL) {
|
||||||
|
// Check for redirect
|
||||||
|
if !ctx.Configuration.Server.DisableAutoHttpsRedirect {
|
||||||
|
// Redirect to https
|
||||||
|
return authz.AuthResponseHeader(envoy_type.StatusCode_PermanentRedirect, "", []*core.HeaderValueOption{
|
||||||
|
{
|
||||||
|
Header: &core.HeaderValue{
|
||||||
|
Key: "Location",
|
||||||
|
Value: fmt.Sprintf("https://%s%s", data.Domain, data.Path),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}), nil
|
||||||
|
}
|
||||||
|
ctx.Logger.Errorf("Target URL '%s' has an insecure scheme '%s', only the 'https' and 'wss' schemes are supported so session cookies can be transmitted securely", object.URL.String(), object.URL.Scheme)
|
||||||
|
return authz.ErrAuthResponse(envoy_type.StatusCode_BadRequest, "Bad protocol for authentication request"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get provider
|
||||||
|
provider, err := ctx.GetSessionProviderByTargetURL(object.URL)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Logger.WithError(err).WithField("target_url", object.URL.String()).Error("Target URL does not appear to have a relevant session cookies configuration")
|
||||||
|
return authz.ErrAuthResponse(envoy_type.StatusCode_BadRequest, "Bad domain for authentication request"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get authelia url
|
||||||
|
if len(data.AutheliaURL) == 0 {
|
||||||
|
ctx.Logger.Info("Received no authelia URL from headers. Using the default of https://auth.rpjosh.de")
|
||||||
|
data.AutheliaURL = "https://auth.rpjosh.de"
|
||||||
|
}
|
||||||
|
autheliaURI, err := data.GetAutheliaURI(provider)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Logger.WithError(err).WithField("target_url", object.URL.String()).Error("Error occurred trying to determine the external Authelia URL for Target URL")
|
||||||
|
return authz.ErrAuthResponse(envoy_type.StatusCode_BadRequest, "Bad authentication request"), nil
|
||||||
|
}
|
||||||
|
ctx.Logger.Trace("Using authelia URL " + autheliaURI.String())
|
||||||
|
|
||||||
|
var (
|
||||||
|
authn Authn
|
||||||
|
strategy AuthnStrategy
|
||||||
|
)
|
||||||
|
|
||||||
|
// Get strategie
|
||||||
|
if authn, strategy, err = authz.Authz.authn(ctx, provider); err != nil {
|
||||||
|
authn.Object = object
|
||||||
|
|
||||||
|
ctx.Logger.WithError(err).Error("Error occurred while attempting to authenticate a request")
|
||||||
|
|
||||||
|
switch strategy {
|
||||||
|
case nil:
|
||||||
|
ctx.Logger.Trace("Received no strategy to use")
|
||||||
|
return authz.ErrAuthResponse(envoy_type.StatusCode_Unauthorized, "Unauthorized"), nil
|
||||||
|
default:
|
||||||
|
// Because the response is modified directly, we have to catch this and rewrite this function
|
||||||
|
if s, ok := strategy.(*HeaderAuthnStrategy); ok {
|
||||||
|
ctx.Logger.Trace("Rewriting HeaderAuthnStrategy")
|
||||||
|
ctx.Logger.Debugf("Responding %d %s", s.authn, autheliaURI)
|
||||||
|
|
||||||
|
headers := make([]*core.HeaderValueOption, 0)
|
||||||
|
if s.headerAuthenticate != nil {
|
||||||
|
headers = append(headers, &core.HeaderValueOption{
|
||||||
|
Header: &core.HeaderValue{
|
||||||
|
Key: string(s.headerAuthenticate),
|
||||||
|
Value: string(headerValueAuthenticateBasic),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return authz.AuthResponseHeader(envoy_type.StatusCode(s.statusAuthenticate), "", headers), nil
|
||||||
|
} else if _, ok := strategy.(*CookieSessionAuthnStrategy); ok {
|
||||||
|
ctx.Logger.Trace("Rewriting CookieSessionAuthnStrategy")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Logger.Error("Received unsupported auth strategy for gRPC server")
|
||||||
|
// strategy.HandleUnauthorized(ctx, &authn, authz.Authz.getRedirectionURL(&object, autheliaURI))
|
||||||
|
}
|
||||||
|
|
||||||
|
return authz.ErrAuthResponse(envoy_type.StatusCode_Unauthorized, "Unauthorized"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
authn.Object = object
|
||||||
|
authn.Method = friendlyMethod(authn.Object.Method)
|
||||||
|
|
||||||
|
ruleHasSubject, required := ctx.Providers.Authorizer.GetRequiredLevel(
|
||||||
|
authorization.Subject{
|
||||||
|
Username: authn.Details.Username,
|
||||||
|
Groups: authn.Details.Groups,
|
||||||
|
IP: net.ParseIP(data.RemoteHost),
|
||||||
|
},
|
||||||
|
object,
|
||||||
|
)
|
||||||
|
|
||||||
|
switch isAuthzResult(authn.Level, required, ruleHasSubject) {
|
||||||
|
case AuthzResultForbidden:
|
||||||
|
ctx.Logger.Infof("Access to '%s' is forbidden to user '%s'", object.URL.String(), authn.Username)
|
||||||
|
return authz.ErrAuthResponse(envoy_type.StatusCode_Forbidden, "Forbidden"), nil
|
||||||
|
case AuthzResultUnauthorized:
|
||||||
|
if strategy != nil {
|
||||||
|
ctx.Logger.Error("Handling not supported handler")
|
||||||
|
//strategy.HandleUnauthorized(ctx, &authn, authz.Authz.getRedirectionURL(&object, autheliaURI))
|
||||||
|
} else {
|
||||||
|
ctx.Logger.Debugf("Redirecting user")
|
||||||
|
return authz.HandleUnauthorizedRedirect(ctx, &authn, authz.Authz.getRedirectionURL(&object, autheliaURI)), nil
|
||||||
|
}
|
||||||
|
case AuthzResultAuthorized:
|
||||||
|
ctx.Logger.Debugf("Authorized request")
|
||||||
|
return authz.HandleAuthorized(ctx, &authn), nil
|
||||||
|
//authz.Authz.handleAuthorized(ctx, &authn)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Logger.Info("Handling default redirection")
|
||||||
|
return authz.HandleUnauthorizedRedirect(ctx, &authn, authz.Authz.getRedirectionURL(&object, autheliaURI)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetHttpCtxFromGRPC is an Adapter that converts common fields between a grpc request
|
||||||
|
// a "normal" http request.
|
||||||
|
// It also returns the
|
||||||
|
func (authz *AuthzGRCP) GetHttpCtxFromGRPC(req *autha.CheckRequest) (*fasthttp.RequestCtx, *GRCPRequestData) {
|
||||||
|
|
||||||
|
// Extract headers
|
||||||
|
headers := req.Attributes.Request.Http.Headers
|
||||||
|
|
||||||
|
// Parse the header data
|
||||||
|
data := &GRCPRequestData{
|
||||||
|
RemoteHost: headers["x-forwarded-for"],
|
||||||
|
Domain: headers[":authority"],
|
||||||
|
Method: headers[":method"],
|
||||||
|
Protocol: headers["x-forwarded-proto"],
|
||||||
|
Path: headers[":path"],
|
||||||
|
AutheliaURL: headers[""],
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build fasthttp request with common types
|
||||||
|
rtc := &fasthttp.RequestCtx{}
|
||||||
|
|
||||||
|
// General headers
|
||||||
|
rtc.Request.Header.Set(fasthttp.HeaderXForwardedFor, data.RemoteHost)
|
||||||
|
|
||||||
|
// Needed for NewHeaderProxyAuthorizationAuthnStrategy and NewHeaderAuthorizationAuthnStrategy
|
||||||
|
authz.setHeaderIfSet(fasthttp.HeaderAuthorization, rtc, &headers)
|
||||||
|
authz.setHeaderIfSet(fasthttp.HeaderProxyAuthorization, rtc, &headers)
|
||||||
|
authz.setHeaderIfSet(fasthttp.HeaderWWWAuthenticate, rtc, &headers)
|
||||||
|
authz.setHeaderIfSet(fasthttp.HeaderProxyAuthenticate, rtc, &headers)
|
||||||
|
|
||||||
|
// Needed for CookieSesseionauthnStrategy
|
||||||
|
rtc.Request.Header.Set("cookie", headers["cookie"])
|
||||||
|
|
||||||
|
return rtc, data
|
||||||
|
}
|
||||||
|
|
||||||
|
// setHeaderIfSet sets the header in the given fastHttp request if the header from the envoy authentication
|
||||||
|
// request was also set
|
||||||
|
func (authz *AuthzGRCP) setHeaderIfSet(headerKeyFast string, rtc *fasthttp.RequestCtx, envoyHeaders *map[string]string) {
|
||||||
|
// Envoys provided header keys are always lower case
|
||||||
|
envoyHeaderKey := strings.ToLower(headerKeyFast)
|
||||||
|
|
||||||
|
if val, isSet := (*envoyHeaders)[envoyHeaderKey]; isSet {
|
||||||
|
rtc.Request.Header.Set(headerKeyFast, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrAuthResponse returns an authentication error for envoy with the given status code
|
||||||
|
// and the given text body
|
||||||
|
func (authz *AuthzGRCP) ErrAuthResponse(statuscode envoy_type.StatusCode, body string) *autha.CheckResponse {
|
||||||
|
return &autha.CheckResponse{
|
||||||
|
Status: &rpcstatus.Status{
|
||||||
|
Code: int32(rpc.UNAUTHENTICATED),
|
||||||
|
},
|
||||||
|
HttpResponse: &autha.CheckResponse_DeniedResponse{
|
||||||
|
DeniedResponse: &autha.DeniedHttpResponse{
|
||||||
|
Status: &envoy_type.HttpStatus{
|
||||||
|
Code: statuscode,
|
||||||
|
},
|
||||||
|
Body: body,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrAuthResponse returns an authentication error for envoy with the given status code
|
||||||
|
// and the given text body
|
||||||
|
func (authz *AuthzGRCP) AuthResponseHeader(statuscode envoy_type.StatusCode, body string, headers []*core.HeaderValueOption) *autha.CheckResponse {
|
||||||
|
return &autha.CheckResponse{
|
||||||
|
Status: &rpcstatus.Status{
|
||||||
|
Code: int32(rpc.UNAUTHENTICATED),
|
||||||
|
},
|
||||||
|
HttpResponse: &autha.CheckResponse_DeniedResponse{
|
||||||
|
DeniedResponse: &autha.DeniedHttpResponse{
|
||||||
|
Status: &envoy_type.HttpStatus{
|
||||||
|
Code: statuscode,
|
||||||
|
},
|
||||||
|
Body: body,
|
||||||
|
Headers: headers,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (authz *AuthzGRCP) HandleAuthorized(ctx *middlewares.AutheliaCtx, authn *Authn) *autha.CheckResponse {
|
||||||
|
return &autha.CheckResponse{
|
||||||
|
Status: &rpcstatus.Status{
|
||||||
|
Code: int32(rpc.OK),
|
||||||
|
},
|
||||||
|
HttpResponse: &autha.CheckResponse_OkResponse{
|
||||||
|
OkResponse: &autha.OkHttpResponse{
|
||||||
|
Headers: []*core.HeaderValueOption{
|
||||||
|
{
|
||||||
|
Header: &core.HeaderValue{
|
||||||
|
Key: "Auth-Handler",
|
||||||
|
Value: getAuthType(authn.Type),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: &core.HeaderValue{
|
||||||
|
Key: "Auth-Username",
|
||||||
|
Value: authn.Username,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: &core.HeaderValue{
|
||||||
|
Key: "Auth-Username-Display",
|
||||||
|
Value: authn.Details.DisplayName,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: &core.HeaderValue{
|
||||||
|
Key: "Auth-Realm",
|
||||||
|
Value: authn.Object.Domain,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (authz *AuthzGRCP) HandleUnauthorizedRedirect(ctx *middlewares.AutheliaCtx, authn *Authn, redirectionURL *url.URL) *autha.CheckResponse {
|
||||||
|
return &autha.CheckResponse{
|
||||||
|
Status: &rpcstatus.Status{
|
||||||
|
Code: int32(rpc.UNAUTHENTICATED),
|
||||||
|
},
|
||||||
|
HttpResponse: &autha.CheckResponse_DeniedResponse{
|
||||||
|
DeniedResponse: &autha.DeniedHttpResponse{
|
||||||
|
Status: &envoy_type.HttpStatus{
|
||||||
|
Code: 302,
|
||||||
|
},
|
||||||
|
Headers: []*core.HeaderValueOption{
|
||||||
|
{
|
||||||
|
Header: &core.HeaderValue{
|
||||||
|
Key: "Location",
|
||||||
|
Value: redirectionURL.String(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAuthType(t AuthnType) string {
|
||||||
|
|
||||||
|
switch t {
|
||||||
|
case AuthnTypeNone:
|
||||||
|
return "none"
|
||||||
|
case AuthnTypeCookie:
|
||||||
|
return "cookie"
|
||||||
|
case AuthnTypeProxyAuthorization:
|
||||||
|
return "proxy"
|
||||||
|
case AuthnTypeAuthorization:
|
||||||
|
return "header"
|
||||||
|
default:
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HealthEndpoint implements a health function to check if the appliction is
|
||||||
|
// still alive
|
||||||
|
type HealthEndpoint struct{}
|
||||||
|
|
||||||
|
func (s *HealthEndpoint) Check(ctx context.Context, in *healthpb.HealthCheckRequest) (*healthpb.HealthCheckResponse, error) {
|
||||||
|
return &healthpb.HealthCheckResponse{Status: healthpb.HealthCheckResponse_SERVING}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *HealthEndpoint) Watch(in *healthpb.HealthCheckRequest, srv healthpb.Health_WatchServer) error {
|
||||||
|
return status.Error(codes.Unimplemented, "Watch is not implemented")
|
||||||
|
}
|
|
@ -33,7 +33,7 @@ func FirstFactorPOST(delayFunc middlewares.TimingAttackDelayFunc) middlewares.Re
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if bannedUntil, err := ctx.Providers.Regulator.Regulate(ctx, bodyJSON.Username); err != nil {
|
if bannedUntil, err := ctx.Providers.Regulator.Regulate(ctx, bodyJSON.Username, ctx.RemoteIP().String()); err != nil {
|
||||||
if errors.Is(err, regulation.ErrUserIsBanned) {
|
if errors.Is(err, regulation.ErrUserIsBanned) {
|
||||||
_ = markAuthenticationAttempt(ctx, false, &bannedUntil, bodyJSON.Username, regulation.AuthType1FA, nil)
|
_ = markAuthenticationAttempt(ctx, false, &bannedUntil, bodyJSON.Username, regulation.AuthType1FA, nil)
|
||||||
|
|
||||||
|
|
|
@ -2,8 +2,6 @@ package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/go-webauthn/webauthn/protocol"
|
"github.com/go-webauthn/webauthn/protocol"
|
||||||
"github.com/go-webauthn/webauthn/webauthn"
|
"github.com/go-webauthn/webauthn/webauthn"
|
||||||
|
@ -13,21 +11,35 @@ import (
|
||||||
"github.com/authelia/authelia/v4/internal/model"
|
"github.com/authelia/authelia/v4/internal/model"
|
||||||
"github.com/authelia/authelia/v4/internal/regulation"
|
"github.com/authelia/authelia/v4/internal/regulation"
|
||||||
"github.com/authelia/authelia/v4/internal/session"
|
"github.com/authelia/authelia/v4/internal/session"
|
||||||
"github.com/authelia/authelia/v4/internal/storage"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// WebAuthnRegistrationPUT returns the attestation challenge from the server.
|
// WebauthnIdentityStart the handler for initiating the identity validation.
|
||||||
func WebAuthnRegistrationPUT(ctx *middlewares.AutheliaCtx) {
|
var WebauthnIdentityStart = middlewares.IdentityVerificationStart(middlewares.IdentityVerificationStartArgs{
|
||||||
|
MailTitle: "Register your key",
|
||||||
|
MailButtonContent: "Register",
|
||||||
|
TargetEndpoint: "/webauthn/register",
|
||||||
|
ActionClaim: ActionWebAuthnRegistration,
|
||||||
|
IdentityRetrieverFunc: identityRetrieverFromSession,
|
||||||
|
}, nil)
|
||||||
|
|
||||||
|
// WebauthnIdentityFinish the handler for finishing the identity validation.
|
||||||
|
var WebauthnIdentityFinish = middlewares.IdentityVerificationFinish(
|
||||||
|
middlewares.IdentityVerificationFinishArgs{
|
||||||
|
ActionClaim: ActionWebAuthnRegistration,
|
||||||
|
IsTokenUserValidFunc: isTokenUserValidFor2FARegistration,
|
||||||
|
}, SecondFactorWebAuthnAttestationGET)
|
||||||
|
|
||||||
|
// SecondFactorWebAuthnAttestationGET returns the attestation challenge from the server.
|
||||||
|
func SecondFactorWebAuthnAttestationGET(ctx *middlewares.AutheliaCtx, _ string) {
|
||||||
var (
|
var (
|
||||||
w *webauthn.WebAuthn
|
w *webauthn.WebAuthn
|
||||||
user *model.WebAuthnUser
|
user *model.WebAuthnUser
|
||||||
userSession session.UserSession
|
userSession session.UserSession
|
||||||
bodyJSON bodyRegisterWebAuthnPUTRequest
|
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
|
|
||||||
if userSession, err = ctx.GetSession(); err != nil {
|
if userSession, err = ctx.GetSession(); err != nil {
|
||||||
ctx.Logger.WithError(err).Errorf("Error occurred retrieving session for %s registration challenge", regulation.AuthTypeWebAuthn)
|
ctx.Logger.WithError(err).Errorf("Error occurred retrieving session for %s attestation challenge", regulation.AuthTypeWebAuthn)
|
||||||
|
|
||||||
respondUnauthorized(ctx, messageUnableToRegisterSecurityKey)
|
respondUnauthorized(ctx, messageUnableToRegisterSecurityKey)
|
||||||
|
|
||||||
|
@ -35,90 +47,40 @@ func WebAuthnRegistrationPUT(ctx *middlewares.AutheliaCtx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if w, err = newWebAuthn(ctx); err != nil {
|
if w, err = newWebAuthn(ctx); err != nil {
|
||||||
ctx.Logger.Errorf("Unable to create provider to generate %s registration challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
|
ctx.Logger.Errorf("Unable to create %s attestation challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
|
||||||
|
|
||||||
respondUnauthorized(ctx, messageUnableToRegisterSecurityKey)
|
respondUnauthorized(ctx, messageUnableToRegisterSecurityKey)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = json.Unmarshal(ctx.PostBody(), &bodyJSON); err != nil {
|
if user, err = getWebAuthnUser(ctx, userSession); err != nil {
|
||||||
ctx.Logger.Errorf("Unable to parse %s registration request PUT data for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
|
ctx.Logger.Errorf("Unable to load %s devices for assertion challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
|
||||||
|
|
||||||
|
respondUnauthorized(ctx, messageMFAValidationFailed)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var credentialCreation *protocol.CredentialCreation
|
||||||
|
|
||||||
|
if credentialCreation, userSession.WebAuthn, err = w.BeginRegistration(user); err != nil {
|
||||||
|
ctx.Logger.Errorf("Unable to create %s attestation challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
|
||||||
|
|
||||||
respondUnauthorized(ctx, messageUnableToRegisterSecurityKey)
|
respondUnauthorized(ctx, messageUnableToRegisterSecurityKey)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if length := len(bodyJSON.Description); length == 0 || length > 64 {
|
|
||||||
ctx.Logger.Errorf("Failed to validate the user chosen display name for during %s registration for user '%s': the value has a length of %d but must be between 1 and 64", regulation.AuthTypeWebAuthn, userSession.Username, length)
|
|
||||||
|
|
||||||
respondUnauthorized(ctx, messageUnableToRegisterSecurityKey)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
devices, err := ctx.Providers.StorageProvider.LoadWebAuthnDevicesByUsername(ctx, w.Config.RPID, userSession.Username)
|
|
||||||
if err != nil && err != storage.ErrNoWebAuthnDevice {
|
|
||||||
ctx.Logger.Errorf("Unable to load existing %s devices for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
|
|
||||||
|
|
||||||
respondUnauthorized(ctx, messageUnableToRegisterSecurityKey)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, device := range devices {
|
|
||||||
if strings.EqualFold(device.Description, bodyJSON.Description) {
|
|
||||||
ctx.Logger.Errorf("Unable to generate %s registration challenge: device for for user '%s' with display name '%s' already exists", regulation.AuthTypeWebAuthn, userSession.Username, bodyJSON.Description)
|
|
||||||
|
|
||||||
ctx.SetStatusCode(fasthttp.StatusConflict)
|
|
||||||
ctx.SetJSONError(messageSecurityKeyDuplicateName)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if user, err = getWebAuthnUserByRPID(ctx, userSession.Username, userSession.DisplayName, w.Config.RPID); err != nil {
|
|
||||||
ctx.Logger.Errorf("Unable to load %s devices for registration challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
|
|
||||||
|
|
||||||
respondUnauthorized(ctx, messageUnableToRegisterSecurityKey)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
creation *protocol.CredentialCreation
|
|
||||||
)
|
|
||||||
|
|
||||||
opts := []webauthn.RegistrationOption{
|
|
||||||
webauthn.WithExclusions(user.WebAuthnCredentialDescriptors()),
|
|
||||||
webauthn.WithExtensions(map[string]any{"credProps": true}),
|
|
||||||
webauthn.WithResidentKeyRequirement(protocol.ResidentKeyRequirementDiscouraged),
|
|
||||||
}
|
|
||||||
|
|
||||||
data := session.WebAuthn{
|
|
||||||
Description: bodyJSON.Description,
|
|
||||||
}
|
|
||||||
|
|
||||||
if creation, data.SessionData, err = w.BeginRegistration(user, opts...); err != nil {
|
|
||||||
ctx.Logger.Errorf("Unable to create %s registration challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
|
|
||||||
|
|
||||||
respondUnauthorized(ctx, messageUnableToRegisterSecurityKey)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
userSession.WebAuthn = &data
|
|
||||||
|
|
||||||
if err = ctx.SaveSession(userSession); err != nil {
|
if err = ctx.SaveSession(userSession); err != nil {
|
||||||
ctx.Logger.Errorf(logFmtErrSessionSave, "registration challenge", regulation.AuthTypeWebAuthn, userSession.Username, err)
|
ctx.Logger.Errorf(logFmtErrSessionSave, "attestation challenge", regulation.AuthTypeWebAuthn, userSession.Username, err)
|
||||||
|
|
||||||
respondUnauthorized(ctx, messageUnableToRegisterSecurityKey)
|
respondUnauthorized(ctx, messageUnableToRegisterSecurityKey)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = ctx.SetJSONBody(creation); err != nil {
|
if err = ctx.SetJSONBody(credentialCreation); err != nil {
|
||||||
ctx.Logger.Errorf(logFmtErrWriteResponseBody, regulation.AuthTypeWebAuthn, userSession.Username, err)
|
ctx.Logger.Errorf(logFmtErrWriteResponseBody, regulation.AuthTypeWebAuthn, userSession.Username, err)
|
||||||
|
|
||||||
respondUnauthorized(ctx, messageUnableToRegisterSecurityKey)
|
respondUnauthorized(ctx, messageUnableToRegisterSecurityKey)
|
||||||
|
@ -127,8 +89,8 @@ func WebAuthnRegistrationPUT(ctx *middlewares.AutheliaCtx) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WebAuthnRegistrationPOST processes the attestation challenge response from the client.
|
// WebAuthnAttestationPOST processes the attestation challenge response from the client.
|
||||||
func WebAuthnRegistrationPOST(ctx *middlewares.AutheliaCtx) {
|
func WebAuthnAttestationPOST(ctx *middlewares.AutheliaCtx) {
|
||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
w *webauthn.WebAuthn
|
w *webauthn.WebAuthn
|
||||||
|
@ -136,89 +98,75 @@ func WebAuthnRegistrationPOST(ctx *middlewares.AutheliaCtx) {
|
||||||
|
|
||||||
userSession session.UserSession
|
userSession session.UserSession
|
||||||
|
|
||||||
response *protocol.ParsedCredentialCreationData
|
attestationResponse *protocol.ParsedCredentialCreationData
|
||||||
|
|
||||||
credential *webauthn.Credential
|
credential *webauthn.Credential
|
||||||
)
|
)
|
||||||
|
|
||||||
if userSession, err = ctx.GetSession(); err != nil {
|
if userSession, err = ctx.GetSession(); err != nil {
|
||||||
ctx.Logger.WithError(err).Errorf("Error occurred retrieving session for %s registration response", regulation.AuthTypeWebAuthn)
|
ctx.Logger.WithError(err).Errorf("Error occurred retrieving session for %s attestation response", regulation.AuthTypeWebAuthn)
|
||||||
|
|
||||||
respondUnauthorized(ctx, messageUnableToRegisterSecurityKey)
|
respondUnauthorized(ctx, messageUnableToRegisterSecurityKey)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if userSession.WebAuthn == nil || userSession.WebAuthn.SessionData == nil {
|
if userSession.WebAuthn == nil {
|
||||||
ctx.Logger.Errorf("WebAuthn session data is not present in order to handle %s registration for user '%s'. This could indicate a user trying to POST to the wrong endpoint, or the session data is not present for the browser they used.", regulation.AuthTypeWebAuthn, userSession.Username)
|
ctx.Logger.Errorf("WebAuthn session data is not present in order to handle attestation for user '%s'. This could indicate a user trying to POST to the wrong endpoint, or the session data is not present for the browser they used.", userSession.Username)
|
||||||
|
|
||||||
respondUnauthorized(ctx, messageUnableToRegisterSecurityKey)
|
respondUnauthorized(ctx, messageMFAValidationFailed)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if w, err = newWebAuthn(ctx); err != nil {
|
if w, err = newWebAuthn(ctx); err != nil {
|
||||||
ctx.Logger.Errorf("Unable to configure %s during registration for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
|
ctx.Logger.Errorf("Unable to configure %s during assertion challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
|
||||||
|
|
||||||
respondUnauthorized(ctx, messageUnableToRegisterSecurityKey)
|
respondUnauthorized(ctx, messageUnableToRegisterSecurityKey)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if response, err = protocol.ParseCredentialCreationResponseBody(bytes.NewReader(ctx.PostBody())); err != nil {
|
if attestationResponse, err = protocol.ParseCredentialCreationResponseBody(bytes.NewReader(ctx.PostBody())); err != nil {
|
||||||
switch e := err.(type) {
|
ctx.Logger.Errorf("Unable to parse %s assertionfor user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
|
||||||
case *protocol.Error:
|
|
||||||
ctx.Logger.Errorf("Unable to parse %s registration for user '%s': %+v (%s)", regulation.AuthTypeWebAuthn, userSession.Username, err, e.DevInfo)
|
|
||||||
default:
|
|
||||||
ctx.Logger.Errorf("Unable to parse %s registration for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
respondUnauthorized(ctx, messageUnableToRegisterSecurityKey)
|
respondUnauthorized(ctx, messageMFAValidationFailed)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if user, err = getWebAuthnUserByRPID(ctx, userSession.Username, userSession.DisplayName, w.Config.RPID); err != nil {
|
if user, err = getWebAuthnUser(ctx, userSession); err != nil {
|
||||||
ctx.Logger.Errorf("Unable to load %s user details for registration for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
|
ctx.Logger.Errorf("Unable to load %s devices for assertion challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
|
||||||
|
|
||||||
respondUnauthorized(ctx, messageUnableToRegisterSecurityKey)
|
respondUnauthorized(ctx, messageMFAValidationFailed)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if credential, err = w.CreateCredential(user, *userSession.WebAuthn.SessionData, response); err != nil {
|
if credential, err = w.CreateCredential(user, *userSession.WebAuthn, attestationResponse); err != nil {
|
||||||
switch e := err.(type) {
|
ctx.Logger.Errorf("Unable to load %s devices for assertion challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
|
||||||
case *protocol.Error:
|
|
||||||
ctx.Logger.Errorf("Unable to create %s credential for user '%s': %+v (%s)", regulation.AuthTypeWebAuthn, userSession.Username, err, e.DevInfo)
|
|
||||||
default:
|
|
||||||
ctx.Logger.Errorf("Unable to create %s credential for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
respondUnauthorized(ctx, messageUnableToRegisterSecurityKey)
|
respondUnauthorized(ctx, messageMFAValidationFailed)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
device := model.NewWebAuthnDeviceFromCredential(w.Config.RPID, userSession.Username, userSession.WebAuthn.Description, credential)
|
device := model.NewWebAuthnDeviceFromCredential(w.Config.RPID, userSession.Username, "Primary", credential)
|
||||||
|
|
||||||
device.Discoverable = webauthnCredentialCreationIsDiscoverable(ctx, response)
|
|
||||||
|
|
||||||
if err = ctx.Providers.StorageProvider.SaveWebAuthnDevice(ctx, device); err != nil {
|
if err = ctx.Providers.StorageProvider.SaveWebAuthnDevice(ctx, device); err != nil {
|
||||||
ctx.Logger.Errorf("Unable to save %s device registration for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
|
ctx.Logger.Errorf("Unable to load %s devices for assertion challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
|
||||||
|
|
||||||
respondUnauthorized(ctx, messageUnableToRegisterSecurityKey)
|
respondUnauthorized(ctx, messageMFAValidationFailed)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
userSession.WebAuthn = nil
|
userSession.WebAuthn = nil
|
||||||
|
|
||||||
if err = ctx.SaveSession(userSession); err != nil {
|
if err = ctx.SaveSession(userSession); err != nil {
|
||||||
ctx.Logger.Errorf(logFmtErrSessionSave, "removal of the registration challenge", regulation.AuthTypeWebAuthn, userSession.Username, err)
|
ctx.Logger.Errorf(logFmtErrSessionSave, "removal of the attestation challenge", regulation.AuthTypeWebAuthn, userSession.Username, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.ReplyOK()
|
ctx.ReplyOK()
|
||||||
ctx.SetStatusCode(fasthttp.StatusCreated)
|
ctx.SetStatusCode(fasthttp.StatusCreated)
|
||||||
|
|
||||||
ctxLogEvent(ctx, userSession.Username, "Second Factor Method Added", map[string]any{"Action": "Second Factor Method Added", "Category": "WebAuthn Credential", "Credential Description": device.Description})
|
ctxLogEvent(ctx, userSession.Username, "Second Factor Method Added", map[string]any{"Action": "Second Factor Method Added", "Category": "WebAuthn Credential", "Device Name": "Primary"})
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,50 +30,45 @@ func WebAuthnAssertionGET(ctx *middlewares.AutheliaCtx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if w, err = newWebAuthn(ctx); err != nil {
|
if w, err = newWebAuthn(ctx); err != nil {
|
||||||
ctx.Logger.Errorf("Unable to configure %s during authentication challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
|
ctx.Logger.Errorf("Unable to configure %s during assertion challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
|
||||||
|
|
||||||
respondUnauthorized(ctx, messageMFAValidationFailed)
|
respondUnauthorized(ctx, messageMFAValidationFailed)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if user, err = getWebAuthnUserByRPID(ctx, userSession.Username, userSession.DisplayName, w.Config.RPID); err != nil {
|
if user, err = getWebAuthnUser(ctx, userSession); err != nil {
|
||||||
ctx.Logger.Errorf("Unable to load %s user details during authentication challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
|
ctx.Logger.Errorf("Unable to create %s assertion challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
|
||||||
|
|
||||||
respondUnauthorized(ctx, messageMFAValidationFailed)
|
respondUnauthorized(ctx, messageMFAValidationFailed)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
extensions := map[string]any{}
|
|
||||||
|
|
||||||
if user.HasFIDOU2F() {
|
|
||||||
extensions["appid"] = w.Config.RPOrigins[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
var opts = []webauthn.LoginOption{
|
var opts = []webauthn.LoginOption{
|
||||||
webauthn.WithAllowedCredentials(user.WebAuthnCredentialDescriptors()),
|
webauthn.WithAllowedCredentials(user.WebAuthnCredentialDescriptors()),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extensions := map[string]any{}
|
||||||
|
|
||||||
|
if user.HasFIDOU2F() {
|
||||||
|
extensions["appid"] = w.Config.RPOrigin
|
||||||
|
}
|
||||||
|
|
||||||
if len(extensions) != 0 {
|
if len(extensions) != 0 {
|
||||||
opts = append(opts, webauthn.WithAssertionExtensions(extensions))
|
opts = append(opts, webauthn.WithAssertionExtensions(extensions))
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var assertion *protocol.CredentialAssertion
|
||||||
assertion *protocol.CredentialAssertion
|
|
||||||
data session.WebAuthn
|
|
||||||
)
|
|
||||||
|
|
||||||
if assertion, data.SessionData, err = w.BeginLogin(user, opts...); err != nil {
|
if assertion, userSession.WebAuthn, err = w.BeginLogin(user, opts...); err != nil {
|
||||||
ctx.Logger.Errorf("Unable to create %s authentication challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
|
ctx.Logger.Errorf("Unable to create %s assertion challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
|
||||||
|
|
||||||
respondUnauthorized(ctx, messageMFAValidationFailed)
|
respondUnauthorized(ctx, messageMFAValidationFailed)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
userSession.WebAuthn = &data
|
|
||||||
|
|
||||||
if err = ctx.SaveSession(userSession); err != nil {
|
if err = ctx.SaveSession(userSession); err != nil {
|
||||||
ctx.Logger.Errorf(logFmtErrSessionSave, "assertion challenge", regulation.AuthTypeWebAuthn, userSession.Username, err)
|
ctx.Logger.Errorf(logFmtErrSessionSave, "assertion challenge", regulation.AuthTypeWebAuthn, userSession.Username, err)
|
||||||
|
|
||||||
|
@ -120,8 +115,8 @@ func WebAuthnAssertionPOST(ctx *middlewares.AutheliaCtx) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if userSession.WebAuthn == nil || userSession.WebAuthn.SessionData == nil {
|
if userSession.WebAuthn == nil {
|
||||||
ctx.Logger.Errorf("WebAuthn session data is not present in order to handle authentication challenge for user '%s'. This could indicate a user trying to POST to the wrong endpoint, or the session data is not present for the browser they used.", userSession.Username)
|
ctx.Logger.Errorf("WebAuthn session data is not present in order to handle assertion for user '%s'. This could indicate a user trying to POST to the wrong endpoint, or the session data is not present for the browser they used.", userSession.Username)
|
||||||
|
|
||||||
respondUnauthorized(ctx, messageMFAValidationFailed)
|
respondUnauthorized(ctx, messageMFAValidationFailed)
|
||||||
|
|
||||||
|
@ -129,7 +124,7 @@ func WebAuthnAssertionPOST(ctx *middlewares.AutheliaCtx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if w, err = newWebAuthn(ctx); err != nil {
|
if w, err = newWebAuthn(ctx); err != nil {
|
||||||
ctx.Logger.Errorf("Unable to configure %s during authentication challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
|
ctx.Logger.Errorf("Unable to configure %s during assertion challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
|
||||||
|
|
||||||
respondUnauthorized(ctx, messageMFAValidationFailed)
|
respondUnauthorized(ctx, messageMFAValidationFailed)
|
||||||
|
|
||||||
|
@ -142,23 +137,23 @@ func WebAuthnAssertionPOST(ctx *middlewares.AutheliaCtx) {
|
||||||
user *model.WebAuthnUser
|
user *model.WebAuthnUser
|
||||||
)
|
)
|
||||||
|
|
||||||
if assertionResponse, err = protocol.ParseCredentialRequestResponseBody(bytes.NewReader(bodyJSON.Response)); err != nil {
|
if assertionResponse, err = protocol.ParseCredentialRequestResponseBody(bytes.NewReader(ctx.PostBody())); err != nil {
|
||||||
ctx.Logger.Errorf("Unable to parse %s authentication challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
|
ctx.Logger.Errorf("Unable to parse %s assertionfor user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
|
||||||
|
|
||||||
respondUnauthorized(ctx, messageMFAValidationFailed)
|
respondUnauthorized(ctx, messageMFAValidationFailed)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if user, err = getWebAuthnUserByRPID(ctx, userSession.Username, userSession.DisplayName, w.Config.RPID); err != nil {
|
if user, err = getWebAuthnUser(ctx, userSession); err != nil {
|
||||||
ctx.Logger.Errorf("Unable to load %s credentials for authentication challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
|
ctx.Logger.Errorf("Unable to load %s devices for assertion challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
|
||||||
|
|
||||||
respondUnauthorized(ctx, messageMFAValidationFailed)
|
respondUnauthorized(ctx, messageMFAValidationFailed)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if credential, err = w.ValidateLogin(user, *userSession.WebAuthn.SessionData, assertionResponse); err != nil {
|
if credential, err = w.ValidateLogin(user, *userSession.WebAuthn, assertionResponse); err != nil {
|
||||||
_ = markAuthenticationAttempt(ctx, false, nil, userSession.Username, regulation.AuthTypeWebAuthn, err)
|
_ = markAuthenticationAttempt(ctx, false, nil, userSession.Username, regulation.AuthTypeWebAuthn, err)
|
||||||
|
|
||||||
respondUnauthorized(ctx, messageMFAValidationFailed)
|
respondUnauthorized(ctx, messageMFAValidationFailed)
|
||||||
|
@ -174,8 +169,8 @@ func WebAuthnAssertionPOST(ctx *middlewares.AutheliaCtx) {
|
||||||
|
|
||||||
found = true
|
found = true
|
||||||
|
|
||||||
if err = ctx.Providers.StorageProvider.UpdateWebAuthnDeviceSignIn(ctx, device); err != nil {
|
if err = ctx.Providers.StorageProvider.UpdateWebAuthnDeviceSignIn(ctx, device.ID, device.RPID, device.LastUsedAt, device.SignCount, device.CloneWarning); err != nil {
|
||||||
ctx.Logger.Errorf("Unable to save %s device signin count for authentication challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
|
ctx.Logger.Errorf("Unable to save %s device signin count for assertion challenge for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
|
||||||
|
|
||||||
respondUnauthorized(ctx, messageMFAValidationFailed)
|
respondUnauthorized(ctx, messageMFAValidationFailed)
|
||||||
|
|
||||||
|
@ -187,7 +182,7 @@ func WebAuthnAssertionPOST(ctx *middlewares.AutheliaCtx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if !found {
|
if !found {
|
||||||
ctx.Logger.Errorf("Unable to save %s device signin count for authentication challenge for user '%s' device '%x' count '%d': unable to find device", regulation.AuthTypeWebAuthn, userSession.Username, credential.ID, credential.Authenticator.SignCount)
|
ctx.Logger.Errorf("Unable to save %s device signin count for assertion challenge for user '%s' device '%x' count '%d': unable to find device", regulation.AuthTypeWebAuthn, userSession.Username, credential.ID, credential.Authenticator.SignCount)
|
||||||
|
|
||||||
respondUnauthorized(ctx, messageMFAValidationFailed)
|
respondUnauthorized(ctx, messageMFAValidationFailed)
|
||||||
|
|
||||||
|
@ -209,11 +204,11 @@ func WebAuthnAssertionPOST(ctx *middlewares.AutheliaCtx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
userSession.SetTwoFactorWebAuthn(ctx.Clock.Now(),
|
userSession.SetTwoFactorWebAuthn(ctx.Clock.Now(),
|
||||||
assertionResponse.Response.AuthenticatorData.Flags.HasUserPresent(),
|
assertionResponse.Response.AuthenticatorData.Flags.UserPresent(),
|
||||||
assertionResponse.Response.AuthenticatorData.Flags.HasUserVerified())
|
assertionResponse.Response.AuthenticatorData.Flags.UserVerified())
|
||||||
|
|
||||||
if err = ctx.SaveSession(userSession); err != nil {
|
if err = ctx.SaveSession(userSession); err != nil {
|
||||||
ctx.Logger.Errorf(logFmtErrSessionSave, "removal of the authentiation challenge and authentication time", regulation.AuthTypeWebAuthn, userSession.Username, err)
|
ctx.Logger.Errorf(logFmtErrSessionSave, "removal of the assertion challenge and authentication time", regulation.AuthTypeWebAuthn, userSession.Username, err)
|
||||||
|
|
||||||
respondUnauthorized(ctx, messageMFAValidationFailed)
|
respondUnauthorized(ctx, messageMFAValidationFailed)
|
||||||
|
|
||||||
|
|
|
@ -1,160 +0,0 @@
|
||||||
package handlers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net/url"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/valyala/fasthttp"
|
|
||||||
|
|
||||||
"github.com/authelia/authelia/v4/internal/middlewares"
|
|
||||||
"github.com/authelia/authelia/v4/internal/model"
|
|
||||||
"github.com/authelia/authelia/v4/internal/regulation"
|
|
||||||
"github.com/authelia/authelia/v4/internal/session"
|
|
||||||
"github.com/authelia/authelia/v4/internal/storage"
|
|
||||||
)
|
|
||||||
|
|
||||||
func getWebAuthnDeviceIDFromContext(ctx *middlewares.AutheliaCtx) (int, error) {
|
|
||||||
deviceIDStr, ok := ctx.UserValue("deviceID").(string)
|
|
||||||
if !ok {
|
|
||||||
ctx.SetStatusCode(fasthttp.StatusBadRequest)
|
|
||||||
return 0, errors.New("Invalid device ID type")
|
|
||||||
}
|
|
||||||
|
|
||||||
deviceID, err := strconv.Atoi(deviceIDStr)
|
|
||||||
if err != nil {
|
|
||||||
ctx.Error(err, messageOperationFailed)
|
|
||||||
ctx.SetStatusCode(fasthttp.StatusBadRequest)
|
|
||||||
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return deviceID, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// WebAuthnDevicesGET returns all devices registered for the current user.
|
|
||||||
func WebAuthnDevicesGET(ctx *middlewares.AutheliaCtx) {
|
|
||||||
var (
|
|
||||||
userSession session.UserSession
|
|
||||||
origin *url.URL
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
|
|
||||||
if userSession, err = ctx.GetSession(); err != nil {
|
|
||||||
ctx.Logger.WithError(err).Error("Error occurred retrieving user session")
|
|
||||||
|
|
||||||
ctx.ReplyForbidden()
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if origin, err = ctx.GetOrigin(); err != nil {
|
|
||||||
ctx.Logger.WithError(err).Error("Error occurred retrieving origin")
|
|
||||||
|
|
||||||
ctx.ReplyForbidden()
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
devices, err := ctx.Providers.StorageProvider.LoadWebAuthnDevicesByUsername(ctx, origin.Hostname(), userSession.Username)
|
|
||||||
|
|
||||||
if err != nil && err != storage.ErrNoWebAuthnDevice {
|
|
||||||
ctx.Error(err, messageOperationFailed)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = ctx.SetJSONBody(devices); err != nil {
|
|
||||||
ctx.Error(err, messageOperationFailed)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WebAuthnDevicePUT updates the description for a specific device for the current user.
|
|
||||||
func WebAuthnDevicePUT(ctx *middlewares.AutheliaCtx) {
|
|
||||||
var (
|
|
||||||
bodyJSON bodyEditWebAuthnDeviceRequest
|
|
||||||
|
|
||||||
id int
|
|
||||||
device *model.WebAuthnDevice
|
|
||||||
userSession session.UserSession
|
|
||||||
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
|
|
||||||
if userSession, err = ctx.GetSession(); err != nil {
|
|
||||||
ctx.Logger.WithError(err).Error("Error occurred retrieving user session")
|
|
||||||
|
|
||||||
ctx.ReplyForbidden()
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = json.Unmarshal(ctx.PostBody(), &bodyJSON); err != nil {
|
|
||||||
ctx.Logger.Errorf("Unable to parse %s update request data for user '%s': %+v", regulation.AuthTypeWebAuthn, userSession.Username, err)
|
|
||||||
|
|
||||||
ctx.SetStatusCode(fasthttp.StatusBadRequest)
|
|
||||||
ctx.Error(err, messageOperationFailed)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if id, err = getWebAuthnDeviceIDFromContext(ctx); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if device, err = ctx.Providers.StorageProvider.LoadWebAuthnDeviceByID(ctx, id); err != nil {
|
|
||||||
ctx.Error(err, messageOperationFailed)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if device.Username != userSession.Username {
|
|
||||||
ctx.Error(fmt.Errorf("user '%s' tried to delete device with id '%d' which belongs to '%s", userSession.Username, device.ID, device.Username), messageOperationFailed)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = ctx.Providers.StorageProvider.UpdateWebAuthnDeviceDescription(ctx, userSession.Username, id, bodyJSON.Description); err != nil {
|
|
||||||
ctx.Error(err, messageOperationFailed)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WebAuthnDeviceDELETE deletes a specific device for the current user.
|
|
||||||
func WebAuthnDeviceDELETE(ctx *middlewares.AutheliaCtx) {
|
|
||||||
var (
|
|
||||||
id int
|
|
||||||
device *model.WebAuthnDevice
|
|
||||||
userSession session.UserSession
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
|
|
||||||
if id, err = getWebAuthnDeviceIDFromContext(ctx); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if device, err = ctx.Providers.StorageProvider.LoadWebAuthnDeviceByID(ctx, id); err != nil {
|
|
||||||
ctx.Error(err, messageOperationFailed)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if userSession, err = ctx.GetSession(); err != nil {
|
|
||||||
ctx.Logger.WithError(err).Error("Error occurred retrieving user session")
|
|
||||||
|
|
||||||
ctx.ReplyForbidden()
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if device.Username != userSession.Username {
|
|
||||||
ctx.Error(fmt.Errorf("user '%s' tried to delete device with id '%d' which belongs to '%s", userSession.Username, device.ID, device.Username), messageOperationFailed)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = ctx.Providers.StorageProvider.DeleteWebAuthnDevice(ctx, device.KID.String()); err != nil {
|
|
||||||
ctx.Error(err, messageOperationFailed)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.ReplyOK()
|
|
||||||
}
|
|
|
@ -277,13 +277,18 @@ func markAuthenticationAttempt(ctx *middlewares.AutheliaCtx, successful bool, ba
|
||||||
if successful {
|
if successful {
|
||||||
ctx.Logger.Debugf("Successful %s authentication attempt made by user '%s'", authType, username)
|
ctx.Logger.Debugf("Successful %s authentication attempt made by user '%s'", authType, username)
|
||||||
} else {
|
} else {
|
||||||
|
reasonPhrase := "by user '" + username + "'"
|
||||||
|
if ctx.Configuration.Server.UseIPInsteadOfUserForBan {
|
||||||
|
reasonPhrase = fmt.Sprintf("by ip %q (user %q)", ctx.RemoteIP().String(), username)
|
||||||
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case errAuth != nil:
|
case errAuth != nil:
|
||||||
ctx.Logger.Errorf("Unsuccessful %s authentication attempt by user '%s': %+v", authType, username, errAuth)
|
ctx.Logger.Errorf("Unsuccessful %s authentication attempt %s: %+v", authType, reasonPhrase, errAuth)
|
||||||
case bannedUntil != nil:
|
case bannedUntil != nil:
|
||||||
ctx.Logger.Errorf("Unsuccessful %s authentication attempt by user '%s' and they are banned until %s", authType, username, bannedUntil)
|
ctx.Logger.Errorf("Unsuccessful %s authentication attempt %s and they are banned until %s", authType, reasonPhrase, bannedUntil)
|
||||||
default:
|
default:
|
||||||
ctx.Logger.Errorf("Unsuccessful %s authentication attempt by user '%s'", authType, username)
|
ctx.Logger.Errorf("Unsuccessful %s authentication attempt %s", authType, reasonPhrase)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
|
@ -36,16 +35,6 @@ type bodySignWebAuthnRequest struct {
|
||||||
TargetURL string `json:"targetURL"`
|
TargetURL string `json:"targetURL"`
|
||||||
Workflow string `json:"workflow"`
|
Workflow string `json:"workflow"`
|
||||||
WorkflowID string `json:"workflowID"`
|
WorkflowID string `json:"workflowID"`
|
||||||
|
|
||||||
Response json.RawMessage `json:"response"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type bodyRegisterWebAuthnPUTRequest struct {
|
|
||||||
Description string `json:"description"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type bodyEditWebAuthnDeviceRequest struct {
|
|
||||||
Description string `json:"description"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// bodySignDuoRequest is the model of the request body of Duo 2FA authentication endpoint.
|
// bodySignDuoRequest is the model of the request body of Duo 2FA authentication endpoint.
|
||||||
|
|
|
@ -1,42 +1,28 @@
|
||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/go-webauthn/webauthn/protocol"
|
"github.com/go-webauthn/webauthn/protocol"
|
||||||
"github.com/go-webauthn/webauthn/webauthn"
|
"github.com/go-webauthn/webauthn/webauthn"
|
||||||
|
|
||||||
"github.com/authelia/authelia/v4/internal/middlewares"
|
"github.com/authelia/authelia/v4/internal/middlewares"
|
||||||
"github.com/authelia/authelia/v4/internal/model"
|
"github.com/authelia/authelia/v4/internal/model"
|
||||||
"github.com/authelia/authelia/v4/internal/random"
|
"github.com/authelia/authelia/v4/internal/session"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getWebAuthnUserByRPID(ctx *middlewares.AutheliaCtx, username, displayname string, rpid string) (user *model.WebAuthnUser, err error) {
|
func getWebAuthnUser(ctx *middlewares.AutheliaCtx, userSession session.UserSession) (user *model.WebAuthnUser, err error) {
|
||||||
if user, err = ctx.Providers.StorageProvider.LoadWebAuthnUser(ctx, rpid, username); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if user == nil {
|
|
||||||
user = &model.WebAuthnUser{
|
user = &model.WebAuthnUser{
|
||||||
RPID: rpid,
|
Username: userSession.Username,
|
||||||
Username: username,
|
DisplayName: userSession.DisplayName,
|
||||||
UserID: ctx.Providers.Random.StringCustom(64, random.CharSetASCII),
|
|
||||||
DisplayName: displayname,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = ctx.Providers.StorageProvider.SaveWebAuthnUser(ctx, *user); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
user.DisplayName = displayname
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if user.DisplayName == "" {
|
if user.DisplayName == "" {
|
||||||
user.DisplayName = user.Username
|
user.DisplayName = user.Username
|
||||||
}
|
}
|
||||||
|
|
||||||
if user.Devices, err = ctx.Providers.StorageProvider.LoadWebAuthnDevicesByUsername(ctx, rpid, user.Username); err != nil {
|
if user.Devices, err = ctx.Providers.StorageProvider.LoadWebAuthnDevicesByUsername(ctx, userSession.Username); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,72 +31,33 @@ func getWebAuthnUserByRPID(ctx *middlewares.AutheliaCtx, username, displayname s
|
||||||
|
|
||||||
func newWebAuthn(ctx *middlewares.AutheliaCtx) (w *webauthn.WebAuthn, err error) {
|
func newWebAuthn(ctx *middlewares.AutheliaCtx) (w *webauthn.WebAuthn, err error) {
|
||||||
var (
|
var (
|
||||||
origin *url.URL
|
u *url.URL
|
||||||
)
|
)
|
||||||
|
|
||||||
if origin, err = ctx.GetOrigin(); err != nil {
|
if u, err = ctx.GetXOriginalURLOrXForwardedURL(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rpID := u.Hostname()
|
||||||
|
origin := fmt.Sprintf("%s://%s", u.Scheme, u.Host)
|
||||||
|
|
||||||
config := &webauthn.Config{
|
config := &webauthn.Config{
|
||||||
RPID: origin.Hostname(),
|
|
||||||
RPDisplayName: ctx.Configuration.WebAuthn.DisplayName,
|
RPDisplayName: ctx.Configuration.WebAuthn.DisplayName,
|
||||||
RPOrigins: []string{origin.String()},
|
RPID: rpID,
|
||||||
|
RPOrigin: origin,
|
||||||
|
RPIcon: "",
|
||||||
|
|
||||||
AttestationPreference: ctx.Configuration.WebAuthn.ConveyancePreference,
|
AttestationPreference: ctx.Configuration.WebAuthn.ConveyancePreference,
|
||||||
AuthenticatorSelection: protocol.AuthenticatorSelection{
|
AuthenticatorSelection: protocol.AuthenticatorSelection{
|
||||||
AuthenticatorAttachment: protocol.CrossPlatform,
|
AuthenticatorAttachment: protocol.CrossPlatform,
|
||||||
RequireResidentKey: protocol.ResidentKeyNotRequired(),
|
|
||||||
ResidentKey: protocol.ResidentKeyRequirementDiscouraged,
|
|
||||||
UserVerification: ctx.Configuration.WebAuthn.UserVerification,
|
UserVerification: ctx.Configuration.WebAuthn.UserVerification,
|
||||||
|
RequireResidentKey: protocol.ResidentKeyNotRequired(),
|
||||||
},
|
},
|
||||||
Debug: false,
|
|
||||||
EncodeUserIDAsString: true,
|
Timeout: int(ctx.Configuration.WebAuthn.Timeout.Milliseconds()),
|
||||||
Timeouts: webauthn.TimeoutsConfig{
|
|
||||||
Login: webauthn.TimeoutConfig{
|
|
||||||
Enforce: true,
|
|
||||||
Timeout: ctx.Configuration.WebAuthn.Timeout,
|
|
||||||
TimeoutUVD: ctx.Configuration.WebAuthn.Timeout,
|
|
||||||
},
|
|
||||||
Registration: webauthn.TimeoutConfig{
|
|
||||||
Enforce: true,
|
|
||||||
Timeout: ctx.Configuration.WebAuthn.Timeout,
|
|
||||||
TimeoutUVD: ctx.Configuration.WebAuthn.Timeout,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Logger.Tracef("Creating new WebAuthn RP instance with ID %s and Origins %s", config.RPID, strings.Join(config.RPOrigins, ", "))
|
ctx.Logger.Tracef("Creating new WebAuthn RP instance with ID %s and Origins %s", config.RPID, config.RPOrigin)
|
||||||
|
|
||||||
return webauthn.New(config)
|
return webauthn.New(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
func webauthnCredentialCreationIsDiscoverable(ctx *middlewares.AutheliaCtx, response *protocol.ParsedCredentialCreationData) (discoverable bool) {
|
|
||||||
if value, ok := response.ClientExtensionResults["credProps"]; ok {
|
|
||||||
switch credentialProperties := value.(type) {
|
|
||||||
case map[string]any:
|
|
||||||
var v any
|
|
||||||
|
|
||||||
if v, ok = credentialProperties["rk"]; ok {
|
|
||||||
if discoverable, ok = v.(bool); ok {
|
|
||||||
ctx.Logger.WithFields(map[string]any{"discoverable": discoverable}).Trace("Determined Credential Discoverability via Client Extension Results")
|
|
||||||
|
|
||||||
return discoverable
|
|
||||||
} else {
|
|
||||||
ctx.Logger.WithFields(map[string]any{"discoverable": false}).Trace("Assuming Credential Discoverability is false as the 'rk' field for the 'credProps' extension in the Client Extension Results was not a boolean")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ctx.Logger.WithFields(map[string]any{"discoverable": false}).Trace("Assuming Credential Discoverability is false as the 'rk' field for the 'credProps' extension was missing from the Client Extension Results")
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
default:
|
|
||||||
ctx.Logger.WithFields(map[string]any{"discoverable": false}).Trace("Assuming Credential Discoverability is false as the 'credProps' extension in the Client Extension Results does not appear to be a dictionary")
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.Logger.WithFields(map[string]any{"discoverable": false}).Trace("Assuming Credential Discoverability is false as the 'credProps' extension is missing from the Client Extension Results")
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
|
@ -5,14 +5,12 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/go-webauthn/webauthn/protocol"
|
"github.com/go-webauthn/webauthn/protocol"
|
||||||
"github.com/golang/mock/gomock"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/valyala/fasthttp"
|
"github.com/valyala/fasthttp"
|
||||||
|
|
||||||
"github.com/authelia/authelia/v4/internal/mocks"
|
"github.com/authelia/authelia/v4/internal/mocks"
|
||||||
"github.com/authelia/authelia/v4/internal/model"
|
"github.com/authelia/authelia/v4/internal/model"
|
||||||
"github.com/authelia/authelia/v4/internal/random"
|
|
||||||
"github.com/authelia/authelia/v4/internal/session"
|
"github.com/authelia/authelia/v4/internal/session"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -24,14 +22,10 @@ func TestWebAuthnGetUser(t *testing.T) {
|
||||||
DisplayName: "John Smith",
|
DisplayName: "John Smith",
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.StorageMock.EXPECT().
|
ctx.StorageMock.EXPECT().LoadWebAuthnDevicesByUsername(ctx.Ctx, "john").Return([]model.WebAuthnDevice{
|
||||||
LoadWebAuthnUser(ctx.Ctx, "example.com", "john").
|
|
||||||
Return(&model.WebAuthnUser{ID: 1, RPID: "example.com", Username: "john", UserID: "john123"}, nil)
|
|
||||||
|
|
||||||
ctx.StorageMock.EXPECT().LoadWebAuthnDevicesByUsername(ctx.Ctx, "example.com", "john").Return([]model.WebAuthnDevice{
|
|
||||||
{
|
{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
RPID: "example.com",
|
RPID: "https://example.com",
|
||||||
Username: "john",
|
Username: "john",
|
||||||
Description: "Primary",
|
Description: "Primary",
|
||||||
KID: model.NewBase64([]byte("abc123")),
|
KID: model.NewBase64([]byte("abc123")),
|
||||||
|
@ -54,12 +48,12 @@ func TestWebAuthnGetUser(t *testing.T) {
|
||||||
},
|
},
|
||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
user, err := getWebAuthnUserByRPID(ctx.Ctx, userSession.Username, userSession.DisplayName, "example.com")
|
user, err := getWebAuthnUser(ctx.Ctx, userSession)
|
||||||
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotNil(t, user)
|
require.NotNil(t, user)
|
||||||
|
|
||||||
assert.Equal(t, []byte("john123"), user.WebAuthnID())
|
assert.Equal(t, []byte{}, user.WebAuthnID())
|
||||||
assert.Equal(t, "john", user.WebAuthnName())
|
assert.Equal(t, "john", user.WebAuthnName())
|
||||||
assert.Equal(t, "john", user.Username)
|
assert.Equal(t, "john", user.Username)
|
||||||
|
|
||||||
|
@ -71,107 +65,7 @@ func TestWebAuthnGetUser(t *testing.T) {
|
||||||
require.Len(t, user.Devices, 2)
|
require.Len(t, user.Devices, 2)
|
||||||
|
|
||||||
assert.Equal(t, 1, user.Devices[0].ID)
|
assert.Equal(t, 1, user.Devices[0].ID)
|
||||||
assert.Equal(t, "example.com", user.Devices[0].RPID)
|
assert.Equal(t, "https://example.com", user.Devices[0].RPID)
|
||||||
assert.Equal(t, "john", user.Devices[0].Username)
|
|
||||||
assert.Equal(t, "Primary", user.Devices[0].Description)
|
|
||||||
assert.Equal(t, "", user.Devices[0].Transport)
|
|
||||||
assert.Equal(t, "fido-u2f", user.Devices[0].AttestationType)
|
|
||||||
assert.Equal(t, []byte("data"), user.Devices[0].PublicKey)
|
|
||||||
assert.Equal(t, uint32(0), user.Devices[0].SignCount)
|
|
||||||
assert.False(t, user.Devices[0].CloneWarning)
|
|
||||||
|
|
||||||
descriptors := user.WebAuthnCredentialDescriptors()
|
|
||||||
assert.Equal(t, "fido-u2f", descriptors[0].AttestationType)
|
|
||||||
assert.Equal(t, "abc123", string(descriptors[0].CredentialID))
|
|
||||||
assert.Equal(t, protocol.PublicKeyCredentialType, descriptors[0].Type)
|
|
||||||
|
|
||||||
assert.Len(t, descriptors[0].Transport, 0)
|
|
||||||
|
|
||||||
assert.Equal(t, 2, user.Devices[1].ID)
|
|
||||||
assert.Equal(t, "example.com", user.Devices[1].RPID)
|
|
||||||
assert.Equal(t, "john", user.Devices[1].Username)
|
|
||||||
assert.Equal(t, "Secondary", user.Devices[1].Description)
|
|
||||||
assert.Equal(t, "usb,nfc", user.Devices[1].Transport)
|
|
||||||
assert.Equal(t, "packed", user.Devices[1].AttestationType)
|
|
||||||
assert.Equal(t, []byte("data"), user.Devices[1].PublicKey)
|
|
||||||
assert.Equal(t, uint32(100), user.Devices[1].SignCount)
|
|
||||||
assert.False(t, user.Devices[1].CloneWarning)
|
|
||||||
|
|
||||||
assert.Equal(t, "packed", descriptors[1].AttestationType)
|
|
||||||
assert.Equal(t, "123abc", string(descriptors[1].CredentialID))
|
|
||||||
assert.Equal(t, protocol.PublicKeyCredentialType, descriptors[1].Type)
|
|
||||||
|
|
||||||
assert.Len(t, descriptors[1].Transport, 2)
|
|
||||||
assert.Equal(t, protocol.AuthenticatorTransport("usb"), descriptors[1].Transport[0])
|
|
||||||
assert.Equal(t, protocol.AuthenticatorTransport("nfc"), descriptors[1].Transport[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWebAuthnGetNewUser(t *testing.T) {
|
|
||||||
ctx := mocks.NewMockAutheliaCtx(t)
|
|
||||||
|
|
||||||
// Use the random mock.
|
|
||||||
ctx.Ctx.Providers.Random = ctx.RandomMock
|
|
||||||
|
|
||||||
userSession := session.UserSession{
|
|
||||||
Username: "john",
|
|
||||||
DisplayName: "John Smith",
|
|
||||||
}
|
|
||||||
|
|
||||||
gomock.InOrder(
|
|
||||||
ctx.StorageMock.EXPECT().
|
|
||||||
LoadWebAuthnUser(ctx.Ctx, "example.com", "john").
|
|
||||||
Return(nil, nil),
|
|
||||||
ctx.RandomMock.EXPECT().
|
|
||||||
StringCustom(64, random.CharSetASCII).
|
|
||||||
Return("=ckBRe.%fp{w#K[qw4)AWMZrAP)(z3NUt5n3g?;>'^Rp>+eE4z>[^.<3?&n;LM#w"),
|
|
||||||
ctx.StorageMock.EXPECT().
|
|
||||||
SaveWebAuthnUser(ctx.Ctx, model.WebAuthnUser{RPID: "example.com", Username: "john", DisplayName: "John Smith", UserID: "=ckBRe.%fp{w#K[qw4)AWMZrAP)(z3NUt5n3g?;>'^Rp>+eE4z>[^.<3?&n;LM#w"}).
|
|
||||||
Return(nil),
|
|
||||||
ctx.StorageMock.EXPECT().LoadWebAuthnDevicesByUsername(ctx.Ctx, "example.com", "john").Return([]model.WebAuthnDevice{
|
|
||||||
{
|
|
||||||
ID: 1,
|
|
||||||
RPID: "example.com",
|
|
||||||
Username: "john",
|
|
||||||
Description: "Primary",
|
|
||||||
KID: model.NewBase64([]byte("abc123")),
|
|
||||||
AttestationType: "fido-u2f",
|
|
||||||
PublicKey: []byte("data"),
|
|
||||||
SignCount: 0,
|
|
||||||
CloneWarning: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: 2,
|
|
||||||
RPID: "example.com",
|
|
||||||
Username: "john",
|
|
||||||
Description: "Secondary",
|
|
||||||
KID: model.NewBase64([]byte("123abc")),
|
|
||||||
AttestationType: "packed",
|
|
||||||
Transport: "usb,nfc",
|
|
||||||
PublicKey: []byte("data"),
|
|
||||||
SignCount: 100,
|
|
||||||
CloneWarning: false,
|
|
||||||
},
|
|
||||||
}, nil),
|
|
||||||
)
|
|
||||||
|
|
||||||
user, err := getWebAuthnUserByRPID(ctx.Ctx, userSession.Username, userSession.DisplayName, "example.com")
|
|
||||||
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotNil(t, user)
|
|
||||||
|
|
||||||
assert.Equal(t, []byte("=ckBRe.%fp{w#K[qw4)AWMZrAP)(z3NUt5n3g?;>'^Rp>+eE4z>[^.<3?&n;LM#w"), user.WebAuthnID())
|
|
||||||
assert.Equal(t, "john", user.WebAuthnName())
|
|
||||||
assert.Equal(t, "john", user.Username)
|
|
||||||
|
|
||||||
assert.Equal(t, "", user.WebAuthnIcon())
|
|
||||||
|
|
||||||
assert.Equal(t, "John Smith", user.WebAuthnDisplayName())
|
|
||||||
assert.Equal(t, "John Smith", user.DisplayName)
|
|
||||||
|
|
||||||
require.Len(t, user.Devices, 2)
|
|
||||||
|
|
||||||
assert.Equal(t, 1, user.Devices[0].ID)
|
|
||||||
assert.Equal(t, "example.com", user.Devices[0].RPID)
|
|
||||||
assert.Equal(t, "john", user.Devices[0].Username)
|
assert.Equal(t, "john", user.Devices[0].Username)
|
||||||
assert.Equal(t, "Primary", user.Devices[0].Description)
|
assert.Equal(t, "Primary", user.Devices[0].Description)
|
||||||
assert.Equal(t, "", user.Devices[0].Transport)
|
assert.Equal(t, "", user.Devices[0].Transport)
|
||||||
|
@ -213,11 +107,7 @@ func TestWebAuthnGetUserWithoutDisplayName(t *testing.T) {
|
||||||
Username: "john",
|
Username: "john",
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.StorageMock.EXPECT().
|
ctx.StorageMock.EXPECT().LoadWebAuthnDevicesByUsername(ctx.Ctx, "john").Return([]model.WebAuthnDevice{
|
||||||
LoadWebAuthnUser(ctx.Ctx, "example.com", "john").
|
|
||||||
Return(&model.WebAuthnUser{ID: 1, RPID: "example.com", Username: "john", UserID: "john123"}, nil)
|
|
||||||
|
|
||||||
ctx.StorageMock.EXPECT().LoadWebAuthnDevicesByUsername(ctx.Ctx, "example.com", "john").Return([]model.WebAuthnDevice{
|
|
||||||
{
|
{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
RPID: "example.com",
|
RPID: "example.com",
|
||||||
|
@ -231,7 +121,7 @@ func TestWebAuthnGetUserWithoutDisplayName(t *testing.T) {
|
||||||
},
|
},
|
||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
user, err := getWebAuthnUserByRPID(ctx.Ctx, userSession.Username, userSession.DisplayName, "example.com")
|
user, err := getWebAuthnUser(ctx.Ctx, userSession)
|
||||||
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotNil(t, user)
|
require.NotNil(t, user)
|
||||||
|
@ -247,15 +137,9 @@ func TestWebAuthnGetUserWithErr(t *testing.T) {
|
||||||
Username: "john",
|
Username: "john",
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.StorageMock.EXPECT().
|
ctx.StorageMock.EXPECT().LoadWebAuthnDevicesByUsername(ctx.Ctx, "john").Return(nil, errors.New("not found"))
|
||||||
LoadWebAuthnUser(ctx.Ctx, "example.com", "john").
|
|
||||||
Return(&model.WebAuthnUser{ID: 1, RPID: "example.com", Username: "john", UserID: "john123"}, nil)
|
|
||||||
|
|
||||||
ctx.StorageMock.EXPECT().
|
user, err := getWebAuthnUser(ctx.Ctx, userSession)
|
||||||
LoadWebAuthnDevicesByUsername(ctx.Ctx, "example.com", "john").
|
|
||||||
Return(nil, errors.New("not found"))
|
|
||||||
|
|
||||||
user, err := getWebAuthnUserByRPID(ctx.Ctx, userSession.Username, userSession.DisplayName, "example.com")
|
|
||||||
|
|
||||||
assert.EqualError(t, err, "not found")
|
assert.EqualError(t, err, "not found")
|
||||||
assert.Nil(t, user)
|
assert.Nil(t, user)
|
||||||
|
@ -281,5 +165,5 @@ func TestWebAuthnNewWebAuthnShouldReturnErrWhenWebAuthnNotConfigured(t *testing.
|
||||||
w, err := newWebAuthn(ctx.Ctx)
|
w, err := newWebAuthn(ctx.Ctx)
|
||||||
|
|
||||||
assert.Nil(t, w)
|
assert.Nil(t, w)
|
||||||
assert.EqualError(t, err, "error occurred validating the configuration: the field 'RPDisplayName' must be configured but it is empty")
|
assert.EqualError(t, err, "Configuration error: Missing RPDisplayName")
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,19 +69,8 @@ func (ctx *AutheliaCtx) Error(err error, message string) {
|
||||||
|
|
||||||
// SetJSONError sets the body of the response to an JSON error KO message.
|
// SetJSONError sets the body of the response to an JSON error KO message.
|
||||||
func (ctx *AutheliaCtx) SetJSONError(message string) {
|
func (ctx *AutheliaCtx) SetJSONError(message string) {
|
||||||
if err := ctx.ReplyJSON(ErrorResponse{Status: "KO", Message: message}, 0); err != nil {
|
if replyErr := ctx.ReplyJSON(ErrorResponse{Status: "KO", Message: message}, 0); replyErr != nil {
|
||||||
ctx.Logger.Error(err)
|
ctx.Logger.Error(replyErr)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetAuthenticationErrorJSON sets the body of the response to an JSON error KO message.
|
|
||||||
func (ctx *AutheliaCtx) SetAuthenticationErrorJSON(status int, message string, authentication, elevation bool) {
|
|
||||||
if status > fasthttp.StatusOK {
|
|
||||||
ctx.SetStatusCode(status)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := ctx.ReplyJSON(AuthenticationErrorResponse{Status: "KO", Message: message, Authentication: authentication, Elevation: elevation}, 0); err != nil {
|
|
||||||
ctx.Logger.Error(err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -528,18 +517,6 @@ func (ctx *AutheliaCtx) GetXOriginalURLOrXForwardedURL() (requestURI *url.URL, e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetOrigin returns the expected origin for requests from this endpoint.
|
|
||||||
func (ctx *AutheliaCtx) GetOrigin() (origin *url.URL, err error) {
|
|
||||||
if origin, err = ctx.GetXOriginalURLOrXForwardedURL(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
origin.Path = ""
|
|
||||||
origin.RawPath = ""
|
|
||||||
|
|
||||||
return origin, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// IssuerURL returns the expected Issuer.
|
// IssuerURL returns the expected Issuer.
|
||||||
func (ctx *AutheliaCtx) IssuerURL() (issuerURL *url.URL, err error) {
|
func (ctx *AutheliaCtx) IssuerURL() (issuerURL *url.URL, err error) {
|
||||||
issuerURL = &url.URL{
|
issuerURL = &url.URL{
|
||||||
|
|
|
@ -95,22 +95,26 @@ func IdentityVerificationStart(args IdentityVerificationStartArgs, delayFunc Tim
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func identityVerificationValidateToken(ctx *AutheliaCtx) (*jwt.Token, error) {
|
// IdentityVerificationFinish the middleware for finishing the identity validation process.
|
||||||
var bodyJSON IdentityVerificationFinishBody
|
func IdentityVerificationFinish(args IdentityVerificationFinishArgs, next func(ctx *AutheliaCtx, username string)) RequestHandler {
|
||||||
|
return func(ctx *AutheliaCtx) {
|
||||||
|
var finishBody IdentityVerificationFinishBody
|
||||||
|
|
||||||
err := json.Unmarshal(ctx.PostBody(), &bodyJSON)
|
b := ctx.PostBody()
|
||||||
|
|
||||||
|
err := json.Unmarshal(b, &finishBody)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Error(err, messageOperationFailed)
|
ctx.Error(err, messageOperationFailed)
|
||||||
return nil, err
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if bodyJSON.Token == "" {
|
if finishBody.Token == "" {
|
||||||
ctx.Error(fmt.Errorf("No token provided"), messageOperationFailed)
|
ctx.Error(fmt.Errorf("No token provided"), messageOperationFailed)
|
||||||
return nil, err
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
token, err := jwt.ParseWithClaims(bodyJSON.Token, &model.IdentityVerificationClaim{},
|
token, err := jwt.ParseWithClaims(finishBody.Token, &model.IdentityVerificationClaim{},
|
||||||
func(token *jwt.Token) (any, error) {
|
func(token *jwt.Token) (any, error) {
|
||||||
return []byte(ctx.Configuration.JWTSecret), nil
|
return []byte(ctx.Configuration.JWTSecret), nil
|
||||||
})
|
})
|
||||||
|
@ -120,30 +124,19 @@ func identityVerificationValidateToken(ctx *AutheliaCtx) (*jwt.Token, error) {
|
||||||
switch {
|
switch {
|
||||||
case ve.Errors&jwt.ValidationErrorMalformed != 0:
|
case ve.Errors&jwt.ValidationErrorMalformed != 0:
|
||||||
ctx.Error(fmt.Errorf("Cannot parse token"), messageOperationFailed)
|
ctx.Error(fmt.Errorf("Cannot parse token"), messageOperationFailed)
|
||||||
return nil, err
|
return
|
||||||
case ve.Errors&(jwt.ValidationErrorExpired|jwt.ValidationErrorNotValidYet) != 0:
|
case ve.Errors&(jwt.ValidationErrorExpired|jwt.ValidationErrorNotValidYet) != 0:
|
||||||
// Token is either expired or not active yet.
|
// Token is either expired or not active yet.
|
||||||
ctx.Error(fmt.Errorf("Token expired"), messageIdentityVerificationTokenHasExpired)
|
ctx.Error(fmt.Errorf("Token expired"), messageIdentityVerificationTokenHasExpired)
|
||||||
return nil, err
|
return
|
||||||
default:
|
default:
|
||||||
ctx.Error(fmt.Errorf("Cannot handle this token: %s", ve), messageOperationFailed)
|
ctx.Error(fmt.Errorf("Cannot handle this token: %s", ve), messageOperationFailed)
|
||||||
return nil, err
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Error(err, messageOperationFailed)
|
ctx.Error(err, messageOperationFailed)
|
||||||
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return token, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// IdentityVerificationFinish the middleware for finishing the identity validation process.
|
|
||||||
func IdentityVerificationFinish(args IdentityVerificationFinishArgs, next func(ctx *AutheliaCtx, username string)) RequestHandler {
|
|
||||||
return func(ctx *AutheliaCtx) {
|
|
||||||
token, err := identityVerificationValidateToken(ctx)
|
|
||||||
if token == nil || err != nil {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -183,7 +176,8 @@ func IdentityVerificationFinish(args IdentityVerificationFinishArgs, next func(c
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = ctx.Providers.StorageProvider.ConsumeIdentityVerification(ctx, claims.ID, model.NewNullIP(ctx.RemoteIP())); err != nil {
|
err = ctx.Providers.StorageProvider.ConsumeIdentityVerification(ctx, claims.ID, model.NewNullIP(ctx.RemoteIP()))
|
||||||
|
if err != nil {
|
||||||
ctx.Error(err, messageOperationFailed)
|
ctx.Error(err, messageOperationFailed)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,43 +0,0 @@
|
||||||
package middlewares
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/valyala/fasthttp"
|
|
||||||
|
|
||||||
"github.com/authelia/authelia/v4/internal/authentication"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Require1FA requires the user to have authenticated with at least one-factor authentication (i.e. password).
|
|
||||||
func Require1FA(next RequestHandler) RequestHandler {
|
|
||||||
return func(ctx *AutheliaCtx) {
|
|
||||||
if session, err := ctx.GetSession(); err != nil || session.AuthenticationLevel < authentication.OneFactor {
|
|
||||||
ctx.ReplyForbidden()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
next(ctx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Require2FA requires the user to have authenticated with two-factor authentication.
|
|
||||||
func Require2FA(next RequestHandler) RequestHandler {
|
|
||||||
return func(ctx *AutheliaCtx) {
|
|
||||||
if session, err := ctx.GetSession(); err != nil || session.AuthenticationLevel < authentication.TwoFactor {
|
|
||||||
ctx.ReplyForbidden()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
next(ctx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Require2FAWithAPIResponse requires the user to have authenticated with two-factor authentication.
|
|
||||||
func Require2FAWithAPIResponse(next RequestHandler) RequestHandler {
|
|
||||||
return func(ctx *AutheliaCtx) {
|
|
||||||
if session, err := ctx.GetSession(); err != nil || session.AuthenticationLevel < authentication.TwoFactor {
|
|
||||||
ctx.SetAuthenticationErrorJSON(fasthttp.StatusForbidden, "Authentication Required.", true, false)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
next(ctx)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
package middlewares
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/authelia/authelia/v4/internal/authentication"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Require1FA check if user has enough permissions to execute the next handler.
|
||||||
|
func Require1FA(next RequestHandler) RequestHandler {
|
||||||
|
return func(ctx *AutheliaCtx) {
|
||||||
|
if s, err := ctx.GetSession(); err != nil || s.AuthenticationLevel < authentication.OneFactor {
|
||||||
|
ctx.ReplyForbidden()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
next(ctx)
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue