mirror of https://github.com/grafana/grafana.git
				
				
				
			
		
			
				
	
	
		
			308 lines
		
	
	
		
			8.1 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			308 lines
		
	
	
		
			8.1 KiB
		
	
	
	
		
			Go
		
	
	
	
| package clients
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"fmt"
 | |
| 	"net/http"
 | |
| 	"net/url"
 | |
| 	"testing"
 | |
| 
 | |
| 	"github.com/stretchr/testify/assert"
 | |
| 	"github.com/stretchr/testify/require"
 | |
| 
 | |
| 	"github.com/grafana/grafana/pkg/models"
 | |
| 	"github.com/grafana/grafana/pkg/models/roletype"
 | |
| 	"github.com/grafana/grafana/pkg/services/auth/jwt"
 | |
| 	"github.com/grafana/grafana/pkg/services/authn"
 | |
| 	"github.com/grafana/grafana/pkg/setting"
 | |
| )
 | |
| 
 | |
| func stringPtr(s string) *string {
 | |
| 	return &s
 | |
| }
 | |
| 
 | |
| func TestAuthenticateJWT(t *testing.T) {
 | |
| 	jwtService := &jwt.FakeJWTService{
 | |
| 		VerifyProvider: func(context.Context, string) (jwt.JWTClaims, error) {
 | |
| 			return jwt.JWTClaims{
 | |
| 				"sub":                "1234567890",
 | |
| 				"email":              "eai.doe@cor.po",
 | |
| 				"preferred_username": "eai-doe",
 | |
| 				"name":               "Eai Doe",
 | |
| 				"roles":              "Admin",
 | |
| 			}, nil
 | |
| 		},
 | |
| 	}
 | |
| 	jwtHeaderName := "X-Forwarded-User"
 | |
| 	wantID := &authn.Identity{
 | |
| 		OrgID:          0,
 | |
| 		OrgCount:       0,
 | |
| 		OrgName:        "",
 | |
| 		OrgRoles:       map[int64]roletype.RoleType{1: roletype.RoleAdmin},
 | |
| 		ID:             "",
 | |
| 		Login:          "eai-doe",
 | |
| 		Name:           "Eai Doe",
 | |
| 		Email:          "eai.doe@cor.po",
 | |
| 		IsGrafanaAdmin: boolPtr(false),
 | |
| 		AuthModule:     "jwt",
 | |
| 		AuthID:         "1234567890",
 | |
| 		IsDisabled:     false,
 | |
| 		HelpFlags1:     0,
 | |
| 		ClientParams: authn.ClientParams{
 | |
| 			SyncTeamMembers: false,
 | |
| 			SyncUser:        true,
 | |
| 			AllowSignUp:     true,
 | |
| 			LookUpParams: models.UserLookupParams{
 | |
| 				UserID: nil,
 | |
| 				Email:  stringPtr("eai.doe@cor.po"),
 | |
| 				Login:  stringPtr("eai-doe"),
 | |
| 			},
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	cfg := &setting.Cfg{
 | |
| 		JWTAuthEnabled:                 true,
 | |
| 		JWTAuthHeaderName:              jwtHeaderName,
 | |
| 		JWTAuthEmailClaim:              "email",
 | |
| 		JWTAuthUsernameClaim:           "preferred_username",
 | |
| 		JWTAuthAutoSignUp:              true,
 | |
| 		JWTAuthAllowAssignGrafanaAdmin: true,
 | |
| 		JWTAuthRoleAttributeStrict:     true,
 | |
| 		JWTAuthRoleAttributePath:       "roles",
 | |
| 	}
 | |
| 	jwtClient := ProvideJWT(jwtService, cfg)
 | |
| 	validHTTPReq := &http.Request{
 | |
| 		Header: map[string][]string{
 | |
| 			jwtHeaderName: {"sample-token"}},
 | |
| 	}
 | |
| 
 | |
| 	id, err := jwtClient.Authenticate(context.Background(), &authn.Request{
 | |
| 		OrgID:       1,
 | |
| 		HTTPRequest: validHTTPReq,
 | |
| 		Resp:        nil,
 | |
| 	})
 | |
| 	require.NoError(t, err)
 | |
| 
 | |
| 	assert.EqualValues(t, wantID, id, fmt.Sprintf("%+v", id))
 | |
| }
 | |
| 
 | |
| func TestJWTClaimConfig(t *testing.T) {
 | |
| 	jwtService := &jwt.FakeJWTService{
 | |
| 		VerifyProvider: func(context.Context, string) (jwt.JWTClaims, error) {
 | |
| 			return jwt.JWTClaims{
 | |
| 				"sub":                "1234567890",
 | |
| 				"email":              "eai.doe@cor.po",
 | |
| 				"preferred_username": "eai-doe",
 | |
| 				"name":               "Eai Doe",
 | |
| 				"roles":              "Admin",
 | |
| 			}, nil
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	jwtHeaderName := "X-Forwarded-User"
 | |
| 
 | |
| 	cfg := &setting.Cfg{
 | |
| 		JWTAuthEnabled:                 true,
 | |
| 		JWTAuthHeaderName:              jwtHeaderName,
 | |
| 		JWTAuthAutoSignUp:              true,
 | |
| 		JWTAuthAllowAssignGrafanaAdmin: true,
 | |
| 		JWTAuthRoleAttributeStrict:     true,
 | |
| 		JWTAuthRoleAttributePath:       "roles",
 | |
| 	}
 | |
| 
 | |
| 	// #nosec G101 -- This is a dummy/test token
 | |
| 	token := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.XbPfbIHMI6arZ3Y922BhjWgQzWXcXNrz0ogtVhfEd2o"
 | |
| 
 | |
| 	type Dictionary map[string]interface{}
 | |
| 
 | |
| 	type testCase struct {
 | |
| 		desc                 string
 | |
| 		claimsConfigurations []Dictionary
 | |
| 		valid                bool
 | |
| 	}
 | |
| 
 | |
| 	testCases := []testCase{
 | |
| 		{
 | |
| 			desc: "JWT configuration with email and username claims",
 | |
| 			claimsConfigurations: []Dictionary{
 | |
| 				{
 | |
| 					"JWTAuthEmailClaim":    true,
 | |
| 					"JWTAuthUsernameClaim": true,
 | |
| 				},
 | |
| 			},
 | |
| 			valid: true,
 | |
| 		},
 | |
| 		{
 | |
| 			desc: "JWT configuration with email claim",
 | |
| 			claimsConfigurations: []Dictionary{
 | |
| 				{
 | |
| 					"JWTAuthEmailClaim":    true,
 | |
| 					"JWTAuthUsernameClaim": false,
 | |
| 				},
 | |
| 			},
 | |
| 			valid: true,
 | |
| 		},
 | |
| 		{
 | |
| 			desc: "JWT configuration with username claim",
 | |
| 			claimsConfigurations: []Dictionary{
 | |
| 				{
 | |
| 					"JWTAuthEmailClaim":    false,
 | |
| 					"JWTAuthUsernameClaim": true,
 | |
| 				},
 | |
| 			},
 | |
| 			valid: true,
 | |
| 		},
 | |
| 		{
 | |
| 			desc: "JWT configuration without email and username claims",
 | |
| 			claimsConfigurations: []Dictionary{
 | |
| 				{
 | |
| 					"JWTAuthEmailClaim":    false,
 | |
| 					"JWTAuthUsernameClaim": false,
 | |
| 				},
 | |
| 			},
 | |
| 			valid: false,
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	for _, tc := range testCases {
 | |
| 		t.Run(tc.desc, func(t *testing.T) {
 | |
| 			for _, claims := range tc.claimsConfigurations {
 | |
| 				cfg.JWTAuthEmailClaim = ""
 | |
| 				cfg.JWTAuthUsernameClaim = ""
 | |
| 
 | |
| 				if claims["JWTAuthEmailClaim"] == true {
 | |
| 					cfg.JWTAuthEmailClaim = "email"
 | |
| 				}
 | |
| 				if claims["JWTAuthUsernameClaim"] == true {
 | |
| 					cfg.JWTAuthUsernameClaim = "preferred_username"
 | |
| 				}
 | |
| 			}
 | |
| 		})
 | |
| 		httpReq := &http.Request{
 | |
| 			URL: &url.URL{RawQuery: "auth_token=" + token},
 | |
| 			Header: map[string][]string{
 | |
| 				jwtHeaderName: {token}},
 | |
| 		}
 | |
| 		jwtClient := ProvideJWT(jwtService, cfg)
 | |
| 		_, err := jwtClient.Authenticate(context.Background(), &authn.Request{
 | |
| 			OrgID:       1,
 | |
| 			HTTPRequest: httpReq,
 | |
| 			Resp:        nil,
 | |
| 		})
 | |
| 		if tc.valid {
 | |
| 			require.NoError(t, err)
 | |
| 		} else {
 | |
| 			require.Error(t, err)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestJWTTest(t *testing.T) {
 | |
| 	jwtService := &jwt.FakeJWTService{}
 | |
| 	jwtHeaderName := "X-Forwarded-User"
 | |
| 	// #nosec G101 -- This is dummy/test token
 | |
| 	validFormatToken := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.XbPfbIHMI6arZ3Y922BhjWgQzWXcXNrz0ogtVhfEd2o"
 | |
| 	invalidFormatToken := "sampletokeninvalid"
 | |
| 	// #nosec G101 -- This is dummy/test token
 | |
| 	missingSubToken := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE1MTYyMzkwMjJ9.8nYFUX869Y1mnDDDU4yL11aANgVRuifoxrE8BHZY1iE"
 | |
| 	// #nosec G101 -- This is dummy/test token
 | |
| 	emptySubToken := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJzdWIiOiIiLCJpYXQiOjE1MTYyMzkwMjJ9.tnwtOHK58d47dO4DHW4b9MzeToxa1kGiko5Oo887Rqc"
 | |
| 
 | |
| 	type testCase struct {
 | |
| 		desc          string
 | |
| 		reqHeaderName string
 | |
| 		cfgHeaderName string
 | |
| 		urlLogin      bool
 | |
| 		token         string
 | |
| 		want          bool
 | |
| 	}
 | |
| 
 | |
| 	testCases := []testCase{
 | |
| 		{
 | |
| 			desc:          "valid",
 | |
| 			reqHeaderName: jwtHeaderName,
 | |
| 			cfgHeaderName: jwtHeaderName,
 | |
| 			token:         validFormatToken,
 | |
| 			want:          true,
 | |
| 		},
 | |
| 		{
 | |
| 			desc:          "not in the right header",
 | |
| 			reqHeaderName: "other-header",
 | |
| 			cfgHeaderName: jwtHeaderName,
 | |
| 			token:         validFormatToken,
 | |
| 			want:          false,
 | |
| 		},
 | |
| 		{
 | |
| 			desc:          "valid format in Authorization",
 | |
| 			reqHeaderName: "Authorization",
 | |
| 			cfgHeaderName: "Authorization",
 | |
| 			token:         validFormatToken,
 | |
| 			want:          true,
 | |
| 		},
 | |
| 		{
 | |
| 			desc:          "invalid format in Authorization",
 | |
| 			reqHeaderName: "Authorization",
 | |
| 			cfgHeaderName: "Authorization",
 | |
| 			token:         invalidFormatToken,
 | |
| 			want:          false,
 | |
| 		},
 | |
| 		{
 | |
| 			desc:          "url login enabled",
 | |
| 			reqHeaderName: "other-header",
 | |
| 			cfgHeaderName: jwtHeaderName,
 | |
| 			urlLogin:      true,
 | |
| 			token:         validFormatToken,
 | |
| 			want:          true,
 | |
| 		},
 | |
| 		{
 | |
| 			desc:          "url login enabled",
 | |
| 			reqHeaderName: "other-header",
 | |
| 			cfgHeaderName: jwtHeaderName,
 | |
| 			urlLogin:      false,
 | |
| 			token:         validFormatToken,
 | |
| 			want:          false,
 | |
| 		},
 | |
| 		{
 | |
| 			desc:          "token without a sub claim",
 | |
| 			reqHeaderName: "Authorization",
 | |
| 			cfgHeaderName: "Authorization",
 | |
| 			token:         missingSubToken,
 | |
| 			want:          false,
 | |
| 		},
 | |
| 		{
 | |
| 			desc:          "token with an empty sub claim",
 | |
| 			reqHeaderName: "Authorization",
 | |
| 			cfgHeaderName: "Authorization",
 | |
| 			token:         emptySubToken,
 | |
| 			want:          false,
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	for _, tc := range testCases {
 | |
| 		t.Run(tc.desc, func(t *testing.T) {
 | |
| 			cfg := &setting.Cfg{
 | |
| 				JWTAuthEnabled:                 true,
 | |
| 				JWTAuthURLLogin:                tc.urlLogin,
 | |
| 				JWTAuthHeaderName:              tc.cfgHeaderName,
 | |
| 				JWTAuthAutoSignUp:              true,
 | |
| 				JWTAuthAllowAssignGrafanaAdmin: true,
 | |
| 				JWTAuthRoleAttributeStrict:     true,
 | |
| 			}
 | |
| 			jwtClient := ProvideJWT(jwtService, cfg)
 | |
| 			httpReq := &http.Request{
 | |
| 				URL: &url.URL{RawQuery: "auth_token=" + tc.token},
 | |
| 				Header: map[string][]string{
 | |
| 					tc.reqHeaderName: {tc.token}},
 | |
| 			}
 | |
| 
 | |
| 			got := jwtClient.Test(context.Background(), &authn.Request{
 | |
| 				OrgID:       1,
 | |
| 				HTTPRequest: httpReq,
 | |
| 				Resp:        nil,
 | |
| 			})
 | |
| 
 | |
| 			require.Equal(t, tc.want, got)
 | |
| 		})
 | |
| 	}
 | |
| }
 |