mirror of
https://github.com/grafana/grafana.git
synced 2025-07-31 11:22:16 +08:00
Chore: add setting to skip org assignment for external users (#34834)
* Chore: add setting to skip org assignment for external users Introduce 'skip_org_role_update_sync' setting to skip any kind of org assignment during the login of external users. As a consequence manual organization assignments won't be overridden during the upsert of an external user. Part of #22605 * Chore: Rename skip_org_role_update_sync to oauth_skip_org_role_update_sync and relocate it to auth section * Chore: replace global setting access where possible
This commit is contained in:
@ -403,6 +403,9 @@ oauth_auto_login = false
|
|||||||
# OAuth state max age cookie duration in seconds. Defaults to 600 seconds.
|
# OAuth state max age cookie duration in seconds. Defaults to 600 seconds.
|
||||||
oauth_state_cookie_max_age = 600
|
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
|
# limit of api_key seconds to live before expiration
|
||||||
api_key_max_seconds_to_live = -1
|
api_key_max_seconds_to_live = -1
|
||||||
|
|
||||||
|
@ -397,6 +397,9 @@
|
|||||||
# OAuth state max age cookie duration in seconds. Defaults to 600 seconds.
|
# OAuth state max age cookie duration in seconds. Defaults to 600 seconds.
|
||||||
;oauth_state_cookie_max_age = 600
|
;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
|
# limit of api_key seconds to live before expiration
|
||||||
;api_key_max_seconds_to_live = -1
|
;api_key_max_seconds_to_live = -1
|
||||||
|
|
||||||
|
@ -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)
|
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.
|
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
|
### api_key_max_seconds_to_live
|
||||||
|
|
||||||
Limit of API key seconds to live before expiration. Default is -1 (unlimited).
|
Limit of API key seconds to live before expiration. Default is -1 (unlimited).
|
||||||
|
@ -11,6 +11,8 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/api/response"
|
"github.com/grafana/grafana/pkg/api/response"
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
"github.com/grafana/grafana/pkg/infra/metrics"
|
"github.com/grafana/grafana/pkg/infra/metrics"
|
||||||
@ -20,7 +22,6 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/models"
|
"github.com/grafana/grafana/pkg/models"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
"github.com/grafana/grafana/pkg/web"
|
"github.com/grafana/grafana/pkg/web"
|
||||||
"golang.org/x/oauth2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -131,7 +132,7 @@ func (hs *HTTPServer) OAuthLogin(ctx *models.ReqContext) response.Response {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
hashedState := hashStatecode(state, provider.ClientSecret)
|
hashedState := hs.hashStatecode(state, provider.ClientSecret)
|
||||||
cookies.WriteCookie(ctx.Resp, OauthStateCookieName, hashedState, hs.Cfg.OAuthCookieMaxAge, hs.CookieOptionsFromCfg)
|
cookies.WriteCookie(ctx.Resp, OauthStateCookieName, hashedState, hs.Cfg.OAuthCookieMaxAge, hs.CookieOptionsFromCfg)
|
||||||
if provider.HostedDomain != "" {
|
if provider.HostedDomain != "" {
|
||||||
opts = append(opts, oauth2.SetAuthURLParam("hd", 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
|
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)
|
oauthLogger.Info("state check", "queryState", queryState, "cookieState", cookieState)
|
||||||
if cookieState != queryState {
|
if cookieState != queryState {
|
||||||
hs.handleOAuthLoginError(ctx, loginInfo, LoginError{
|
hs.handleOAuthLoginError(ctx, loginInfo, LoginError{
|
||||||
@ -233,7 +234,7 @@ func (hs *HTTPServer) OAuthLogin(ctx *models.ReqContext) response.Response {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
loginInfo.ExternalUser = *buildExternalUserInfo(token, userInfo, name)
|
loginInfo.ExternalUser = *hs.buildExternalUserInfo(token, userInfo, name)
|
||||||
loginInfo.User, err = hs.SyncUser(ctx, &loginInfo.ExternalUser, connect)
|
loginInfo.User, err = hs.SyncUser(ctx, &loginInfo.ExternalUser, connect)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
hs.handleOAuthLoginErrorWithRedirect(ctx, loginInfo, err)
|
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
|
// 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")
|
oauthLogger.Debug("Building external user info from OAuth user info")
|
||||||
|
|
||||||
extUser := &models.ExternalUserInfo{
|
extUser := &models.ExternalUserInfo{
|
||||||
@ -278,13 +279,13 @@ func buildExternalUserInfo(token *oauth2.Token, userInfo *social.BasicUserInfo,
|
|||||||
Groups: userInfo.Groups,
|
Groups: userInfo.Groups,
|
||||||
}
|
}
|
||||||
|
|
||||||
if userInfo.Role != "" {
|
if userInfo.Role != "" && !hs.Cfg.OAuthSkipOrgRoleUpdateSync {
|
||||||
rt := models.RoleType(userInfo.Role)
|
rt := models.RoleType(userInfo.Role)
|
||||||
if rt.IsValid() {
|
if rt.IsValid() {
|
||||||
// The user will be assigned a role in either the auto-assigned organization or in the default one
|
// The user will be assigned a role in either the auto-assigned organization or in the default one
|
||||||
var orgID int64
|
var orgID int64
|
||||||
if setting.AutoAssignOrg && setting.AutoAssignOrgId > 0 {
|
if hs.Cfg.AutoAssignOrg && hs.Cfg.AutoAssignOrgId > 0 {
|
||||||
orgID = int64(setting.AutoAssignOrgId)
|
orgID = int64(hs.Cfg.AutoAssignOrgId)
|
||||||
plog.Debug("The user has a role assignment and organization membership is auto-assigned",
|
plog.Debug("The user has a role assignment and organization membership is auto-assigned",
|
||||||
"role", userInfo.Role, "orgId", orgID)
|
"role", userInfo.Role, "orgId", orgID)
|
||||||
} else {
|
} else {
|
||||||
@ -327,8 +328,8 @@ func (hs *HTTPServer) SyncUser(
|
|||||||
return cmd.Result, nil
|
return cmd.Result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func hashStatecode(code, seed string) string {
|
func (hs *HTTPServer) hashStatecode(code, seed string) string {
|
||||||
hashBytes := sha256.Sum256([]byte(code + setting.SecretKey + seed))
|
hashBytes := sha256.Sum256([]byte(code + hs.Cfg.SecretKey + seed))
|
||||||
return hex.EncodeToString(hashBytes[:])
|
return hex.EncodeToString(hashBytes[:])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -394,9 +394,10 @@ type Cfg struct {
|
|||||||
DefaultTheme string
|
DefaultTheme string
|
||||||
HomePage string
|
HomePage string
|
||||||
|
|
||||||
AutoAssignOrg bool
|
AutoAssignOrg bool
|
||||||
AutoAssignOrgId int
|
AutoAssignOrgId int
|
||||||
AutoAssignOrgRole string
|
AutoAssignOrgRole string
|
||||||
|
OAuthSkipOrgRoleUpdateSync bool
|
||||||
|
|
||||||
// ExpressionsEnabled specifies whether expressions are enabled.
|
// ExpressionsEnabled specifies whether expressions are enabled.
|
||||||
ExpressionsEnabled bool
|
ExpressionsEnabled bool
|
||||||
@ -1252,6 +1253,7 @@ func readAuthSettings(iniFile *ini.File, cfg *Cfg) (err error) {
|
|||||||
OAuthAutoLogin = auth.Key("oauth_auto_login").MustBool(false)
|
OAuthAutoLogin = auth.Key("oauth_auto_login").MustBool(false)
|
||||||
cfg.OAuthCookieMaxAge = auth.Key("oauth_state_cookie_max_age").MustInt(600)
|
cfg.OAuthCookieMaxAge = auth.Key("oauth_state_cookie_max_age").MustInt(600)
|
||||||
SignoutRedirectUrl = valueAsString(auth, "signout_redirect_url", "")
|
SignoutRedirectUrl = valueAsString(auth, "signout_redirect_url", "")
|
||||||
|
cfg.OAuthSkipOrgRoleUpdateSync = auth.Key("oauth_skip_org_role_update_sync").MustBool(false)
|
||||||
|
|
||||||
// SigV4
|
// SigV4
|
||||||
SigV4AuthEnabled = auth.Key("sigv4_auth_enabled").MustBool(false)
|
SigV4AuthEnabled = auth.Key("sigv4_auth_enabled").MustBool(false)
|
||||||
|
Reference in New Issue
Block a user