diff --git a/conf/defaults.ini b/conf/defaults.ini index b514f480436..ad610c4d112 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -400,6 +400,9 @@ oauth_auto_login = false # OAuth state max age cookie duration in seconds. Defaults to 600 seconds. oauth_state_cookie_max_age = 600 +# Skip forced assignment of OrgID 1 or 'auto_assign_org_id' for social logins +oauth_skip_org_role_update_sync = false + # limit of api_key seconds to live before expiration api_key_max_seconds_to_live = -1 diff --git a/conf/sample.ini b/conf/sample.ini index 710a8ea4810..9f1e822933d 100644 --- a/conf/sample.ini +++ b/conf/sample.ini @@ -394,6 +394,9 @@ # OAuth state max age cookie duration in seconds. Defaults to 600 seconds. ;oauth_state_cookie_max_age = 600 +# Skip forced assignment of OrgID 1 or 'auto_assign_org_id' for social logins +;oauth_skip_org_role_update_sync = false + # limit of api_key seconds to live before expiration ;api_key_max_seconds_to_live = -1 diff --git a/docs/sources/administration/configuration.md b/docs/sources/administration/configuration.md index 97d454d71ad..9287a8a056c 100644 --- a/docs/sources/administration/configuration.md +++ b/docs/sources/administration/configuration.md @@ -751,6 +751,12 @@ This setting is ignored if multiple OAuth providers are configured. Default is ` How many seconds the OAuth state cookie lives before being deleted. Default is `600` (seconds) Administrators can increase this if they experience OAuth login state mismatch errors. +### oauth_skip_org_role_update_sync + +Skip forced assignment of OrgID `1` or `auto_assign_org_id` for external logins. Default is `false`. +Use this setting to distribute users with external login to multiple organizations. +Otherwise, the users' organization would get reset on every new login, for example, via AzureAD. + ### api_key_max_seconds_to_live Limit of API key seconds to live before expiration. Default is -1 (unlimited). diff --git a/pkg/api/login_oauth.go b/pkg/api/login_oauth.go index 8060092a4a6..92d19b4afdf 100644 --- a/pkg/api/login_oauth.go +++ b/pkg/api/login_oauth.go @@ -11,6 +11,8 @@ import ( "net/http" "net/url" + "golang.org/x/oauth2" + "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/infra/log" @@ -21,7 +23,6 @@ import ( "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/web" - "golang.org/x/oauth2" ) var ( @@ -132,7 +133,7 @@ func (hs *HTTPServer) OAuthLogin(ctx *models.ReqContext) response.Response { return nil } - hashedState := hashStatecode(state, provider.ClientSecret) + hashedState := hs.hashStatecode(state, provider.ClientSecret) cookies.WriteCookie(ctx.Resp, OauthStateCookieName, hashedState, hs.Cfg.OAuthCookieMaxAge, hs.CookieOptionsFromCfg) if provider.HostedDomain != "" { opts = append(opts, oauth2.SetAuthURLParam("hd", provider.HostedDomain)) @@ -155,7 +156,7 @@ func (hs *HTTPServer) OAuthLogin(ctx *models.ReqContext) response.Response { return nil } - queryState := hashStatecode(ctx.Query("state"), provider.ClientSecret) + queryState := hs.hashStatecode(ctx.Query("state"), provider.ClientSecret) oauthLogger.Info("state check", "queryState", queryState, "cookieState", cookieState) if cookieState != queryState { hs.handleOAuthLoginError(ctx, loginInfo, LoginError{ @@ -234,7 +235,7 @@ func (hs *HTTPServer) OAuthLogin(ctx *models.ReqContext) response.Response { return nil } - loginInfo.ExternalUser = *buildExternalUserInfo(token, userInfo, name) + loginInfo.ExternalUser = *hs.buildExternalUserInfo(token, userInfo, name) loginInfo.User, err = syncUser(ctx, &loginInfo.ExternalUser, connect) if err != nil { hs.handleOAuthLoginErrorWithRedirect(ctx, loginInfo, err) @@ -265,7 +266,7 @@ func (hs *HTTPServer) OAuthLogin(ctx *models.ReqContext) response.Response { } // buildExternalUserInfo returns a ExternalUserInfo struct from OAuth user profile -func buildExternalUserInfo(token *oauth2.Token, userInfo *social.BasicUserInfo, name string) *models.ExternalUserInfo { +func (hs *HTTPServer) buildExternalUserInfo(token *oauth2.Token, userInfo *social.BasicUserInfo, name string) *models.ExternalUserInfo { oauthLogger.Debug("Building external user info from OAuth user info") extUser := &models.ExternalUserInfo{ @@ -279,13 +280,13 @@ func buildExternalUserInfo(token *oauth2.Token, userInfo *social.BasicUserInfo, Groups: userInfo.Groups, } - if userInfo.Role != "" { + if userInfo.Role != "" && !hs.Cfg.OAuthSkipOrgRoleUpdateSync { rt := models.RoleType(userInfo.Role) if rt.IsValid() { // The user will be assigned a role in either the auto-assigned organization or in the default one var orgID int64 - if setting.AutoAssignOrg && setting.AutoAssignOrgId > 0 { - orgID = int64(setting.AutoAssignOrgId) + if hs.Cfg.AutoAssignOrg && hs.Cfg.AutoAssignOrgId > 0 { + orgID = int64(hs.Cfg.AutoAssignOrgId) plog.Debug("The user has a role assignment and organization membership is auto-assigned", "role", userInfo.Role, "orgId", orgID) } else { @@ -327,8 +328,8 @@ func syncUser( return cmd.Result, nil } -func hashStatecode(code, seed string) string { - hashBytes := sha256.Sum256([]byte(code + setting.SecretKey + seed)) +func (hs *HTTPServer) hashStatecode(code, seed string) string { + hashBytes := sha256.Sum256([]byte(code + hs.Cfg.SecretKey + seed)) return hex.EncodeToString(hashBytes[:]) } diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index 70c8de3de05..6436680646a 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -393,9 +393,10 @@ type Cfg struct { DefaultTheme string HomePage string - AutoAssignOrg bool - AutoAssignOrgId int - AutoAssignOrgRole string + AutoAssignOrg bool + AutoAssignOrgId int + AutoAssignOrgRole string + OAuthSkipOrgRoleUpdateSync bool // ExpressionsEnabled specifies whether expressions are enabled. ExpressionsEnabled bool @@ -1256,6 +1257,7 @@ func readAuthSettings(iniFile *ini.File, cfg *Cfg) (err error) { OAuthAutoLogin = auth.Key("oauth_auto_login").MustBool(false) cfg.OAuthCookieMaxAge = auth.Key("oauth_state_cookie_max_age").MustInt(600) SignoutRedirectUrl = valueAsString(auth, "signout_redirect_url", "") + cfg.OAuthSkipOrgRoleUpdateSync = auth.Key("oauth_skip_org_role_update_sync").MustBool(false) // SigV4 SigV4AuthEnabled = auth.Key("sigv4_auth_enabled").MustBool(false)