login_oauth.go 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. package api
  2. import (
  3. "context"
  4. "crypto/rand"
  5. "crypto/sha256"
  6. "crypto/tls"
  7. "crypto/x509"
  8. "encoding/base64"
  9. "encoding/hex"
  10. "fmt"
  11. "io/ioutil"
  12. "net/http"
  13. "net/url"
  14. "golang.org/x/oauth2"
  15. "github.com/grafana/grafana/pkg/bus"
  16. "github.com/grafana/grafana/pkg/infra/metrics"
  17. "github.com/grafana/grafana/pkg/log"
  18. "github.com/grafana/grafana/pkg/login"
  19. m "github.com/grafana/grafana/pkg/models"
  20. "github.com/grafana/grafana/pkg/setting"
  21. "github.com/grafana/grafana/pkg/social"
  22. )
  23. var (
  24. oauthLogger = log.New("oauth")
  25. OauthStateCookieName = "oauth_state"
  26. )
  27. func GenStateString() string {
  28. rnd := make([]byte, 32)
  29. rand.Read(rnd)
  30. return base64.URLEncoding.EncodeToString(rnd)
  31. }
  32. func (hs *HTTPServer) OAuthLogin(ctx *m.ReqContext) {
  33. if setting.OAuthService == nil {
  34. ctx.Handle(404, "OAuth not enabled", nil)
  35. return
  36. }
  37. name := ctx.Params(":name")
  38. connect, ok := social.SocialMap[name]
  39. if !ok {
  40. ctx.Handle(404, fmt.Sprintf("No OAuth with name %s configured", name), nil)
  41. return
  42. }
  43. errorParam := ctx.Query("error")
  44. if errorParam != "" {
  45. errorDesc := ctx.Query("error_description")
  46. oauthLogger.Error("failed to login ", "error", errorParam, "errorDesc", errorDesc)
  47. hs.redirectWithError(ctx, login.ErrProviderDeniedRequest, "error", errorParam, "errorDesc", errorDesc)
  48. return
  49. }
  50. code := ctx.Query("code")
  51. if code == "" {
  52. state := GenStateString()
  53. hashedState := hashStatecode(state, setting.OAuthService.OAuthInfos[name].ClientSecret)
  54. hs.writeCookie(ctx.Resp, OauthStateCookieName, hashedState, 60)
  55. if setting.OAuthService.OAuthInfos[name].HostedDomain == "" {
  56. ctx.Redirect(connect.AuthCodeURL(state, oauth2.AccessTypeOnline))
  57. } else {
  58. ctx.Redirect(connect.AuthCodeURL(state, oauth2.SetAuthURLParam("hd", setting.OAuthService.OAuthInfos[name].HostedDomain), oauth2.AccessTypeOnline))
  59. }
  60. return
  61. }
  62. cookieState := ctx.GetCookie(OauthStateCookieName)
  63. // delete cookie
  64. ctx.Resp.Header().Del("Set-Cookie")
  65. hs.deleteCookie(ctx.Resp, OauthStateCookieName)
  66. if cookieState == "" {
  67. ctx.Handle(500, "login.OAuthLogin(missing saved state)", nil)
  68. return
  69. }
  70. queryState := hashStatecode(ctx.Query("state"), setting.OAuthService.OAuthInfos[name].ClientSecret)
  71. oauthLogger.Info("state check", "queryState", queryState, "cookieState", cookieState)
  72. if cookieState != queryState {
  73. ctx.Handle(500, "login.OAuthLogin(state mismatch)", nil)
  74. return
  75. }
  76. // handle call back
  77. tr := &http.Transport{
  78. Proxy: http.ProxyFromEnvironment,
  79. TLSClientConfig: &tls.Config{
  80. InsecureSkipVerify: setting.OAuthService.OAuthInfos[name].TlsSkipVerify,
  81. },
  82. }
  83. oauthClient := &http.Client{
  84. Transport: tr,
  85. }
  86. if setting.OAuthService.OAuthInfos[name].TlsClientCert != "" || setting.OAuthService.OAuthInfos[name].TlsClientKey != "" {
  87. cert, err := tls.LoadX509KeyPair(setting.OAuthService.OAuthInfos[name].TlsClientCert, setting.OAuthService.OAuthInfos[name].TlsClientKey)
  88. if err != nil {
  89. ctx.Logger.Error("Failed to setup TlsClientCert", "oauth", name, "error", err)
  90. ctx.Handle(500, "login.OAuthLogin(Failed to setup TlsClientCert)", nil)
  91. return
  92. }
  93. tr.TLSClientConfig.Certificates = append(tr.TLSClientConfig.Certificates, cert)
  94. }
  95. if setting.OAuthService.OAuthInfos[name].TlsClientCa != "" {
  96. caCert, err := ioutil.ReadFile(setting.OAuthService.OAuthInfos[name].TlsClientCa)
  97. if err != nil {
  98. ctx.Logger.Error("Failed to setup TlsClientCa", "oauth", name, "error", err)
  99. ctx.Handle(500, "login.OAuthLogin(Failed to setup TlsClientCa)", nil)
  100. return
  101. }
  102. caCertPool := x509.NewCertPool()
  103. caCertPool.AppendCertsFromPEM(caCert)
  104. tr.TLSClientConfig.RootCAs = caCertPool
  105. }
  106. oauthCtx := context.WithValue(context.Background(), oauth2.HTTPClient, oauthClient)
  107. // get token from provider
  108. token, err := connect.Exchange(oauthCtx, code)
  109. if err != nil {
  110. ctx.Handle(500, "login.OAuthLogin(NewTransportWithCode)", err)
  111. return
  112. }
  113. // token.TokenType was defaulting to "bearer", which is out of spec, so we explicitly set to "Bearer"
  114. token.TokenType = "Bearer"
  115. oauthLogger.Debug("OAuthLogin Got token", "token", token)
  116. // set up oauth2 client
  117. client := connect.Client(oauthCtx, token)
  118. // get user info
  119. userInfo, err := connect.UserInfo(client, token)
  120. if err != nil {
  121. if sErr, ok := err.(*social.Error); ok {
  122. hs.redirectWithError(ctx, sErr)
  123. } else {
  124. ctx.Handle(500, fmt.Sprintf("login.OAuthLogin(get info from %s)", name), err)
  125. }
  126. return
  127. }
  128. oauthLogger.Debug("OAuthLogin got user info", "userInfo", userInfo)
  129. // validate that we got at least an email address
  130. if userInfo.Email == "" {
  131. hs.redirectWithError(ctx, login.ErrNoEmail)
  132. return
  133. }
  134. // validate that the email is allowed to login to grafana
  135. if !connect.IsEmailAllowed(userInfo.Email) {
  136. hs.redirectWithError(ctx, login.ErrEmailNotAllowed)
  137. return
  138. }
  139. extUser := &m.ExternalUserInfo{
  140. AuthModule: "oauth_" + name,
  141. AuthId: userInfo.Id,
  142. Name: userInfo.Name,
  143. Login: userInfo.Login,
  144. Email: userInfo.Email,
  145. OrgRoles: map[int64]m.RoleType{},
  146. }
  147. if userInfo.Role != "" {
  148. extUser.OrgRoles[1] = m.RoleType(userInfo.Role)
  149. }
  150. // add/update user in grafana
  151. cmd := &m.UpsertUserCommand{
  152. ReqContext: ctx,
  153. ExternalUser: extUser,
  154. SignupAllowed: connect.IsSignupAllowed(),
  155. }
  156. err = bus.Dispatch(cmd)
  157. if err != nil {
  158. hs.redirectWithError(ctx, err)
  159. return
  160. }
  161. // login
  162. hs.loginUserWithUser(cmd.Result, ctx)
  163. metrics.M_Api_Login_OAuth.Inc()
  164. if redirectTo, _ := url.QueryUnescape(ctx.GetCookie("redirect_to")); len(redirectTo) > 0 {
  165. ctx.SetCookie("redirect_to", "", -1, setting.AppSubUrl+"/")
  166. ctx.Redirect(redirectTo)
  167. return
  168. }
  169. ctx.Redirect(setting.AppSubUrl + "/")
  170. }
  171. func (hs *HTTPServer) deleteCookie(w http.ResponseWriter, name string) {
  172. hs.writeCookie(w, name, "", -1)
  173. }
  174. func (hs *HTTPServer) writeCookie(w http.ResponseWriter, name string, value string, maxAge int) {
  175. http.SetCookie(w, &http.Cookie{
  176. Name: name,
  177. MaxAge: maxAge,
  178. Value: value,
  179. HttpOnly: true,
  180. Path: setting.AppSubUrl + "/",
  181. Secure: hs.Cfg.CookieSecure,
  182. SameSite: hs.Cfg.CookieSameSite,
  183. })
  184. }
  185. func hashStatecode(code, seed string) string {
  186. hashBytes := sha256.Sum256([]byte(code + setting.SecretKey + seed))
  187. return hex.EncodeToString(hashBytes[:])
  188. }
  189. func (hs *HTTPServer) redirectWithError(ctx *m.ReqContext, err error, v ...interface{}) {
  190. ctx.Logger.Error(err.Error(), v...)
  191. hs.trySetEncryptedCookie(ctx, LoginErrorCookieName, err.Error(), 60)
  192. ctx.Redirect(setting.AppSubUrl + "/login")
  193. }