login_oauth.go 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  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/log"
  17. "github.com/grafana/grafana/pkg/login"
  18. "github.com/grafana/grafana/pkg/metrics"
  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. 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.writeOauthStateCookie(ctx, 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. savedState := ctx.GetCookie(OauthStateCookieName)
  63. // delete cookie
  64. ctx.Resp.Header().Del("Set-Cookie")
  65. hs.writeOauthStateCookie(ctx, "", -1)
  66. if savedState == "" {
  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. if savedState != queryState {
  72. ctx.Handle(500, "login.OAuthLogin(state mismatch)", nil)
  73. return
  74. }
  75. // handle call back
  76. tr := &http.Transport{
  77. Proxy: http.ProxyFromEnvironment,
  78. TLSClientConfig: &tls.Config{
  79. InsecureSkipVerify: setting.OAuthService.OAuthInfos[name].TlsSkipVerify,
  80. },
  81. }
  82. oauthClient := &http.Client{
  83. Transport: tr,
  84. }
  85. if setting.OAuthService.OAuthInfos[name].TlsClientCert != "" || setting.OAuthService.OAuthInfos[name].TlsClientKey != "" {
  86. cert, err := tls.LoadX509KeyPair(setting.OAuthService.OAuthInfos[name].TlsClientCert, setting.OAuthService.OAuthInfos[name].TlsClientKey)
  87. if err != nil {
  88. ctx.Logger.Error("Failed to setup TlsClientCert", "oauth", name, "error", err)
  89. ctx.Handle(500, "login.OAuthLogin(Failed to setup TlsClientCert)", nil)
  90. return
  91. }
  92. tr.TLSClientConfig.Certificates = append(tr.TLSClientConfig.Certificates, cert)
  93. }
  94. if setting.OAuthService.OAuthInfos[name].TlsClientCa != "" {
  95. caCert, err := ioutil.ReadFile(setting.OAuthService.OAuthInfos[name].TlsClientCa)
  96. if err != nil {
  97. ctx.Logger.Error("Failed to setup TlsClientCa", "oauth", name, "error", err)
  98. ctx.Handle(500, "login.OAuthLogin(Failed to setup TlsClientCa)", nil)
  99. return
  100. }
  101. caCertPool := x509.NewCertPool()
  102. caCertPool.AppendCertsFromPEM(caCert)
  103. tr.TLSClientConfig.RootCAs = caCertPool
  104. }
  105. oauthCtx := context.WithValue(context.Background(), oauth2.HTTPClient, oauthClient)
  106. // get token from provider
  107. token, err := connect.Exchange(oauthCtx, code)
  108. if err != nil {
  109. ctx.Handle(500, "login.OAuthLogin(NewTransportWithCode)", err)
  110. return
  111. }
  112. // token.TokenType was defaulting to "bearer", which is out of spec, so we explicitly set to "Bearer"
  113. token.TokenType = "Bearer"
  114. oauthLogger.Debug("OAuthLogin Got token", "token", token)
  115. // set up oauth2 client
  116. client := connect.Client(oauthCtx, token)
  117. // get user info
  118. userInfo, err := connect.UserInfo(client, token)
  119. if err != nil {
  120. if sErr, ok := err.(*social.Error); ok {
  121. redirectWithError(ctx, sErr)
  122. } else {
  123. ctx.Handle(500, fmt.Sprintf("login.OAuthLogin(get info from %s)", name), err)
  124. }
  125. return
  126. }
  127. oauthLogger.Debug("OAuthLogin got user info", "userInfo", userInfo)
  128. // validate that we got at least an email address
  129. if userInfo.Email == "" {
  130. redirectWithError(ctx, login.ErrNoEmail)
  131. return
  132. }
  133. // validate that the email is allowed to login to grafana
  134. if !connect.IsEmailAllowed(userInfo.Email) {
  135. redirectWithError(ctx, login.ErrEmailNotAllowed)
  136. return
  137. }
  138. extUser := &m.ExternalUserInfo{
  139. AuthModule: "oauth_" + name,
  140. AuthId: userInfo.Id,
  141. Name: userInfo.Name,
  142. Login: userInfo.Login,
  143. Email: userInfo.Email,
  144. OrgRoles: map[int64]m.RoleType{},
  145. }
  146. if userInfo.Role != "" {
  147. extUser.OrgRoles[1] = m.RoleType(userInfo.Role)
  148. }
  149. // add/update user in grafana
  150. cmd := &m.UpsertUserCommand{
  151. ReqContext: ctx,
  152. ExternalUser: extUser,
  153. SignupAllowed: connect.IsSignupAllowed(),
  154. }
  155. err = bus.Dispatch(cmd)
  156. if err != nil {
  157. redirectWithError(ctx, err)
  158. return
  159. }
  160. // login
  161. hs.loginUserWithUser(cmd.Result, ctx)
  162. metrics.M_Api_Login_OAuth.Inc()
  163. if redirectTo, _ := url.QueryUnescape(ctx.GetCookie("redirect_to")); len(redirectTo) > 0 {
  164. ctx.SetCookie("redirect_to", "", -1, setting.AppSubUrl+"/")
  165. ctx.Redirect(redirectTo)
  166. return
  167. }
  168. ctx.Redirect(setting.AppSubUrl + "/")
  169. }
  170. func (hs *HTTPServer) writeOauthStateCookie(ctx *m.ReqContext, value string, maxAge int) {
  171. http.SetCookie(ctx.Resp, &http.Cookie{
  172. Name: OauthStateCookieName,
  173. MaxAge: maxAge,
  174. Value: value,
  175. HttpOnly: true,
  176. Path: setting.AppSubUrl + "/",
  177. Secure: hs.Cfg.LoginCookieSecure,
  178. })
  179. }
  180. func hashStatecode(code, seed string) string {
  181. hashBytes := sha256.Sum256([]byte(code + setting.SecretKey + seed))
  182. return hex.EncodeToString(hashBytes[:])
  183. }
  184. func redirectWithError(ctx *m.ReqContext, err error, v ...interface{}) {
  185. ctx.Logger.Error(err.Error(), v...)
  186. ctx.Session.Set("loginError", err.Error())
  187. ctx.Redirect(setting.AppSubUrl + "/login")
  188. }