From 4ccc0a35ab45b2207fc414147f2ccba48bc58686 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Sat, 23 Oct 2021 14:16:50 +0100 Subject: [PATCH] fix(auth): add missing test coverage --- CHANGELOG.md | 4 + cmd/karma/auth.go | 36 ++-- cmd/karma/main.go | 8 +- .../testscript/059_log_full_config_env.txt | 2 +- .../testscript/060_log_full_config_file.txt | 10 +- .../testscript/065_proxy-with-readonly.txt | 2 +- cmd/karma/tests/testscript/066_proxy.txt | 2 +- cmd/karma/tests/testscript/067_readonly.txt | 2 +- cmd/karma/tests/testscript/068_sentry.txt | 2 +- .../tests/testscript/070_upper_case_keys.txt | 2 +- .../tests/testscript/097_proxy_url_config.txt | 2 +- .../100_auth_header_groups_no_name.txt | 14 ++ .../101_auth_header_groups_invalid_regex.txt | 15 ++ .../102_auth_header_groups_no_regex.txt | 14 ++ cmd/karma/views.go | 3 + cmd/karma/views_test.go | 169 +++++++++++++++++- docs/CONFIGURATION.md | 22 ++- internal/config/config.go | 15 ++ internal/models/api.go | 5 +- 19 files changed, 299 insertions(+), 30 deletions(-) create mode 100644 cmd/karma/tests/testscript/100_auth_header_groups_no_name.txt create mode 100644 cmd/karma/tests/testscript/101_auth_header_groups_invalid_regex.txt create mode 100644 cmd/karma/tests/testscript/102_auth_header_groups_no_regex.txt diff --git a/CHANGELOG.md b/CHANGELOG.md index f7941876c..a7832339c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ - Toast popup messages didn't respond to clicks. +### Added + +- Loading user groups from HTTP headers, #3361 (@supertassu). + ## v0.92 ### Fixed diff --git a/cmd/karma/auth.go b/cmd/karma/auth.go index f14f2e1cf..721f1c960 100644 --- a/cmd/karma/auth.go +++ b/cmd/karma/auth.go @@ -22,6 +22,20 @@ func userGroups(username string) []string { return groups } +func groupsFromHeaders(r *http.Request, groupName, groupValueRegex, groupValueSeparator string) []string { + groups := []string{} + groupRegex := regex.MustCompileAnchored(groupValueRegex) + rawGroups := groupRegex.FindAllStringSubmatch(r.Header.Get(groupName), 1) + if len(rawGroups) > 0 && len(rawGroups[0]) > 1 { + for _, group := range strings.Split(rawGroups[0][1], groupValueSeparator) { + if v := strings.TrimSpace(group); v != "" { + groups = append(groups, v) + } + } + } + return groups +} + func headerAuth(name, valueRegex, groupName, groupValueRegex, groupValueSeparator string, allowBypass []string) func(next http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -49,18 +63,7 @@ func headerAuth(name, valueRegex, groupName, groupValueRegex, groupValueSeparato groups := userGroups(userName) if groupName != "" { - rawGroups := []string{r.Header.Get(groupName)} - if groupValueSeparator != "" { - rawGroups = strings.Split(rawGroups[0], groupValueSeparator) - } - - groupRegex := regex.MustCompileAnchored(groupValueRegex) - for _, group := range rawGroups { - groupMatches := groupRegex.FindAllStringSubmatch(group, 1) - if len(groupMatches) != 0 && len(groupMatches[0]) > 1 { - groups = append(groups, groupMatches[0][1]) - } - } + groups = append(groups, groupsFromHeaders(r, groupName, groupValueRegex, groupValueSeparator)...) } ctx := context.WithValue(r.Context(), authUserKey("user"), userName) @@ -86,7 +89,7 @@ func getGroupsFromContext(r *http.Request) []string { return groups.([]string) } -func basicAuth(creds map[string]string, allowBypass []string) func(next http.Handler) http.Handler { +func basicAuth(creds map[string]string, groupName, groupValueRegex, groupValueSeparator string, allowBypass []string) func(next http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if slices.StringInSlice(allowBypass, r.URL.Path) { @@ -106,8 +109,13 @@ func basicAuth(creds map[string]string, allowBypass []string) func(next http.Han return } + groups := userGroups(user) + if groupName != "" { + groups = append(groups, groupsFromHeaders(r, groupName, groupValueRegex, groupValueSeparator)...) + } + ctx := context.WithValue(r.Context(), authUserKey("user"), user) - ctx = context.WithValue(ctx, authUserKey("groups"), userGroups(user)) + ctx = context.WithValue(ctx, authUserKey("groups"), groups) next.ServeHTTP(w, r.WithContext(ctx)) }) } diff --git a/cmd/karma/main.go b/cmd/karma/main.go index b052d03dc..40bd158b1 100644 --- a/cmd/karma/main.go +++ b/cmd/karma/main.go @@ -132,7 +132,13 @@ func setupRouter(router *chi.Mux, historyPoller *historyPoller) { for _, u := range config.Config.Authentication.BasicAuth.Users { users[u.Username] = u.Password } - router.Use(basicAuth(users, allowAuthBypass)) + router.Use(basicAuth( + users, + config.Config.Authentication.Header.GroupName, + config.Config.Authentication.Header.GroupValueRegex, + config.Config.Authentication.Header.GroupValueSeparator, + allowAuthBypass, + )) } if config.Config.Listen.Prefix != "/" { diff --git a/cmd/karma/tests/testscript/059_log_full_config_env.txt b/cmd/karma/tests/testscript/059_log_full_config_env.txt index 87e1e0665..8eeaacbb0 100644 --- a/cmd/karma/tests/testscript/059_log_full_config_env.txt +++ b/cmd/karma/tests/testscript/059_log_full_config_env.txt @@ -85,7 +85,7 @@ level=info msg=" name: X-Auth" level=info msg=" value_re: ^(.+)$" level=info msg=" group_name: \"\"" level=info msg=" group_value_re: \"\"" -level=info msg=" group_value_separator: \"\"" +level=info msg=" group_value_separator: ' '" level=info msg=" basicAuth:" level=info msg=" users: []" level=info msg="authorization:" diff --git a/cmd/karma/tests/testscript/060_log_full_config_file.txt b/cmd/karma/tests/testscript/060_log_full_config_file.txt index 89aeecaa6..70bcc602e 100644 --- a/cmd/karma/tests/testscript/060_log_full_config_file.txt +++ b/cmd/karma/tests/testscript/060_log_full_config_file.txt @@ -11,9 +11,9 @@ level=info msg="authentication:" level=info msg=" header:" level=info msg=" name: \"\"" level=info msg=" value_re: \"\"" -level=info msg=" group_name: \"\"" -level=info msg=" group_value_re: \"\"" -level=info msg=" group_value_separator: \"\"" +level=info msg=" group_name: X-Groups" +level=info msg=" group_value_re: .+" +level=info msg=" group_value_separator: ','" level=info msg=" basicAuth:" level=info msg=" users:" level=info msg=" - username: number" @@ -267,6 +267,10 @@ level=info msg="Setting up proxy endpoints" alertmanager=local level=info msg="Configuration is valid" -- custom.yaml -- authentication: + header: + group_name: X-Groups + group_value_re: .+ + group_value_separator: ',' basicAuth: users: - username: number diff --git a/cmd/karma/tests/testscript/065_proxy-with-readonly.txt b/cmd/karma/tests/testscript/065_proxy-with-readonly.txt index af9e1e868..81984b7a0 100644 --- a/cmd/karma/tests/testscript/065_proxy-with-readonly.txt +++ b/cmd/karma/tests/testscript/065_proxy-with-readonly.txt @@ -13,7 +13,7 @@ level=info msg=" name: \"\"" level=info msg=" value_re: \"\"" level=info msg=" group_name: \"\"" level=info msg=" group_value_re: \"\"" -level=info msg=" group_value_separator: \"\"" +level=info msg=" group_value_separator: ' '" level=info msg=" basicAuth:" level=info msg=" users: []" level=info msg="authorization:" diff --git a/cmd/karma/tests/testscript/066_proxy.txt b/cmd/karma/tests/testscript/066_proxy.txt index 302199b40..1031e5112 100644 --- a/cmd/karma/tests/testscript/066_proxy.txt +++ b/cmd/karma/tests/testscript/066_proxy.txt @@ -13,7 +13,7 @@ level=info msg=" name: \"\"" level=info msg=" value_re: \"\"" level=info msg=" group_name: \"\"" level=info msg=" group_value_re: \"\"" -level=info msg=" group_value_separator: \"\"" +level=info msg=" group_value_separator: ' '" level=info msg=" basicAuth:" level=info msg=" users: []" level=info msg="authorization:" diff --git a/cmd/karma/tests/testscript/067_readonly.txt b/cmd/karma/tests/testscript/067_readonly.txt index 912db3c22..12a9b1124 100644 --- a/cmd/karma/tests/testscript/067_readonly.txt +++ b/cmd/karma/tests/testscript/067_readonly.txt @@ -13,7 +13,7 @@ level=info msg=" name: \"\"" level=info msg=" value_re: \"\"" level=info msg=" group_name: \"\"" level=info msg=" group_value_re: \"\"" -level=info msg=" group_value_separator: \"\"" +level=info msg=" group_value_separator: ' '" level=info msg=" basicAuth:" level=info msg=" users: []" level=info msg="authorization:" diff --git a/cmd/karma/tests/testscript/068_sentry.txt b/cmd/karma/tests/testscript/068_sentry.txt index ac6a3848e..40821848e 100644 --- a/cmd/karma/tests/testscript/068_sentry.txt +++ b/cmd/karma/tests/testscript/068_sentry.txt @@ -15,7 +15,7 @@ level=info msg=" name: \"\"" level=info msg=" value_re: \"\"" level=info msg=" group_name: \"\"" level=info msg=" group_value_re: \"\"" -level=info msg=" group_value_separator: \"\"" +level=info msg=" group_value_separator: ' '" level=info msg=" basicAuth:" level=info msg=" users: []" level=info msg="authorization:" diff --git a/cmd/karma/tests/testscript/070_upper_case_keys.txt b/cmd/karma/tests/testscript/070_upper_case_keys.txt index 30a4fb48b..892f1f62e 100644 --- a/cmd/karma/tests/testscript/070_upper_case_keys.txt +++ b/cmd/karma/tests/testscript/070_upper_case_keys.txt @@ -13,7 +13,7 @@ level=info msg=" name: \"\"" level=info msg=" value_re: \"\"" level=info msg=" group_name: \"\"" level=info msg=" group_value_re: \"\"" -level=info msg=" group_value_separator: \"\"" +level=info msg=" group_value_separator: ' '" level=info msg=" basicAuth:" level=info msg=" users: []" level=info msg="authorization:" diff --git a/cmd/karma/tests/testscript/097_proxy_url_config.txt b/cmd/karma/tests/testscript/097_proxy_url_config.txt index 18a39767f..47ce0e0ba 100644 --- a/cmd/karma/tests/testscript/097_proxy_url_config.txt +++ b/cmd/karma/tests/testscript/097_proxy_url_config.txt @@ -13,7 +13,7 @@ level=info msg=" name: \"\"" level=info msg=" value_re: \"\"" level=info msg=" group_name: \"\"" level=info msg=" group_value_re: \"\"" -level=info msg=" group_value_separator: \"\"" +level=info msg=" group_value_separator: ' '" level=info msg=" basicAuth:" level=info msg=" users: []" level=info msg="authorization:" diff --git a/cmd/karma/tests/testscript/100_auth_header_groups_no_name.txt b/cmd/karma/tests/testscript/100_auth_header_groups_no_name.txt new file mode 100644 index 000000000..282ded263 --- /dev/null +++ b/cmd/karma/tests/testscript/100_auth_header_groups_no_name.txt @@ -0,0 +1,14 @@ +karma.bin-should-fail --config.file=karma.yaml +! stdout . +cmp stderr stderr.txt + +-- stderr.txt -- +level=error msg="Execution failed" error="authentication.header.group_name is required when authentication.header.group_value_re is set" +-- karma.yaml -- +alertmanager: + servers: + - name: default + uri: https://127.0.0.1:9093 +authentication: + header: + group_value_re: "(.+)" diff --git a/cmd/karma/tests/testscript/101_auth_header_groups_invalid_regex.txt b/cmd/karma/tests/testscript/101_auth_header_groups_invalid_regex.txt new file mode 100644 index 000000000..a6f5c9aff --- /dev/null +++ b/cmd/karma/tests/testscript/101_auth_header_groups_invalid_regex.txt @@ -0,0 +1,15 @@ +karma.bin-should-fail --config.file=karma.yaml +! stdout . +cmp stderr stderr.txt + +-- stderr.txt -- +level=error msg="Execution failed" error="invalid regex for authentication.header.group_value_re: error parsing regexp: invalid nested repetition operator: `++`" +-- karma.yaml -- +alertmanager: + servers: + - name: default + uri: https://127.0.0.1:9093 +authentication: + header: + group_name: X-Groups + group_value_re: "(.+++++.)" diff --git a/cmd/karma/tests/testscript/102_auth_header_groups_no_regex.txt b/cmd/karma/tests/testscript/102_auth_header_groups_no_regex.txt new file mode 100644 index 000000000..3232dcc0a --- /dev/null +++ b/cmd/karma/tests/testscript/102_auth_header_groups_no_regex.txt @@ -0,0 +1,14 @@ +karma.bin-should-fail --config.file=karma.yaml +! stdout . +cmp stderr stderr.txt + +-- stderr.txt -- +level=error msg="Execution failed" error="authentication.header.group_value_re is required when authentication.header.group_name is set" +-- karma.yaml -- +alertmanager: + servers: + - name: default + uri: https://127.0.0.1:9093 +authentication: + header: + group_name: X-Groups diff --git a/cmd/karma/views.go b/cmd/karma/views.go index 3ed2bf211..e36c855e2 100644 --- a/cmd/karma/views.go +++ b/cmd/karma/views.go @@ -163,8 +163,10 @@ func alerts(w http.ResponseWriter, r *http.Request) { ts, _ := start.UTC().MarshalText() var username string + var groups []string if config.Config.Authentication.Enabled { username = getUserFromContext(r) + groups = getGroupsFromContext(r) } upstreams := getUpstreams() @@ -207,6 +209,7 @@ func alerts(w http.ResponseWriter, r *http.Request) { resp.Authentication = models.AuthenticationInfo{ Enabled: config.Config.Authentication.Enabled, Username: username, + Groups: groups, } if config.Config.Grid.Sorting.CustomValues.Labels != nil { diff --git a/cmd/karma/views_test.go b/cmd/karma/views_test.go index 4812729cb..889dd3807 100644 --- a/cmd/karma/views_test.go +++ b/cmd/karma/views_test.go @@ -6,6 +6,7 @@ import ( "encoding/json" "errors" "fmt" + "io" "net/http" "net/http/httptest" "os" @@ -1140,12 +1141,16 @@ func TestAuthentication(t *testing.T) { name string headerName string headerRe string + groupName string + groupRe string + groupSeparator string basicAuthUsers []config.AuthenticationUser requestHeaders map[string]string requestBasicAuthUser string requestBasicAuthPassword string responseCode int responseUsername string + responseGroups []string } authTests := []authTest{ @@ -1207,6 +1212,7 @@ func TestAuthentication(t *testing.T) { requestBasicAuthPassword: "foobar", responseCode: 200, responseUsername: "john", + responseGroups: []string{}, }, { name: "header auth, missing header, 401", @@ -1241,6 +1247,7 @@ func TestAuthentication(t *testing.T) { }, responseCode: 200, responseUsername: "john", + responseGroups: []string{}, }, { name: "header auth, header correct #2, 200", @@ -1251,19 +1258,159 @@ func TestAuthentication(t *testing.T) { }, responseCode: 200, responseUsername: "john", + responseGroups: []string{}, + }, + { + name: "header auth, no groups, 200", + headerName: "X-Auth", + headerRe: "Username (.+)", + groupName: "X-Auth-Groups", + groupRe: "(.+)", + groupSeparator: ",", + requestHeaders: map[string]string{ + "X-Auth": "Username john", + }, + responseCode: 200, + responseUsername: "john", + responseGroups: []string{}, + }, + { + name: "header auth, group present, 200", + headerName: "X-Auth", + headerRe: "Username (.+)", + groupName: "X-Auth-Groups", + groupRe: "(.+)", + groupSeparator: ",", + requestHeaders: map[string]string{ + "X-Auth": "Username john", + "X-Auth-Groups": "foo", + }, + responseCode: 200, + responseUsername: "john", + responseGroups: []string{"foo"}, + }, + { + name: "header auth, unmatched groups, 200", + headerName: "X-Auth", + headerRe: "Username (.+)", + groupName: "X-Auth-Groups", + groupRe: "Groups: (.+)", + groupSeparator: ",", + requestHeaders: map[string]string{ + "X-Auth": "Username john", + "X-Auth-Groups": "foo", + }, + responseCode: 200, + responseUsername: "john", + responseGroups: []string{}, + }, + { + name: "header auth, empty groups, 200", + headerName: "X-Auth", + headerRe: "Username (.+)", + groupName: "X-Auth-Groups", + groupRe: "Groups: (.+)", + groupSeparator: ",", + requestHeaders: map[string]string{ + "X-Auth": "Username john", + "X-Auth-Groups": "Groups:", + }, + responseCode: 200, + responseUsername: "john", + responseGroups: []string{}, + }, + { + name: "header auth, empty groups with spaces, 200", + headerName: "X-Auth", + headerRe: "Username (.+)", + groupName: "X-Auth-Groups", + groupRe: "Groups: (.+)", + groupSeparator: ",", + requestHeaders: map[string]string{ + "X-Auth": "Username john", + "X-Auth-Groups": "Groups: ", + }, + responseCode: 200, + responseUsername: "john", + responseGroups: []string{}, + }, + { + name: "header auth, multiple groups, 200", + headerName: "X-Auth", + headerRe: "Username (.+)", + groupName: "X-Auth-Groups", + groupRe: "(.+)", + groupSeparator: ",", + requestHeaders: map[string]string{ + "X-Auth": "Username john", + "X-Auth-Groups": "foo,bar, baz baz ", + }, + responseCode: 200, + responseUsername: "john", + responseGroups: []string{"foo", "bar", "baz baz"}, + }, + { + name: "header auth, multiple groups separated by spaces, 200", + headerName: "X-Auth", + headerRe: "Username (.+)", + groupName: "X-Auth-Groups", + groupRe: "(.+)", + groupSeparator: " ", + requestHeaders: map[string]string{ + "X-Auth": "Username john", + "X-Auth-Groups": "foo bar baz baz ", + }, + responseCode: 200, + responseUsername: "john", + responseGroups: []string{"foo", "bar", "baz", "baz"}, + }, + { + name: "header auth, only groups enabled, no basic auth, 200", + groupName: "X-Auth-Groups", + groupRe: "(.+)", + groupSeparator: " ", + requestHeaders: map[string]string{ + "X-Auth": "Username john", + "X-Auth-Groups": "foo", + }, + responseCode: 200, + responseUsername: "", + responseGroups: []string{}, + }, + { + name: "header auth, only groups enabled, basic auth, 200", + basicAuthUsers: []config.AuthenticationUser{ + {Username: "john", Password: "foobar"}, + }, + requestBasicAuthUser: "john", + requestBasicAuthPassword: "foobar", + groupName: "X-Auth-Groups", + groupRe: "Groups (.+)", + groupSeparator: " ", + requestHeaders: map[string]string{ + "X-Auth-Groups": "Groups foo bar", + }, + responseCode: 200, + responseUsername: "john", + responseGroups: []string{"foo", "bar"}, }, } + zerolog.SetGlobalLevel(zerolog.FatalLevel) for _, testCase := range authTests { t.Run(testCase.name, func(t *testing.T) { config.Config.Authentication.Header.Name = testCase.headerName config.Config.Authentication.Header.ValueRegex = testCase.headerRe + config.Config.Authentication.Header.GroupName = testCase.groupName + config.Config.Authentication.Header.GroupValueRegex = testCase.groupRe + config.Config.Authentication.Header.GroupValueSeparator = testCase.groupSeparator config.Config.Authentication.BasicAuth.Users = testCase.basicAuthUsers r := testRouter() setupRouter(r, nil) mockCache() for _, path := range []string{ "/", + "/alerts.json", "/alertList.json", "/autocomplete.json?term=foo", "/labelNames.json", @@ -1276,7 +1423,23 @@ func TestAuthentication(t *testing.T) { "/metrics", "/metrics?bar=foo", } { - req := httptest.NewRequest("GET", path, nil) + method := "GET" + var body io.Reader + if path == "/alerts.json" { + method = "POST" + payload, err := json.Marshal(models.AlertsRequest{ + Filters: []string{}, + GridLimits: map[string]int{}, + DefaultGroupLimit: 50, + }) + if err != nil { + t.Error(err) + t.FailNow() + } + body = bytes.NewReader(payload) + } + + req := httptest.NewRequest(method, path, body) for k, v := range testCase.requestHeaders { req.Header.Set(k, v) } @@ -1307,6 +1470,10 @@ func TestAuthentication(t *testing.T) { if ur.Authentication.Username != testCase.responseUsername { t.Errorf("Got Authentication.Username=%s, expected %s", ur.Authentication.Username, testCase.responseUsername) } + if diff := cmp.Diff(ur.Authentication.Groups, testCase.responseGroups); diff != "" { + t.Errorf("Incorrect groups list (-want +got):\n%s", diff) + break + } } } }) diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index 9325e6fdc..7f11cb158 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -45,6 +45,9 @@ authentication: header: name: string value_re: regex + group_name: string + group_value_re: regex + group_value_separator: string basicAuth: users: - username: string @@ -66,9 +69,9 @@ authentication: - `authentication:users:header:group_value_re` - Similar to `authentication:users:header:value_re`, but for groups instead of usernames. Must be set when `authentication:users:header:group_name` is set. -- `authentication:users:header:group_value_separator` - If set, this will be +- `authentication:users:header:group_value_separator` - This will be used to split the group header to multiple group names. The split is done - before evaluating the value regex. Optional. + after evaluating the value regex. Default value is `" "`. - `authentication:users` - list of users (username & password) allowed to login. Passwords are stored plain without any encryption. When set HTTP basic authentication will be used. @@ -107,6 +110,21 @@ authentication: value_re: ^(.+)$ ``` +Example where the `X-Auth-User` and `X-Auth-Groups` headers will be used to +set username and list of groups. This assume that `X-Auth-Groups` value has +`Groups: foo,bar` syntax, where `foo` and `bar` are two groups user belongs to. + +```YAML +authentication: + header: + name: X-Auth-User + value_re: ^(.+)$ + group_name: X-Auth-Groups + group_value_re: 'Groups: (.+)' + group_value_separator: ',' + +``` + ### Authorization `authorization` section allows to configure authorization groups used in diff --git a/internal/config/config.go b/internal/config/config.go index 5eb0ba36d..5aefdd1ed 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -289,6 +289,10 @@ func (config *configSchema) Read(flags *pflag.FlagSet) (string, error) { return "", fmt.Errorf("both authentication.basicAuth.users and authentication.header.name is set, only one can be enabled") } + if config.Authentication.Header.GroupValueSeparator == "" { + config.Authentication.Header.GroupValueSeparator = " " + } + if config.Authentication.Header.ValueRegex != "" { _, err = regex.CompileAnchored(config.Authentication.Header.ValueRegex) if err != nil { @@ -300,6 +304,17 @@ func (config *configSchema) Read(flags *pflag.FlagSet) (string, error) { } else if config.Authentication.Header.Name != "" { return "", fmt.Errorf("authentication.header.value_re is required when authentication.header.name is set") } + if config.Authentication.Header.GroupValueRegex != "" { + _, err = regex.CompileAnchored(config.Authentication.Header.GroupValueRegex) + if err != nil { + return "", fmt.Errorf("invalid regex for authentication.header.group_value_re: %s", err.Error()) + } + if config.Authentication.Header.GroupName == "" { + return "", fmt.Errorf("authentication.header.group_name is required when authentication.header.group_value_re is set") + } + } else if config.Authentication.Header.GroupName != "" { + return "", fmt.Errorf("authentication.header.group_value_re is required when authentication.header.group_name is set") + } for _, u := range config.Authentication.BasicAuth.Users { if u.Username == "" || u.Password == "" { diff --git a/internal/models/api.go b/internal/models/api.go index 5af6cae12..0e9405c37 100644 --- a/internal/models/api.go +++ b/internal/models/api.go @@ -425,8 +425,9 @@ type Settings struct { } type AuthenticationInfo struct { - Enabled bool `json:"enabled"` - Username string `json:"username"` + Enabled bool `json:"enabled"` + Username string `json:"username"` + Groups []string `json:"groups"` } type APIGrid struct {