소스 검색

Add github organizations support

Indrek Juhkam 10 년 전
부모
커밋
b55d9350e7
4개의 변경된 파일112개의 추가작업 그리고 29개의 파일을 삭제
  1. 1 0
      conf/defaults.ini
  2. 2 1
      conf/sample.ini
  3. 2 0
      pkg/api/login_oauth.go
  4. 107 28
      pkg/social/social.go

+ 1 - 0
conf/defaults.ini

@@ -153,6 +153,7 @@ token_url = https://github.com/login/oauth/access_token
 api_url = https://api.github.com/user
 team_ids =
 allowed_domains =
+allowed_organizations =
 
 #################################### Google Auth ##########################
 [auth.google]

+ 2 - 1
conf/sample.ini

@@ -146,12 +146,13 @@
 ;allow_sign_up = false
 ;client_id = some_id
 ;client_secret = some_secret
-;scopes = user:email
+;scopes = user:email,read:org
 ;auth_url = https://github.com/login/oauth/authorize
 ;token_url = https://github.com/login/oauth/access_token
 ;api_url = https://api.github.com/user
 ;team_ids =
 ;allowed_domains =
+;allowed_organizations =
 
 #################################### Google Auth ##########################
 [auth.google]

+ 2 - 0
pkg/api/login_oauth.go

@@ -48,6 +48,8 @@ func OAuthLogin(ctx *middleware.Context) {
 	if err != nil {
 		if err == social.ErrMissingTeamMembership {
 			ctx.Redirect(setting.AppSubUrl + "/login?failedMsg=" + url.QueryEscape("Required Github team membership not fulfilled"))
+		} else if err == social.ErrMissingOrganizationMembership {
+			ctx.Redirect(setting.AppSubUrl + "/login?failedMsg=" + url.QueryEscape("Required Github organization membership not fulfilled"))
 		} else {
 			ctx.Handle(500, fmt.Sprintf("login.OAuthLogin(get info from %s)", name), err)
 		}

+ 107 - 28
pkg/social/social.go

@@ -78,12 +78,14 @@ func NewOAuthService() {
 		if name == "github" {
 			setting.OAuthService.GitHub = true
 			teamIds := sec.Key("team_ids").Ints(",")
+			allowedOrganizations := sec.Key("allowed_organizations").Strings(" ")
 			SocialMap["github"] = &SocialGithub{
-				Config:         &config,
-				allowedDomains: info.AllowedDomains,
-				apiUrl:         info.ApiUrl,
-				allowSignup:    info.AllowSignup,
-				teamIds:        teamIds,
+				Config:               &config,
+				allowedDomains:       info.AllowedDomains,
+				apiUrl:               info.ApiUrl,
+				allowSignup:          info.AllowSignup,
+				teamIds:              teamIds,
+				allowedOrganizations: allowedOrganizations,
 			}
 		}
 
@@ -115,16 +117,21 @@ func isEmailAllowed(email string, allowedDomains []string) bool {
 
 type SocialGithub struct {
 	*oauth2.Config
-	allowedDomains []string
-	apiUrl         string
-	allowSignup    bool
-	teamIds        []int
+	allowedDomains       []string
+	allowedOrganizations []string
+	apiUrl               string
+	allowSignup          bool
+	teamIds              []int
 }
 
 var (
 	ErrMissingTeamMembership = errors.New("User not a member of one of the required teams")
 )
 
+var (
+	ErrMissingOrganizationMembership = errors.New("User not a member of one of the required organizations")
+)
+
 func (s *SocialGithub) Type() int {
 	return int(models.GITHUB)
 }
@@ -137,26 +144,100 @@ func (s *SocialGithub) IsSignupAllowed() bool {
 	return s.allowSignup
 }
 
-func (s *SocialGithub) IsTeamMember(client *http.Client, username string, teamId int) bool {
-	var data struct {
-		Url   string `json:"url"`
-		State string `json:"state"`
+func (s *SocialGithub) IsTeamMember(client *http.Client) bool {
+	if len(s.teamIds) == 0 {
+		return true
 	}
 
-	membershipUrl := fmt.Sprintf("https://api.github.com/teams/%d/memberships/%s", teamId, username)
-	r, err := client.Get(membershipUrl)
+	teamMemberships, err := s.FetchTeamMemberships(client)
 	if err != nil {
 		return false
 	}
 
-	defer r.Body.Close()
+	for _, teamId := range s.teamIds {
+		for _, membershipId := range teamMemberships {
+			if teamId == membershipId {
+				return true
+			}
+		}
+	}
 
-	if err = json.NewDecoder(r.Body).Decode(&data); err != nil {
+	return false
+}
+
+func (s *SocialGithub) IsOrganizationMember(client *http.Client) bool {
+	if len(s.allowedOrganizations) == 0 {
+		return true
+	}
+
+	organizations, err := s.FetchOrganizations(client)
+	if err != nil {
 		return false
 	}
 
-	active := data.State == "active"
-	return active
+	for _, allowedOrganization := range s.allowedOrganizations {
+		for _, organization := range organizations {
+			if organization == allowedOrganization {
+				return true
+			}
+		}
+	}
+
+	return false
+}
+
+func (s *SocialGithub) FetchTeamMemberships(client *http.Client) ([]int, error) {
+	type Record struct {
+		Id int `json:"id"`
+	}
+
+	membershipUrl := fmt.Sprintf("https://api.github.com/user/teams")
+	r, err := client.Get(membershipUrl)
+	if err != nil {
+		return nil, err
+	}
+
+	defer r.Body.Close()
+
+	var records []Record
+
+	if err = json.NewDecoder(r.Body).Decode(&records); err != nil {
+		return nil, err
+	}
+
+	var ids = make([]int, len(records))
+	for i, record := range records {
+		ids[i] = record.Id
+	}
+
+	return ids, nil
+}
+
+func (s *SocialGithub) FetchOrganizations(client *http.Client) ([]string, error) {
+	type Record struct {
+		Login string `json:"login"`
+	}
+
+	url := fmt.Sprintf("https://api.github.com/user/orgs")
+	r, err := client.Get(url)
+	if err != nil {
+		return nil, err
+	}
+
+	defer r.Body.Close()
+
+	var records []Record
+
+	if err = json.NewDecoder(r.Body).Decode(&records); err != nil {
+		return nil, err
+	}
+
+	var logins = make([]string, len(records))
+	for i, record := range records {
+		logins[i] = record.Login
+	}
+
+	return logins, nil
 }
 
 func (s *SocialGithub) UserInfo(token *oauth2.Token) (*BasicUserInfo, error) {
@@ -185,17 +266,15 @@ func (s *SocialGithub) UserInfo(token *oauth2.Token) (*BasicUserInfo, error) {
 		Email:    data.Email,
 	}
 
-	if len(s.teamIds) > 0 {
-		for _, teamId := range s.teamIds {
-			if s.IsTeamMember(client, data.Name, teamId) {
-				return userInfo, nil
-			}
-		}
-
+	if !s.IsTeamMember(client) {
 		return nil, ErrMissingTeamMembership
-	} else {
-		return userInfo, nil
 	}
+
+	if !s.IsOrganizationMember(client) {
+		return nil, ErrMissingOrganizationMembership
+	}
+
+	return userInfo, nil
 }
 
 //   ________                     .__