mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
JWT Authentication: Add support for specifying groups in auth.jwt for teamsync (#82175)
* merge JSON search logic * document public methods * improve test coverage * use separate JWT setting struct * correct use of cfg.JWTAuth * add group tests * fix DynMap typing * add settings to default ini * add groups option to devenv path * fix test * lint * revert jwt-proxy change * remove redundant check * fix parallel test
This commit is contained in:
@@ -2,13 +2,9 @@ package clients
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/jmespath/go-jmespath"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/services/auth"
|
||||
authJWT "github.com/grafana/grafana/pkg/services/auth/jwt"
|
||||
@@ -16,6 +12,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/login"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
"github.com/grafana/grafana/pkg/util/errutil"
|
||||
)
|
||||
|
||||
@@ -73,15 +70,16 @@ func (s *JWT) Authenticate(ctx context.Context, r *authn.Request) (*authn.Identi
|
||||
SyncUser: true,
|
||||
FetchSyncedUser: true,
|
||||
SyncPermissions: true,
|
||||
SyncOrgRoles: !s.cfg.JWTAuthSkipOrgRoleSync,
|
||||
AllowSignUp: s.cfg.JWTAuthAutoSignUp,
|
||||
SyncOrgRoles: !s.cfg.JWTAuth.SkipOrgRoleSync,
|
||||
AllowSignUp: s.cfg.JWTAuth.AutoSignUp,
|
||||
SyncTeams: s.cfg.JWTAuth.GroupsAttributePath != "",
|
||||
}}
|
||||
|
||||
if key := s.cfg.JWTAuthUsernameClaim; key != "" {
|
||||
if key := s.cfg.JWTAuth.UsernameClaim; key != "" {
|
||||
id.Login, _ = claims[key].(string)
|
||||
id.ClientParams.LookUpParams.Login = &id.Login
|
||||
}
|
||||
if key := s.cfg.JWTAuthEmailClaim; key != "" {
|
||||
if key := s.cfg.JWTAuth.EmailClaim; key != "" {
|
||||
id.Email, _ = claims[key].(string)
|
||||
id.ClientParams.LookUpParams.Email = &id.Email
|
||||
}
|
||||
@@ -91,16 +89,16 @@ func (s *JWT) Authenticate(ctx context.Context, r *authn.Request) (*authn.Identi
|
||||
}
|
||||
|
||||
orgRoles, isGrafanaAdmin, err := getRoles(s.cfg, func() (org.RoleType, *bool, error) {
|
||||
if s.cfg.JWTAuthSkipOrgRoleSync {
|
||||
if s.cfg.JWTAuth.SkipOrgRoleSync {
|
||||
return "", nil, nil
|
||||
}
|
||||
|
||||
role, grafanaAdmin := s.extractRoleAndAdmin(claims)
|
||||
if s.cfg.JWTAuthRoleAttributeStrict && !role.IsValid() {
|
||||
if s.cfg.JWTAuth.RoleAttributeStrict && !role.IsValid() {
|
||||
return "", nil, errJWTInvalidRole.Errorf("invalid role claim in JWT: %s", role)
|
||||
}
|
||||
|
||||
if !s.cfg.JWTAuthAllowAssignGrafanaAdmin {
|
||||
if !s.cfg.JWTAuth.AllowAssignGrafanaAdmin {
|
||||
return role, nil, nil
|
||||
}
|
||||
|
||||
@@ -114,6 +112,11 @@ func (s *JWT) Authenticate(ctx context.Context, r *authn.Request) (*authn.Identi
|
||||
id.OrgRoles = orgRoles
|
||||
id.IsGrafanaAdmin = isGrafanaAdmin
|
||||
|
||||
id.Groups, err = s.extractGroups(claims)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if id.Login == "" && id.Email == "" {
|
||||
s.log.FromContext(ctx).Debug("Failed to get an authentication claim from JWT",
|
||||
"login", id.Login, "email", id.Email)
|
||||
@@ -126,7 +129,7 @@ func (s *JWT) Authenticate(ctx context.Context, r *authn.Request) (*authn.Identi
|
||||
// remove sensitive query param
|
||||
// avoid JWT URL login passing auth_token in URL
|
||||
func (s *JWT) stripSensitiveParam(httpRequest *http.Request) {
|
||||
if s.cfg.JWTAuthURLLogin {
|
||||
if s.cfg.JWTAuth.URLLogin {
|
||||
params := httpRequest.URL.Query()
|
||||
if params.Has(authQueryParamName) {
|
||||
params.Del(authQueryParamName)
|
||||
@@ -137,8 +140,8 @@ func (s *JWT) stripSensitiveParam(httpRequest *http.Request) {
|
||||
|
||||
// retrieveToken retrieves the JWT token from the request.
|
||||
func (s *JWT) retrieveToken(httpRequest *http.Request) string {
|
||||
jwtToken := httpRequest.Header.Get(s.cfg.JWTAuthHeaderName)
|
||||
if jwtToken == "" && s.cfg.JWTAuthURLLogin {
|
||||
jwtToken := httpRequest.Header.Get(s.cfg.JWTAuth.HeaderName)
|
||||
if jwtToken == "" && s.cfg.JWTAuth.URLLogin {
|
||||
jwtToken = httpRequest.URL.Query().Get("auth_token")
|
||||
}
|
||||
// Strip the 'Bearer' prefix if it exists.
|
||||
@@ -146,7 +149,7 @@ func (s *JWT) retrieveToken(httpRequest *http.Request) string {
|
||||
}
|
||||
|
||||
func (s *JWT) Test(ctx context.Context, r *authn.Request) bool {
|
||||
if !s.cfg.JWTAuthEnabled || s.cfg.JWTAuthHeaderName == "" {
|
||||
if !s.cfg.JWTAuth.Enabled || s.cfg.JWTAuth.HeaderName == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -171,11 +174,11 @@ func (s *JWT) Priority() uint {
|
||||
const roleGrafanaAdmin = "GrafanaAdmin"
|
||||
|
||||
func (s *JWT) extractRoleAndAdmin(claims map[string]any) (org.RoleType, bool) {
|
||||
if s.cfg.JWTAuthRoleAttributePath == "" {
|
||||
if s.cfg.JWTAuth.RoleAttributePath == "" {
|
||||
return "", false
|
||||
}
|
||||
|
||||
role, err := searchClaimsForStringAttr(s.cfg.JWTAuthRoleAttributePath, claims)
|
||||
role, err := util.SearchJSONForStringAttr(s.cfg.JWTAuth.RoleAttributePath, claims)
|
||||
if err != nil || role == "" {
|
||||
return "", false
|
||||
}
|
||||
@@ -186,33 +189,10 @@ func (s *JWT) extractRoleAndAdmin(claims map[string]any) (org.RoleType, bool) {
|
||||
return org.RoleType(role), false
|
||||
}
|
||||
|
||||
func searchClaimsForStringAttr(attributePath string, claims map[string]any) (string, error) {
|
||||
val, err := searchClaimsForAttr(attributePath, claims)
|
||||
if err != nil {
|
||||
return "", err
|
||||
func (s *JWT) extractGroups(claims map[string]any) ([]string, error) {
|
||||
if s.cfg.JWTAuth.GroupsAttributePath == "" {
|
||||
return []string{}, nil
|
||||
}
|
||||
|
||||
strVal, ok := val.(string)
|
||||
if ok {
|
||||
return strVal, nil
|
||||
}
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func searchClaimsForAttr(attributePath string, claims map[string]any) (any, 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
|
||||
return util.SearchJSONForStringSliceAttr(s.cfg.JWTAuth.GroupsAttributePath, claims)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user