diff options
author | Unknwon <u@gogs.io> | 2017-02-23 18:25:12 -0500 |
---|---|---|
committer | Unknwon <u@gogs.io> | 2017-02-23 18:25:12 -0500 |
commit | 6072e9a52cf01723aea2b9a5ca7dfe22b101fbfc (patch) | |
tree | 17fe0e830584dfef2ff855bc75992df727997c08 /models | |
parent | b78e03934d057bdb4c628fefb364dda6eb1f260a (diff) |
repo: add protect branch whitelist (#4177)
Add options to add users and teams to whitelist of a protected
branch. This is only available for organizational repositories.
Diffstat (limited to 'models')
-rw-r--r-- | models/models.go | 2 | ||||
-rw-r--r-- | models/org.go | 15 | ||||
-rw-r--r-- | models/org_team.go | 37 | ||||
-rw-r--r-- | models/repo.go | 22 | ||||
-rw-r--r-- | models/repo_branch.go | 145 |
5 files changed, 193 insertions, 28 deletions
diff --git a/models/models.go b/models/models.go index 0d63ea1a..bf071292 100644 --- a/models/models.go +++ b/models/models.go @@ -66,7 +66,7 @@ func init() { new(Issue), new(PullRequest), new(Comment), new(Attachment), new(IssueUser), new(Label), new(IssueLabel), new(Milestone), new(Mirror), new(Release), new(LoginSource), new(Webhook), new(HookTask), - new(ProtectBranch), + new(ProtectBranch), new(ProtectBranchWhitelist), new(Team), new(OrgUser), new(TeamUser), new(TeamRepo), new(Notice), new(EmailAddress)) diff --git a/models/org.go b/models/org.go index 543a517f..3d08458b 100644 --- a/models/org.go +++ b/models/org.go @@ -32,10 +32,10 @@ func (org *User) IsOrgMember(uid int64) bool { } func (org *User) getTeam(e Engine, name string) (*Team, error) { - return getTeam(e, org.ID, name) + return getTeamOfOrgByName(e, org.ID, name) } -// GetTeam returns named team of organization. +// GetTeamOfOrgByName returns named team of organization. func (org *User) GetTeam(name string) (*Team, error) { return org.getTeam(x, name) } @@ -49,8 +49,9 @@ func (org *User) GetOwnerTeam() (*Team, error) { return org.getOwnerTeam(x) } -func (org *User) getTeams(e Engine) error { - return e.Where("org_id=?", org.ID).Find(&org.Teams) +func (org *User) getTeams(e Engine) (err error) { + org.Teams, err = getTeamsByOrgID(e, org.ID) + return err } // GetTeams returns all teams that belong to organization. @@ -502,7 +503,7 @@ func (org *User) GetUserRepositories(userID int64, page, pageSize int) ([]*Repos repos := make([]*Repository, 0, pageSize) // FIXME: use XORM chain operations instead of raw SQL. if err = x.Sql(fmt.Sprintf(`SELECT repository.* FROM repository - INNER JOIN team_repo + INNER JOIN team_repo ON team_repo.repo_id = repository.id WHERE (repository.owner_id = ? AND repository.is_private = ?) OR team_repo.team_id IN (%s) GROUP BY repository.id @@ -514,7 +515,7 @@ func (org *User) GetUserRepositories(userID int64, page, pageSize int) ([]*Repos } results, err := x.Query(fmt.Sprintf(`SELECT repository.id FROM repository - INNER JOIN team_repo + INNER JOIN team_repo ON team_repo.repo_id = repository.id WHERE (repository.owner_id = ? AND repository.is_private = ?) OR team_repo.team_id IN (%s) GROUP BY repository.id @@ -541,7 +542,7 @@ func (org *User) GetUserMirrorRepositories(userID int64) ([]*Repository, error) repos := make([]*Repository, 0, 10) if err = x.Sql(fmt.Sprintf(`SELECT repository.* FROM repository - INNER JOIN team_repo + INNER JOIN team_repo ON team_repo.repo_id = repository.id AND repository.is_mirror = ? WHERE (repository.owner_id = ? AND repository.is_private = ?) OR team_repo.team_id IN (%s) GROUP BY repository.id diff --git a/models/org_team.go b/models/org_team.go index 217febdf..9d2835cb 100644 --- a/models/org_team.go +++ b/models/org_team.go @@ -43,9 +43,14 @@ func (t *Team) IsOwnerTeam() bool { return t.Name == OWNER_TEAM } +// HasWriteAccess returns true if team has at least write level access mode. +func (t *Team) HasWriteAccess() bool { + return t.Authorize >= ACCESS_MODE_WRITE +} + // IsTeamMember returns true if given user is a member of team. -func (t *Team) IsMember(uid int64) bool { - return IsTeamMember(t.OrgID, t.ID, uid) +func (t *Team) IsMember(userID int64) bool { + return IsTeamMember(t.OrgID, t.ID, userID) } func (t *Team) getRepositories(e Engine) (err error) { @@ -260,9 +265,9 @@ func NewTeam(t *Team) error { return sess.Commit() } -func getTeam(e Engine, orgId int64, name string) (*Team, error) { +func getTeamOfOrgByName(e Engine, orgID int64, name string) (*Team, error) { t := &Team{ - OrgID: orgId, + OrgID: orgID, LowerName: strings.ToLower(name), } has, err := e.Get(t) @@ -274,14 +279,14 @@ func getTeam(e Engine, orgId int64, name string) (*Team, error) { return t, nil } -// GetTeam returns team by given team name and organization. -func GetTeam(orgId int64, name string) (*Team, error) { - return getTeam(x, orgId, name) +// GetTeamOfOrgByName returns team by given team name and organization. +func GetTeamOfOrgByName(orgID int64, name string) (*Team, error) { + return getTeamOfOrgByName(x, orgID, name) } -func getTeamByID(e Engine, teamId int64) (*Team, error) { +func getTeamByID(e Engine, teamID int64) (*Team, error) { t := new(Team) - has, err := e.Id(teamId).Get(t) + has, err := e.Id(teamID).Get(t) if err != nil { return nil, err } else if !has { @@ -291,8 +296,18 @@ func getTeamByID(e Engine, teamId int64) (*Team, error) { } // GetTeamByID returns team by given ID. -func GetTeamByID(teamId int64) (*Team, error) { - return getTeamByID(x, teamId) +func GetTeamByID(teamID int64) (*Team, error) { + return getTeamByID(x, teamID) +} + +func getTeamsByOrgID(e Engine, orgID int64) ([]*Team, error) { + teams := make([]*Team, 0, 3) + return teams, e.Where("org_id = ?", orgID).Find(&teams) +} + +// GetTeamsByOrgID returns all teams belong to given organization. +func GetTeamsByOrgID(orgID int64) ([]*Team, error) { + return getTeamsByOrgID(x, orgID) } // UpdateTeam updates information of team. diff --git a/models/repo.go b/models/repo.go index dfa04d88..0d93e0bb 100644 --- a/models/repo.go +++ b/models/repo.go @@ -329,14 +329,14 @@ func (repo *Repository) DeleteWiki() { } } -// getAssignees returns a list of users who can be assigned to issues in this repository. -func (repo *Repository) getAssignees(e Engine) (_ []*User, err error) { +// getUsersWithAccesMode returns users that have at least given access mode to the repository. +func (repo *Repository) getUsersWithAccesMode(e Engine, mode AccessMode) (_ []*User, err error) { if err = repo.getOwner(e); err != nil { return nil, err } accesses := make([]*Access, 0, 10) - if err = e.Where("repo_id = ? AND mode >= ?", repo.ID, ACCESS_MODE_READ).Find(&accesses); err != nil { + if err = e.Where("repo_id = ? AND mode >= ?", repo.ID, mode).Find(&accesses); err != nil { return nil, err } @@ -360,7 +360,12 @@ func (repo *Repository) getAssignees(e Engine) (_ []*User, err error) { return users, nil } -// GetAssignees returns all users that have write access and can be assigned to issues +// getAssignees returns a list of users who can be assigned to issues in this repository. +func (repo *Repository) getAssignees(e Engine) (_ []*User, err error) { + return repo.getUsersWithAccesMode(e, ACCESS_MODE_READ) +} + +// GetAssignees returns all users that have read access and can be assigned to issues // of the repository, func (repo *Repository) GetAssignees() (_ []*User, err error) { return repo.getAssignees(x) @@ -371,6 +376,11 @@ func (repo *Repository) GetAssigneeByID(userID int64) (*User, error) { return GetAssigneeByID(repo, userID) } +// GetWriters returns all users that have write access to the repository. +func (repo *Repository) GetWriters() (_ []*User, err error) { + return repo.getUsersWithAccesMode(x, ACCESS_MODE_WRITE) +} + // GetMilestoneByID returns the milestone belongs to repository by given ID. func (repo *Repository) GetMilestoneByID(milestoneID int64) (*Milestone, error) { return GetMilestoneByRepoID(repo.ID, milestoneID) @@ -1015,10 +1025,10 @@ func CreateRepository(u *User, opts CreateRepoOptions) (_ *Repository, err error } _, stderr, err := process.ExecDir(-1, - repoPath, fmt.Sprintf("CreateRepository(git update-server-info): %s", repoPath), + repoPath, fmt.Sprintf("CreateRepository 'git update-server-info': %s", repoPath), "git", "update-server-info") if err != nil { - return nil, errors.New("CreateRepository(git update-server-info): " + stderr) + return nil, errors.New("CreateRepository 'git update-server-info': " + stderr) } } diff --git a/models/repo_branch.go b/models/repo_branch.go index 6fe2222d..77e1db7f 100644 --- a/models/repo_branch.go +++ b/models/repo_branch.go @@ -6,8 +6,12 @@ package models import ( "fmt" + "strings" + "github.com/Unknwon/com" "github.com/gogits/git-module" + + "github.com/gogits/gogs/modules/base" ) type Branch struct { @@ -58,6 +62,20 @@ func (br *Branch) GetCommit() (*git.Commit, error) { return gitRepo.GetBranchCommit(br.Name) } +type ProtectBranchWhitelist struct { + ID int64 + ProtectBranchID int64 + RepoID int64 `xorm:"UNIQUE(protect_branch_whitelist)"` + Name string `xorm:"UNIQUE(protect_branch_whitelist)"` + UserID int64 `xorm:"UNIQUE(protect_branch_whitelist)"` +} + +// IsUserInProtectBranchWhitelist returns true if given user is in the whitelist of a branch in a repository. +func IsUserInProtectBranchWhitelist(repoID, userID int64, branch string) bool { + has, err := x.Where("repo_id = ?", repoID).And("user_id = ?", userID).And("name = ?", branch).Get(new(ProtectBranchWhitelist)) + return has && err == nil +} + // ProtectBranch contains options of a protected branch. type ProtectBranch struct { ID int64 @@ -65,6 +83,9 @@ type ProtectBranch struct { Name string `xorm:"UNIQUE(protect_branch)"` Protected bool RequirePullRequest bool + EnableWhitelist bool + WhitelistUserIDs string `xorm:"TEXT"` + WhitelistTeamIDs string `xorm:"TEXT"` } // GetProtectBranchOfRepoByName returns *ProtectBranch by branch name in given repostiory. @@ -94,15 +115,133 @@ func IsBranchOfRepoRequirePullRequest(repoID int64, name string) bool { // UpdateProtectBranch saves branch protection options. // If ID is 0, it creates a new record. Otherwise, updates existing record. func UpdateProtectBranch(protectBranch *ProtectBranch) (err error) { + sess := x.NewSession() + defer sessionRelease(sess) + if err = sess.Begin(); err != nil { + return err + } + + if protectBranch.ID == 0 { + if _, err = sess.Insert(protectBranch); err != nil { + return fmt.Errorf("Insert: %v", err) + } + return + } + + if _, err = sess.Id(protectBranch.ID).AllCols().Update(protectBranch); err != nil { + return fmt.Errorf("Update: %v", err) + } + + return sess.Commit() +} + +// UpdateOrgProtectBranch saves branch protection options of organizational repository. +// If ID is 0, it creates a new record. Otherwise, updates existing record. +// This function also performs check if whitelist user and team's IDs have been changed +// to avoid unnecessary whitelist delete and regenerate. +func UpdateOrgProtectBranch(repo *Repository, protectBranch *ProtectBranch, whitelistUserIDs, whitelistTeamIDs string) (err error) { + if err = repo.GetOwner(); err != nil { + return fmt.Errorf("GetOwner: %v", err) + } else if !repo.Owner.IsOrganization() { + return fmt.Errorf("expect repository owner to be an organization") + } + + hasUsersChanged := false + validUserIDs := base.StringsToInt64s(strings.Split(protectBranch.WhitelistUserIDs, ",")) + if protectBranch.WhitelistUserIDs != whitelistUserIDs { + hasUsersChanged = true + userIDs := base.StringsToInt64s(strings.Split(whitelistUserIDs, ",")) + validUserIDs = make([]int64, 0, len(userIDs)) + for _, userID := range userIDs { + has, err := HasAccess(userID, repo, ACCESS_MODE_WRITE) + if err != nil { + return fmt.Errorf("HasAccess [user_id: %d, repo_id: %d]: %v", userID, protectBranch.RepoID, err) + } else if !has { + continue // Drop invalid user ID + } + + validUserIDs = append(validUserIDs, userID) + } + + protectBranch.WhitelistUserIDs = strings.Join(base.Int64sToStrings(validUserIDs), ",") + } + + hasTeamsChanged := false + validTeamIDs := base.StringsToInt64s(strings.Split(protectBranch.WhitelistTeamIDs, ",")) + if protectBranch.WhitelistTeamIDs != whitelistTeamIDs { + hasTeamsChanged = true + teamIDs := base.StringsToInt64s(strings.Split(whitelistTeamIDs, ",")) + teams, err := GetTeamsByOrgID(repo.OwnerID) + if err != nil { + return fmt.Errorf("GetTeamsByOrgID [org_id: %d]: %v", repo.OwnerID, err) + } + validTeamIDs = make([]int64, 0, len(teams)) + for i := range teams { + if teams[i].HasWriteAccess() && com.IsSliceContainsInt64(teamIDs, teams[i].ID) { + validTeamIDs = append(validTeamIDs, teams[i].ID) + } + } + + protectBranch.WhitelistTeamIDs = strings.Join(base.Int64sToStrings(validTeamIDs), ",") + } + + // Merge users and members of teams + var whitelists []*ProtectBranchWhitelist + if hasUsersChanged || hasTeamsChanged { + mergedUserIDs := make(map[int64]bool) + for _, userID := range validUserIDs { + mergedUserIDs[userID] = true + } + + for _, teamID := range validTeamIDs { + members, err := GetTeamMembers(teamID) + if err != nil { + return fmt.Errorf("GetTeamMembers [team_id: %d]: %v", teamID, err) + } + + for i := range members { + mergedUserIDs[members[i].ID] = true + } + } + + whitelists = make([]*ProtectBranchWhitelist, 0, len(mergedUserIDs)) + for userID := range mergedUserIDs { + whitelists = append(whitelists, &ProtectBranchWhitelist{ + ProtectBranchID: protectBranch.ID, + RepoID: repo.ID, + Name: protectBranch.Name, + UserID: userID, + }) + } + } + + sess := x.NewSession() + defer sessionRelease(sess) + if err = sess.Begin(); err != nil { + return err + } + if protectBranch.ID == 0 { - if _, err = x.Insert(protectBranch); err != nil { + if _, err = sess.Insert(protectBranch); err != nil { return fmt.Errorf("Insert: %v", err) } return } - _, err = x.Id(protectBranch.ID).AllCols().Update(protectBranch) - return err + if _, err = sess.Id(protectBranch.ID).AllCols().Update(protectBranch); err != nil { + return fmt.Errorf("Update: %v", err) + } + + // Refresh whitelists + if hasUsersChanged || hasTeamsChanged { + if _, err = sess.Delete(&ProtectBranchWhitelist{ProtectBranchID: protectBranch.ID}); err != nil { + return fmt.Errorf("delete old protect branch whitelists: %v", err) + } else if _, err = sess.Insert(whitelists); err != nil { + return fmt.Errorf("insert new protect branch whitelists: %v", err) + } + } + + return sess.Commit() } // GetProtectBranchesByRepoID returns a list of *ProtectBranch in given repostiory. |