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:
Alexander Zobnin 2020-04-17 10:48:37 +03:00 committed by GitHub
parent 0205c42bfa
commit f023e7a399
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 30 additions and 103 deletions

View File

@ -423,47 +423,6 @@ tls_client_cert =
tls_client_key =
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 ##########################
[auth.basic]
enabled = true

View File

@ -413,47 +413,6 @@
;tls_client_key =
;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 ##########################
[auth.basic]
;enabled = true

View File

@ -29,7 +29,7 @@ var getViewIndex = func() string {
return ViewIndex
}
func (hs *HTTPServer) validateRedirectTo(redirectTo string) error {
func (hs *HTTPServer) ValidateRedirectTo(redirectTo string) error {
to, err := url.Parse(redirectTo)
if err != nil {
return login.ErrInvalidRedirectTo
@ -45,7 +45,7 @@ func (hs *HTTPServer) validateRedirectTo(redirectTo string) error {
return nil
}
func (hs *HTTPServer) cookieOptionsFromCfg() middleware.CookieOptions {
func (hs *HTTPServer) CookieOptionsFromCfg() middleware.CookieOptions {
path := "/"
if len(hs.Cfg.AppSubUrl) > 0 {
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
//and the view should return immediately before attempting
//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
c.HTML(200, getViewIndex(), viewData)
return
@ -100,13 +100,13 @@ func (hs *HTTPServer) LoginView(c *models.ReqContext) {
}
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
// it should be redirected to the home page.
log.Debug("Ignored invalid redirect_to cookie value: %v", redirectTo)
redirectTo = hs.Cfg.AppSubUrl + "/"
}
middleware.DeleteCookie(c.Resp, "redirect_to", hs.cookieOptionsFromCfg)
middleware.DeleteCookie(c.Resp, "redirect_to", hs.CookieOptionsFromCfg)
c.Redirect(redirectTo)
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 err := hs.validateRedirectTo(redirectTo); err == nil {
if err := hs.ValidateRedirectTo(redirectTo); err == nil {
result["redirectUrl"] = redirectTo
} else {
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()
@ -247,7 +247,25 @@ func (hs *HTTPServer) trySetEncryptedCookie(ctx *models.ReqContext, cookieName s
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
}
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")
}

View File

@ -70,7 +70,7 @@ func (hs *HTTPServer) OAuthLogin(ctx *models.ReqContext) {
}
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 == "" {
ctx.Redirect(connect.AuthCodeURL(state, oauth2.AccessTypeOnline))
} else {
@ -82,7 +82,7 @@ func (hs *HTTPServer) OAuthLogin(ctx *models.ReqContext) {
cookieState := ctx.GetCookie(OauthStateCookieName)
// delete cookie
middleware.DeleteCookie(ctx.Resp, OauthStateCookieName, hs.cookieOptionsFromCfg)
middleware.DeleteCookie(ctx.Resp, OauthStateCookieName, hs.CookieOptionsFromCfg)
if cookieState == "" {
ctx.Handle(500, "login.OAuthLogin(missing saved state)", nil)
@ -227,8 +227,8 @@ func (hs *HTTPServer) OAuthLogin(ctx *models.ReqContext) {
metrics.MApiLoginOAuth.Inc()
if redirectTo, _ := url.QueryUnescape(ctx.GetCookie("redirect_to")); len(redirectTo) > 0 {
if err := hs.validateRedirectTo(redirectTo); err == nil {
middleware.DeleteCookie(ctx.Resp, "redirect_to", hs.cookieOptionsFromCfg)
if err := hs.ValidateRedirectTo(redirectTo); err == nil {
middleware.DeleteCookie(ctx.Resp, "redirect_to", hs.CookieOptionsFromCfg)
ctx.Redirect(redirectTo)
return
}
@ -242,12 +242,3 @@ func hashStatecode(code, seed string) string {
hashBytes := sha256.Sum256([]byte(code + setting.SecretKey + seed))
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")
}