login_oauth.go 5.3 KB

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