login_oauth.go 5.2 KB

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