diff --git a/conf/defaults.ini b/conf/defaults.ini index 738b33889ac..c87e9b467fb 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -403,6 +403,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 759fd56af35..96f19917bf1 100644 --- a/conf/sample.ini +++ b/conf/sample.ini @@ -397,6 +397,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 1600c0420b5..9440a278a46 100644 --- a/docs/sources/administration/configuration.md +++ b/docs/sources/administration/configuration.md @@ -766,6 +766,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 82d1e76fbbb..501c0ad6095 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/infra/log" "github.com/grafana/grafana/pkg/infra/metrics" @@ -20,7 +22,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 ( @@ -131,7 +132,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)) @@ -154,7 +155,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{ @@ -233,7 +234,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 = hs.SyncUser(ctx, &loginInfo.ExternalUser, connect) if err != nil { hs.handleOAuthLoginErrorWithRedirect(ctx, loginInfo, err) @@ -264,7 +265,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{ @@ -278,13 +279,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 (hs *HTTPServer) 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 51639e6748c..3728affc65a 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -394,9 +394,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 @@ -1252,6 +1253,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)