mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
SAML Role and Team sync (open source part) (#23391)
* SAML: add default params for role and team sync * SAML: add org_mapping option * SAML: support allowed_organizations option * Chore: expose RedirectWithError from HTTPServer * Chore: return RedirectResponse (fix superfluous response.writeheader message) * HTTPServer: expose ValidateRedirectTo() and CookieOptionsFromCfg() * Config: move SAML section to the enterprise
This commit is contained in:
parent
0205c42bfa
commit
f023e7a399
@ -423,47 +423,6 @@ tls_client_cert =
|
|||||||
tls_client_key =
|
tls_client_key =
|
||||||
tls_client_ca =
|
tls_client_ca =
|
||||||
|
|
||||||
#################################### SAML Auth ###########################
|
|
||||||
[auth.saml] # Enterprise only
|
|
||||||
# Defaults to false. If true, the feature is enabled
|
|
||||||
enabled = false
|
|
||||||
|
|
||||||
# Base64-encoded public X.509 certificate. Used to sign requests to the IdP
|
|
||||||
certificate =
|
|
||||||
|
|
||||||
# Path to the public X.509 certificate. Used to sign requests to the IdP
|
|
||||||
certificate_path =
|
|
||||||
|
|
||||||
# Base64-encoded private key. Used to decrypt assertions from the IdP
|
|
||||||
private_key =
|
|
||||||
|
|
||||||
# Path to the private key. Used to decrypt assertions from the IdP
|
|
||||||
private_key_path =
|
|
||||||
|
|
||||||
# Base64-encoded IdP SAML metadata XML. Used to verify and obtain binding locations from the IdP
|
|
||||||
idp_metadata =
|
|
||||||
|
|
||||||
# Path to the SAML metadata XML. Used to verify and obtain binding locations from the IdP
|
|
||||||
idp_metadata_path =
|
|
||||||
|
|
||||||
# URL to fetch SAML IdP metadata. Used to verify and obtain binding locations from the IdP
|
|
||||||
idp_metadata_url =
|
|
||||||
|
|
||||||
# Duration, since the IdP issued a response and the SP is allowed to process it. Defaults to 90 seconds
|
|
||||||
max_issue_delay = 90s
|
|
||||||
|
|
||||||
# Duration, for how long the SP's metadata should be valid. Defaults to 48 hours
|
|
||||||
metadata_valid_duration = 48h
|
|
||||||
|
|
||||||
# Friendly name or name of the attribute within the SAML assertion to use as the user's name
|
|
||||||
assertion_attribute_name = displayName
|
|
||||||
|
|
||||||
# Friendly name or name of the attribute within the SAML assertion to use as the user's login handle
|
|
||||||
assertion_attribute_login = mail
|
|
||||||
|
|
||||||
# Friendly name or name of the attribute within the SAML assertion to use as the user's email
|
|
||||||
assertion_attribute_email = mail
|
|
||||||
|
|
||||||
#################################### Basic Auth ##########################
|
#################################### Basic Auth ##########################
|
||||||
[auth.basic]
|
[auth.basic]
|
||||||
enabled = true
|
enabled = true
|
||||||
|
@ -413,47 +413,6 @@
|
|||||||
;tls_client_key =
|
;tls_client_key =
|
||||||
;tls_client_ca =
|
;tls_client_ca =
|
||||||
|
|
||||||
#################################### SAML Auth ###########################
|
|
||||||
[auth.saml] # Enterprise only
|
|
||||||
# Defaults to false. If true, the feature is enabled.
|
|
||||||
;enabled = false
|
|
||||||
|
|
||||||
# Base64-encoded public X.509 certificate. Used to sign requests to the IdP
|
|
||||||
;certificate =
|
|
||||||
|
|
||||||
# Path to the public X.509 certificate. Used to sign requests to the IdP
|
|
||||||
;certificate_path =
|
|
||||||
|
|
||||||
# Base64-encoded private key. Used to decrypt assertions from the IdP
|
|
||||||
;private_key =
|
|
||||||
|
|
||||||
;# Path to the private key. Used to decrypt assertions from the IdP
|
|
||||||
;private_key_path =
|
|
||||||
|
|
||||||
# Base64-encoded IdP SAML metadata XML. Used to verify and obtain binding locations from the IdP
|
|
||||||
;idp_metadata =
|
|
||||||
|
|
||||||
# Path to the SAML metadata XML. Used to verify and obtain binding locations from the IdP
|
|
||||||
;idp_metadata_path =
|
|
||||||
|
|
||||||
# URL to fetch SAML IdP metadata. Used to verify and obtain binding locations from the IdP
|
|
||||||
;idp_metadata_url =
|
|
||||||
|
|
||||||
# Duration, since the IdP issued a response and the SP is allowed to process it. Defaults to 90 seconds.
|
|
||||||
;max_issue_delay = 90s
|
|
||||||
|
|
||||||
# Duration, for how long the SP's metadata should be valid. Defaults to 48 hours.
|
|
||||||
;metadata_valid_duration = 48h
|
|
||||||
|
|
||||||
# Friendly name or name of the attribute within the SAML assertion to use as the user's name
|
|
||||||
;assertion_attribute_name = displayName
|
|
||||||
|
|
||||||
# Friendly name or name of the attribute within the SAML assertion to use as the user's login handle
|
|
||||||
;assertion_attribute_login = mail
|
|
||||||
|
|
||||||
# Friendly name or name of the attribute within the SAML assertion to use as the user's email
|
|
||||||
;assertion_attribute_email = mail
|
|
||||||
|
|
||||||
#################################### Basic Auth ##########################
|
#################################### Basic Auth ##########################
|
||||||
[auth.basic]
|
[auth.basic]
|
||||||
;enabled = true
|
;enabled = true
|
||||||
|
@ -29,7 +29,7 @@ var getViewIndex = func() string {
|
|||||||
return ViewIndex
|
return ViewIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hs *HTTPServer) validateRedirectTo(redirectTo string) error {
|
func (hs *HTTPServer) ValidateRedirectTo(redirectTo string) error {
|
||||||
to, err := url.Parse(redirectTo)
|
to, err := url.Parse(redirectTo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return login.ErrInvalidRedirectTo
|
return login.ErrInvalidRedirectTo
|
||||||
@ -45,7 +45,7 @@ func (hs *HTTPServer) validateRedirectTo(redirectTo string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hs *HTTPServer) cookieOptionsFromCfg() middleware.CookieOptions {
|
func (hs *HTTPServer) CookieOptionsFromCfg() middleware.CookieOptions {
|
||||||
path := "/"
|
path := "/"
|
||||||
if len(hs.Cfg.AppSubUrl) > 0 {
|
if len(hs.Cfg.AppSubUrl) > 0 {
|
||||||
path = hs.Cfg.AppSubUrl
|
path = hs.Cfg.AppSubUrl
|
||||||
@ -78,7 +78,7 @@ func (hs *HTTPServer) LoginView(c *models.ReqContext) {
|
|||||||
//therefore the loginError should be passed to the view data
|
//therefore the loginError should be passed to the view data
|
||||||
//and the view should return immediately before attempting
|
//and the view should return immediately before attempting
|
||||||
//to login again via OAuth and enter to a redirect loop
|
//to login again via OAuth and enter to a redirect loop
|
||||||
middleware.DeleteCookie(c.Resp, LoginErrorCookieName, hs.cookieOptionsFromCfg)
|
middleware.DeleteCookie(c.Resp, LoginErrorCookieName, hs.CookieOptionsFromCfg)
|
||||||
viewData.Settings["loginError"] = loginError
|
viewData.Settings["loginError"] = loginError
|
||||||
c.HTML(200, getViewIndex(), viewData)
|
c.HTML(200, getViewIndex(), viewData)
|
||||||
return
|
return
|
||||||
@ -100,13 +100,13 @@ func (hs *HTTPServer) LoginView(c *models.ReqContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if redirectTo, _ := url.QueryUnescape(c.GetCookie("redirect_to")); len(redirectTo) > 0 {
|
if redirectTo, _ := url.QueryUnescape(c.GetCookie("redirect_to")); len(redirectTo) > 0 {
|
||||||
if err := hs.validateRedirectTo(redirectTo); err != nil {
|
if err := hs.ValidateRedirectTo(redirectTo); err != nil {
|
||||||
// the user is already logged so instead of rendering the login page with error
|
// the user is already logged so instead of rendering the login page with error
|
||||||
// it should be redirected to the home page.
|
// it should be redirected to the home page.
|
||||||
log.Debug("Ignored invalid redirect_to cookie value: %v", redirectTo)
|
log.Debug("Ignored invalid redirect_to cookie value: %v", redirectTo)
|
||||||
redirectTo = hs.Cfg.AppSubUrl + "/"
|
redirectTo = hs.Cfg.AppSubUrl + "/"
|
||||||
}
|
}
|
||||||
middleware.DeleteCookie(c.Resp, "redirect_to", hs.cookieOptionsFromCfg)
|
middleware.DeleteCookie(c.Resp, "redirect_to", hs.CookieOptionsFromCfg)
|
||||||
c.Redirect(redirectTo)
|
c.Redirect(redirectTo)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -184,12 +184,12 @@ func (hs *HTTPServer) LoginPost(c *models.ReqContext, cmd dtos.LoginCommand) Res
|
|||||||
}
|
}
|
||||||
|
|
||||||
if redirectTo, _ := url.QueryUnescape(c.GetCookie("redirect_to")); len(redirectTo) > 0 {
|
if redirectTo, _ := url.QueryUnescape(c.GetCookie("redirect_to")); len(redirectTo) > 0 {
|
||||||
if err := hs.validateRedirectTo(redirectTo); err == nil {
|
if err := hs.ValidateRedirectTo(redirectTo); err == nil {
|
||||||
result["redirectUrl"] = redirectTo
|
result["redirectUrl"] = redirectTo
|
||||||
} else {
|
} else {
|
||||||
log.Info("Ignored invalid redirect_to cookie value: %v", redirectTo)
|
log.Info("Ignored invalid redirect_to cookie value: %v", redirectTo)
|
||||||
}
|
}
|
||||||
middleware.DeleteCookie(c.Resp, "redirect_to", hs.cookieOptionsFromCfg)
|
middleware.DeleteCookie(c.Resp, "redirect_to", hs.CookieOptionsFromCfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
metrics.MApiLoginPost.Inc()
|
metrics.MApiLoginPost.Inc()
|
||||||
@ -247,7 +247,25 @@ func (hs *HTTPServer) trySetEncryptedCookie(ctx *models.ReqContext, cookieName s
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
middleware.WriteCookie(ctx.Resp, cookieName, hex.EncodeToString(encryptedError), 60, hs.cookieOptionsFromCfg)
|
middleware.WriteCookie(ctx.Resp, cookieName, hex.EncodeToString(encryptedError), 60, hs.CookieOptionsFromCfg)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (hs *HTTPServer) redirectWithError(ctx *models.ReqContext, err error, v ...interface{}) {
|
||||||
|
ctx.Logger.Error(err.Error(), v...)
|
||||||
|
if err := hs.trySetEncryptedCookie(ctx, LoginErrorCookieName, err.Error(), 60); err != nil {
|
||||||
|
hs.log.Error("Failed to set encrypted cookie", "err", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Redirect(setting.AppSubUrl + "/login")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hs *HTTPServer) RedirectResponseWithError(ctx *models.ReqContext, err error, v ...interface{}) *RedirectResponse {
|
||||||
|
ctx.Logger.Error(err.Error(), v...)
|
||||||
|
if err := hs.trySetEncryptedCookie(ctx, LoginErrorCookieName, err.Error(), 60); err != nil {
|
||||||
|
hs.log.Error("Failed to set encrypted cookie", "err", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Redirect(setting.AppSubUrl + "/login")
|
||||||
|
}
|
||||||
|
@ -70,7 +70,7 @@ func (hs *HTTPServer) OAuthLogin(ctx *models.ReqContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
hashedState := hashStatecode(state, setting.OAuthService.OAuthInfos[name].ClientSecret)
|
hashedState := hashStatecode(state, setting.OAuthService.OAuthInfos[name].ClientSecret)
|
||||||
middleware.WriteCookie(ctx.Resp, OauthStateCookieName, hashedState, hs.Cfg.OAuthCookieMaxAge, hs.cookieOptionsFromCfg)
|
middleware.WriteCookie(ctx.Resp, OauthStateCookieName, hashedState, hs.Cfg.OAuthCookieMaxAge, hs.CookieOptionsFromCfg)
|
||||||
if setting.OAuthService.OAuthInfos[name].HostedDomain == "" {
|
if setting.OAuthService.OAuthInfos[name].HostedDomain == "" {
|
||||||
ctx.Redirect(connect.AuthCodeURL(state, oauth2.AccessTypeOnline))
|
ctx.Redirect(connect.AuthCodeURL(state, oauth2.AccessTypeOnline))
|
||||||
} else {
|
} else {
|
||||||
@ -82,7 +82,7 @@ func (hs *HTTPServer) OAuthLogin(ctx *models.ReqContext) {
|
|||||||
cookieState := ctx.GetCookie(OauthStateCookieName)
|
cookieState := ctx.GetCookie(OauthStateCookieName)
|
||||||
|
|
||||||
// delete cookie
|
// delete cookie
|
||||||
middleware.DeleteCookie(ctx.Resp, OauthStateCookieName, hs.cookieOptionsFromCfg)
|
middleware.DeleteCookie(ctx.Resp, OauthStateCookieName, hs.CookieOptionsFromCfg)
|
||||||
|
|
||||||
if cookieState == "" {
|
if cookieState == "" {
|
||||||
ctx.Handle(500, "login.OAuthLogin(missing saved state)", nil)
|
ctx.Handle(500, "login.OAuthLogin(missing saved state)", nil)
|
||||||
@ -227,8 +227,8 @@ func (hs *HTTPServer) OAuthLogin(ctx *models.ReqContext) {
|
|||||||
metrics.MApiLoginOAuth.Inc()
|
metrics.MApiLoginOAuth.Inc()
|
||||||
|
|
||||||
if redirectTo, _ := url.QueryUnescape(ctx.GetCookie("redirect_to")); len(redirectTo) > 0 {
|
if redirectTo, _ := url.QueryUnescape(ctx.GetCookie("redirect_to")); len(redirectTo) > 0 {
|
||||||
if err := hs.validateRedirectTo(redirectTo); err == nil {
|
if err := hs.ValidateRedirectTo(redirectTo); err == nil {
|
||||||
middleware.DeleteCookie(ctx.Resp, "redirect_to", hs.cookieOptionsFromCfg)
|
middleware.DeleteCookie(ctx.Resp, "redirect_to", hs.CookieOptionsFromCfg)
|
||||||
ctx.Redirect(redirectTo)
|
ctx.Redirect(redirectTo)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -242,12 +242,3 @@ func hashStatecode(code, seed string) string {
|
|||||||
hashBytes := sha256.Sum256([]byte(code + setting.SecretKey + seed))
|
hashBytes := sha256.Sum256([]byte(code + setting.SecretKey + seed))
|
||||||
return hex.EncodeToString(hashBytes[:])
|
return hex.EncodeToString(hashBytes[:])
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hs *HTTPServer) redirectWithError(ctx *models.ReqContext, err error, v ...interface{}) {
|
|
||||||
ctx.Logger.Error(err.Error(), v...)
|
|
||||||
if err := hs.trySetEncryptedCookie(ctx, LoginErrorCookieName, err.Error(), 60); err != nil {
|
|
||||||
oauthLogger.Error("Failed to set encrypted cookie", "err", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.Redirect(setting.AppSubUrl + "/login")
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user