diff options
author | Lunny Xiao <xiaolunwen@gmail.com> | 2017-09-14 16:16:22 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-09-14 16:16:22 +0800 |
commit | 1739e84ac02c0384c04576a00abab9348293f9c7 (patch) | |
tree | 1015e68f36421f274d2e883ff3ddb0cb29b6af71 /models | |
parent | be3319b3d545289b772d7a92b4b62205863954d9 (diff) | |
download | gitea-1739e84ac02c0384c04576a00abab9348293f9c7.tar.gz gitea-1739e84ac02c0384c04576a00abab9348293f9c7.zip |
improve protected branch to add whitelist support (#2451)
* improve protected branch to add whitelist support
* fix lint
* fix style check
* fix tests
* fix description on UI and import
* fix test
* bug fixed
* fix tests and languages
* move isSliceInt64Eq to util pkg; improve function names & typo
Diffstat (limited to 'models')
-rw-r--r-- | models/branches.go | 192 | ||||
-rw-r--r-- | models/migrations/migrations.go | 2 | ||||
-rw-r--r-- | models/migrations/v40.go | 55 | ||||
-rw-r--r-- | models/org.go | 5 | ||||
-rw-r--r-- | models/org_team.go | 20 | ||||
-rw-r--r-- | models/repo.go | 36 |
6 files changed, 230 insertions, 80 deletions
diff --git a/models/branches.go b/models/branches.go index 4461da0067..1c3c0d17be 100644 --- a/models/branches.go +++ b/models/branches.go @@ -8,6 +8,12 @@ import ( "fmt" "strings" "time" + + "code.gitea.io/gitea/modules/base" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/util" + + "github.com/Unknwon/com" ) const ( @@ -17,14 +23,43 @@ const ( // ProtectedBranch struct type ProtectedBranch struct { - ID int64 `xorm:"pk autoincr"` - RepoID int64 `xorm:"UNIQUE(s)"` - BranchName string `xorm:"UNIQUE(s)"` - CanPush bool - Created time.Time `xorm:"-"` - CreatedUnix int64 `xorm:"created"` - Updated time.Time `xorm:"-"` - UpdatedUnix int64 `xorm:"updated"` + ID int64 `xorm:"pk autoincr"` + RepoID int64 `xorm:"UNIQUE(s)"` + BranchName string `xorm:"UNIQUE(s)"` + EnableWhitelist bool + WhitelistUserIDs []int64 `xorm:"JSON TEXT"` + WhitelistTeamIDs []int64 `xorm:"JSON TEXT"` + Created time.Time `xorm:"-"` + CreatedUnix int64 `xorm:"created"` + Updated time.Time `xorm:"-"` + UpdatedUnix int64 `xorm:"updated"` +} + +// IsProtected returns if the branch is protected +func (protectBranch *ProtectedBranch) IsProtected() bool { + return protectBranch.ID > 0 +} + +// CanUserPush returns if some user could push to this protected branch +func (protectBranch *ProtectedBranch) CanUserPush(userID int64) bool { + if !protectBranch.EnableWhitelist { + return false + } + + if base.Int64sContains(protectBranch.WhitelistUserIDs, userID) { + return true + } + + if len(protectBranch.WhitelistTeamIDs) == 0 { + return false + } + + in, err := IsUserInTeams(userID, protectBranch.WhitelistTeamIDs) + if err != nil { + log.Error(1, "IsUserInTeams:", err) + return false + } + return in } // GetProtectedBranchByRepoID getting protected branch by repo ID @@ -46,6 +81,73 @@ func GetProtectedBranchBy(repoID int64, BranchName string) (*ProtectedBranch, er return rel, nil } +// GetProtectedBranchByID getting protected branch by ID +func GetProtectedBranchByID(id int64) (*ProtectedBranch, error) { + rel := &ProtectedBranch{ID: id} + has, err := x.Get(rel) + if err != nil { + return nil, err + } + if !has { + return nil, nil + } + return rel, nil +} + +// UpdateProtectBranch saves branch protection options of 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 UpdateProtectBranch(repo *Repository, protectBranch *ProtectedBranch, whitelistUserIDs, whitelistTeamIDs []int64) (err error) { + if err = repo.GetOwner(); err != nil { + return fmt.Errorf("GetOwner: %v", err) + } + + hasUsersChanged := !util.IsSliceInt64Eq(protectBranch.WhitelistUserIDs, whitelistUserIDs) + if hasUsersChanged { + protectBranch.WhitelistUserIDs = make([]int64, 0, len(whitelistUserIDs)) + for _, userID := range whitelistUserIDs { + has, err := hasAccess(x, userID, repo, AccessModeWrite) + 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 + } + + protectBranch.WhitelistUserIDs = append(protectBranch.WhitelistUserIDs, userID) + } + } + + // if the repo is in an orgniziation + hasTeamsChanged := !util.IsSliceInt64Eq(protectBranch.WhitelistTeamIDs, whitelistTeamIDs) + if hasTeamsChanged { + teams, err := GetTeamsWithAccessToRepo(repo.OwnerID, repo.ID, AccessModeWrite) + if err != nil { + return fmt.Errorf("GetTeamsWithAccessToRepo [org_id: %d, repo_id: %d]: %v", repo.OwnerID, repo.ID, err) + } + protectBranch.WhitelistTeamIDs = make([]int64, 0, len(teams)) + for i := range teams { + if teams[i].HasWriteAccess() && com.IsSliceContainsInt64(whitelistTeamIDs, teams[i].ID) { + protectBranch.WhitelistTeamIDs = append(protectBranch.WhitelistTeamIDs, teams[i].ID) + } + } + } + + // Make sure protectBranch.ID is not 0 for whitelists + if protectBranch.ID == 0 { + if _, err = x.Insert(protectBranch); err != nil { + return fmt.Errorf("Insert: %v", err) + } + return nil + } + + if _, err = x.Id(protectBranch.ID).AllCols().Update(protectBranch); err != nil { + return fmt.Errorf("Update: %v", err) + } + + return nil +} + // GetProtectedBranches get all protected branches func (repo *Repository) GetProtectedBranches() ([]*ProtectedBranch, error) { protectedBranches := make([]*ProtectedBranch, 0) @@ -53,7 +155,7 @@ func (repo *Repository) GetProtectedBranches() ([]*ProtectedBranch, error) { } // IsProtectedBranch checks if branch is protected -func (repo *Repository) IsProtectedBranch(branchName string) (bool, error) { +func (repo *Repository) IsProtectedBranch(branchName string, doer *User) (bool, error) { protectedBranch := &ProtectedBranch{ RepoID: repo.ID, BranchName: branchName, @@ -63,70 +165,12 @@ func (repo *Repository) IsProtectedBranch(branchName string) (bool, error) { if err != nil { return true, err } else if has { - return true, nil + return !protectedBranch.CanUserPush(doer.ID), nil } return false, nil } -// AddProtectedBranch add protection to branch -func (repo *Repository) AddProtectedBranch(branchName string, canPush bool) error { - protectedBranch := &ProtectedBranch{ - RepoID: repo.ID, - BranchName: branchName, - } - - has, err := x.Get(protectedBranch) - if err != nil { - return err - } else if has { - return nil - } - - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return err - } - protectedBranch.CanPush = canPush - if _, err = sess.InsertOne(protectedBranch); err != nil { - return err - } - - return sess.Commit() -} - -// ChangeProtectedBranch access mode sets new access mode for the ProtectedBranch. -func (repo *Repository) ChangeProtectedBranch(id int64, canPush bool) error { - ProtectedBranch := &ProtectedBranch{ - RepoID: repo.ID, - ID: id, - } - has, err := x.Get(ProtectedBranch) - if err != nil { - return fmt.Errorf("get ProtectedBranch: %v", err) - } else if !has { - return nil - } - - if ProtectedBranch.CanPush == canPush { - return nil - } - ProtectedBranch.CanPush = canPush - - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return err - } - - if _, err = sess.Id(ProtectedBranch.ID).AllCols().Update(ProtectedBranch); err != nil { - return fmt.Errorf("update ProtectedBranch: %v", err) - } - - return sess.Commit() -} - // DeleteProtectedBranch removes ProtectedBranch relation between the user and repository. func (repo *Repository) DeleteProtectedBranch(id int64) (err error) { protectedBranch := &ProtectedBranch{ @@ -148,15 +192,3 @@ func (repo *Repository) DeleteProtectedBranch(id int64) (err error) { return sess.Commit() } - -// newProtectedBranch insert one queue -func newProtectedBranch(protectedBranch *ProtectedBranch) error { - _, err := x.InsertOne(protectedBranch) - return err -} - -// UpdateProtectedBranch update queue -func UpdateProtectedBranch(protectedBranch *ProtectedBranch) error { - _, err := x.Update(protectedBranch) - return err -} diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index a796c6d6af..e7542954d7 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -128,6 +128,8 @@ var migrations = []Migration{ NewMigration("remove commits and settings unit types", removeCommitsUnitType), // v39 -> v40 NewMigration("adds time tracking and stopwatches", addTimetracking), + // v40 -> v41 + NewMigration("migrate protected branch struct", migrateProtectedBranchStruct), } // Migrate database to current version diff --git a/models/migrations/v40.go b/models/migrations/v40.go new file mode 100644 index 0000000000..324521e0b6 --- /dev/null +++ b/models/migrations/v40.go @@ -0,0 +1,55 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package migrations + +import ( + "fmt" + "time" + + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" + + "github.com/go-xorm/xorm" +) + +func migrateProtectedBranchStruct(x *xorm.Engine) error { + type ProtectedBranch struct { + ID int64 `xorm:"pk autoincr"` + RepoID int64 `xorm:"UNIQUE(s)"` + BranchName string `xorm:"UNIQUE(s)"` + CanPush bool + Created time.Time `xorm:"-"` + CreatedUnix int64 + Updated time.Time `xorm:"-"` + UpdatedUnix int64 + } + + var pbs []ProtectedBranch + err := x.Find(&pbs) + if err != nil { + return err + } + + for _, pb := range pbs { + if pb.CanPush { + if _, err = x.ID(pb.ID).Delete(new(ProtectedBranch)); err != nil { + return err + } + } + } + + switch { + case setting.UseSQLite3: + log.Warn("Unable to drop columns in SQLite") + case setting.UseMySQL, setting.UsePostgreSQL, setting.UseMSSQL, setting.UseTiDB: + if _, err := x.Exec("ALTER TABLE protected_branch DROP COLUMN can_push"); err != nil { + return fmt.Errorf("DROP COLUMN can_push: %v", err) + } + default: + log.Fatal(4, "Unrecognized DB") + } + + return nil +} diff --git a/models/org.go b/models/org.go index d43f15f9aa..fd81753931 100644 --- a/models/org.go +++ b/models/org.go @@ -577,6 +577,11 @@ func (org *User) getUserTeamIDs(e Engine, userID int64) ([]int64, error) { Find(&teamIDs) } +// TeamsWithAccessToRepo returns all teamsthat have given access level to the repository. +func (org *User) TeamsWithAccessToRepo(repoID int64, mode AccessMode) ([]*Team, error) { + return GetTeamsWithAccessToRepo(org.ID, repoID, mode) +} + // GetUserTeamIDs returns of all team IDs of the organization that user is member of. func (org *User) GetUserTeamIDs(userID int64) ([]int64, error) { return org.getUserTeamIDs(x, userID) diff --git a/models/org_team.go b/models/org_team.go index bc0e12b504..acddc70b58 100644 --- a/models/org_team.go +++ b/models/org_team.go @@ -35,6 +35,11 @@ func (t *Team) GetUnitTypes() []UnitType { return t.UnitTypes } +// HasWriteAccess returns true if team has at least write level access mode. +func (t *Team) HasWriteAccess() bool { + return t.Authorize >= AccessModeWrite +} + // IsOwnerTeam returns true if team is owner team. func (t *Team) IsOwnerTeam() bool { return t.Name == ownerTeamName @@ -594,6 +599,11 @@ func RemoveTeamMember(team *Team, userID int64) error { return sess.Commit() } +// IsUserInTeams returns if a user in some teams +func IsUserInTeams(userID int64, teamIDs []int64) (bool, error) { + return x.Where("uid=?", userID).In("team_id", teamIDs).Exist(new(TeamUser)) +} + // ___________ __________ // \__ ___/___ _____ _____\______ \ ____ ______ ____ // | |_/ __ \\__ \ / \| _// __ \\____ \ / _ \ @@ -639,3 +649,13 @@ func removeTeamRepo(e Engine, teamID, repoID int64) error { }) return err } + +// GetTeamsWithAccessToRepo returns all teams in an organization that have given access level to the repository. +func GetTeamsWithAccessToRepo(orgID, repoID int64, mode AccessMode) ([]*Team, error) { + teams := make([]*Team, 0, 5) + return teams, x.Where("team.authorize >= ?", mode). + Join("INNER", "team_repo", "team_repo.team_id = team.id"). + And("team_repo.org_id = ?", orgID). + And("team_repo.repo_id = ?", repoID). + Find(&teams) +} diff --git a/models/repo.go b/models/repo.go index a2e63e2af7..8d5b3b87c7 100644 --- a/models/repo.go +++ b/models/repo.go @@ -656,6 +656,42 @@ func (repo *Repository) CanEnableEditor() bool { return !repo.IsMirror } +// GetWriters returns all users that have write access to the repository. +func (repo *Repository) GetWriters() (_ []*User, err error) { + return repo.getUsersWithAccessMode(x, AccessModeWrite) +} + +// getUsersWithAccessMode returns users that have at least given access mode to the repository. +func (repo *Repository) getUsersWithAccessMode(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, mode).Find(&accesses); err != nil { + return nil, err + } + + // Leave a seat for owner itself to append later, but if owner is an organization + // and just waste 1 unit is cheaper than re-allocate memory once. + users := make([]*User, 0, len(accesses)+1) + if len(accesses) > 0 { + userIDs := make([]int64, len(accesses)) + for i := 0; i < len(accesses); i++ { + userIDs[i] = accesses[i].UserID + } + + if err = e.In("id", userIDs).Find(&users); err != nil { + return nil, err + } + } + if !repo.Owner.IsOrganization() { + users = append(users, repo.Owner) + } + + return users, nil +} + // NextIssueIndex returns the next issue index // FIXME: should have a mutex to prevent producing same index for two issues that are created // closely enough. |