diff options
author | ᴜɴᴋɴᴡᴏɴ <u@gogs.io> | 2020-10-06 15:43:28 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-10-06 15:43:28 +0800 |
commit | 2eaf1d693ae08c0312dd1f977f81675f418f3770 (patch) | |
tree | b42a26180e813918a05a676bd18e13a3a1bb73ec /internal/db | |
parent | a92d818aa35583594a6b3098f4e1e1f4edec17dd (diff) |
db: migrate access table to use GORM (#6371)
Diffstat (limited to 'internal/db')
-rw-r--r-- | internal/db/access.go | 240 | ||||
-rw-r--r-- | internal/db/backup_test.go | 17 | ||||
-rw-r--r-- | internal/db/db.go | 2 | ||||
-rw-r--r-- | internal/db/issue.go | 15 | ||||
-rw-r--r-- | internal/db/mocks.go | 12 | ||||
-rw-r--r-- | internal/db/models.go | 2 | ||||
-rw-r--r-- | internal/db/org_team.go | 32 | ||||
-rw-r--r-- | internal/db/perms.go | 73 | ||||
-rw-r--r-- | internal/db/perms_test.go | 50 | ||||
-rw-r--r-- | internal/db/repo.go | 111 | ||||
-rw-r--r-- | internal/db/repo_branch.go | 10 | ||||
-rw-r--r-- | internal/db/ssh_key.go | 10 | ||||
-rw-r--r-- | internal/db/testdata/backup/Access.golden.json | 2 | ||||
-rw-r--r-- | internal/db/user.go | 74 |
14 files changed, 336 insertions, 314 deletions
diff --git a/internal/db/access.go b/internal/db/access.go deleted file mode 100644 index 987abe80..00000000 --- a/internal/db/access.go +++ /dev/null @@ -1,240 +0,0 @@ -// Copyright 2014 The Gogs 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 db - -import ( - "fmt" - - log "unknwon.dev/clog/v2" -) - -type AccessMode int - -const ( - AccessModeNone AccessMode = iota // 0 - AccessModeRead // 1 - AccessModeWrite // 2 - AccessModeAdmin // 3 - AccessModeOwner // 4 -) - -func (mode AccessMode) String() string { - switch mode { - case AccessModeRead: - return "read" - case AccessModeWrite: - return "write" - case AccessModeAdmin: - return "admin" - case AccessModeOwner: - return "owner" - default: - return "none" - } -} - -// ParseAccessMode returns corresponding access mode to given permission string. -func ParseAccessMode(permission string) AccessMode { - switch permission { - case "write": - return AccessModeWrite - case "admin": - return AccessModeAdmin - default: - return AccessModeRead - } -} - -// Access represents the highest access level of a user to a repository. The only access type -// that is not in this table is the real owner of a repository. In case of an organization -// repository, the members of the owners team are in this table. -type Access struct { - ID int64 - UserID int64 `xorm:"UNIQUE(s)"` - RepoID int64 `xorm:"UNIQUE(s)"` - Mode AccessMode -} - -func userAccessMode(e Engine, userID int64, repo *Repository) (AccessMode, error) { - mode := AccessModeNone - // Everyone has read access to public repository - if !repo.IsPrivate { - mode = AccessModeRead - } - - if userID <= 0 { - return mode, nil - } - - if userID == repo.OwnerID { - return AccessModeOwner, nil - } - - access := &Access{ - UserID: userID, - RepoID: repo.ID, - } - if has, err := e.Get(access); !has || err != nil { - return mode, err - } - return access.Mode, nil -} - -// UserAccessMode returns the access mode of given user to the repository. -func UserAccessMode(userID int64, repo *Repository) (AccessMode, error) { - return userAccessMode(x, userID, repo) -} - -func hasAccess(e Engine, userID int64, repo *Repository, testMode AccessMode) (bool, error) { - mode, err := userAccessMode(e, userID, repo) - return mode >= testMode, err -} - -// HasAccess returns true if someone has the request access level. User can be nil! -// Deprecated: Use Perms.Authorize instead. -func HasAccess(userID int64, repo *Repository, testMode AccessMode) (bool, error) { - return hasAccess(x, userID, repo, testMode) -} - -// GetRepositoryAccesses finds all repositories with their access mode where a user has access but does not own. -func (u *User) GetRepositoryAccesses() (map[*Repository]AccessMode, error) { - accesses := make([]*Access, 0, 10) - if err := x.Find(&accesses, &Access{UserID: u.ID}); err != nil { - return nil, err - } - - repos := make(map[*Repository]AccessMode, len(accesses)) - for _, access := range accesses { - repo, err := GetRepositoryByID(access.RepoID) - if err != nil { - if IsErrRepoNotExist(err) { - log.Error("Failed to get repository by ID: %v", err) - continue - } - return nil, err - } - if repo.OwnerID == u.ID { - continue - } - repos[repo] = access.Mode - } - return repos, nil -} - -// GetAccessibleRepositories finds repositories which the user has access but does not own. -// If limit is smaller than 1 means returns all found results. -func (user *User) GetAccessibleRepositories(limit int) (repos []*Repository, _ error) { - sess := x.Where("owner_id !=? ", user.ID).Desc("updated_unix") - if limit > 0 { - sess.Limit(limit) - repos = make([]*Repository, 0, limit) - } else { - repos = make([]*Repository, 0, 10) - } - return repos, sess.Join("INNER", "access", "access.user_id = ? AND access.repo_id = repository.id", user.ID).Find(&repos) -} - -func maxAccessMode(modes ...AccessMode) AccessMode { - max := AccessModeNone - for _, mode := range modes { - if mode > max { - max = mode - } - } - return max -} - -// Deprecated: Use Perms.SetRepoPerms instead. -func (repo *Repository) refreshAccesses(e Engine, accessMap map[int64]AccessMode) (err error) { - newAccesses := make([]Access, 0, len(accessMap)) - for userID, mode := range accessMap { - newAccesses = append(newAccesses, Access{ - UserID: userID, - RepoID: repo.ID, - Mode: mode, - }) - } - - // Delete old accesses and insert new ones for repository. - if _, err = e.Delete(&Access{RepoID: repo.ID}); err != nil { - return fmt.Errorf("delete old accesses: %v", err) - } else if _, err = e.Insert(newAccesses); err != nil { - return fmt.Errorf("insert new accesses: %v", err) - } - return nil -} - -// refreshCollaboratorAccesses retrieves repository collaborations with their access modes. -func (repo *Repository) refreshCollaboratorAccesses(e Engine, accessMap map[int64]AccessMode) error { - collaborations, err := repo.getCollaborations(e) - if err != nil { - return fmt.Errorf("getCollaborations: %v", err) - } - for _, c := range collaborations { - accessMap[c.UserID] = c.Mode - } - return nil -} - -// recalculateTeamAccesses recalculates new accesses for teams of an organization -// except the team whose ID is given. It is used to assign a team ID when -// remove repository from that team. -func (repo *Repository) recalculateTeamAccesses(e Engine, ignTeamID int64) (err error) { - accessMap := make(map[int64]AccessMode, 20) - - if err = repo.getOwner(e); err != nil { - return err - } else if !repo.Owner.IsOrganization() { - return fmt.Errorf("owner is not an organization: %d", repo.OwnerID) - } - - if err = repo.refreshCollaboratorAccesses(e, accessMap); err != nil { - return fmt.Errorf("refreshCollaboratorAccesses: %v", err) - } - - if err = repo.Owner.getTeams(e); err != nil { - return err - } - - for _, t := range repo.Owner.Teams { - if t.ID == ignTeamID { - continue - } - - // Owner team gets owner access, and skip for teams that do not - // have relations with repository. - if t.IsOwnerTeam() { - t.Authorize = AccessModeOwner - } else if !t.hasRepository(e, repo.ID) { - continue - } - - if err = t.getMembers(e); err != nil { - return fmt.Errorf("getMembers '%d': %v", t.ID, err) - } - for _, m := range t.Members { - accessMap[m.ID] = maxAccessMode(accessMap[m.ID], t.Authorize) - } - } - - return repo.refreshAccesses(e, accessMap) -} - -func (repo *Repository) recalculateAccesses(e Engine) error { - if repo.Owner.IsOrganization() { - return repo.recalculateTeamAccesses(e, 0) - } - - accessMap := make(map[int64]AccessMode, 10) - if err := repo.refreshCollaboratorAccesses(e, accessMap); err != nil { - return fmt.Errorf("refreshCollaboratorAccesses: %v", err) - } - return repo.refreshAccesses(e, accessMap) -} - -// RecalculateAccesses recalculates all accesses for repository. -func (repo *Repository) RecalculateAccesses() error { - return repo.recalculateAccesses(x) -} diff --git a/internal/db/backup_test.go b/internal/db/backup_test.go index 27688889..4bbef8f6 100644 --- a/internal/db/backup_test.go +++ b/internal/db/backup_test.go @@ -29,8 +29,8 @@ func Test_dumpAndImport(t *testing.T) { t.Parallel() - if len(Tables) != 3 { - t.Fatalf("New table has added (want 3 got %d), please add new tests for the table and update this check", len(Tables)) + if len(Tables) != 4 { + t.Fatalf("New table has added (want 4 got %d), please add new tests for the table and update this check", len(Tables)) } db := initTestDB(t, "dumpAndImport", Tables...) @@ -46,6 +46,19 @@ func setupDBToDump(t *testing.T, db *gorm.DB) { t.Helper() vals := []interface{}{ + &Access{ + ID: 1, + UserID: 1, + RepoID: 11, + Mode: AccessModeRead, + }, + &Access{ + ID: 2, + UserID: 2, + RepoID: 22, + Mode: AccessModeWrite, + }, + &AccessToken{ UserID: 1, Name: "test1", diff --git a/internal/db/db.go b/internal/db/db.go index 7f82c43f..739dea68 100644 --- a/internal/db/db.go +++ b/internal/db/db.go @@ -141,7 +141,7 @@ func openDB(opts conf.DatabaseOpts, cfg *gorm.Config) (*gorm.DB, error) { // // NOTE: Lines are sorted in alphabetical order, each letter in its own line. var Tables = []interface{}{ - new(AccessToken), + new(Access), new(AccessToken), new(LFSObject), new(LoginSource), } diff --git a/internal/db/issue.go b/internal/db/issue.go index 79d4a7b9..d7726beb 100644 --- a/internal/db/issue.go +++ b/internal/db/issue.go @@ -679,17 +679,12 @@ func newIssue(e *xorm.Session, opts NewIssueOptions) (err error) { return fmt.Errorf("get user by ID: %v", err) } - // Assume assignee is invalid and drop silently. - opts.Issue.AssigneeID = 0 if assignee != nil { - valid, err := hasAccess(e, assignee.ID, opts.Repo, AccessModeRead) - if err != nil { - return fmt.Errorf("hasAccess [user_id: %d, repo_id: %d]: %v", assignee.ID, opts.Repo.ID, err) - } - if valid { - opts.Issue.AssigneeID = assignee.ID - opts.Issue.Assignee = assignee - } + opts.Issue.AssigneeID = assignee.ID + opts.Issue.Assignee = assignee + } else { + // The assignee does not exist, drop it + opts.Issue.AssigneeID = 0 } } diff --git a/internal/db/mocks.go b/internal/db/mocks.go index f7f45cab..791992c6 100644 --- a/internal/db/mocks.go +++ b/internal/db/mocks.go @@ -134,17 +134,17 @@ func (m *mockLoginSourceFileStore) Save() error { var _ PermsStore = (*MockPermsStore)(nil) type MockPermsStore struct { - MockAccessMode func(userID int64, repo *Repository) AccessMode - MockAuthorize func(userID int64, repo *Repository, desired AccessMode) bool + MockAccessMode func(userID, repoID int64, opts AccessModeOptions) AccessMode + MockAuthorize func(userID, repoID int64, desired AccessMode, opts AccessModeOptions) bool MockSetRepoPerms func(repoID int64, accessMap map[int64]AccessMode) error } -func (m *MockPermsStore) AccessMode(userID int64, repo *Repository) AccessMode { - return m.MockAccessMode(userID, repo) +func (m *MockPermsStore) AccessMode(userID, repoID int64, opts AccessModeOptions) AccessMode { + return m.MockAccessMode(userID, repoID, opts) } -func (m *MockPermsStore) Authorize(userID int64, repo *Repository, desired AccessMode) bool { - return m.MockAuthorize(userID, repo, desired) +func (m *MockPermsStore) Authorize(userID, repoID int64, desired AccessMode, opts AccessModeOptions) bool { + return m.MockAuthorize(userID, repoID, desired, opts) } func (m *MockPermsStore) SetRepoPerms(repoID int64, accessMap map[int64]AccessMode) error { diff --git a/internal/db/models.go b/internal/db/models.go index ac9cbe32..f80cad92 100644 --- a/internal/db/models.go +++ b/internal/db/models.go @@ -51,7 +51,7 @@ var ( func init() { legacyTables = append(legacyTables, new(User), new(PublicKey), new(TwoFactor), new(TwoFactorRecoveryCode), - new(Repository), new(DeployKey), new(Collaboration), new(Access), new(Upload), + new(Repository), new(DeployKey), new(Collaboration), new(Upload), new(Watch), new(Star), new(Follow), new(Action), new(Issue), new(PullRequest), new(Comment), new(Attachment), new(IssueUser), new(Label), new(IssueLabel), new(Milestone), diff --git a/internal/db/org_team.go b/internal/db/org_team.go index b2d6fd56..216aa0e9 100644 --- a/internal/db/org_team.go +++ b/internal/db/org_team.go @@ -173,6 +173,38 @@ func (t *Team) removeRepository(e Engine, repo *Repository, recalculate bool) (e if err = t.getMembers(e); err != nil { return fmt.Errorf("get team members: %v", err) } + + // TODO: Delete me when this method is migrated to use GORM. + userAccessMode := func(e Engine, userID int64, repo *Repository) (AccessMode, error) { + mode := AccessModeNone + // Everyone has read access to public repository + if !repo.IsPrivate { + mode = AccessModeRead + } + + if userID <= 0 { + return mode, nil + } + + if userID == repo.OwnerID { + return AccessModeOwner, nil + } + + access := &Access{ + UserID: userID, + RepoID: repo.ID, + } + if has, err := e.Get(access); !has || err != nil { + return mode, err + } + return access.Mode, nil + } + + hasAccess := func(e Engine, userID int64, repo *Repository, testMode AccessMode) (bool, error) { + mode, err := userAccessMode(e, userID, repo) + return mode >= testMode, err + } + for _, member := range t.Members { has, err := hasAccess(e, member.ID, repo, AccessModeRead) if err != nil { diff --git a/internal/db/perms.go b/internal/db/perms.go index 1295300d..4a9e6330 100644 --- a/internal/db/perms.go +++ b/internal/db/perms.go @@ -14,9 +14,9 @@ import ( // NOTE: All methods are sorted in alphabetical order. type PermsStore interface { // AccessMode returns the access mode of given user has to the repository. - AccessMode(userID int64, repo *Repository) AccessMode + AccessMode(userID, repoID int64, opts AccessModeOptions) AccessMode // Authorize returns true if the user has as good as desired access mode to the repository. - Authorize(userID int64, repo *Repository, desired AccessMode) bool + Authorize(userID, repoID int64, desired AccessMode, opts AccessModeOptions) bool // SetRepoPerms does a full update to which users have which level of access to given repository. // Keys of the "accessMap" are user IDs. SetRepoPerms(repoID int64, accessMap map[int64]AccessMode) error @@ -24,19 +24,72 @@ type PermsStore interface { var Perms PermsStore +// Access represents the highest access level of a user has to a repository. +// The only access type that is not in this table is the real owner of a repository. +// In case of an organization repository, the members of the owners team are in this table. +type Access struct { + ID int64 + UserID int64 `xorm:"UNIQUE(s)" gorm:"uniqueIndex:access_user_repo_unique;NOT NULL"` + RepoID int64 `xorm:"UNIQUE(s)" gorm:"uniqueIndex:access_user_repo_unique;NOT NULL"` + Mode AccessMode `gorm:"NOT NULL"` +} + +// AccessMode is the access mode of a user has to a repository. +type AccessMode int + +const ( + AccessModeNone AccessMode = iota // 0 + AccessModeRead // 1 + AccessModeWrite // 2 + AccessModeAdmin // 3 + AccessModeOwner // 4 +) + +func (mode AccessMode) String() string { + switch mode { + case AccessModeRead: + return "read" + case AccessModeWrite: + return "write" + case AccessModeAdmin: + return "admin" + case AccessModeOwner: + return "owner" + default: + return "none" + } +} + +// ParseAccessMode returns corresponding access mode to given permission string. +func ParseAccessMode(permission string) AccessMode { + switch permission { + case "write": + return AccessModeWrite + case "admin": + return AccessModeAdmin + default: + return AccessModeRead + } +} + var _ PermsStore = (*perms)(nil) type perms struct { *gorm.DB } -func (db *perms) AccessMode(userID int64, repo *Repository) (mode AccessMode) { - if repo == nil { +type AccessModeOptions struct { + OwnerID int64 // The ID of the repository owner. + Private bool // Whether the repository is private. +} + +func (db *perms) AccessMode(userID, repoID int64, opts AccessModeOptions) (mode AccessMode) { + if repoID <= 0 { return AccessModeNone } // Everyone has read access to public repository. - if !repo.IsPrivate { + if !opts.Private { mode = AccessModeRead } @@ -45,23 +98,23 @@ func (db *perms) AccessMode(userID int64, repo *Repository) (mode AccessMode) { return mode } - if userID == repo.OwnerID { + if userID == opts.OwnerID { return AccessModeOwner } access := new(Access) - err := db.Where("user_id = ? AND repo_id = ?", userID, repo.ID).First(access).Error + err := db.Where("user_id = ? AND repo_id = ?", userID, repoID).First(access).Error if err != nil { if err != gorm.ErrRecordNotFound { - log.Error("Failed to get access [user_id: %d, repo_id: %d]: %v", userID, repo.ID, err) + log.Error("Failed to get access [user_id: %d, repo_id: %d]: %v", userID, repoID, err) } return mode } return access.Mode } -func (db *perms) Authorize(userID int64, repo *Repository, desired AccessMode) bool { - return desired <= db.AccessMode(userID, repo) +func (db *perms) Authorize(userID, repoID int64, desired AccessMode, opts AccessModeOptions) bool { + return desired <= db.AccessMode(userID, repoID, opts) } func (db *perms) SetRepoPerms(repoID int64, accessMap map[int64]AccessMode) error { diff --git a/internal/db/perms_test.go b/internal/db/perms_test.go index f11a6d60..c3b463ee 100644 --- a/internal/db/perms_test.go +++ b/internal/db/perms_test.go @@ -57,20 +57,22 @@ func test_perms_AccessMode(t *testing.T, db *perms) { t.Fatal(err) } - publicRepo := &Repository{ - ID: 1, + publicRepoID := int64(1) + publicRepoOpts := AccessModeOptions{ OwnerID: 98, } - privateRepo := &Repository{ - ID: 2, - OwnerID: 99, - IsPrivate: true, + + privateRepoID := int64(2) + privateRepoOpts := AccessModeOptions{ + OwnerID: 99, + Private: true, } tests := []struct { name string userID int64 - repo *Repository + repoID int64 + opts AccessModeOptions expAccessMode AccessMode }{ { @@ -80,62 +82,71 @@ func test_perms_AccessMode(t *testing.T, db *perms) { { name: "anonymous user has read access to public repository", - repo: publicRepo, + repoID: publicRepoID, + opts: publicRepoOpts, expAccessMode: AccessModeRead, }, { name: "anonymous user has no access to private repository", - repo: privateRepo, + repoID: privateRepoID, + opts: privateRepoOpts, expAccessMode: AccessModeNone, }, { name: "user is the owner", userID: 98, - repo: publicRepo, + repoID: publicRepoID, + opts: publicRepoOpts, expAccessMode: AccessModeOwner, }, { name: "user 1 has read access to public repo", userID: 1, - repo: publicRepo, + repoID: publicRepoID, + opts: publicRepoOpts, expAccessMode: AccessModeRead, }, { name: "user 2 has write access to public repo", userID: 2, - repo: publicRepo, + repoID: publicRepoID, + opts: publicRepoOpts, expAccessMode: AccessModeWrite, }, { name: "user 3 has admin access to public repo", userID: 3, - repo: publicRepo, + repoID: publicRepoID, + opts: publicRepoOpts, expAccessMode: AccessModeAdmin, }, { name: "user 1 has read access to private repo", userID: 1, - repo: privateRepo, + repoID: privateRepoID, + opts: privateRepoOpts, expAccessMode: AccessModeRead, }, { name: "user 2 has no access to private repo", userID: 2, - repo: privateRepo, + repoID: privateRepoID, + opts: privateRepoOpts, expAccessMode: AccessModeNone, }, { name: "user 3 has no access to private repo", userID: 3, - repo: privateRepo, + repoID: privateRepoID, + opts: privateRepoOpts, expAccessMode: AccessModeNone, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - mode := db.AccessMode(test.userID, test.repo) + mode := db.AccessMode(test.userID, test.repoID, test.opts) assert.Equal(t, test.expAccessMode, mode) }) } @@ -216,7 +227,10 @@ func test_perms_Authorize(t *testing.T, db *perms) { } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - authorized := db.Authorize(test.userID, repo, test.desired) + authorized := db.Authorize(test.userID, repo.ID, test.desired, AccessModeOptions{ + OwnerID: repo.OwnerID, + Private: repo.IsPrivate, + }) assert.Equal(t, test.expAuthorized, authorized) }) } diff --git a/internal/db/repo.go b/internal/db/repo.go index 31ea9ac6..a2d23a63 100644 --- a/internal/db/repo.go +++ b/internal/db/repo.go @@ -555,8 +555,12 @@ func (repo *Repository) ComposeCompareURL(oldCommitID, newCommitID string) strin } func (repo *Repository) HasAccess(userID int64) bool { - has, _ := HasAccess(userID, repo, AccessModeRead) - return has + return Perms.Authorize(userID, repo.ID, AccessModeRead, + AccessModeOptions{ + OwnerID: repo.OwnerID, + Private: repo.IsPrivate, + }, + ) } func (repo *Repository) IsOwnedBy(userID int64) bool { @@ -2511,3 +2515,106 @@ func (repo *Repository) CreateNewBranch(oldBranch, newBranch string) (err error) return nil } + +// Deprecated: Use Perms.SetRepoPerms instead. +func (repo *Repository) refreshAccesses(e Engine, accessMap map[int64]AccessMode) (err error) { + newAccesses := make([]Access, 0, len(accessMap)) + for userID, mode := range accessMap { + newAccesses = append(newAccesses, Access{ + UserID: userID, + RepoID: repo.ID, + Mode: mode, + }) + } + + // Delete old accesses and insert new ones for repository. + if _, err = e.Delete(&Access{RepoID: repo.ID}); err != nil { + return fmt.Errorf("delete old accesses: %v", err) + } else if _, err = e.Insert(newAccesses); err != nil { + return fmt.Errorf("insert new accesses: %v", err) + } + return nil +} + +// refreshCollaboratorAccesses retrieves repository collaborations with their access modes. +func (repo *Repository) refreshCollaboratorAccesses(e Engine, accessMap map[int64]AccessMode) error { + collaborations, err := repo.getCollaborations(e) + if err != nil { + return fmt.Errorf("getCollaborations: %v", err) + } + for _, c := range collaborations { + accessMap[c.UserID] = c.Mode + } + return nil +} + +// recalculateTeamAccesses recalculates new accesses for teams of an organization +// except the team whose ID is given. It is used to assign a team ID when +// remove repository from that team. +func (repo *Repository) recalculateTeamAccesses(e Engine, ignTeamID int64) (err error) { + accessMap := make(map[int64]AccessMode, 20) + + if err = repo.getOwner(e); err != nil { + return err + } else if !repo.Owner.IsOrganization() { + return fmt.Errorf("owner is not an organization: %d", repo.OwnerID) + } + + if err = repo.refreshCollaboratorAccesses(e, accessMap); err != nil { + return fmt.Errorf("refreshCollaboratorAccesses: %v", err) + } + + if err = repo.Owner.getTeams(e); err != nil { + return err + } + + maxAccessMode := func(modes ...AccessMode) AccessMode { + max := AccessModeNone + for _, mode := range modes { + if mode > max { + max = mode + } + } + return max + } + + for _, t := range repo.Owner.Teams { + if t.ID == ignTeamID { + continue + } + + // Owner team gets owner access, and skip for teams that do not + // have relations with repository. + if t.IsOwnerTeam() { + t.Authorize = AccessModeOwner + } else if !t.hasRepository(e, repo.ID) { + continue + } + + if err = t.getMembers(e); err != nil { + return fmt.Errorf("getMembers '%d': %v", t.ID, err) + } + for _, m := range t.Members { + accessMap[m.ID] = maxAccessMode(accessMap[m.ID], t.Authorize) + } + } + + return repo.refreshAccesses(e, accessMap) +} + +func (repo *Repository) recalculateAccesses(e Engine) error { + if repo.Owner.IsOrganization() { + return repo.recalculateTeamAccesses(e, 0) + } + + accessMap := make(map[int64]AccessMode, 10) + if err := repo.refreshCollaboratorAccesses(e, accessMap); err != nil { + return fmt.Errorf("refreshCollaboratorAccesses: %v", err) + } + return repo.refreshAccesses(e, accessMap) +} + +// RecalculateAccesses recalculates all accesses for repository. +func (repo *Repository) RecalculateAccesses() error { + return repo.recalculateAccesses(x) +} diff --git a/internal/db/repo_branch.go b/internal/db/repo_branch.go index ceb99231..3b7f9137 100644 --- a/internal/db/repo_branch.go +++ b/internal/db/repo_branch.go @@ -175,10 +175,12 @@ func UpdateOrgProtectBranch(repo *Repository, protectBranch *ProtectBranch, whit userIDs := tool.StringsToInt64s(strings.Split(whitelistUserIDs, ",")) validUserIDs = make([]int64, 0, len(userIDs)) for _, userID := range userIDs { - has, err := HasAccess(userID, repo, AccessModeWrite) - if err != nil { - return fmt.Errorf("HasAccess [user_id: %d, repo_id: %d]: %v", userID, protectBranch.RepoID, err) - } else if !has { + if !Perms.Authorize(userID, repo.ID, AccessModeWrite, + AccessModeOptions{ + OwnerID: repo.OwnerID, + Private: repo.IsPrivate, + }, + ) { continue // Drop invalid user ID } diff --git a/internal/db/ssh_key.go b/internal/db/ssh_key.go index 49cee17a..37bf9ae1 100644 --- a/internal/db/ssh_key.go +++ b/internal/db/ssh_key.go @@ -753,10 +753,12 @@ func DeleteDeployKey(doer *User, id int64) error { if err != nil { return fmt.Errorf("GetRepositoryByID: %v", err) } - yes, err := HasAccess(doer.ID, repo, AccessModeAdmin) - if err != nil { - return fmt.Errorf("HasAccess: %v", err) - } else if !yes { + if !Perms.Authorize(doer.ID, repo.ID, AccessModeAdmin, + AccessModeOptions{ + OwnerID: repo.OwnerID, + Private: repo.IsPrivate, + }, + ) { return ErrKeyAccessDenied{doer.ID, key.ID, "deploy"} } } diff --git a/internal/db/testdata/backup/Access.golden.json b/internal/db/testdata/backup/Access.golden.json new file mode 100644 index 00000000..3e2e2a46 --- /dev/null +++ b/internal/db/testdata/backup/Access.golden.json @@ -0,0 +1,2 @@ +{"ID":1,"UserID":1,"RepoID":11,"Mode":1} +{"ID":2,"UserID":2,"RepoID":22,"Mode":2} diff --git a/internal/db/user.go b/internal/db/user.go index 22f25b5d..0e3c106e 100644 --- a/internal/db/user.go +++ b/internal/db/user.go @@ -369,20 +369,22 @@ func (u *User) DeleteAvatar() error { // IsAdminOfRepo returns true if user has admin or higher access of repository. func (u *User) IsAdminOfRepo(repo *Repository) bool { - has, err := HasAccess(u.ID, repo, AccessModeAdmin) - if err != nil { - log.Error("HasAccess: %v", err) - } - return has + return Perms.Authorize(u.ID, repo.ID, AccessModeAdmin, + AccessModeOptions{ + OwnerID: repo.OwnerID, + Private: repo.IsPrivate, + }, + ) } // IsWriterOfRepo returns true if user has write access to given repository. func (u *User) IsWriterOfRepo(repo *Repository) bool { - has, err := HasAccess(u.ID, repo, AccessModeWrite) - if err != nil { - log.Error("HasAccess: %v", err) - } - return has + return Perms.Authorize(u.ID, repo.ID, AccessModeWrite, + AccessModeOptions{ + OwnerID: repo.OwnerID, + Private: repo.IsPrivate, + }, + ) } // IsOrganization returns true if user is actually a organization. @@ -937,15 +939,17 @@ func GetUserByID(id int64) (*User, error) { return getUserByID(x, id) } -// GetAssigneeByID returns the user with write access of repository by given ID. +// GetAssigneeByID returns the user with read access of repository by given ID. func GetAssigneeByID(repo *Repository, userID int64) (*User, error) { - has, err := HasAccess(userID, repo, AccessModeRead) - if err != nil { - return nil, err - } else if !has { + if !Perms.Authorize(userID, repo.ID, AccessModeRead, + AccessModeOptions{ + OwnerID: repo.OwnerID, + Private: repo.IsPrivate, + }, + ) { return nil, ErrUserNotExist{args: map[string]interface{}{"userID": userID}} } - return GetUserByID(userID) + return Users.GetByID(userID) } // GetUserByName returns a user by given name. @@ -1171,3 +1175,41 @@ func UnfollowUser(userID, followID int64) (err error) { } return sess.Commit() } + +// GetRepositoryAccesses finds all repositories with their access mode where a user has access but does not own. +func (u *User) GetRepositoryAccesses() (map[*Repository]AccessMode, error) { + accesses := make([]*Access, 0, 10) + if err := x.Find(&accesses, &Access{UserID: u.ID}); err != nil { + return nil, err + } + + repos := make(map[*Repository]AccessMode, len(accesses)) + for _, access := range accesses { + repo, err := GetRepositoryByID(access.RepoID) + if err != nil { + if IsErrRepoNotExist(err) { + log.Error("Failed to get repository by ID: %v", err) + continue + } + return nil, err + } + if repo.OwnerID == u.ID { + continue + } + repos[repo] = access.Mode + } + return repos, nil +} + +// GetAccessibleRepositories finds repositories which the user has access but does not own. +// If limit is smaller than 1 means returns all found results. +func (user *User) GetAccessibleRepositories(limit int) (repos []*Repository, _ error) { + sess := x.Where("owner_id !=? ", user.ID).Desc("updated_unix") + if limit > 0 { + sess.Limit(limit) + repos = make([]*Repository, 0, limit) + } else { + repos = make([]*Repository, 0, 10) + } + return repos, sess.Join("INNER", "access", "access.user_id = ? AND access.repo_id = repository.id", user.ID).Find(&repos) +} |