diff --git a/pkg/api/login_oauth.go b/pkg/api/login_oauth.go index 4edbe195186..b9715d4b7e3 100644 --- a/pkg/api/login_oauth.go +++ b/pkg/api/login_oauth.go @@ -339,16 +339,17 @@ func (hs *HTTPServer) SyncUser( connect social.SocialConnector, ) (*user.User, error) { oauthLogger.Debug("Syncing Grafana user with corresponding OAuth profile") + lookupParams := loginservice.UserLookupParams{} + if hs.Cfg.OAuthAllowInsecureEmailLookup { + lookupParams.Email = &extUser.Email + } + // add/update user in Grafana cmd := &loginservice.UpsertUserCommand{ - ReqContext: ctx, - ExternalUser: extUser, - SignupAllowed: connect.IsSignupAllowed(), - UserLookupParams: loginservice.UserLookupParams{ - Email: &extUser.Email, - UserID: nil, - Login: nil, - }, + ReqContext: ctx, + ExternalUser: extUser, + SignupAllowed: connect.IsSignupAllowed(), + UserLookupParams: lookupParams, } upsertedUser, err := hs.Login.UpsertUser(ctx.Req.Context(), cmd) diff --git a/pkg/services/authn/clients/oauth.go b/pkg/services/authn/clients/oauth.go index e6f4eeb95c6..0d774057bee 100644 --- a/pkg/services/authn/clients/oauth.go +++ b/pkg/services/authn/clients/oauth.go @@ -140,6 +140,11 @@ func (c *OAuth) Authenticate(ctx context.Context, r *authn.Request) (*authn.Iden return userInfo.Role, userInfo.IsGrafanaAdmin, nil }) + lookupParams := login.UserLookupParams{} + if c.cfg.OAuthAllowInsecureEmailLookup { + lookupParams.Email = &userInfo.Email + } + return &authn.Identity{ Login: userInfo.Login, Name: userInfo.Name, @@ -158,7 +163,7 @@ func (c *OAuth) Authenticate(ctx context.Context, r *authn.Request) (*authn.Iden AllowSignUp: c.connector.IsSignupAllowed(), // skip org role flag is checked and handled in the connector. For now we can skip the hook if no roles are passed SyncOrgRoles: len(orgRoles) > 0, - LookUpParams: login.UserLookupParams{Email: &userInfo.Email}, + LookUpParams: lookupParams, }, }, nil } diff --git a/pkg/services/authn/clients/oauth_test.go b/pkg/services/authn/clients/oauth_test.go index 4999e5d0293..da59f4e9f12 100644 --- a/pkg/services/authn/clients/oauth_test.go +++ b/pkg/services/authn/clients/oauth_test.go @@ -19,9 +19,10 @@ import ( func TestOAuth_Authenticate(t *testing.T) { type testCase struct { - desc string - req *authn.Request - oauthCfg *social.OAuthInfo + desc string + req *authn.Request + oauthCfg *social.OAuthInfo + allowInsecureTakeover bool addStateCookie bool stateCookieValue string @@ -140,16 +141,42 @@ func TestOAuth_Authenticate(t *testing.T) { AllowSignUp: true, FetchSyncedUser: true, SyncOrgRoles: true, - LookUpParams: login.UserLookupParams{Email: strPtr("some@email.com")}, + LookUpParams: login.UserLookupParams{}, }, }, }, + { + desc: "should return identity for valid request - and lookup user by email", + req: &authn.Request{HTTPRequest: &http.Request{ + Header: map[string][]string{}, + URL: mustParseURL("http://grafana.com/?state=some-state"), + }, + }, + oauthCfg: &social.OAuthInfo{UsePKCE: true}, + allowInsecureTakeover: true, + addStateCookie: true, + stateCookieValue: "some-state", + addPKCECookie: true, + pkceCookieValue: "some-pkce-value", + isEmailAllowed: true, + userInfo: &social.BasicUserInfo{ + Id: "123", + Name: "name", + Email: "some@email.com", + Role: "Admin", + Groups: []string{"grp1", "grp2"}, + }, + }, } for _, tt := range tests { t.Run(tt.desc, func(t *testing.T) { cfg := setting.NewCfg() + if tt.allowInsecureTakeover { + cfg.OAuthAllowInsecureEmailLookup = true + } + if tt.addStateCookie { v := tt.stateCookieValue if v != "" { diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index 3ab6d493951..3ffbc8964af 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -291,8 +291,9 @@ type Cfg struct { AuthProxySyncTTL int // OAuth - OAuthAutoLogin bool - OAuthCookieMaxAge int + OAuthAutoLogin bool + OAuthCookieMaxAge int + OAuthAllowInsecureEmailLookup bool // JWT Auth JWTAuthEnabled bool @@ -1444,6 +1445,8 @@ func readAuthSettings(iniFile *ini.File, cfg *Cfg) (err error) { return err } + cfg.OAuthAllowInsecureEmailLookup = auth.Key("oauth_allow_insecure_email_lookup").MustBool(false) + const defaultMaxLifetime = "30d" maxLifetimeDurationVal := valueAsString(auth, "login_maximum_lifetime_duration", defaultMaxLifetime) cfg.LoginMaxLifetime, err = gtime.ParseDuration(maxLifetimeDurationVal)