diff --git a/pkg/api/login_oauth.go b/pkg/api/login_oauth.go index 3176164fd34..14154536bcc 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 b459dc69b26..5109b89aa68 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 84329f87d56..b42017d4fc1 100644 --- a/pkg/services/authn/clients/oauth_test.go +++ b/pkg/services/authn/clients/oauth_test.go @@ -20,9 +20,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 @@ -127,6 +128,45 @@ func TestOAuth_Authenticate(t *testing.T) { Role: "Admin", Groups: []string{"grp1", "grp2"}, }, + expectedIdentity: &authn.Identity{ + Email: "some@email.com", + AuthModule: "oauth_azuread", + AuthID: "123", + Name: "name", + Groups: []string{"grp1", "grp2"}, + OAuthToken: &oauth2.Token{}, + OrgRoles: map[int64]org.RoleType{1: org.RoleAdmin}, + ClientParams: authn.ClientParams{ + SyncUser: true, + SyncTeams: true, + AllowSignUp: true, + FetchSyncedUser: true, + SyncOrgRoles: true, + 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"}, + }, expectedIdentity: &authn.Identity{ Email: "some@email.com", AuthModule: "oauth_azuread", @@ -151,6 +191,10 @@ func TestOAuth_Authenticate(t *testing.T) { 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 e714cad97c9..bc9e051dc03 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -301,8 +301,9 @@ type Cfg struct { AuthProxySyncTTL int // OAuth - OAuthAutoLogin bool - OAuthCookieMaxAge int + OAuthAutoLogin bool + OAuthCookieMaxAge int + OAuthAllowInsecureEmailLookup bool // JWT Auth JWTAuthEnabled bool @@ -1477,7 +1478,6 @@ func readAuthSettings(iniFile *ini.File, cfg *Cfg) (err error) { auth := iniFile.Section("auth") cfg.LoginCookieName = valueAsString(auth, "login_cookie_name", "grafana_session") - const defaultMaxInactiveLifetime = "7d" maxInactiveDurationVal := valueAsString(auth, "login_maximum_inactive_lifetime_duration", defaultMaxInactiveLifetime) cfg.LoginMaxInactiveLifetime, err = gtime.ParseDuration(maxInactiveDurationVal) @@ -1485,6 +1485,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)