mirror of
https://github.com/grafana/grafana.git
synced 2026-08-13 06:34:55 -05:00
Auth: Use authn.Service for all tests (#72921)
* Dashboards: Fix tests when authn broker is enabled. StarService was not configured for tests, the call was guarded by !c.IsSignedIn * Change default to be anon user to match expectations from tests * OAuth: rewrite tests to work with authn.Service * Setup template renderer by default * Extract cookie options from cfg instead of relying on global variables * Fix test to work with authn service * Middleware: rewrite auth tests * Remvoe session cookie if we cannot refresh access token
This commit is contained in:
@@ -72,6 +72,7 @@ func loggedInUserScenarioWithRole(t *testing.T, desc string, method string, url
|
||||
sc.context.OrgID = testOrgID
|
||||
sc.context.Login = testUserLogin
|
||||
sc.context.OrgRole = role
|
||||
sc.context.IsAnonymous = false
|
||||
if sc.handlerFunc != nil {
|
||||
return sc.handlerFunc(sc.context)
|
||||
}
|
||||
@@ -212,7 +213,7 @@ func getContextHandler(t *testing.T, cfg *setting.Cfg) *contexthandler.ContextHa
|
||||
remoteCacheSvc, renderSvc, sqlStore, tracer, authProxy, loginService, nil,
|
||||
authenticator, usertest.NewUserServiceFake(), orgtest.NewOrgServiceFake(),
|
||||
nil, featuremgmt.WithFeatures(), &authntest.FakeService{
|
||||
ExpectedIdentity: &authn.Identity{OrgID: 1, ID: "user:1", SessionToken: &usertoken.UserToken{}}}, &anontest.FakeAnonymousSessionService{})
|
||||
ExpectedIdentity: &authn.Identity{IsAnonymous: true, SessionToken: &usertoken.UserToken{}}}, &anontest.FakeAnonymousSessionService{})
|
||||
|
||||
return ctxHdlr
|
||||
}
|
||||
@@ -310,6 +311,11 @@ func SetupAPITestServer(t *testing.T, opts ...APITestServerOption) *webtest.Serv
|
||||
hs.registerRoutes()
|
||||
|
||||
s := webtest.NewServer(t, hs.RouteRegister)
|
||||
|
||||
viewsPath, err := filepath.Abs("../../public/views")
|
||||
require.NoError(t, err)
|
||||
s.Mux.UseMiddleware(web.Renderer(viewsPath, "[[", "]]"))
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
|
||||
@@ -52,6 +52,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/publicdashboards"
|
||||
"github.com/grafana/grafana/pkg/services/publicdashboards/api"
|
||||
"github.com/grafana/grafana/pkg/services/quota/quotatest"
|
||||
"github.com/grafana/grafana/pkg/services/star/startest"
|
||||
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
|
||||
"github.com/grafana/grafana/pkg/services/team/teamtest"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
@@ -160,6 +161,7 @@ func TestDashboardAPIEndpoint(t *testing.T) {
|
||||
dashboardVersionService: fakeDashboardVersionService,
|
||||
Kinds: corekind.NewBase(nil),
|
||||
QuotaService: quotatest.New(false, nil),
|
||||
starService: startest.NewStarServiceFake(),
|
||||
userService: &usertest.FakeUserService{
|
||||
ExpectedUser: &user.User{ID: 1, Login: "test-user"},
|
||||
},
|
||||
@@ -933,6 +935,7 @@ func TestDashboardAPIEndpoint(t *testing.T) {
|
||||
DashboardService: dashboardService,
|
||||
Features: featuremgmt.WithFeatures(),
|
||||
Kinds: corekind.NewBase(nil),
|
||||
starService: startest.NewStarServiceFake(),
|
||||
}
|
||||
hs.callGetDashboard(sc)
|
||||
|
||||
@@ -1121,6 +1124,7 @@ func getDashboardShouldReturn200WithConfig(t *testing.T, sc *scenarioContext, pr
|
||||
DashboardService: dashboardService,
|
||||
Features: featuremgmt.WithFeatures(),
|
||||
Kinds: corekind.NewBase(nil),
|
||||
starService: startest.NewStarServiceFake(),
|
||||
}
|
||||
|
||||
hs.callGetDashboard(sc)
|
||||
|
||||
@@ -92,19 +92,20 @@ func (hs *HTTPServer) OAuthLogin(reqCtx *contextmodel.ReqContext) {
|
||||
return
|
||||
}
|
||||
|
||||
cookies.WriteCookie(reqCtx.Resp, OauthStateCookieName, redirect.Extra[authn.KeyOAuthState], hs.Cfg.OAuthCookieMaxAge, hs.CookieOptionsFromCfg)
|
||||
|
||||
if pkce := redirect.Extra[authn.KeyOAuthPKCE]; pkce != "" {
|
||||
cookies.WriteCookie(reqCtx.Resp, OauthPKCECookieName, pkce, hs.Cfg.OAuthCookieMaxAge, hs.CookieOptionsFromCfg)
|
||||
}
|
||||
|
||||
cookies.WriteCookie(reqCtx.Resp, OauthStateCookieName, redirect.Extra[authn.KeyOAuthState], hs.Cfg.OAuthCookieMaxAge, hs.CookieOptionsFromCfg)
|
||||
reqCtx.Redirect(redirect.URL)
|
||||
return
|
||||
}
|
||||
|
||||
identity, err := hs.authnService.Login(reqCtx.Req.Context(), authn.ClientWithPrefix(name), req)
|
||||
// NOTE: always delete these cookies, even if login failed
|
||||
cookies.DeleteCookie(reqCtx.Resp, OauthPKCECookieName, hs.CookieOptionsFromCfg)
|
||||
cookies.DeleteCookie(reqCtx.Resp, OauthStateCookieName, hs.CookieOptionsFromCfg)
|
||||
cookies.DeleteCookie(reqCtx.Resp, OauthPKCECookieName, hs.CookieOptionsFromCfg)
|
||||
|
||||
if err != nil {
|
||||
reqCtx.Redirect(hs.redirectURLWithErrorCookie(reqCtx, err))
|
||||
|
||||
+194
-219
@@ -1,237 +1,212 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/db"
|
||||
"github.com/grafana/grafana/pkg/infra/remotecache"
|
||||
"github.com/grafana/grafana/pkg/infra/usagestats"
|
||||
"github.com/grafana/grafana/pkg/login/social"
|
||||
"github.com/grafana/grafana/pkg/models/roletype"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/hooks"
|
||||
"github.com/grafana/grafana/pkg/services/licensing"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/models/usertoken"
|
||||
"github.com/grafana/grafana/pkg/services/authn"
|
||||
"github.com/grafana/grafana/pkg/services/authn/authntest"
|
||||
"github.com/grafana/grafana/pkg/services/secrets/fakes"
|
||||
"github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/web"
|
||||
)
|
||||
|
||||
func setupSocialHTTPServerWithConfig(t *testing.T, cfg *setting.Cfg) *HTTPServer {
|
||||
sqlStore := db.InitTestDB(t)
|
||||
features := featuremgmt.WithFeatures()
|
||||
|
||||
return &HTTPServer{
|
||||
Cfg: cfg,
|
||||
License: &licensing.OSSLicensingService{Cfg: cfg},
|
||||
SQLStore: sqlStore,
|
||||
SocialService: social.ProvideService(cfg, features, &usagestats.UsageStatsMock{}, supportbundlestest.NewFakeBundleService(), remotecache.NewFakeCacheStorage()),
|
||||
HooksService: hooks.ProvideService(),
|
||||
SecretsService: fakes.NewFakeSecretsService(),
|
||||
Features: features,
|
||||
}
|
||||
}
|
||||
|
||||
func setupOAuthTest(t *testing.T, cfg *setting.Cfg) *web.Mux {
|
||||
func setClientWithoutRedirectFollow(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
if cfg == nil {
|
||||
cfg = setting.NewCfg()
|
||||
}
|
||||
cfg.ErrTemplateName = "error-template"
|
||||
hs := setupSocialHTTPServerWithConfig(t, cfg)
|
||||
|
||||
m := web.New()
|
||||
m.Use(getContextHandler(t, cfg).Middleware)
|
||||
viewPath, err := filepath.Abs("../../public/views")
|
||||
require.NoError(t, err)
|
||||
|
||||
m.UseMiddleware(web.Renderer(viewPath, "[[", "]]"))
|
||||
|
||||
m.Get("/login/:name", hs.OAuthLogin)
|
||||
return m
|
||||
}
|
||||
|
||||
func TestOAuthLogin_UnknownProvider(t *testing.T) {
|
||||
m := setupOAuthTest(t, nil)
|
||||
req := httptest.NewRequest(http.MethodGet, "/login/notaprovider", nil)
|
||||
recorder := httptest.NewRecorder()
|
||||
|
||||
m.ServeHTTP(recorder, req)
|
||||
// expect to be redirected to /login
|
||||
assert.Equal(t, http.StatusFound, recorder.Code)
|
||||
assert.Equal(t, "/login", recorder.Header().Get("Location"))
|
||||
}
|
||||
|
||||
func TestOAuthLogin_Base(t *testing.T) {
|
||||
cfg := setting.NewCfg()
|
||||
sec := cfg.Raw.Section("auth.generic_oauth")
|
||||
_, err := sec.NewKey("enabled", "true")
|
||||
require.NoError(t, err)
|
||||
|
||||
m := setupOAuthTest(t, cfg)
|
||||
req := httptest.NewRequest(http.MethodGet, "/login/generic_oauth", nil)
|
||||
recorder := httptest.NewRecorder()
|
||||
|
||||
m.ServeHTTP(recorder, req)
|
||||
|
||||
assert.Equal(t, http.StatusFound, recorder.Code)
|
||||
|
||||
location := recorder.Header().Get("Location")
|
||||
assert.NotEmpty(t, location)
|
||||
|
||||
u, err := url.Parse(location)
|
||||
require.NoError(t, err)
|
||||
assert.False(t, u.Query().Has("code_challenge"))
|
||||
assert.False(t, u.Query().Has("code_challenge_method"))
|
||||
|
||||
resp := recorder.Result()
|
||||
require.NoError(t, resp.Body.Close())
|
||||
|
||||
cookies := resp.Cookies()
|
||||
var stateCookie *http.Cookie
|
||||
for _, c := range cookies {
|
||||
if c.Name == OauthStateCookieName {
|
||||
stateCookie = c
|
||||
}
|
||||
}
|
||||
require.NotNil(t, stateCookie)
|
||||
|
||||
req = httptest.NewRequest(
|
||||
http.MethodGet,
|
||||
(&url.URL{
|
||||
Path: "/login/generic_oauth",
|
||||
RawQuery: url.Values{
|
||||
"code": []string{"helloworld"},
|
||||
"state": []string{u.Query().Get("state")},
|
||||
}.Encode(),
|
||||
}).String(),
|
||||
nil,
|
||||
)
|
||||
req.AddCookie(stateCookie)
|
||||
recorder = httptest.NewRecorder()
|
||||
|
||||
m.ServeHTTP(recorder, req)
|
||||
// TODO: validate that 'creating a token works'
|
||||
assert.Equal(t, http.StatusInternalServerError, recorder.Code)
|
||||
assert.Contains(t, recorder.Body.String(), "login.OAuthLogin(NewTransportWithCode)")
|
||||
}
|
||||
|
||||
func TestOAuthLogin_UsePKCE(t *testing.T) {
|
||||
cfg := setting.NewCfg()
|
||||
sec := cfg.Raw.Section("auth.generic_oauth")
|
||||
_, err := sec.NewKey("enabled", "true")
|
||||
require.NoError(t, err)
|
||||
_, err = sec.NewKey("use_pkce", "true")
|
||||
require.NoError(t, err)
|
||||
|
||||
m := setupOAuthTest(t, cfg)
|
||||
req := httptest.NewRequest(http.MethodGet, "/login/generic_oauth", nil)
|
||||
recorder := httptest.NewRecorder()
|
||||
|
||||
m.ServeHTTP(recorder, req)
|
||||
|
||||
assert.Equal(t, http.StatusFound, recorder.Code)
|
||||
|
||||
location := recorder.Header().Get("Location")
|
||||
assert.NotEmpty(t, location)
|
||||
|
||||
u, err := url.Parse(location)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, u.Query().Has("code_challenge"))
|
||||
assert.Equal(t, "S256", u.Query().Get("code_challenge_method"))
|
||||
|
||||
resp := recorder.Result()
|
||||
require.NoError(t, resp.Body.Close())
|
||||
|
||||
var oauthCookie *http.Cookie
|
||||
for _, cookie := range resp.Cookies() {
|
||||
if cookie.Name == OauthPKCECookieName {
|
||||
oauthCookie = cookie
|
||||
}
|
||||
}
|
||||
require.NotNil(t, oauthCookie)
|
||||
|
||||
shasum := sha256.Sum256([]byte(oauthCookie.Value))
|
||||
assert.Equal(
|
||||
t,
|
||||
u.Query().Get("code_challenge"),
|
||||
base64.RawURLEncoding.EncodeToString(shasum[:]),
|
||||
)
|
||||
}
|
||||
|
||||
func TestOAuthLogin_BuildExternalUserInfo(t *testing.T) {
|
||||
t.Helper()
|
||||
cfgOAuthSkipRoleSync := setting.NewCfg()
|
||||
authOAuthSec := cfgOAuthSkipRoleSync.Raw.Section("auth")
|
||||
_, err := authOAuthSec.NewKey("oauth_skip_org_role_update_sync", "true")
|
||||
require.NoError(t, err)
|
||||
cfgOAuthSkipRoleSync.ErrTemplateName = "error-template"
|
||||
|
||||
cfgOAuthOrgRoleSync := setting.NewCfg()
|
||||
authOAutoWithoutSec := cfgOAuthOrgRoleSync.Raw.Section("auth")
|
||||
_, err = authOAutoWithoutSec.NewKey("oauth_skip_org_role_update_sync", "false")
|
||||
require.NoError(t, err)
|
||||
cfgOAuthOrgRoleSync.ErrTemplateName = "error-template"
|
||||
|
||||
testcases := []struct {
|
||||
name string
|
||||
cfg *setting.Cfg
|
||||
basicUser *social.BasicUserInfo
|
||||
expectedOrgRoles map[int64]org.RoleType
|
||||
}{
|
||||
{
|
||||
name: "should return empty map of org role mapping if the role for the basic info is empty",
|
||||
cfg: cfgOAuthOrgRoleSync,
|
||||
basicUser: &social.BasicUserInfo{
|
||||
Id: "1",
|
||||
Name: "first lastname",
|
||||
Email: "example@github.com",
|
||||
Login: "example",
|
||||
Role: "",
|
||||
},
|
||||
expectedOrgRoles: map[int64]org.RoleType{},
|
||||
},
|
||||
{
|
||||
name: "should set internal role if role exists and we are skipping org role sync",
|
||||
cfg: cfgOAuthSkipRoleSync,
|
||||
basicUser: &social.BasicUserInfo{
|
||||
Id: "1",
|
||||
Name: "first lastname",
|
||||
Email: "example@github.com",
|
||||
Login: "example",
|
||||
Role: roletype.RoleAdmin,
|
||||
},
|
||||
expectedOrgRoles: map[int64]org.RoleType{1: roletype.RoleAdmin},
|
||||
},
|
||||
{
|
||||
name: "should return empty external role, if the role for the basic info is empty",
|
||||
cfg: cfgOAuthSkipRoleSync,
|
||||
basicUser: &social.BasicUserInfo{
|
||||
Id: "1",
|
||||
Name: "first lastname",
|
||||
Email: "example@github.com",
|
||||
Login: "example",
|
||||
Role: "",
|
||||
},
|
||||
expectedOrgRoles: map[int64]org.RoleType{},
|
||||
old := http.DefaultClient
|
||||
http.DefaultClient = &http.Client{
|
||||
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||
return http.ErrUseLastResponse
|
||||
},
|
||||
}
|
||||
for _, tc := range testcases {
|
||||
t.Logf("%s", tc.name)
|
||||
cfg := tc.cfg
|
||||
hs := setupSocialHTTPServerWithConfig(t, cfg)
|
||||
externalUser := hs.buildExternalUserInfo(nil, tc.basicUser, "")
|
||||
require.Equal(t, tc.expectedOrgRoles, externalUser.OrgRoles)
|
||||
|
||||
t.Cleanup(func() {
|
||||
http.DefaultClient = old
|
||||
})
|
||||
}
|
||||
|
||||
func TestOAuthLogin_Redirect(t *testing.T) {
|
||||
type testCase struct {
|
||||
desc string
|
||||
expectedErr error
|
||||
expectedCode int
|
||||
expectedRedirect *authn.Redirect
|
||||
}
|
||||
|
||||
tests := []testCase{
|
||||
{
|
||||
desc: "should be redirected to /login when passing un-configured provider",
|
||||
expectedErr: authn.ErrClientNotConfigured,
|
||||
expectedCode: http.StatusFound,
|
||||
},
|
||||
{
|
||||
desc: "should be redirected to provider",
|
||||
expectedCode: http.StatusFound,
|
||||
expectedRedirect: &authn.Redirect{
|
||||
URL: "https://some-provider.com",
|
||||
Extra: map[string]string{
|
||||
authn.KeyOAuthState: "some-state",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "should set pkce cookie",
|
||||
expectedCode: http.StatusFound,
|
||||
expectedRedirect: &authn.Redirect{
|
||||
URL: "https://some-provider.com",
|
||||
Extra: map[string]string{
|
||||
authn.KeyOAuthState: "some-state",
|
||||
authn.KeyOAuthPKCE: "pkce-",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.desc, func(t *testing.T) {
|
||||
server := SetupAPITestServer(t, func(hs *HTTPServer) {
|
||||
hs.Cfg = setting.NewCfg()
|
||||
hs.SecretsService = fakes.NewFakeSecretsService()
|
||||
hs.authnService = &authntest.FakeService{
|
||||
ExpectedErr: tt.expectedErr,
|
||||
ExpectedRedirect: tt.expectedRedirect,
|
||||
}
|
||||
})
|
||||
|
||||
// we need to prevent the http.Client from following redirects
|
||||
setClientWithoutRedirectFollow(t)
|
||||
|
||||
res, err := server.Send(server.NewGetRequest("/login/generic_oauth"))
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, http.StatusFound, res.StatusCode)
|
||||
|
||||
// on every error we should get redirected to /login
|
||||
if tt.expectedErr != nil {
|
||||
assert.Equal(t, "/login", res.Header.Get("Location"))
|
||||
} else {
|
||||
// check that we get correct redirect url
|
||||
assert.Equal(t, tt.expectedRedirect.URL, res.Header.Get("Location"))
|
||||
|
||||
require.GreaterOrEqual(t, len(res.Cookies()), 1)
|
||||
if tt.expectedRedirect.Extra[authn.KeyOAuthPKCE] != "" {
|
||||
require.Len(t, res.Cookies(), 2)
|
||||
} else {
|
||||
require.Len(t, res.Cookies(), 1)
|
||||
}
|
||||
|
||||
require.GreaterOrEqual(t, len(res.Cookies()), 1)
|
||||
stateCookie := res.Cookies()[0]
|
||||
assert.Equal(t, OauthStateCookieName, stateCookie.Name)
|
||||
assert.Equal(t, tt.expectedRedirect.Extra[authn.KeyOAuthState], stateCookie.Value)
|
||||
|
||||
if tt.expectedRedirect.Extra[authn.KeyOAuthPKCE] != "" {
|
||||
require.Len(t, res.Cookies(), 2)
|
||||
pkceCookie := res.Cookies()[1]
|
||||
assert.Equal(t, OauthPKCECookieName, pkceCookie.Name)
|
||||
assert.Equal(t, tt.expectedRedirect.Extra[authn.KeyOAuthPKCE], pkceCookie.Value)
|
||||
} else {
|
||||
require.Len(t, res.Cookies(), 1)
|
||||
}
|
||||
|
||||
require.NoError(t, res.Body.Close())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestOAuthLogin_AuthorizationCode(t *testing.T) {
|
||||
type testCase struct {
|
||||
desc string
|
||||
expectedErr error
|
||||
expectedIdentity *authn.Identity
|
||||
}
|
||||
|
||||
tests := []testCase{
|
||||
{
|
||||
desc: "should redirect to /login on error",
|
||||
expectedErr: errors.New("some error"),
|
||||
},
|
||||
{
|
||||
desc: "should redirect to / and set session cookie on successful authentication",
|
||||
expectedIdentity: &authn.Identity{
|
||||
SessionToken: &usertoken.UserToken{UnhashedToken: "some-token"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.desc, func(t *testing.T) {
|
||||
var cfg *setting.Cfg
|
||||
server := SetupAPITestServer(t, func(hs *HTTPServer) {
|
||||
cfg = setting.NewCfg()
|
||||
hs.Cfg = cfg
|
||||
hs.Cfg.LoginCookieName = "some_name"
|
||||
hs.SecretsService = fakes.NewFakeSecretsService()
|
||||
hs.authnService = &authntest.FakeService{
|
||||
ExpectedErr: tt.expectedErr,
|
||||
ExpectedIdentity: tt.expectedIdentity,
|
||||
}
|
||||
})
|
||||
|
||||
// we need to prevent the http.Client from following redirects
|
||||
setClientWithoutRedirectFollow(t)
|
||||
|
||||
res, err := server.Send(server.NewGetRequest("/login/generic_oauth?code=code"))
|
||||
require.NoError(t, err)
|
||||
|
||||
require.GreaterOrEqual(t, len(res.Cookies()), 3)
|
||||
|
||||
// make sure oauth state cookie is deleted
|
||||
assert.Equal(t, OauthStateCookieName, res.Cookies()[0].Name)
|
||||
assert.Equal(t, "", res.Cookies()[0].Value)
|
||||
assert.Equal(t, -1, res.Cookies()[0].MaxAge)
|
||||
|
||||
// make sure oauth pkce cookie is deleted
|
||||
assert.Equal(t, OauthPKCECookieName, res.Cookies()[1].Name)
|
||||
assert.Equal(t, "", res.Cookies()[1].Value)
|
||||
assert.Equal(t, -1, res.Cookies()[1].MaxAge)
|
||||
|
||||
if tt.expectedErr != nil {
|
||||
require.Len(t, res.Cookies(), 3)
|
||||
assert.Equal(t, http.StatusFound, res.StatusCode)
|
||||
assert.Equal(t, "/login", res.Header.Get("Location"))
|
||||
assert.Equal(t, loginErrorCookieName, res.Cookies()[2].Name)
|
||||
} else {
|
||||
require.Len(t, res.Cookies(), 4)
|
||||
assert.Equal(t, http.StatusFound, res.StatusCode)
|
||||
assert.Equal(t, "/", res.Header.Get("Location"))
|
||||
|
||||
// verify session expiry cookie is set
|
||||
assert.Equal(t, cfg.LoginCookieName, res.Cookies()[2].Name)
|
||||
assert.Equal(t, "grafana_session_expiry", res.Cookies()[3].Name)
|
||||
}
|
||||
|
||||
require.NoError(t, res.Body.Close())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestOAuthLogin_Error(t *testing.T) {
|
||||
server := SetupAPITestServer(t, func(hs *HTTPServer) {
|
||||
hs.Cfg = setting.NewCfg()
|
||||
hs.SecretsService = fakes.NewFakeSecretsService()
|
||||
})
|
||||
|
||||
setClientWithoutRedirectFollow(t)
|
||||
|
||||
res, err := server.Send(server.NewGetRequest("/login/azuread?error=someerror"))
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, http.StatusFound, res.StatusCode)
|
||||
assert.Equal(t, "/login", res.Header.Get("Location"))
|
||||
|
||||
require.Len(t, res.Cookies(), 1)
|
||||
errCookie := res.Cookies()[0]
|
||||
assert.Equal(t, loginErrorCookieName, errCookie.Name)
|
||||
require.NoError(t, res.Body.Close())
|
||||
}
|
||||
|
||||
+14
-131
@@ -20,14 +20,15 @@ import (
|
||||
"github.com/grafana/grafana/pkg/api/routing"
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/login"
|
||||
"github.com/grafana/grafana/pkg/login/social"
|
||||
"github.com/grafana/grafana/pkg/models/usertoken"
|
||||
"github.com/grafana/grafana/pkg/services/auth/authtest"
|
||||
"github.com/grafana/grafana/pkg/services/authn"
|
||||
"github.com/grafana/grafana/pkg/services/authn/authntest"
|
||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/hooks"
|
||||
"github.com/grafana/grafana/pkg/services/licensing"
|
||||
loginservice "github.com/grafana/grafana/pkg/services/login"
|
||||
"github.com/grafana/grafana/pkg/services/navtree"
|
||||
"github.com/grafana/grafana/pkg/services/secrets"
|
||||
"github.com/grafana/grafana/pkg/services/secrets/fakes"
|
||||
@@ -317,11 +318,15 @@ func TestLoginPostRedirect(t *testing.T) {
|
||||
|
||||
fakeViewIndex(t)
|
||||
sc := setupScenarioContext(t, "/login")
|
||||
|
||||
hs := &HTTPServer{
|
||||
log: log.NewNopLogger(),
|
||||
Cfg: setting.NewCfg(),
|
||||
HooksService: &hooks.HooksService{},
|
||||
License: &licensing.OSSLicensingService{},
|
||||
log: log.NewNopLogger(),
|
||||
Cfg: setting.NewCfg(),
|
||||
HooksService: &hooks.HooksService{},
|
||||
License: &licensing.OSSLicensingService{},
|
||||
authnService: &authntest.FakeService{
|
||||
ExpectedIdentity: &authn.Identity{ID: "user:42", SessionToken: &usertoken.UserToken{}},
|
||||
},
|
||||
AuthTokenService: authtest.NewFakeUserAuthTokenService(),
|
||||
Features: featuremgmt.WithFeatures(),
|
||||
}
|
||||
@@ -333,13 +338,6 @@ func TestLoginPostRedirect(t *testing.T) {
|
||||
return hs.LoginPost(c)
|
||||
})
|
||||
|
||||
user := &user.User{
|
||||
ID: 42,
|
||||
Email: "",
|
||||
}
|
||||
|
||||
hs.authenticator = &fakeAuthenticator{user, "", nil}
|
||||
|
||||
redirectCases := []redirectCase{
|
||||
{
|
||||
desc: "grafana relative url without subpath",
|
||||
@@ -429,6 +427,9 @@ func TestLoginPostRedirect(t *testing.T) {
|
||||
hs.Cfg.AppSubURL = c.appSubURL
|
||||
|
||||
t.Run(c.desc, func(t *testing.T) {
|
||||
if c.desc == "grafana invalid relative url starting with subpath" {
|
||||
fmt.Println()
|
||||
}
|
||||
expCookiePath := "/"
|
||||
if len(hs.Cfg.AppSubURL) > 0 {
|
||||
expCookiePath = hs.Cfg.AppSubURL
|
||||
@@ -640,112 +641,6 @@ func setupAuthProxyLoginTest(t *testing.T, enableLoginToken bool) *scenarioConte
|
||||
return sc
|
||||
}
|
||||
|
||||
type loginHookTest struct {
|
||||
info *loginservice.LoginInfo
|
||||
}
|
||||
|
||||
func (r *loginHookTest) LoginHook(loginInfo *loginservice.LoginInfo, req *contextmodel.ReqContext) {
|
||||
r.info = loginInfo
|
||||
}
|
||||
|
||||
// TOREMOVE: remove with context handler auth
|
||||
func TestLoginPostRunLokingHook(t *testing.T) {
|
||||
sc := setupScenarioContext(t, "/login")
|
||||
hookService := &hooks.HooksService{}
|
||||
hs := &HTTPServer{
|
||||
log: log.New("test"),
|
||||
Cfg: sc.cfg,
|
||||
License: &licensing.OSSLicensingService{},
|
||||
AuthTokenService: authtest.NewFakeUserAuthTokenService(),
|
||||
Features: featuremgmt.WithFeatures(),
|
||||
HooksService: hookService,
|
||||
authnService: sc.ctxHdlr.AuthnService,
|
||||
}
|
||||
|
||||
sc.cfg.AuthBrokerEnabled = false
|
||||
|
||||
sc.defaultHandler = routing.Wrap(func(c *contextmodel.ReqContext) response.Response {
|
||||
c.Req.Header.Set("Content-Type", "application/json")
|
||||
c.Req.Body = io.NopCloser(bytes.NewBufferString(`{"user":"admin","password":"admin"}`))
|
||||
x := hs.LoginPost(c)
|
||||
return x
|
||||
})
|
||||
|
||||
testHook := loginHookTest{}
|
||||
hookService.AddLoginHook(testHook.LoginHook)
|
||||
|
||||
testUser := &user.User{
|
||||
ID: 42,
|
||||
Email: "",
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
authUser *user.User
|
||||
authModule string
|
||||
authErr error
|
||||
info loginservice.LoginInfo
|
||||
}{
|
||||
{
|
||||
desc: "invalid credentials",
|
||||
authErr: login.ErrInvalidCredentials,
|
||||
info: loginservice.LoginInfo{
|
||||
AuthModule: "",
|
||||
HTTPStatus: 401,
|
||||
Error: login.ErrInvalidCredentials,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "user disabled",
|
||||
authErr: login.ErrUserDisabled,
|
||||
info: loginservice.LoginInfo{
|
||||
AuthModule: "",
|
||||
HTTPStatus: 401,
|
||||
Error: login.ErrUserDisabled,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "valid Grafana user",
|
||||
authUser: testUser,
|
||||
authModule: "grafana",
|
||||
info: loginservice.LoginInfo{
|
||||
AuthModule: "grafana",
|
||||
User: testUser,
|
||||
HTTPStatus: 200,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "valid LDAP user",
|
||||
authUser: testUser,
|
||||
authModule: loginservice.LDAPAuthModule,
|
||||
info: loginservice.LoginInfo{
|
||||
AuthModule: loginservice.LDAPAuthModule,
|
||||
User: testUser,
|
||||
HTTPStatus: 200,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range testCases {
|
||||
t.Run(c.desc, func(t *testing.T) {
|
||||
hs.authenticator = &fakeAuthenticator{c.authUser, c.authModule, c.authErr}
|
||||
sc.m.Post(sc.url, sc.defaultHandler)
|
||||
sc.fakeReqNoAssertions("POST", sc.url).exec()
|
||||
|
||||
info := testHook.info
|
||||
assert.Equal(t, c.info.AuthModule, info.AuthModule)
|
||||
assert.Equal(t, "admin", info.LoginUsername)
|
||||
assert.Equal(t, c.info.HTTPStatus, info.HTTPStatus)
|
||||
assert.Equal(t, c.info.Error, info.Error)
|
||||
|
||||
if c.info.User != nil {
|
||||
require.NotEmpty(t, info.User)
|
||||
assert.Equal(t, c.info.User.ID, info.User.ID)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type mockSocialService struct {
|
||||
oAuthInfo *social.OAuthInfo
|
||||
oAuthInfos map[string]*social.OAuthInfo
|
||||
@@ -774,15 +669,3 @@ func (m *mockSocialService) GetOAuthHttpClient(name string) (*http.Client, error
|
||||
func (m *mockSocialService) GetConnector(string) (social.SocialConnector, error) {
|
||||
return m.socialConnector, m.err
|
||||
}
|
||||
|
||||
type fakeAuthenticator struct {
|
||||
ExpectedUser *user.User
|
||||
ExpectedAuthModule string
|
||||
ExpectedError error
|
||||
}
|
||||
|
||||
func (fa *fakeAuthenticator) AuthenticateUser(c context.Context, query *loginservice.LoginUserQuery) error {
|
||||
query.User = fa.ExpectedUser
|
||||
query.AuthModule = fa.ExpectedAuthModule
|
||||
return fa.ExpectedError
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user