JWT: Add support for assigning org roles (#54277)

* feat: allow jwt role to be set

* chore: update documentation

* fix: cr suggestions

* fix: lint issues

* respect org auto assign and default org ID

* add server admin to devenv

Co-authored-by: jguer <joao.guerreiro@grafana.com>
This commit is contained in:
Nicholas Wiersma 2022-09-07 14:00:33 +02:00 committed by GitHub
parent 4825707853
commit 9e704fec3c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 289 additions and 17 deletions

View File

@ -611,7 +611,10 @@ jwk_set_file =
cache_ttl = 60m
expected_claims = {}
key_file =
role_attribute_path =
role_attribute_strict = false
auto_sign_up = false
allow_assign_grafana_admin = false
#################################### Auth LDAP ###########################
[auth.ldap]

View File

@ -597,8 +597,11 @@
;cache_ttl = 60m
;expected_claims = {"aud": ["foo", "bar"]}
;key_file = /path/to/key/file
;role_attribute_path =
;role_attribute_strict = false
;auto_sign_up = false
;url_login = false
;allow_assign_grafana_admin = false
#################################### Auth LDAP ##########################
[auth.ldap]

View File

@ -2243,6 +2243,7 @@ d4b2c483-1dd3-47f6-86bf-42548009918d \N password 74e29604-ff35-42bb-a26d-4d0b81e
cb2bd4ed-94b8-4259-bcaa-9250c3fb28d3 \N password 6db3c5e5-b84b-4f9d-a7a8-8d05b03c929d 1657026827644 \N {"value":"q3Z59Nh/5bdezDEpCwEbMPu8d+VgJ5WetafXkR8l0FlsTTkSDQgW+j6GaM3seJR93p3/jCxyfsvZl062d1pq7w==","salt":"ohuHnjLnwF9dBZ38DRJJWg==","additionalParameters":{}} {"hashIterations":27500,"algorithm":"pbkdf2-sha256","additionalParameters":{}} 10
b58e1964-6466-40b2-879c-982b724d7f9c \N password 88692d07-bb9a-46cf-844c-7ff5c529cd04 1657026904515 \N {"value":"+/0zWjiJyE3+dCOEf0SO6G3n1/LsFAVoDAZREKTfN4vQ5xJH8srJoCjxcgb+bI1crMr8gknDlFyGRy7CpYn2VQ==","salt":"v/2okNt3wGOZz+x4DjOCDQ==","additionalParameters":{}} {"hashIterations":27500,"algorithm":"pbkdf2-sha256","additionalParameters":{}} 10
3ff7dd8f-a299-4b51-bf5d-99665ccfd313 \N password 8f58cbec-6e40-4bab-bff0-1c5ff899fe2e 1657026943075 \N {"value":"nMYodMJMiq/J8g9vRPktGc7WSWnOKr6leMDZX4p9K9KgAUYeXFDSu+d29PWWn0rFn93dL0PNdIdHWNQhfkIDMg==","salt":"rmi9WLHgarmIXGukecSIig==","additionalParameters":{}} {"hashIterations":27500,"algorithm":"pbkdf2-sha256","additionalParameters":{}} 10
c9582964-cfdd-49a1-99fb-847604b0c78c \N password 1a85b7e0-4baa-420b-89f8-1cea43a540dd 1662480997923 \N {"value":"ViNTHbpBUNdtH1qGSlip7WFI8Z9lvcGQdbL8Yw48zUgB46jVFbD1eNrOw68p3ovDwfDCIJKm34EFNbw9/uzHSg==","salt":"Z9P8RfnrQwCn0xUTpWC2DQ==","additionalParameters":{}} {"hashIterations":27500,"algorithm":"pbkdf2-sha256","additionalParameters":{}} 10
\.
@ -2599,6 +2600,7 @@ c49bddc6-ec92-4caa-bc04-57ba80a92eb9 grafana f ${role_offline-access} offline_ac
c029a218-4519-4537-ae12-d8f3c27a0003 grafana f Grafana Server Admin serveradmin grafana \N grafana
c9a776f9-2740-435f-a725-4dbcc17a6c91 grafana f Grafana Viewer viewer grafana \N grafana
c4c74006-c346-48cf-8cf1-1617e3e1cde1 grafana f Grafana Editor editor grafana \N grafana
31150c12-e9fe-4465-a792-816b7298a595 grafana f Grafana Server Administrator grafanaadmin grafana \N grafana
\.
@ -3008,7 +3010,7 @@ df78645e-c32b-4160-b79f-42e622d71982 String jsonType.label
COPY public.realm (id, access_code_lifespan, user_action_lifespan, access_token_lifespan, account_theme, admin_theme, email_theme, enabled, events_enabled, events_expiration, login_theme, name, not_before, password_policy, registration_allowed, remember_me, reset_password_allowed, social, ssl_required, sso_idle_timeout, sso_max_lifespan, update_profile_on_soc_login, verify_email, master_admin_client, login_lifespan, internationalization_enabled, default_locale, reg_email_as_username, admin_events_enabled, admin_events_details_enabled, edit_username_allowed, otp_policy_counter, otp_policy_window, otp_policy_period, otp_policy_digits, otp_policy_alg, otp_policy_type, browser_flow, registration_flow, direct_grant_flow, reset_credentials_flow, client_auth_flow, offline_session_idle_timeout, revoke_refresh_token, access_token_life_implicit, login_with_email_allowed, duplicate_emails_allowed, docker_auth_flow, refresh_token_max_reuse, allow_user_managed_access, sso_max_lifespan_remember_me, sso_idle_timeout_remember_me) FROM stdin;
master 60 300 60 \N \N \N t f 0 \N master 1643820855 \N f f f f EXTERNAL 1800 36000 f f 3cd285ea-0f6e-43b6-ab5c-d021c33a551b 1800 f \N f f f f 0 1 30 6 HmacSHA1 totp ef998ef5-ca12-45db-a252-2e71b1419039 1695e7d2-ad80-4502-8479-8121a6e2a2f0 5f6f801e-0588-4a6e-860a-35483f5c1ec7 954b046d-2b24-405e-84ee-c44ffe603df2 023dc515-c259-42bb-88a8-2e8d84abca92 2592000 f 900 t f 032b05cf-0007-44da-a370-b42039f6b762 0 f 0 0
grafana 60 300 300 \N \N \N t f 0 \N grafana 1643820879 \N f f f f EXTERNAL 1800 36000 f f ef7f6eac-9fff-44aa-a86c-5125d52acc82 1800 f \N f f f f 0 1 30 6 HmacSHA1 totp a38aeb47-f27e-4e68-82ff-7cc7371a47a7 9d02badd-cb1c-4655-bf5e-f888861433ff b478ecfb-db7e-4797-a245-8fc3b4dec884 3085fb68-fc1f-4e1c-a8be-33fb45194b04 cbb4b3ca-ced6-4046-8b59-f1c3959c7948 2592000 f 900 t f 95e02703-f5bc-4e04-8bef-f6adc2d8173f 0 f 0 0
grafana 60 300 300 \N \N \N t f 0 \N grafana 1662482026 \N f f f f EXTERNAL 1800 36000 f f ef7f6eac-9fff-44aa-a86c-5125d52acc82 1800 f \N f f f f 0 1 30 6 HmacSHA1 totp a38aeb47-f27e-4e68-82ff-7cc7371a47a7 9d02badd-cb1c-4655-bf5e-f888861433ff b478ecfb-db7e-4797-a245-8fc3b4dec884 3085fb68-fc1f-4e1c-a8be-33fb45194b04 cbb4b3ca-ced6-4046-8b59-f1c3959c7948 2592000 f 900 t f 95e02703-f5bc-4e04-8bef-f6adc2d8173f 0 f 0 0
\.
@ -3325,6 +3327,7 @@ COPY public.user_entity (id, email, email_constraint, email_verified, enabled, f
6db3c5e5-b84b-4f9d-a7a8-8d05b03c929d jwt-admin@example.org jwt-admin@example.org f t \N Admin JWT grafana jwt-admin 1657026796311 \N 0
88692d07-bb9a-46cf-844c-7ff5c529cd04 jwt-editor@example.com jwt-editor@example.com f t \N Editor JWT grafana jwt-editor 1657026894275 \N 0
8f58cbec-6e40-4bab-bff0-1c5ff899fe2e jwt-viewer@example.com jwt-viewer@example.com f t \N Viewer JWT grafana jwt-viewer 1657026933578 \N 0
1a85b7e0-4baa-420b-89f8-1cea43a540dd jwt-grafanaadmin@example.com jwt-grafanaadmin@example.com t t \N Grafanaadmin JWT grafana jwt-grafanaadmin 1662480983434 \N 0
\.
@ -3401,6 +3404,11 @@ f1311ecb-6a6a-49d6-bb16-5132daf93a64 8f58cbec-6e40-4bab-bff0-1c5ff899fe2e
18a7066b-fe71-410e-9581-69f78347ec29 8f58cbec-6e40-4bab-bff0-1c5ff899fe2e
c9a776f9-2740-435f-a725-4dbcc17a6c91 8f58cbec-6e40-4bab-bff0-1c5ff899fe2e
c4c74006-c346-48cf-8cf1-1617e3e1cde1 88692d07-bb9a-46cf-844c-7ff5c529cd04
c49bddc6-ec92-4caa-bc04-57ba80a92eb9 1a85b7e0-4baa-420b-89f8-1cea43a540dd
0f3d47bb-002a-4cd0-a502-725f224308a7 1a85b7e0-4baa-420b-89f8-1cea43a540dd
f1311ecb-6a6a-49d6-bb16-5132daf93a64 1a85b7e0-4baa-420b-89f8-1cea43a540dd
18a7066b-fe71-410e-9581-69f78347ec29 1a85b7e0-4baa-420b-89f8-1cea43a540dd
31150c12-e9fe-4465-a792-816b7298a595 1a85b7e0-4baa-420b-89f8-1cea43a540dd
\.

View File

@ -22,6 +22,9 @@ jwk_set_file = devenv/docker/blocks/auth/oauth/jwks.json
cache_ttl = 60m
expected_claims = {"iss": "http://env.grafana.local:8087/auth/realms/grafana", "azp": "grafana-oauth"}
auto_sign_up = true
role_attribute_path = contains(roles[*], 'grafanaadmin') && 'GrafanaAdmin' || contains(roles[*], 'admin') && 'Admin' || contains(roles[*], 'editor') && 'Editor' || 'Viewer'
role_attribute_strict = false
allow_assign_grafana_admin = true
```
Add *env.grafana.local* to /etc/hosts (Mac/Linux) or C:\Windows\System32\drivers\etc\hosts (Windows):

View File

@ -143,3 +143,68 @@ You might also want to validate that other claims are really what you expect the
# This can be seen as a required "subset" of a JWT Claims Set.
expect_claims = {"iss": "https://your-token-issuer", "your-custom-claim": "foo"}
```
## Roles
Grafana checks for the presence of a role using the [JMESPath](http://jmespath.org/examples.html) specified via the `role_attribute_path` configuration option. The JMESPath is applied to JWT token claims. The result after evaluation of the `role_attribute_path` JMESPath expression should be a valid Grafana role, for example, `Viewer`, `Editor` or `Admin`.
The organization that the role is assigned to can be configured using the `X-Grafana-Org-Id` header.
### JMESPath examples
To ease configuration of a proper JMESPath expression, you can test/evaluate expressions with custom payloads at http://jmespath.org/.
### Role mapping
If the `role_attribute_path` property does not return a role, then the user is assigned the `Viewer` role by default. You can disable the role assignment by setting `role_attribute_strict = true`. It denies user access if no role or an invalid role is returned.
**Basic example:**
In the following example user will get `Editor` as role when authenticating. The value of the property `role` will be the resulting role if the role is a proper Grafana role, i.e. `Viewer`, `Editor` or `Admin`.
Payload:
```json
{
...
"role": "Editor",
...
}
```
Config:
```bash
role_attribute_path = role
```
**Advanced example:**
In the following example user will get `Admin` as role when authenticating since it has a role `admin`. If a user has a role `editor` it will get `Editor` as role, otherwise `Viewer`.
Payload:
```json
{
...
"info": {
...
"roles": [
"engineer",
"admin",
],
...
},
...
}
```
Config:
```bash
role_attribute_path = contains(info.roles[*], 'admin') && 'Admin' || contains(info.roles[*], 'editor') && 'Editor' || 'Viewer'
```
### Grafana Admin Role
If the `role_attribute_path` property returns a `GrafanaAdmin` role, Grafana Admin is not assigned by default, instead the `Admin` role is assigned. To allow `Grafana Admin` role to be assigned set `allow_assign_grafana_admin = true`.

View File

@ -5,6 +5,7 @@ import (
"errors"
"testing"
"github.com/grafana/grafana/pkg/services/org"
"github.com/stretchr/testify/assert"
"github.com/grafana/grafana/pkg/models"
@ -14,6 +15,7 @@ import (
)
func TestMiddlewareJWTAuth(t *testing.T) {
const myEmail = "vladimir@example.com"
const id int64 = 12
const orgID int64 = 2
@ -34,6 +36,19 @@ func TestMiddlewareJWTAuth(t *testing.T) {
cfg.JWTAuthAutoSignUp = true
}
configureRole := func(cfg *setting.Cfg) {
cfg.JWTAuthEmailClaim = "sub"
cfg.JWTAuthRoleAttributePath = "role"
}
configureRoleStrict := func(cfg *setting.Cfg) {
cfg.JWTAuthRoleAttributeStrict = true
}
configureRoleAllowAdmin := func(cfg *setting.Cfg) {
cfg.JWTAuthAllowAssignGrafanaAdmin = true
}
token := "some-token"
middlewareScenario(t, "Valid token with valid login claim", func(t *testing.T, sc *scenarioContext) {
@ -58,7 +73,6 @@ func TestMiddlewareJWTAuth(t *testing.T) {
}, configure, configureUsernameClaim)
middlewareScenario(t, "Valid token with valid email claim", func(t *testing.T, sc *scenarioContext) {
myEmail := "vladimir@example.com"
var verifiedToken string
sc.jwtAuthService.VerifyProvider = func(ctx context.Context, token string) (models.JWTClaims, error) {
verifiedToken = token
@ -79,7 +93,6 @@ func TestMiddlewareJWTAuth(t *testing.T) {
}, configure, configureEmailClaim)
middlewareScenario(t, "Valid token with no user and auto_sign_up disabled", func(t *testing.T, sc *scenarioContext) {
myEmail := "vladimir@example.com"
var verifiedToken string
sc.jwtAuthService.VerifyProvider = func(ctx context.Context, token string) (models.JWTClaims, error) {
verifiedToken = token
@ -98,7 +111,6 @@ func TestMiddlewareJWTAuth(t *testing.T) {
}, configure, configureEmailClaim)
middlewareScenario(t, "Valid token with no user and auto_sign_up enabled", func(t *testing.T, sc *scenarioContext) {
myEmail := "vladimir@example.com"
var verifiedToken string
sc.jwtAuthService.VerifyProvider = func(ctx context.Context, token string) (models.JWTClaims, error) {
verifiedToken = token
@ -151,6 +163,97 @@ func TestMiddlewareJWTAuth(t *testing.T) {
assert.Equal(t, contexthandler.InvalidJWT, sc.respJson["message"])
}, configure, configureEmailClaim)
middlewareScenario(t, "Valid token with role", func(t *testing.T, sc *scenarioContext) {
var verifiedToken string
sc.jwtAuthService.VerifyProvider = func(ctx context.Context, token string) (models.JWTClaims, error) {
verifiedToken = token
return models.JWTClaims{
"sub": myEmail,
"role": "Editor",
}, nil
}
sc.userService.ExpectedSignedInUser = &user.SignedInUser{UserID: id, OrgID: orgID, Email: myEmail, OrgRole: org.RoleEditor}
sc.fakeReq("GET", "/").withJWTAuthHeader(token).exec()
assert.Equal(t, verifiedToken, token)
assert.Equal(t, 200, sc.resp.Code)
assert.True(t, sc.context.IsSignedIn)
assert.Equal(t, org.RoleEditor, sc.context.OrgRole)
}, configure, configureAutoSignUp, configureRole)
middlewareScenario(t, "Valid token with invalid role", func(t *testing.T, sc *scenarioContext) {
var verifiedToken string
sc.jwtAuthService.VerifyProvider = func(ctx context.Context, token string) (models.JWTClaims, error) {
verifiedToken = token
return models.JWTClaims{
"sub": myEmail,
"role": "test",
}, nil
}
sc.userService.ExpectedSignedInUser = &user.SignedInUser{UserID: id, OrgID: orgID, Email: myEmail, OrgRole: org.RoleViewer}
sc.fakeReq("GET", "/").withJWTAuthHeader(token).exec()
assert.Equal(t, verifiedToken, token)
assert.Equal(t, 200, sc.resp.Code)
assert.True(t, sc.context.IsSignedIn)
assert.Equal(t, org.RoleViewer, sc.context.OrgRole)
}, configure, configureAutoSignUp, configureRole)
middlewareScenario(t, "Valid token with invalid role in strict mode", func(t *testing.T, sc *scenarioContext) {
var verifiedToken string
sc.jwtAuthService.VerifyProvider = func(ctx context.Context, token string) (models.JWTClaims, error) {
verifiedToken = token
return models.JWTClaims{
"sub": myEmail,
"role": "test",
}, nil
}
sc.userService.ExpectedSignedInUser = &user.SignedInUser{UserID: id, OrgID: orgID, Email: myEmail, OrgRole: org.RoleViewer}
sc.fakeReq("GET", "/").withJWTAuthHeader(token).exec()
assert.Equal(t, verifiedToken, token)
assert.Equal(t, 403, sc.resp.Code)
assert.Equal(t, contexthandler.InvalidRole, sc.respJson["message"])
}, configure, configureAutoSignUp, configureRole, configureRoleStrict)
middlewareScenario(t, "Valid token with grafana admin role not allowed", func(t *testing.T, sc *scenarioContext) {
var verifiedToken string
sc.jwtAuthService.VerifyProvider = func(ctx context.Context, token string) (models.JWTClaims, error) {
verifiedToken = token
return models.JWTClaims{
"sub": myEmail,
"role": "GrafanaAdmin",
}, nil
}
sc.userService.ExpectedSignedInUser = &user.SignedInUser{UserID: id, OrgID: orgID, Email: myEmail, OrgRole: org.RoleAdmin}
sc.fakeReq("GET", "/").withJWTAuthHeader(token).exec()
assert.Equal(t, verifiedToken, token)
assert.Equal(t, 200, sc.resp.Code)
assert.True(t, sc.context.IsSignedIn)
assert.Equal(t, org.RoleAdmin, sc.context.OrgRole)
assert.False(t, sc.context.IsGrafanaAdmin)
}, configure, configureAutoSignUp, configureRole)
middlewareScenario(t, "Valid token with grafana admin role allowed", func(t *testing.T, sc *scenarioContext) {
var verifiedToken string
sc.jwtAuthService.VerifyProvider = func(ctx context.Context, token string) (models.JWTClaims, error) {
verifiedToken = token
return models.JWTClaims{
"sub": myEmail,
"role": "GrafanaAdmin",
}, nil
}
sc.userService.ExpectedSignedInUser = &user.SignedInUser{UserID: id, OrgID: orgID, Email: myEmail, OrgRole: org.RoleAdmin, IsGrafanaAdmin: true}
sc.fakeReq("GET", "/").withJWTAuthHeader(token).exec()
assert.Equal(t, verifiedToken, token)
assert.Equal(t, 200, sc.resp.Code)
assert.True(t, sc.context.IsSignedIn)
assert.Equal(t, org.RoleAdmin, sc.context.OrgRole)
assert.True(t, sc.context.IsGrafanaAdmin)
}, configure, configureAutoSignUp, configureRole, configureRoleAllowAdmin)
middlewareScenario(t, "Invalid token", func(t *testing.T, sc *scenarioContext) {
var verifiedToken string
sc.jwtAuthService.VerifyProvider = func(ctx context.Context, token string) (models.JWTClaims, error) {

View File

@ -2,15 +2,21 @@ package contexthandler
import (
"errors"
"fmt"
"net/http"
"github.com/grafana/grafana/pkg/login"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/user"
"github.com/jmespath/go-jmespath"
)
const InvalidJWT = "Invalid JWT"
const UserNotFound = "User not found"
const (
InvalidJWT = "Invalid JWT"
InvalidRole = "Invalid Role"
UserNotFound = "User not found"
)
func (h *ContextHandler) initContextWithJWT(ctx *models.ReqContext, orgId int64) bool {
if !h.Cfg.JWTAuthEnabled || h.Cfg.JWTAuthHeaderName == "" {
@ -45,6 +51,7 @@ func (h *ContextHandler) initContextWithJWT(ctx *models.ReqContext, orgId int64)
extUser := &models.ExternalUserInfo{
AuthModule: "jwt",
AuthId: sub,
OrgRoles: map[int64]org.RoleType{},
}
if key := h.Cfg.JWTAuthUsernameClaim; key != "" {
@ -60,6 +67,31 @@ func (h *ContextHandler) initContextWithJWT(ctx *models.ReqContext, orgId int64)
extUser.Name = name
}
role, grafanaAdmin := h.extractJWTRoleAndAdmin(claims)
if h.Cfg.JWTAuthRoleAttributeStrict && !role.IsValid() {
ctx.Logger.Debug("Extracted Role is invalid")
ctx.JsonApiErr(http.StatusForbidden, InvalidRole, nil)
return true
}
if role.IsValid() {
var orgID int64
if h.Cfg.AutoAssignOrg && h.Cfg.AutoAssignOrgId > 0 {
orgID = int64(h.Cfg.AutoAssignOrgId)
ctx.Logger.Debug("The user has a role assignment and organization membership is auto-assigned",
"role", role, "orgId", orgID)
} else {
orgID = int64(1)
ctx.Logger.Debug("The user has a role assignment and organization membership is not auto-assigned",
"role", role, "orgId", orgID)
}
extUser.OrgRoles[orgID] = role
if h.Cfg.JWTAuthAllowAssignGrafanaAdmin {
extUser.IsGrafanaAdmin = &grafanaAdmin
}
}
if query.Login == "" && query.Email == "" {
ctx.Logger.Debug("Failed to get an authentication claim from JWT")
ctx.JsonApiErr(http.StatusUnauthorized, InvalidJWT, err)
@ -105,3 +137,52 @@ func (h *ContextHandler) initContextWithJWT(ctx *models.ReqContext, orgId int64)
return true
}
const roleGrafanaAdmin = "GrafanaAdmin"
func (h *ContextHandler) extractJWTRoleAndAdmin(claims map[string]interface{}) (org.RoleType, bool) {
if h.Cfg.JWTAuthRoleAttributePath == "" {
return "", false
}
role, err := searchClaimsForStringAttr(h.Cfg.JWTAuthRoleAttributePath, claims)
if err != nil || role == "" {
return "", false
}
if role == roleGrafanaAdmin {
return org.RoleAdmin, true
}
return org.RoleType(role), false
}
func searchClaimsForAttr(attributePath string, claims map[string]interface{}) (interface{}, error) {
if attributePath == "" {
return "", errors.New("no attribute path specified")
}
if len(claims) == 0 {
return "", errors.New("empty claims provided")
}
val, err := jmespath.Search(attributePath, claims)
if err != nil {
return "", fmt.Errorf("failed to search claims with provided path: %q: %w", attributePath, err)
}
return val, nil
}
func searchClaimsForStringAttr(attributePath string, claims map[string]interface{}) (string, error) {
val, err := searchClaimsForAttr(attributePath, claims)
if err != nil {
return "", err
}
strVal, ok := val.(string)
if ok {
return strVal, nil
}
return "", nil
}

View File

@ -317,17 +317,20 @@ type Cfg struct {
OAuthCookieMaxAge int
// JWT Auth
JWTAuthEnabled bool
JWTAuthHeaderName string
JWTAuthURLLogin bool
JWTAuthEmailClaim string
JWTAuthUsernameClaim string
JWTAuthExpectClaims string
JWTAuthJWKSetURL string
JWTAuthCacheTTL time.Duration
JWTAuthKeyFile string
JWTAuthJWKSetFile string
JWTAuthAutoSignUp bool
JWTAuthEnabled bool
JWTAuthHeaderName string
JWTAuthURLLogin bool
JWTAuthEmailClaim string
JWTAuthUsernameClaim string
JWTAuthExpectClaims string
JWTAuthJWKSetURL string
JWTAuthCacheTTL time.Duration
JWTAuthKeyFile string
JWTAuthJWKSetFile string
JWTAuthAutoSignUp bool
JWTAuthRoleAttributePath string
JWTAuthRoleAttributeStrict bool
JWTAuthAllowAssignGrafanaAdmin bool
// Dataproxy
SendUserHeader bool
@ -1322,6 +1325,9 @@ func readAuthSettings(iniFile *ini.File, cfg *Cfg) (err error) {
cfg.JWTAuthKeyFile = valueAsString(authJWT, "key_file", "")
cfg.JWTAuthJWKSetFile = valueAsString(authJWT, "jwk_set_file", "")
cfg.JWTAuthAutoSignUp = authJWT.Key("auto_sign_up").MustBool(false)
cfg.JWTAuthRoleAttributePath = valueAsString(authJWT, "role_attribute_path", "")
cfg.JWTAuthRoleAttributeStrict = authJWT.Key("role_attribute_strict").MustBool(false)
cfg.JWTAuthAllowAssignGrafanaAdmin = authJWT.Key("allow_assign_grafana_admin").MustBool(false)
authProxy := iniFile.Section("auth.proxy")
AuthProxyEnabled = authProxy.Key("enabled").MustBool(false)