github_oauth.go 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. package social
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "net/http"
  6. "regexp"
  7. "github.com/grafana/grafana/pkg/models"
  8. "golang.org/x/oauth2"
  9. )
  10. type SocialGithub struct {
  11. *SocialBase
  12. allowedDomains []string
  13. allowedOrganizations []string
  14. apiUrl string
  15. allowSignup bool
  16. teamIds []int
  17. }
  18. var (
  19. ErrMissingTeamMembership = &Error{"User not a member of one of the required teams"}
  20. ErrMissingOrganizationMembership = &Error{"User not a member of one of the required organizations"}
  21. )
  22. func (s *SocialGithub) Type() int {
  23. return int(models.GITHUB)
  24. }
  25. func (s *SocialGithub) IsEmailAllowed(email string) bool {
  26. return isEmailAllowed(email, s.allowedDomains)
  27. }
  28. func (s *SocialGithub) IsSignupAllowed() bool {
  29. return s.allowSignup
  30. }
  31. func (s *SocialGithub) IsTeamMember(client *http.Client) bool {
  32. if len(s.teamIds) == 0 {
  33. return true
  34. }
  35. teamMemberships, err := s.FetchTeamMemberships(client)
  36. if err != nil {
  37. return false
  38. }
  39. for _, teamId := range s.teamIds {
  40. for _, membershipId := range teamMemberships {
  41. if teamId == membershipId {
  42. return true
  43. }
  44. }
  45. }
  46. return false
  47. }
  48. func (s *SocialGithub) IsOrganizationMember(client *http.Client, organizationsUrl string) bool {
  49. if len(s.allowedOrganizations) == 0 {
  50. return true
  51. }
  52. organizations, err := s.FetchOrganizations(client, organizationsUrl)
  53. if err != nil {
  54. return false
  55. }
  56. for _, allowedOrganization := range s.allowedOrganizations {
  57. for _, organization := range organizations {
  58. if organization == allowedOrganization {
  59. return true
  60. }
  61. }
  62. }
  63. return false
  64. }
  65. func (s *SocialGithub) FetchPrivateEmail(client *http.Client) (string, error) {
  66. type Record struct {
  67. Email string `json:"email"`
  68. Primary bool `json:"primary"`
  69. Verified bool `json:"verified"`
  70. }
  71. response, err := HttpGet(client, fmt.Sprintf(s.apiUrl+"/emails"))
  72. if err != nil {
  73. return "", fmt.Errorf("Error getting email address: %s", err)
  74. }
  75. var records []Record
  76. err = json.Unmarshal(response.Body, &records)
  77. if err != nil {
  78. return "", fmt.Errorf("Error getting email address: %s", err)
  79. }
  80. var email = ""
  81. for _, record := range records {
  82. if record.Primary {
  83. email = record.Email
  84. }
  85. }
  86. return email, nil
  87. }
  88. func (s *SocialGithub) FetchTeamMemberships(client *http.Client) ([]int, error) {
  89. type Record struct {
  90. Id int `json:"id"`
  91. }
  92. url := fmt.Sprintf(s.apiUrl + "/teams?per_page=100")
  93. hasMore := true
  94. ids := make([]int, 0)
  95. for hasMore {
  96. response, err := HttpGet(client, url)
  97. if err != nil {
  98. return nil, fmt.Errorf("Error getting team memberships: %s", err)
  99. }
  100. var records []Record
  101. err = json.Unmarshal(response.Body, &records)
  102. if err != nil {
  103. return nil, fmt.Errorf("Error getting team memberships: %s", err)
  104. }
  105. newRecords := len(records)
  106. existingRecords := len(ids)
  107. tempIds := make([]int, (newRecords + existingRecords))
  108. copy(tempIds, ids)
  109. ids = tempIds
  110. for i, record := range records {
  111. ids[i] = record.Id
  112. }
  113. url, hasMore = s.HasMoreRecords(response.Headers)
  114. }
  115. return ids, nil
  116. }
  117. func (s *SocialGithub) HasMoreRecords(headers http.Header) (string, bool) {
  118. value, exists := headers["Link"]
  119. if !exists {
  120. return "", false
  121. }
  122. pattern := regexp.MustCompile(`<([^>]+)>; rel="next"`)
  123. matches := pattern.FindStringSubmatch(value[0])
  124. if matches == nil {
  125. return "", false
  126. }
  127. url := matches[1]
  128. return url, true
  129. }
  130. func (s *SocialGithub) FetchOrganizations(client *http.Client, organizationsUrl string) ([]string, error) {
  131. type Record struct {
  132. Login string `json:"login"`
  133. }
  134. response, err := HttpGet(client, organizationsUrl)
  135. if err != nil {
  136. return nil, fmt.Errorf("Error getting organizations: %s", err)
  137. }
  138. var records []Record
  139. err = json.Unmarshal(response.Body, &records)
  140. if err != nil {
  141. return nil, fmt.Errorf("Error getting organizations: %s", err)
  142. }
  143. var logins = make([]string, len(records))
  144. for i, record := range records {
  145. logins[i] = record.Login
  146. }
  147. return logins, nil
  148. }
  149. func (s *SocialGithub) UserInfo(client *http.Client, token *oauth2.Token) (*BasicUserInfo, error) {
  150. var data struct {
  151. Id int `json:"id"`
  152. Login string `json:"login"`
  153. Email string `json:"email"`
  154. OrganizationsUrl string `json:"organizations_url"`
  155. }
  156. response, err := HttpGet(client, s.apiUrl)
  157. if err != nil {
  158. return nil, fmt.Errorf("Error getting user info: %s", err)
  159. }
  160. err = json.Unmarshal(response.Body, &data)
  161. if err != nil {
  162. return nil, fmt.Errorf("Error getting user info: %s", err)
  163. }
  164. data.OrganizationsUrl = s.apiUrl + "/user/orgs"
  165. userInfo := &BasicUserInfo{
  166. Name: data.Login,
  167. Login: data.Login,
  168. Email: data.Email,
  169. }
  170. if !s.IsTeamMember(client) {
  171. return nil, ErrMissingTeamMembership
  172. }
  173. if !s.IsOrganizationMember(client, data.OrganizationsUrl) {
  174. return nil, ErrMissingOrganizationMembership
  175. }
  176. if userInfo.Email == "" {
  177. userInfo.Email, err = s.FetchPrivateEmail(client)
  178. if err != nil {
  179. return nil, err
  180. }
  181. }
  182. return userInfo, nil
  183. }