social.go 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366
  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. allowedOrganizations := sec.Key("allowed_organizations").Strings(" ")
  69. SocialMap["github"] = &SocialGithub{
  70. Config: &config,
  71. allowedDomains: info.AllowedDomains,
  72. apiUrl: info.ApiUrl,
  73. allowSignup: info.AllowSignup,
  74. teamIds: teamIds,
  75. allowedOrganizations: allowedOrganizations,
  76. }
  77. }
  78. // Google.
  79. if name == "google" {
  80. setting.OAuthService.Google = true
  81. SocialMap["google"] = &SocialGoogle{
  82. Config: &config, allowedDomains: info.AllowedDomains,
  83. apiUrl: info.ApiUrl,
  84. allowSignup: info.AllowSignup,
  85. }
  86. }
  87. }
  88. }
  89. func isEmailAllowed(email string, allowedDomains []string) bool {
  90. if len(allowedDomains) == 0 {
  91. return true
  92. }
  93. valid := false
  94. for _, domain := range allowedDomains {
  95. emailSuffix := fmt.Sprintf("@%s", domain)
  96. valid = valid || strings.HasSuffix(email, emailSuffix)
  97. }
  98. return valid
  99. }
  100. type SocialGithub struct {
  101. *oauth2.Config
  102. allowedDomains []string
  103. allowedOrganizations []string
  104. apiUrl string
  105. allowSignup bool
  106. teamIds []int
  107. }
  108. var (
  109. ErrMissingTeamMembership = errors.New("User not a member of one of the required teams")
  110. )
  111. var (
  112. ErrMissingOrganizationMembership = errors.New("User not a member of one of the required organizations")
  113. )
  114. func (s *SocialGithub) Type() int {
  115. return int(models.GITHUB)
  116. }
  117. func (s *SocialGithub) IsEmailAllowed(email string) bool {
  118. return isEmailAllowed(email, s.allowedDomains)
  119. }
  120. func (s *SocialGithub) IsSignupAllowed() bool {
  121. return s.allowSignup
  122. }
  123. func (s *SocialGithub) IsTeamMember(client *http.Client) bool {
  124. if len(s.teamIds) == 0 {
  125. return true
  126. }
  127. teamMemberships, err := s.FetchTeamMemberships(client)
  128. if err != nil {
  129. return false
  130. }
  131. for _, teamId := range s.teamIds {
  132. for _, membershipId := range teamMemberships {
  133. if teamId == membershipId {
  134. return true
  135. }
  136. }
  137. }
  138. return false
  139. }
  140. func (s *SocialGithub) IsOrganizationMember(client *http.Client) bool {
  141. if len(s.allowedOrganizations) == 0 {
  142. return true
  143. }
  144. organizations, err := s.FetchOrganizations(client)
  145. if err != nil {
  146. return false
  147. }
  148. for _, allowedOrganization := range s.allowedOrganizations {
  149. for _, organization := range organizations {
  150. if organization == allowedOrganization {
  151. return true
  152. }
  153. }
  154. }
  155. return false
  156. }
  157. func (s *SocialGithub) FetchPrivateEmail(client *http.Client) (string, error) {
  158. type Record struct {
  159. Email string `json:"email"`
  160. Primary bool `json:"primary"`
  161. Verified bool `json:"verified"`
  162. }
  163. emailsUrl := fmt.Sprintf(s.apiUrl + "/emails")
  164. r, err := client.Get(emailsUrl)
  165. if err != nil {
  166. return "", err
  167. }
  168. defer r.Body.Close()
  169. var records []Record
  170. if err = json.NewDecoder(r.Body).Decode(&records); err != nil {
  171. return "", err
  172. }
  173. var email = ""
  174. for _, record := range records {
  175. if record.Primary {
  176. email = record.Email
  177. }
  178. }
  179. return email, nil
  180. }
  181. func (s *SocialGithub) FetchTeamMemberships(client *http.Client) ([]int, error) {
  182. type Record struct {
  183. Id int `json:"id"`
  184. }
  185. membershipUrl := fmt.Sprintf(s.apiUrl + "/teams")
  186. r, err := client.Get(membershipUrl)
  187. if err != nil {
  188. return nil, err
  189. }
  190. defer r.Body.Close()
  191. var records []Record
  192. if err = json.NewDecoder(r.Body).Decode(&records); err != nil {
  193. return nil, err
  194. }
  195. var ids = make([]int, len(records))
  196. for i, record := range records {
  197. ids[i] = record.Id
  198. }
  199. return ids, nil
  200. }
  201. func (s *SocialGithub) FetchOrganizations(client *http.Client) ([]string, error) {
  202. type Record struct {
  203. Login string `json:"login"`
  204. }
  205. url := fmt.Sprintf(s.apiUrl + "/orgs")
  206. r, err := client.Get(url)
  207. if err != nil {
  208. return nil, err
  209. }
  210. defer r.Body.Close()
  211. var records []Record
  212. if err = json.NewDecoder(r.Body).Decode(&records); err != nil {
  213. return nil, err
  214. }
  215. var logins = make([]string, len(records))
  216. for i, record := range records {
  217. logins[i] = record.Login
  218. }
  219. return logins, nil
  220. }
  221. func (s *SocialGithub) UserInfo(token *oauth2.Token) (*BasicUserInfo, error) {
  222. var data struct {
  223. Id int `json:"id"`
  224. Name string `json:"login"`
  225. Email string `json:"email"`
  226. }
  227. var err error
  228. client := s.Client(oauth2.NoContext, token)
  229. r, err := client.Get(s.apiUrl)
  230. if err != nil {
  231. return nil, err
  232. }
  233. defer r.Body.Close()
  234. if err = json.NewDecoder(r.Body).Decode(&data); err != nil {
  235. return nil, err
  236. }
  237. userInfo := &BasicUserInfo{
  238. Identity: strconv.Itoa(data.Id),
  239. Name: data.Name,
  240. Email: data.Email,
  241. }
  242. if !s.IsTeamMember(client) {
  243. return nil, ErrMissingTeamMembership
  244. }
  245. if !s.IsOrganizationMember(client) {
  246. return nil, ErrMissingOrganizationMembership
  247. }
  248. if userInfo.Email == "" {
  249. userInfo.Email, err = s.FetchPrivateEmail(client)
  250. if err != nil {
  251. return nil, err
  252. }
  253. }
  254. return userInfo, nil
  255. }
  256. // ________ .__
  257. // / _____/ ____ ____ ____ | | ____
  258. // / \ ___ / _ \ / _ \ / ___\| | _/ __ \
  259. // \ \_\ ( <_> | <_> ) /_/ > |_\ ___/
  260. // \______ /\____/ \____/\___ /|____/\___ >
  261. // \/ /_____/ \/
  262. type SocialGoogle struct {
  263. *oauth2.Config
  264. allowedDomains []string
  265. apiUrl string
  266. allowSignup bool
  267. }
  268. func (s *SocialGoogle) Type() int {
  269. return int(models.GOOGLE)
  270. }
  271. func (s *SocialGoogle) IsEmailAllowed(email string) bool {
  272. return isEmailAllowed(email, s.allowedDomains)
  273. }
  274. func (s *SocialGoogle) IsSignupAllowed() bool {
  275. return s.allowSignup
  276. }
  277. func (s *SocialGoogle) UserInfo(token *oauth2.Token) (*BasicUserInfo, error) {
  278. var data struct {
  279. Id string `json:"id"`
  280. Name string `json:"name"`
  281. Email string `json:"email"`
  282. }
  283. var err error
  284. client := s.Client(oauth2.NoContext, token)
  285. r, err := client.Get(s.apiUrl)
  286. if err != nil {
  287. return nil, err
  288. }
  289. defer r.Body.Close()
  290. if err = json.NewDecoder(r.Body).Decode(&data); err != nil {
  291. return nil, err
  292. }
  293. return &BasicUserInfo{
  294. Identity: data.Id,
  295. Name: data.Name,
  296. Email: data.Email,
  297. }, nil
  298. }