social.go 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. package social
  2. import (
  3. "encoding/json"
  4. "errors"
  5. "fmt"
  6. "net/http"
  7. "strconv"
  8. "strings"
  9. "github.com/grafana/grafana/pkg/models"
  10. "github.com/grafana/grafana/pkg/setting"
  11. "golang.org/x/net/context"
  12. "golang.org/x/oauth2"
  13. )
  14. type BasicUserInfo struct {
  15. Identity string
  16. Name string
  17. Email string
  18. Login string
  19. Company string
  20. }
  21. type SocialConnector interface {
  22. Type() int
  23. UserInfo(token *oauth2.Token) (*BasicUserInfo, error)
  24. IsEmailAllowed(email string) bool
  25. IsSignupAllowed() bool
  26. AuthCodeURL(state string, opts ...oauth2.AuthCodeOption) string
  27. Exchange(ctx context.Context, code string) (*oauth2.Token, error)
  28. }
  29. var (
  30. SocialBaseUrl = "/login/"
  31. SocialMap = make(map[string]SocialConnector)
  32. )
  33. func NewOAuthService() {
  34. setting.OAuthService = &setting.OAuther{}
  35. setting.OAuthService.OAuthInfos = make(map[string]*setting.OAuthInfo)
  36. allOauthes := []string{"github", "google"}
  37. for _, name := range allOauthes {
  38. sec := setting.Cfg.Section("auth." + name)
  39. info := &setting.OAuthInfo{
  40. ClientId: sec.Key("client_id").String(),
  41. ClientSecret: sec.Key("client_secret").String(),
  42. Scopes: sec.Key("scopes").Strings(" "),
  43. AuthUrl: sec.Key("auth_url").String(),
  44. TokenUrl: sec.Key("token_url").String(),
  45. ApiUrl: sec.Key("api_url").String(),
  46. Enabled: sec.Key("enabled").MustBool(),
  47. AllowedDomains: sec.Key("allowed_domains").Strings(" "),
  48. AllowSignup: sec.Key("allow_sign_up").MustBool(),
  49. }
  50. if !info.Enabled {
  51. continue
  52. }
  53. setting.OAuthService.OAuthInfos[name] = info
  54. config := oauth2.Config{
  55. ClientID: info.ClientId,
  56. ClientSecret: info.ClientSecret,
  57. Endpoint: oauth2.Endpoint{
  58. AuthURL: info.AuthUrl,
  59. TokenURL: info.TokenUrl,
  60. },
  61. RedirectURL: strings.TrimSuffix(setting.AppUrl, "/") + SocialBaseUrl + name,
  62. Scopes: info.Scopes,
  63. }
  64. // GitHub.
  65. if name == "github" {
  66. setting.OAuthService.GitHub = true
  67. teamIds := sec.Key("team_ids").Ints(",")
  68. SocialMap["github"] = &SocialGithub{
  69. Config: &config,
  70. allowedDomains: info.AllowedDomains,
  71. apiUrl: info.ApiUrl,
  72. allowSignup: info.AllowSignup,
  73. teamIds: teamIds,
  74. }
  75. }
  76. // Google.
  77. if name == "google" {
  78. setting.OAuthService.Google = true
  79. SocialMap["google"] = &SocialGoogle{
  80. Config: &config, allowedDomains: info.AllowedDomains,
  81. apiUrl: info.ApiUrl,
  82. allowSignup: info.AllowSignup,
  83. }
  84. }
  85. }
  86. }
  87. func isEmailAllowed(email string, allowedDomains []string) bool {
  88. if len(allowedDomains) == 0 {
  89. return true
  90. }
  91. valid := false
  92. for _, domain := range allowedDomains {
  93. emailSuffix := fmt.Sprintf("@%s", domain)
  94. valid = valid || strings.HasSuffix(email, emailSuffix)
  95. }
  96. return valid
  97. }
  98. type SocialGithub struct {
  99. *oauth2.Config
  100. allowedDomains []string
  101. apiUrl string
  102. allowSignup bool
  103. teamIds []int
  104. }
  105. var (
  106. ErrMissingTeamMembership = errors.New("User not a member of one of the required teams")
  107. )
  108. func (s *SocialGithub) Type() int {
  109. return int(models.GITHUB)
  110. }
  111. func (s *SocialGithub) IsEmailAllowed(email string) bool {
  112. return isEmailAllowed(email, s.allowedDomains)
  113. }
  114. func (s *SocialGithub) IsSignupAllowed() bool {
  115. return s.allowSignup
  116. }
  117. func (s *SocialGithub) IsTeamMember(client *http.Client, username string, teamId int) bool {
  118. var data struct {
  119. Url string `json:"url"`
  120. State string `json:"state"`
  121. }
  122. membershipUrl := fmt.Sprintf("https://api.github.com/teams/%d/memberships/%s", teamId, username)
  123. r, err := client.Get(membershipUrl)
  124. if err != nil {
  125. return false
  126. }
  127. defer r.Body.Close()
  128. if err = json.NewDecoder(r.Body).Decode(&data); err != nil {
  129. return false
  130. }
  131. active := data.State == "active"
  132. return active
  133. }
  134. func (s *SocialGithub) UserInfo(token *oauth2.Token) (*BasicUserInfo, error) {
  135. var data struct {
  136. Id int `json:"id"`
  137. Name string `json:"login"`
  138. Email string `json:"email"`
  139. }
  140. var err error
  141. client := s.Client(oauth2.NoContext, token)
  142. r, err := client.Get(s.apiUrl)
  143. if err != nil {
  144. return nil, err
  145. }
  146. defer r.Body.Close()
  147. if err = json.NewDecoder(r.Body).Decode(&data); err != nil {
  148. return nil, err
  149. }
  150. userInfo := &BasicUserInfo{
  151. Identity: strconv.Itoa(data.Id),
  152. Name: data.Name,
  153. Email: data.Email,
  154. }
  155. if len(s.teamIds) > 0 {
  156. for _, teamId := range s.teamIds {
  157. if s.IsTeamMember(client, data.Name, teamId) {
  158. return userInfo, nil
  159. }
  160. }
  161. return nil, ErrMissingTeamMembership
  162. } else {
  163. return userInfo, nil
  164. }
  165. }
  166. // ________ .__
  167. // / _____/ ____ ____ ____ | | ____
  168. // / \ ___ / _ \ / _ \ / ___\| | _/ __ \
  169. // \ \_\ ( <_> | <_> ) /_/ > |_\ ___/
  170. // \______ /\____/ \____/\___ /|____/\___ >
  171. // \/ /_____/ \/
  172. type SocialGoogle struct {
  173. *oauth2.Config
  174. allowedDomains []string
  175. apiUrl string
  176. allowSignup bool
  177. }
  178. func (s *SocialGoogle) Type() int {
  179. return int(models.GOOGLE)
  180. }
  181. func (s *SocialGoogle) IsEmailAllowed(email string) bool {
  182. return isEmailAllowed(email, s.allowedDomains)
  183. }
  184. func (s *SocialGoogle) IsSignupAllowed() bool {
  185. return s.allowSignup
  186. }
  187. func (s *SocialGoogle) UserInfo(token *oauth2.Token) (*BasicUserInfo, error) {
  188. var data struct {
  189. Id string `json:"id"`
  190. Name string `json:"name"`
  191. Email string `json:"email"`
  192. }
  193. var err error
  194. client := s.Client(oauth2.NoContext, token)
  195. r, err := client.Get(s.apiUrl)
  196. if err != nil {
  197. return nil, err
  198. }
  199. defer r.Body.Close()
  200. if err = json.NewDecoder(r.Body).Decode(&data); err != nil {
  201. return nil, err
  202. }
  203. return &BasicUserInfo{
  204. Identity: data.Id,
  205. Name: data.Name,
  206. Email: data.Email,
  207. }, nil
  208. }