From cbd45471b1100bffcd2f18719b56a5da5468756b Mon Sep 17 00:00:00 2001
From: Lunny Xiao <xiaolunwen@gmail.com>
Date: Wed, 11 May 2022 18:09:36 +0800
Subject: [PATCH] Move access and repo permission to models/perm/access
 (#19350)

* Move access and repo permission to models/perm/access

* Remove unnecessary code
---
 integrations/api_repo_test.go                |   4 +-
 integrations/delete_user_test.go             |   3 +-
 models/action.go                             |   3 +-
 models/branches.go                           |  11 +-
 models/fixture_generation.go                 |   5 +-
 models/issue.go                              |   5 +-
 models/issue_xref.go                         |   3 +-
 models/lfs_lock.go                           |   3 +-
 models/org.go                                |   3 +-
 models/org_team.go                           |  33 ++--
 models/org_team_test.go                      |  23 ++-
 models/{ => perm/access}/access.go           |  34 ++--
 models/{ => perm/access}/access_test.go      |  37 ++---
 models/perm/access/main_test.go              |  32 ++++
 models/{ => perm/access}/repo_permission.go  |  93 +++++------
 models/pull_list.go                          |  35 +++++
 models/repo.go                               |  51 ++----
 models/repo/collaboration.go                 | 150 ++++++++++++++++++
 models/repo/collaboration_test.go            |  49 ++++++
 models/repo/main_test.go                     |   1 +
 models/repo_collaboration.go                 | 154 +------------------
 models/repo_collaboration_test.go            |  50 +-----
 models/repo_permission_test.go               |  55 +++----
 models/repo_transfer.go                      |   7 +-
 models/review.go                             |   3 +-
 models/statistic.go                          |   3 +-
 models/user.go                               |   6 +-
 modules/context/permission.go                |   2 +-
 modules/context/repo.go                      |  14 +-
 modules/convert/convert.go                   |   9 +-
 modules/convert/package.go                   |   4 +-
 modules/convert/pull.go                      |   5 +-
 modules/notification/webhook/webhook.go      |  39 ++---
 modules/templates/helper.go                  |   2 +-
 modules/test/context_tests.go                |   4 +-
 routers/api/v1/api.go                        |   4 +-
 routers/api/v1/org/team.go                   |   9 +-
 routers/api/v1/repo/collaborators.go         |  12 +-
 routers/api/v1/repo/file.go                  |   2 +-
 routers/api/v1/repo/fork.go                  |   4 +-
 routers/api/v1/repo/issue.go                 |   3 +-
 routers/api/v1/repo/issue_comment.go         |   3 +-
 routers/api/v1/repo/pull.go                  |   9 +-
 routers/api/v1/repo/pull_review.go           |   3 +-
 routers/api/v1/repo/repo.go                  |   5 +-
 routers/api/v1/user/repo.go                  |   5 +-
 routers/api/v1/user/star.go                  |   4 +-
 routers/api/v1/user/watch.go                 |   4 +-
 routers/private/hook_pre_receive.go          |   7 +-
 routers/private/serv.go                      |   4 +-
 routers/web/repo/attachment.go               |   3 +-
 routers/web/repo/compare.go                  |   5 +-
 routers/web/repo/http.go                     |   4 +-
 routers/web/repo/issue.go                    |  15 +-
 routers/web/repo/pull.go                     |   5 +-
 routers/web/repo/setting.go                  |   6 +-
 routers/web/repo/setting_protected_branch.go |   3 +-
 routers/web/repo/settings_test.go            |   4 +-
 routers/web/repo/tag.go                      |   3 +-
 routers/web/user/package.go                  |   5 +-
 services/automerge/automerge.go              |   3 +-
 services/issue/assignee.go                   |  11 +-
 services/issue/commit.go                     |   3 +-
 services/issue/issue.go                      |   3 +-
 services/issue/label.go                      |   3 +-
 services/lfs/server.go                       |   3 +-
 services/pull/check.go                       |   5 +-
 services/pull/edits.go                       |   3 +-
 services/pull/merge.go                       |   3 +-
 services/pull/update.go                      |   5 +-
 services/repository/transfer.go              |   5 +-
 services/repository/transfer_test.go         |   6 +-
 72 files changed, 608 insertions(+), 511 deletions(-)
 rename models/{ => perm/access}/access.go (85%)
 rename models/{ => perm/access}/access_test.go (77%)
 create mode 100644 models/perm/access/main_test.go
 rename models/{ => perm/access}/repo_permission.go (88%)
 create mode 100644 models/repo/collaboration.go
 create mode 100644 models/repo/collaboration_test.go

diff --git a/integrations/api_repo_test.go b/integrations/api_repo_test.go
index b585ad15e3..e635a4f377 100644
--- a/integrations/api_repo_test.go
+++ b/integrations/api_repo_test.go
@@ -12,6 +12,8 @@ import (
 	"testing"
 
 	"code.gitea.io/gitea/models"
+	"code.gitea.io/gitea/models/db"
+	access_model "code.gitea.io/gitea/models/perm/access"
 	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/models/unittest"
 	user_model "code.gitea.io/gitea/models/user"
@@ -205,7 +207,7 @@ func TestAPISearchRepo(t *testing.T) {
 					assert.Len(t, repoNames, expected.count)
 					for _, repo := range body.Data {
 						r := getRepo(t, repo.ID)
-						hasAccess, err := models.HasAccess(userID, r)
+						hasAccess, err := access_model.HasAccess(db.DefaultContext, userID, r)
 						assert.NoError(t, err, "Error when checking if User: %d has access to %s: %v", userID, repo.FullName, err)
 						assert.True(t, hasAccess, "User: %d does not have access to %s", userID, repo.FullName)
 
diff --git a/integrations/delete_user_test.go b/integrations/delete_user_test.go
index 4b67c05951..cf376f6fcc 100644
--- a/integrations/delete_user_test.go
+++ b/integrations/delete_user_test.go
@@ -11,6 +11,7 @@ import (
 
 	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/models/organization"
+	access_model "code.gitea.io/gitea/models/perm/access"
 	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/models/unittest"
 	user_model "code.gitea.io/gitea/models/user"
@@ -21,7 +22,7 @@ func assertUserDeleted(t *testing.T, userID int64) {
 	unittest.AssertNotExistsBean(t, &user_model.Follow{UserID: userID})
 	unittest.AssertNotExistsBean(t, &user_model.Follow{FollowID: userID})
 	unittest.AssertNotExistsBean(t, &repo_model.Repository{OwnerID: userID})
-	unittest.AssertNotExistsBean(t, &models.Access{UserID: userID})
+	unittest.AssertNotExistsBean(t, &access_model.Access{UserID: userID})
 	unittest.AssertNotExistsBean(t, &organization.OrgUser{UID: userID})
 	unittest.AssertNotExistsBean(t, &models.IssueUser{UID: userID})
 	unittest.AssertNotExistsBean(t, &organization.TeamUser{UID: userID})
diff --git a/models/action.go b/models/action.go
index 7055ce81d6..6b662b3107 100644
--- a/models/action.go
+++ b/models/action.go
@@ -16,6 +16,7 @@ import (
 
 	"code.gitea.io/gitea/models/db"
 	"code.gitea.io/gitea/models/organization"
+	access_model "code.gitea.io/gitea/models/perm/access"
 	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/models/unit"
 	user_model "code.gitea.io/gitea/models/user"
@@ -510,7 +511,7 @@ func notifyWatchers(ctx context.Context, actions ...*Action) error {
 					permPR[i] = false
 					continue
 				}
-				perm, err := GetUserRepoPermission(ctx, repo, user)
+				perm, err := access_model.GetUserRepoPermission(ctx, repo, user)
 				if err != nil {
 					permCode[i] = false
 					permIssue[i] = false
diff --git a/models/branches.go b/models/branches.go
index 47fd3dc0a4..008cb8653c 100644
--- a/models/branches.go
+++ b/models/branches.go
@@ -13,6 +13,7 @@ import (
 	"code.gitea.io/gitea/models/db"
 	"code.gitea.io/gitea/models/organization"
 	"code.gitea.io/gitea/models/perm"
+	access_model "code.gitea.io/gitea/models/perm/access"
 	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/models/unit"
 	user_model "code.gitea.io/gitea/models/user"
@@ -79,7 +80,7 @@ func (protectBranch *ProtectedBranch) CanUserPush(userID int64) bool {
 		} else if repo, err := repo_model.GetRepositoryByID(protectBranch.RepoID); err != nil {
 			log.Error("repo_model.GetRepositoryByID: %v", err)
 			return false
-		} else if writeAccess, err := HasAccessUnit(user, repo, unit.TypeCode, perm.AccessModeWrite); err != nil {
+		} else if writeAccess, err := access_model.HasAccessUnit(db.DefaultContext, user, repo, unit.TypeCode, perm.AccessModeWrite); err != nil {
 			log.Error("HasAccessUnit: %v", err)
 			return false
 		} else {
@@ -104,7 +105,7 @@ func (protectBranch *ProtectedBranch) CanUserPush(userID int64) bool {
 }
 
 // IsUserMergeWhitelisted checks if some user is whitelisted to merge to this branch
-func IsUserMergeWhitelisted(ctx context.Context, protectBranch *ProtectedBranch, userID int64, permissionInRepo Permission) bool {
+func IsUserMergeWhitelisted(ctx context.Context, protectBranch *ProtectedBranch, userID int64, permissionInRepo access_model.Permission) bool {
 	if !protectBranch.EnableMergeWhitelist {
 		// Then we need to fall back on whether the user has write permission
 		return permissionInRepo.CanWrite(unit.TypeCode)
@@ -139,7 +140,7 @@ func isUserOfficialReviewer(ctx context.Context, protectBranch *ProtectedBranch,
 
 	if !protectBranch.EnableApprovalsWhitelist {
 		// Anyone with write access is considered official reviewer
-		writeAccess, err := hasAccessUnit(ctx, user, repo, unit.TypeCode, perm.AccessModeWrite)
+		writeAccess, err := access_model.HasAccessUnit(ctx, user, repo, unit.TypeCode, perm.AccessModeWrite)
 		if err != nil {
 			return false, err
 		}
@@ -424,7 +425,7 @@ func updateApprovalWhitelist(ctx context.Context, repo *repo_model.Repository, c
 
 	whitelist = make([]int64, 0, len(newWhitelist))
 	for _, userID := range newWhitelist {
-		if reader, err := IsRepoReader(ctx, repo, userID); err != nil {
+		if reader, err := access_model.IsRepoReader(ctx, repo, userID); err != nil {
 			return nil, err
 		} else if !reader {
 			continue
@@ -449,7 +450,7 @@ func updateUserWhitelist(ctx context.Context, repo *repo_model.Repository, curre
 		if err != nil {
 			return nil, fmt.Errorf("GetUserByID [user_id: %d, repo_id: %d]: %v", userID, repo.ID, err)
 		}
-		perm, err := GetUserRepoPermission(ctx, repo, user)
+		perm, err := access_model.GetUserRepoPermission(ctx, repo, user)
 		if err != nil {
 			return nil, fmt.Errorf("GetUserRepoPermission [user_id: %d, repo_id: %d]: %v", userID, repo.ID, err)
 		}
diff --git a/models/fixture_generation.go b/models/fixture_generation.go
index 56baa1c345..f4644859eb 100644
--- a/models/fixture_generation.go
+++ b/models/fixture_generation.go
@@ -9,6 +9,7 @@ import (
 	"strings"
 
 	"code.gitea.io/gitea/models/db"
+	access_model "code.gitea.io/gitea/models/perm/access"
 	repo_model "code.gitea.io/gitea/models/repo"
 )
 
@@ -22,14 +23,14 @@ func GetYamlFixturesAccess() (string, error) {
 
 	for _, repo := range repos {
 		repo.MustOwner()
-		if err := RecalculateAccesses(repo); err != nil {
+		if err := access_model.RecalculateAccesses(db.DefaultContext, repo); err != nil {
 			return "", err
 		}
 	}
 
 	var b strings.Builder
 
-	accesses := make([]*Access, 0, 200)
+	accesses := make([]*access_model.Access, 0, 200)
 	if err := db.GetEngine(db.DefaultContext).OrderBy("user_id, repo_id").Find(&accesses); err != nil {
 		return "", err
 	}
diff --git a/models/issue.go b/models/issue.go
index 98e64adafd..d9540f25dd 100644
--- a/models/issue.go
+++ b/models/issue.go
@@ -19,6 +19,7 @@ import (
 	issues_model "code.gitea.io/gitea/models/issues"
 	"code.gitea.io/gitea/models/organization"
 	"code.gitea.io/gitea/models/perm"
+	access_model "code.gitea.io/gitea/models/perm/access"
 	project_model "code.gitea.io/gitea/models/project"
 	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/models/unit"
@@ -489,7 +490,7 @@ func ClearIssueLabels(issue *Issue, doer *user_model.User) (err error) {
 		return err
 	}
 
-	perm, err := GetUserRepoPermission(ctx, issue.Repo, doer)
+	perm, err := access_model.GetUserRepoPermission(ctx, issue.Repo, doer)
 	if err != nil {
 		return err
 	}
@@ -2314,7 +2315,7 @@ func ResolveIssueMentionsByVisibility(ctx context.Context, issue *Issue, doer *u
 			continue
 		}
 		// Normal users must have read access to the referencing issue
-		perm, err := GetUserRepoPermission(ctx, issue.Repo, user)
+		perm, err := access_model.GetUserRepoPermission(ctx, issue.Repo, user)
 		if err != nil {
 			return nil, fmt.Errorf("GetUserRepoPermission [%d]: %v", user.ID, err)
 		}
diff --git a/models/issue_xref.go b/models/issue_xref.go
index cd1c122252..c4f0080edd 100644
--- a/models/issue_xref.go
+++ b/models/issue_xref.go
@@ -9,6 +9,7 @@ import (
 	"fmt"
 
 	"code.gitea.io/gitea/models/db"
+	access_model "code.gitea.io/gitea/models/perm/access"
 	repo_model "code.gitea.io/gitea/models/repo"
 	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/log"
@@ -215,7 +216,7 @@ func (issue *Issue) verifyReferencedIssue(stdCtx context.Context, ctx *crossRefe
 
 	// Check doer permissions; set action to None if the doer can't change the destination
 	if refIssue.RepoID != ctx.OrigIssue.RepoID || ref.Action != references.XRefActionNone {
-		perm, err := GetUserRepoPermission(stdCtx, refIssue.Repo, ctx.Doer)
+		perm, err := access_model.GetUserRepoPermission(stdCtx, refIssue.Repo, ctx.Doer)
 		if err != nil {
 			return nil, references.XRefActionNone, err
 		}
diff --git a/models/lfs_lock.go b/models/lfs_lock.go
index b5f8e4907f..4995305393 100644
--- a/models/lfs_lock.go
+++ b/models/lfs_lock.go
@@ -13,6 +13,7 @@ import (
 
 	"code.gitea.io/gitea/models/db"
 	"code.gitea.io/gitea/models/perm"
+	access_model "code.gitea.io/gitea/models/perm/access"
 	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/models/unit"
 	user_model "code.gitea.io/gitea/models/user"
@@ -171,7 +172,7 @@ func CheckLFSAccessForRepo(ctx context.Context, ownerID int64, repo *repo_model.
 	if err != nil {
 		return err
 	}
-	perm, err := GetUserRepoPermission(ctx, repo, u)
+	perm, err := access_model.GetUserRepoPermission(ctx, repo, u)
 	if err != nil {
 		return err
 	}
diff --git a/models/org.go b/models/org.go
index 1e5b403f12..1efa0504ea 100644
--- a/models/org.go
+++ b/models/org.go
@@ -12,6 +12,7 @@ import (
 
 	"code.gitea.io/gitea/models/db"
 	"code.gitea.io/gitea/models/organization"
+	access_model "code.gitea.io/gitea/models/perm/access"
 	repo_model "code.gitea.io/gitea/models/repo"
 	user_model "code.gitea.io/gitea/models/user"
 
@@ -142,7 +143,7 @@ func removeOrgUser(ctx context.Context, orgID, userID int64) error {
 		if _, err = sess.
 			Where("user_id = ?", userID).
 			In("repo_id", repoIDs).
-			Delete(new(Access)); err != nil {
+			Delete(new(access_model.Access)); err != nil {
 			return err
 		}
 	}
diff --git a/models/org_team.go b/models/org_team.go
index 695f803dbf..40eb037452 100644
--- a/models/org_team.go
+++ b/models/org_team.go
@@ -13,6 +13,7 @@ import (
 
 	"code.gitea.io/gitea/models/db"
 	"code.gitea.io/gitea/models/organization"
+	access_model "code.gitea.io/gitea/models/perm/access"
 	repo_model "code.gitea.io/gitea/models/repo"
 	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/log"
@@ -33,7 +34,7 @@ func addRepository(ctx context.Context, t *organization.Team, repo *repo_model.R
 
 	t.NumRepos++
 
-	if err = recalculateTeamAccesses(ctx, repo, 0); err != nil {
+	if err = access_model.RecalculateTeamAccesses(ctx, repo, 0); err != nil {
 		return fmt.Errorf("recalculateAccesses: %v", err)
 	}
 
@@ -62,7 +63,7 @@ func addAllRepositories(ctx context.Context, t *organization.Team) error {
 	}
 
 	for _, repo := range orgRepos {
-		if !hasRepository(ctx, t, repo.ID) {
+		if !organization.HasTeamRepo(ctx, t.OrgID, t.ID, repo.ID) {
 			if err := addRepository(ctx, t, &repo); err != nil {
 				return fmt.Errorf("addRepository: %v", err)
 			}
@@ -108,11 +109,6 @@ func AddRepository(t *organization.Team, repo *repo_model.Repository) (err error
 	return committer.Commit()
 }
 
-// HasRepository returns true if given repository belong to team.
-func HasRepository(t *organization.Team, repoID int64) bool {
-	return hasRepository(db.DefaultContext, t, repoID)
-}
-
 // RemoveAllRepositories removes all repositories from team and recalculates access
 func RemoveAllRepositories(t *organization.Team) (err error) {
 	if t.IncludesAllRepositories {
@@ -138,13 +134,13 @@ func removeAllRepositories(ctx context.Context, t *organization.Team) (err error
 	e := db.GetEngine(ctx)
 	// Delete all accesses.
 	for _, repo := range t.Repos {
-		if err := recalculateTeamAccesses(ctx, repo, t.ID); err != nil {
+		if err := access_model.RecalculateTeamAccesses(ctx, repo, t.ID); err != nil {
 			return err
 		}
 
 		// Remove watches from all users and now unaccessible repos
 		for _, user := range t.Members {
-			has, err := hasAccess(ctx, user.ID, repo)
+			has, err := access_model.HasAccess(ctx, user.ID, repo)
 			if err != nil {
 				return err
 			} else if has {
@@ -177,8 +173,9 @@ func removeAllRepositories(ctx context.Context, t *organization.Team) (err error
 	return nil
 }
 
-func hasRepository(ctx context.Context, t *organization.Team, repoID int64) bool {
-	return organization.HasTeamRepo(ctx, t.OrgID, t.ID, repoID)
+// HasRepository returns true if given repository belong to team.
+func HasRepository(t *organization.Team, repoID int64) bool {
+	return organization.HasTeamRepo(db.DefaultContext, t.OrgID, t.ID, repoID)
 }
 
 // removeRepository removes a repository from a team and recalculates access
@@ -196,7 +193,7 @@ func removeRepository(ctx context.Context, t *organization.Team, repo *repo_mode
 
 	// Don't need to recalculate when delete a repository from organization.
 	if recalculate {
-		if err = recalculateTeamAccesses(ctx, repo, t.ID); err != nil {
+		if err = access_model.RecalculateTeamAccesses(ctx, repo, t.ID); err != nil {
 			return err
 		}
 	}
@@ -206,7 +203,7 @@ func removeRepository(ctx context.Context, t *organization.Team, repo *repo_mode
 		return fmt.Errorf("getTeamUsersByTeamID: %v", err)
 	}
 	for _, teamUser := range teamUsers {
-		has, err := hasAccess(ctx, teamUser.UID, repo)
+		has, err := access_model.HasAccess(ctx, teamUser.UID, repo)
 		if err != nil {
 			return err
 		} else if has {
@@ -378,7 +375,7 @@ func UpdateTeam(t *organization.Team, authChanged, includeAllChanged bool) (err
 		}
 
 		for _, repo := range t.Repos {
-			if err = recalculateTeamAccesses(ctx, repo, 0); err != nil {
+			if err = access_model.RecalculateTeamAccesses(ctx, repo, 0); err != nil {
 				return fmt.Errorf("recalculateTeamAccesses: %v", err)
 			}
 		}
@@ -522,7 +519,7 @@ func AddTeamMember(team *organization.Team, userID int64) error {
 		In("repo_id", subQuery).
 		And("mode < ?", team.AccessMode).
 		SetExpr("mode", team.AccessMode).
-		Update(new(Access)); err != nil {
+		Update(new(access_model.Access)); err != nil {
 		return fmt.Errorf("update user accesses: %v", err)
 	}
 
@@ -533,9 +530,9 @@ func AddTeamMember(team *organization.Team, userID int64) error {
 		return fmt.Errorf("select id accesses: %v", err)
 	}
 
-	accesses := make([]*Access, 0, 100)
+	accesses := make([]*access_model.Access, 0, 100)
 	for i, repoID := range repoIDs {
-		accesses = append(accesses, &Access{RepoID: repoID, UserID: userID, Mode: team.AccessMode})
+		accesses = append(accesses, &access_model.Access{RepoID: repoID, UserID: userID, Mode: team.AccessMode})
 		if (i%100 == 0 || i == len(repoIDs)-1) && len(accesses) > 0 {
 			if err = db.Insert(ctx, accesses); err != nil {
 				return fmt.Errorf("insert new user accesses: %v", err)
@@ -595,7 +592,7 @@ func removeTeamMember(ctx context.Context, team *organization.Team, userID int64
 
 	// Delete access to team repositories.
 	for _, repo := range team.Repos {
-		if err := recalculateUserAccess(ctx, repo, userID); err != nil {
+		if err := access_model.RecalculateUserAccess(ctx, repo, userID); err != nil {
 			return err
 		}
 
diff --git a/models/org_team_test.go b/models/org_team_test.go
index e125f3c65b..35ee9af85a 100644
--- a/models/org_team_test.go
+++ b/models/org_team_test.go
@@ -11,6 +11,7 @@ import (
 	"code.gitea.io/gitea/models/db"
 	"code.gitea.io/gitea/models/organization"
 	"code.gitea.io/gitea/models/perm"
+	access_model "code.gitea.io/gitea/models/perm/access"
 	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/models/unittest"
 	user_model "code.gitea.io/gitea/models/user"
@@ -129,7 +130,7 @@ func TestUpdateTeam(t *testing.T) {
 	team = unittest.AssertExistsAndLoadBean(t, &organization.Team{Name: "newName"}).(*organization.Team)
 	assert.True(t, strings.HasPrefix(team.Description, "A long description!"))
 
-	access := unittest.AssertExistsAndLoadBean(t, &Access{UserID: 4, RepoID: 3}).(*Access)
+	access := unittest.AssertExistsAndLoadBean(t, &access_model.Access{UserID: 4, RepoID: 3}).(*access_model.Access)
 	assert.EqualValues(t, perm.AccessModeAdmin, access.Mode)
 
 	unittest.CheckConsistencyFor(t, &organization.Team{ID: team.ID})
@@ -161,7 +162,7 @@ func TestDeleteTeam(t *testing.T) {
 	// check that team members don't have "leftover" access to repos
 	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}).(*user_model.User)
 	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}).(*repo_model.Repository)
-	accessMode, err := AccessLevel(user, repo)
+	accessMode, err := access_model.AccessLevel(user, repo)
 	assert.NoError(t, err)
 	assert.True(t, accessMode < perm.AccessModeWrite)
 }
@@ -198,3 +199,21 @@ func TestRemoveTeamMember(t *testing.T) {
 	err := RemoveTeamMember(team, 2)
 	assert.True(t, organization.IsErrLastOrgOwner(err))
 }
+
+func TestRepository_RecalculateAccesses3(t *testing.T) {
+	assert.NoError(t, unittest.PrepareTestDatabase())
+	team5 := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 5}).(*organization.Team)
+	user29 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 29}).(*user_model.User)
+
+	has, err := db.GetEngine(db.DefaultContext).Get(&access_model.Access{UserID: 29, RepoID: 23})
+	assert.NoError(t, err)
+	assert.False(t, has)
+
+	// adding user29 to team5 should add an explicit access row for repo 23
+	// even though repo 23 is public
+	assert.NoError(t, AddTeamMember(team5, user29.ID))
+
+	has, err = db.GetEngine(db.DefaultContext).Get(&access_model.Access{UserID: 29, RepoID: 23})
+	assert.NoError(t, err)
+	assert.True(t, has)
+}
diff --git a/models/access.go b/models/perm/access/access.go
similarity index 85%
rename from models/access.go
rename to models/perm/access/access.go
index d49d3430dc..75a3a93a3b 100644
--- a/models/access.go
+++ b/models/perm/access/access.go
@@ -3,7 +3,7 @@
 // Use of this source code is governed by a MIT-style
 // license that can be found in the LICENSE file.
 
-package models
+package access
 
 import (
 	"context"
@@ -118,8 +118,8 @@ func refreshAccesses(e db.Engine, repo *repo_model.Repository, accessMap map[int
 }
 
 // refreshCollaboratorAccesses retrieves repository collaborations with their access modes.
-func refreshCollaboratorAccesses(e db.Engine, repoID int64, accessMap map[int64]*userAccess) error {
-	collaborators, err := getCollaborators(e, repoID, db.ListOptions{})
+func refreshCollaboratorAccesses(ctx context.Context, repoID int64, accessMap map[int64]*userAccess) error {
+	collaborators, err := repo_model.GetCollaborators(ctx, repoID, db.ListOptions{})
 	if err != nil {
 		return fmt.Errorf("getCollaborations: %v", err)
 	}
@@ -132,10 +132,10 @@ func refreshCollaboratorAccesses(e db.Engine, repoID int64, accessMap map[int64]
 	return nil
 }
 
-// recalculateTeamAccesses recalculates new accesses for teams of an organization
+// 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 recalculateTeamAccesses(ctx context.Context, repo *repo_model.Repository, ignTeamID int64) (err error) {
+func RecalculateTeamAccesses(ctx context.Context, repo *repo_model.Repository, ignTeamID int64) (err error) {
 	accessMap := make(map[int64]*userAccess, 20)
 
 	if err = repo.GetOwner(ctx); err != nil {
@@ -146,7 +146,7 @@ func recalculateTeamAccesses(ctx context.Context, repo *repo_model.Repository, i
 
 	e := db.GetEngine(ctx)
 
-	if err = refreshCollaboratorAccesses(e, repo.ID, accessMap); err != nil {
+	if err = refreshCollaboratorAccesses(ctx, repo.ID, accessMap); err != nil {
 		return fmt.Errorf("refreshCollaboratorAccesses: %v", err)
 	}
 
@@ -164,7 +164,7 @@ func recalculateTeamAccesses(ctx context.Context, repo *repo_model.Repository, i
 		// have relations with repository.
 		if t.IsOwnerTeam() {
 			t.AccessMode = perm.AccessModeOwner
-		} else if !hasRepository(ctx, t, repo.ID) {
+		} else if !organization.HasTeamRepo(ctx, t.OrgID, t.ID, repo.ID) {
 			continue
 		}
 
@@ -179,9 +179,9 @@ func recalculateTeamAccesses(ctx context.Context, repo *repo_model.Repository, i
 	return refreshAccesses(e, repo, accessMap)
 }
 
-// recalculateUserAccess recalculates new access for a single user
+// RecalculateUserAccess recalculates new access for a single user
 // Usable if we know access only affected one user
-func recalculateUserAccess(ctx context.Context, repo *repo_model.Repository, uid int64) (err error) {
+func RecalculateUserAccess(ctx context.Context, repo *repo_model.Repository, uid int64) (err error) {
 	minMode := perm.AccessModeRead
 	if !repo.IsPrivate {
 		minMode = perm.AccessModeWrite
@@ -189,7 +189,7 @@ func recalculateUserAccess(ctx context.Context, repo *repo_model.Repository, uid
 
 	accessMode := perm.AccessModeNone
 	e := db.GetEngine(ctx)
-	collaborator, err := getCollaboration(e, repo.ID, uid)
+	collaborator, err := repo_model.GetCollaboration(ctx, repo.ID, uid)
 	if err != nil {
 		return err
 	} else if collaborator != nil {
@@ -222,27 +222,23 @@ func recalculateUserAccess(ctx context.Context, repo *repo_model.Repository, uid
 	if _, err = e.Delete(&Access{RepoID: repo.ID, UserID: uid}); err != nil {
 		return fmt.Errorf("delete old user accesses: %v", err)
 	} else if accessMode >= minMode {
-		if _, err = e.Insert(&Access{RepoID: repo.ID, UserID: uid, Mode: accessMode}); err != nil {
+		if err = db.Insert(ctx, &Access{RepoID: repo.ID, UserID: uid, Mode: accessMode}); err != nil {
 			return fmt.Errorf("insert new user accesses: %v", err)
 		}
 	}
 	return nil
 }
 
-func recalculateAccesses(ctx context.Context, repo *repo_model.Repository) error {
+// RecalculateAccesses recalculates all accesses for repository.
+func RecalculateAccesses(ctx context.Context, repo *repo_model.Repository) error {
 	if repo.Owner.IsOrganization() {
-		return recalculateTeamAccesses(ctx, repo, 0)
+		return RecalculateTeamAccesses(ctx, repo, 0)
 	}
 
 	e := db.GetEngine(ctx)
 	accessMap := make(map[int64]*userAccess, 20)
-	if err := refreshCollaboratorAccesses(e, repo.ID, accessMap); err != nil {
+	if err := refreshCollaboratorAccesses(ctx, repo.ID, accessMap); err != nil {
 		return fmt.Errorf("refreshCollaboratorAccesses: %v", err)
 	}
 	return refreshAccesses(e, repo, accessMap)
 }
-
-// RecalculateAccesses recalculates all accesses for repository.
-func RecalculateAccesses(repo *repo_model.Repository) error {
-	return recalculateAccesses(db.DefaultContext, repo)
-}
diff --git a/models/access_test.go b/models/perm/access/access_test.go
similarity index 77%
rename from models/access_test.go
rename to models/perm/access/access_test.go
index 7533381dca..a9ae9a30f4 100644
--- a/models/access_test.go
+++ b/models/perm/access/access_test.go
@@ -2,13 +2,12 @@
 // Use of this source code is governed by a MIT-style
 // license that can be found in the LICENSE file.
 
-package models
+package access
 
 import (
 	"testing"
 
 	"code.gitea.io/gitea/models/db"
-	"code.gitea.io/gitea/models/organization"
 	"code.gitea.io/gitea/models/perm"
 	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/models/unittest"
@@ -80,17 +79,17 @@ func TestHasAccess(t *testing.T) {
 	repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}).(*repo_model.Repository)
 	assert.True(t, repo2.IsPrivate)
 
-	has, err := HasAccess(user1.ID, repo1)
+	has, err := HasAccess(db.DefaultContext, user1.ID, repo1)
 	assert.NoError(t, err)
 	assert.True(t, has)
 
-	_, err = HasAccess(user1.ID, repo2)
+	_, err = HasAccess(db.DefaultContext, user1.ID, repo2)
 	assert.NoError(t, err)
 
-	_, err = HasAccess(user2.ID, repo1)
+	_, err = HasAccess(db.DefaultContext, user2.ID, repo1)
 	assert.NoError(t, err)
 
-	_, err = HasAccess(user2.ID, repo2)
+	_, err = HasAccess(db.DefaultContext, user2.ID, repo2)
 	assert.NoError(t, err)
 }
 
@@ -100,9 +99,9 @@ func TestRepository_RecalculateAccesses(t *testing.T) {
 	repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}).(*repo_model.Repository)
 	assert.NoError(t, repo1.GetOwner(db.DefaultContext))
 
-	_, err := db.GetEngine(db.DefaultContext).Delete(&Collaboration{UserID: 2, RepoID: 3})
+	_, err := db.GetEngine(db.DefaultContext).Delete(&repo_model.Collaboration{UserID: 2, RepoID: 3})
 	assert.NoError(t, err)
-	assert.NoError(t, RecalculateAccesses(repo1))
+	assert.NoError(t, RecalculateAccesses(db.DefaultContext, repo1))
 
 	access := &Access{UserID: 2, RepoID: 3}
 	has, err := db.GetEngine(db.DefaultContext).Get(access)
@@ -117,29 +116,11 @@ func TestRepository_RecalculateAccesses2(t *testing.T) {
 	repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}).(*repo_model.Repository)
 	assert.NoError(t, repo1.GetOwner(db.DefaultContext))
 
-	_, err := db.GetEngine(db.DefaultContext).Delete(&Collaboration{UserID: 4, RepoID: 4})
+	_, err := db.GetEngine(db.DefaultContext).Delete(&repo_model.Collaboration{UserID: 4, RepoID: 4})
 	assert.NoError(t, err)
-	assert.NoError(t, RecalculateAccesses(repo1))
+	assert.NoError(t, RecalculateAccesses(db.DefaultContext, repo1))
 
 	has, err := db.GetEngine(db.DefaultContext).Get(&Access{UserID: 4, RepoID: 4})
 	assert.NoError(t, err)
 	assert.False(t, has)
 }
-
-func TestRepository_RecalculateAccesses3(t *testing.T) {
-	assert.NoError(t, unittest.PrepareTestDatabase())
-	team5 := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 5}).(*organization.Team)
-	user29 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 29}).(*user_model.User)
-
-	has, err := db.GetEngine(db.DefaultContext).Get(&Access{UserID: 29, RepoID: 23})
-	assert.NoError(t, err)
-	assert.False(t, has)
-
-	// adding user29 to team5 should add an explicit access row for repo 23
-	// even though repo 23 is public
-	assert.NoError(t, AddTeamMember(team5, user29.ID))
-
-	has, err = db.GetEngine(db.DefaultContext).Get(&Access{UserID: 29, RepoID: 23})
-	assert.NoError(t, err)
-	assert.True(t, has)
-}
diff --git a/models/perm/access/main_test.go b/models/perm/access/main_test.go
new file mode 100644
index 0000000000..153ac2540d
--- /dev/null
+++ b/models/perm/access/main_test.go
@@ -0,0 +1,32 @@
+// Copyright 2020 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 access
+
+import (
+	"path/filepath"
+	"testing"
+
+	"code.gitea.io/gitea/models/unittest"
+
+	_ "code.gitea.io/gitea/models/repo"
+)
+
+func TestMain(m *testing.M) {
+	unittest.MainTest(m, &unittest.TestOptions{
+		GiteaRootPath: filepath.Join("..", "..", ".."),
+		FixtureFiles: []string{
+			"access.yml",
+			"user.yml",
+			"repository.yml",
+			"collaboration.yml",
+			"org_user.yml",
+			"repo_unit.yml",
+			"team_user.yml",
+			"team_repo.yml",
+			"team.yml",
+			"team_unit.yml",
+		},
+	})
+}
diff --git a/models/repo_permission.go b/models/perm/access/repo_permission.go
similarity index 88%
rename from models/repo_permission.go
rename to models/perm/access/repo_permission.go
index 8ba6b86145..090c78ff2c 100644
--- a/models/repo_permission.go
+++ b/models/perm/access/repo_permission.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a MIT-style
 // license that can be found in the LICENSE file.
 
-package models
+package access
 
 import (
 	"context"
@@ -103,39 +103,6 @@ func (p *Permission) CanWriteIssuesOrPulls(isPull bool) bool {
 	return p.CanWrite(unit.TypeIssues)
 }
 
-// CanWriteToBranch checks if the branch is writable by the user
-func (p *Permission) CanWriteToBranch(user *user_model.User, branch string) bool {
-	if p.CanWrite(unit.TypeCode) {
-		return true
-	}
-
-	if len(p.Units) < 1 {
-		return false
-	}
-
-	prs, err := GetUnmergedPullRequestsByHeadInfo(p.Units[0].RepoID, branch)
-	if err != nil {
-		return false
-	}
-
-	for _, pr := range prs {
-		if pr.AllowMaintainerEdit {
-			err = pr.LoadBaseRepo()
-			if err != nil {
-				continue
-			}
-			prPerm, err := GetUserRepoPermission(db.DefaultContext, pr.BaseRepo, user)
-			if err != nil {
-				continue
-			}
-			if prPerm.CanWrite(unit.TypeCode) {
-				return true
-			}
-		}
-	}
-	return false
-}
-
 // ColorFormat writes a colored string for these Permissions
 func (p *Permission) ColorFormat(s fmt.State) {
 	noColor := log.ColorBytes(log.Reset)
@@ -205,7 +172,7 @@ func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use
 
 	var is bool
 	if user != nil {
-		is, err = isCollaborator(e, repo.ID, user.ID)
+		is, err = repo_model.IsCollaborator(ctx, repo.ID, user.ID)
 		if err != nil {
 			return perm, err
 		}
@@ -367,13 +334,13 @@ func IsUserRepoAdminCtx(ctx context.Context, repo *repo_model.Repository, user *
 
 // AccessLevel returns the Access a user has to a repository. Will return NoneAccess if the
 // user does not have access.
-func AccessLevel(user *user_model.User, repo *repo_model.Repository) (perm_model.AccessMode, error) {
-	return accessLevelUnit(db.DefaultContext, user, repo, unit.TypeCode)
+func AccessLevel(user *user_model.User, repo *repo_model.Repository) (perm_model.AccessMode, error) { //nolint
+	return AccessLevelUnit(user, repo, unit.TypeCode)
 }
 
 // AccessLevelUnit returns the Access a user has to a repository's. Will return NoneAccess if the
 // user does not have access.
-func AccessLevelUnit(user *user_model.User, repo *repo_model.Repository, unitType unit.Type) (perm_model.AccessMode, error) {
+func AccessLevelUnit(user *user_model.User, repo *repo_model.Repository, unitType unit.Type) (perm_model.AccessMode, error) { //nolint
 	return accessLevelUnit(db.DefaultContext, user, repo, unitType)
 }
 
@@ -385,24 +352,16 @@ func accessLevelUnit(ctx context.Context, user *user_model.User, repo *repo_mode
 	return perm.UnitAccessMode(unitType), nil
 }
 
-func hasAccessUnit(ctx context.Context, user *user_model.User, repo *repo_model.Repository, unitType unit.Type, testMode perm_model.AccessMode) (bool, error) {
+// HasAccessUnit returns true if user has testMode to the unit of the repository
+func HasAccessUnit(ctx context.Context, user *user_model.User, repo *repo_model.Repository, unitType unit.Type, testMode perm_model.AccessMode) (bool, error) {
 	mode, err := accessLevelUnit(ctx, user, repo, unitType)
 	return testMode <= mode, err
 }
 
-// HasAccessUnit returns true if user has testMode to the unit of the repository
-func HasAccessUnit(user *user_model.User, repo *repo_model.Repository, unitType unit.Type, testMode perm_model.AccessMode) (bool, error) {
-	return hasAccessUnit(db.DefaultContext, user, repo, unitType, testMode)
-}
-
 // CanBeAssigned return true if user can be assigned to issue or pull requests in repo
 // Currently any write access (code, issues or pr's) is assignable, to match assignee list in user interface.
 // FIXME: user could send PullRequest also could be assigned???
-func CanBeAssigned(user *user_model.User, repo *repo_model.Repository, isPull bool) (bool, error) {
-	return canBeAssigned(db.DefaultContext, user, repo, isPull)
-}
-
-func canBeAssigned(ctx context.Context, user *user_model.User, repo *repo_model.Repository, _ bool) (bool, error) {
+func CanBeAssigned(ctx context.Context, user *user_model.User, repo *repo_model.Repository, _ bool) (bool, error) {
 	if user.IsOrganization() {
 		return false, fmt.Errorf("Organization can't be added as assignee [user_id: %d, repo_id: %d]", user.ID, repo.ID)
 	}
@@ -413,7 +372,8 @@ func canBeAssigned(ctx context.Context, user *user_model.User, repo *repo_model.
 	return perm.CanAccessAny(perm_model.AccessModeWrite, unit.TypeCode, unit.TypeIssues, unit.TypePullRequests), nil
 }
 
-func hasAccess(ctx context.Context, userID int64, repo *repo_model.Repository) (bool, error) {
+// HasAccess returns true if user has access to repo
+func HasAccess(ctx context.Context, userID int64, repo *repo_model.Repository) (bool, error) {
 	var user *user_model.User
 	var err error
 	if userID > 0 {
@@ -429,9 +389,36 @@ func hasAccess(ctx context.Context, userID int64, repo *repo_model.Repository) (
 	return perm.HasAccess(), nil
 }
 
-// HasAccess returns true if user has access to repo
-func HasAccess(userID int64, repo *repo_model.Repository) (bool, error) {
-	return hasAccess(db.DefaultContext, userID, repo)
+// getUsersWithAccessMode returns users that have at least given access mode to the repository.
+func getUsersWithAccessMode(ctx context.Context, repo *repo_model.Repository, mode perm_model.AccessMode) (_ []*user_model.User, err error) {
+	if err = repo.GetOwner(ctx); err != nil {
+		return nil, err
+	}
+
+	e := db.GetEngine(ctx)
+	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_model.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
 }
 
 // GetRepoReaders returns all users that have explicit read access or higher to the repository.
diff --git a/models/pull_list.go b/models/pull_list.go
index ca09e28a93..60b8299776 100644
--- a/models/pull_list.go
+++ b/models/pull_list.go
@@ -9,6 +9,8 @@ import (
 	"fmt"
 
 	"code.gitea.io/gitea/models/db"
+	access_model "code.gitea.io/gitea/models/perm/access"
+	"code.gitea.io/gitea/models/unit"
 	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/git"
@@ -60,6 +62,39 @@ func GetUnmergedPullRequestsByHeadInfo(repoID int64, branch string) ([]*PullRequ
 		Find(&prs)
 }
 
+// CanMaintainerWriteToBranch check whether user is a matainer and could write to the branch
+func CanMaintainerWriteToBranch(p access_model.Permission, branch string, user *user_model.User) bool {
+	if p.CanWrite(unit.TypeCode) {
+		return true
+	}
+
+	if len(p.Units) < 1 {
+		return false
+	}
+
+	prs, err := GetUnmergedPullRequestsByHeadInfo(p.Units[0].RepoID, branch)
+	if err != nil {
+		return false
+	}
+
+	for _, pr := range prs {
+		if pr.AllowMaintainerEdit {
+			err = pr.LoadBaseRepo()
+			if err != nil {
+				continue
+			}
+			prPerm, err := access_model.GetUserRepoPermission(db.DefaultContext, pr.BaseRepo, user)
+			if err != nil {
+				continue
+			}
+			if prPerm.CanWrite(unit.TypeCode) {
+				return true
+			}
+		}
+	}
+	return false
+}
+
 // HasUnmergedPullRequestsByHeadInfo checks if there are open and not merged pull request
 // by given head information (repo and branch)
 func HasUnmergedPullRequestsByHeadInfo(ctx context.Context, repoID int64, branch string) (bool, error) {
diff --git a/models/repo.go b/models/repo.go
index fb7bbba1e1..598eec7c9e 100644
--- a/models/repo.go
+++ b/models/repo.go
@@ -22,6 +22,7 @@ import (
 	issues_model "code.gitea.io/gitea/models/issues"
 	"code.gitea.io/gitea/models/organization"
 	"code.gitea.io/gitea/models/perm"
+	access_model "code.gitea.io/gitea/models/perm/access"
 	project_model "code.gitea.io/gitea/models/project"
 	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/models/unit"
@@ -54,7 +55,7 @@ func checkRepoUnitUser(ctx context.Context, repo *repo_model.Repository, user *u
 	if user != nil && user.IsAdmin {
 		return true
 	}
-	perm, err := GetUserRepoPermission(ctx, repo, user)
+	perm, err := access_model.GetUserRepoPermission(ctx, repo, user)
 	if err != nil {
 		log.Error("GetUserRepoPermission(): %v", err)
 		return false
@@ -302,38 +303,6 @@ func CanUserDelete(repo *repo_model.Repository, user *user_model.User) (bool, er
 	return false, nil
 }
 
-// getUsersWithAccessMode returns users that have at least given access mode to the repository.
-func getUsersWithAccessMode(ctx context.Context, repo *repo_model.Repository, mode perm.AccessMode) (_ []*user_model.User, err error) {
-	if err = repo.GetOwner(ctx); err != nil {
-		return nil, err
-	}
-
-	e := db.GetEngine(ctx)
-	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_model.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
-}
-
 // SetRepoReadBy sets repo to be visited by given user.
 func SetRepoReadBy(repoID, userID int64) error {
 	return setRepoNotificationStatusReadIfUnread(db.GetEngine(db.DefaultContext), userID, repoID)
@@ -452,18 +421,18 @@ func CreateRepository(ctx context.Context, doer, u *user_model.User, repo *repo_
 			}
 		}
 
-		if isAdmin, err := IsUserRepoAdminCtx(ctx, repo, doer); err != nil {
+		if isAdmin, err := access_model.IsUserRepoAdminCtx(ctx, repo, doer); err != nil {
 			return fmt.Errorf("IsUserRepoAdminCtx: %v", err)
 		} else if !isAdmin {
 			// Make creator repo admin if it wasn't assigned automatically
 			if err = addCollaborator(ctx, repo, doer); err != nil {
 				return fmt.Errorf("AddCollaborator: %v", err)
 			}
-			if err = changeCollaborationAccessMode(db.GetEngine(ctx), repo, doer.ID, perm.AccessModeAdmin); err != nil {
+			if err = repo_model.ChangeCollaborationAccessModeCtx(ctx, repo, doer.ID, perm.AccessModeAdmin); err != nil {
 				return fmt.Errorf("ChangeCollaborationAccessMode: %v", err)
 			}
 		}
-	} else if err = recalculateAccesses(ctx, repo); err != nil {
+	} else if err = access_model.RecalculateAccesses(ctx, repo); err != nil {
 		// Organization automatically called this in addRepository method.
 		return fmt.Errorf("recalculateAccesses: %v", err)
 	}
@@ -551,7 +520,7 @@ func UpdateRepositoryCtx(ctx context.Context, repo *repo_model.Repository, visib
 		}
 		if repo.Owner.IsOrganization() {
 			// Organization repository need to recalculate access table when visibility is changed.
-			if err = recalculateTeamAccesses(ctx, repo, 0); err != nil {
+			if err = access_model.RecalculateTeamAccesses(ctx, repo, 0); err != nil {
 				return fmt.Errorf("recalculateTeamAccesses: %v", err)
 			}
 		}
@@ -659,7 +628,7 @@ func DeleteRepository(doer *user_model.User, uid, repoID int64) error {
 			return err
 		}
 		for _, t := range teams {
-			if !hasRepository(ctx, t, repoID) {
+			if !organization.HasTeamRepo(ctx, t.OrgID, t.ID, repoID) {
 				continue
 			} else if err = removeRepository(ctx, t, repo, false); err != nil {
 				return err
@@ -683,9 +652,9 @@ func DeleteRepository(doer *user_model.User, uid, repoID int64) error {
 	}
 
 	if err := db.DeleteBeans(ctx,
-		&Access{RepoID: repo.ID},
+		&access_model.Access{RepoID: repo.ID},
 		&Action{RepoID: repo.ID},
-		&Collaboration{RepoID: repoID},
+		&repo_model.Collaboration{RepoID: repoID},
 		&Comment{RefRepoID: repoID},
 		&CommitStatus{RepoID: repoID},
 		&DeletedBranch{RepoID: repoID},
@@ -1212,7 +1181,7 @@ func DeleteDeployKey(ctx context.Context, doer *user_model.User, id int64) error
 		if err != nil {
 			return fmt.Errorf("GetRepositoryByID: %v", err)
 		}
-		has, err := IsUserRepoAdminCtx(ctx, repo, doer)
+		has, err := access_model.IsUserRepoAdminCtx(ctx, repo, doer)
 		if err != nil {
 			return fmt.Errorf("GetUserRepoPermission: %v", err)
 		} else if !has {
diff --git a/models/repo/collaboration.go b/models/repo/collaboration.go
new file mode 100644
index 0000000000..3ebb688143
--- /dev/null
+++ b/models/repo/collaboration.go
@@ -0,0 +1,150 @@
+// Copyright 2022 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 repo
+
+import (
+	"context"
+	"fmt"
+
+	"code.gitea.io/gitea/models/db"
+	"code.gitea.io/gitea/models/perm"
+	user_model "code.gitea.io/gitea/models/user"
+	"code.gitea.io/gitea/modules/log"
+	"code.gitea.io/gitea/modules/timeutil"
+)
+
+// Collaboration represent the relation between an individual and a repository.
+type Collaboration struct {
+	ID          int64              `xorm:"pk autoincr"`
+	RepoID      int64              `xorm:"UNIQUE(s) INDEX NOT NULL"`
+	UserID      int64              `xorm:"UNIQUE(s) INDEX NOT NULL"`
+	Mode        perm.AccessMode    `xorm:"DEFAULT 2 NOT NULL"`
+	CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
+	UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
+}
+
+func init() {
+	db.RegisterModel(new(Collaboration))
+}
+
+// Collaborator represents a user with collaboration details.
+type Collaborator struct {
+	*user_model.User
+	Collaboration *Collaboration
+}
+
+// GetCollaborators returns the collaborators for a repository
+func GetCollaborators(ctx context.Context, repoID int64, listOptions db.ListOptions) ([]*Collaborator, error) {
+	e := db.GetEngine(ctx)
+	collaborations, err := getCollaborations(e, repoID, listOptions)
+	if err != nil {
+		return nil, fmt.Errorf("getCollaborations: %v", err)
+	}
+
+	collaborators := make([]*Collaborator, 0, len(collaborations))
+	for _, c := range collaborations {
+		user, err := user_model.GetUserByIDEngine(e, c.UserID)
+		if err != nil {
+			if user_model.IsErrUserNotExist(err) {
+				log.Warn("Inconsistent DB: User: %d is listed as collaborator of %-v but does not exist", c.UserID, repoID)
+				user = user_model.NewGhostUser()
+			} else {
+				return nil, err
+			}
+		}
+		collaborators = append(collaborators, &Collaborator{
+			User:          user,
+			Collaboration: c,
+		})
+	}
+	return collaborators, nil
+}
+
+// CountCollaborators returns total number of collaborators for a repository
+func CountCollaborators(repoID int64) (int64, error) {
+	return db.GetEngine(db.DefaultContext).Where("repo_id = ? ", repoID).Count(&Collaboration{})
+}
+
+// GetCollaboration get collaboration for a repository id with a user id
+func GetCollaboration(ctx context.Context, repoID, uid int64) (*Collaboration, error) {
+	collaboration := &Collaboration{
+		RepoID: repoID,
+		UserID: uid,
+	}
+	has, err := db.GetEngine(ctx).Get(collaboration)
+	if !has {
+		collaboration = nil
+	}
+	return collaboration, err
+}
+
+// IsCollaborator check if a user is a collaborator of a repository
+func IsCollaborator(ctx context.Context, repoID, userID int64) (bool, error) {
+	return db.GetEngine(ctx).Get(&Collaboration{RepoID: repoID, UserID: userID})
+}
+
+func getCollaborations(e db.Engine, repoID int64, listOptions db.ListOptions) ([]*Collaboration, error) {
+	if listOptions.Page == 0 {
+		collaborations := make([]*Collaboration, 0, 8)
+		return collaborations, e.Find(&collaborations, &Collaboration{RepoID: repoID})
+	}
+
+	e = db.SetEnginePagination(e, &listOptions)
+
+	collaborations := make([]*Collaboration, 0, listOptions.PageSize)
+	return collaborations, e.Find(&collaborations, &Collaboration{RepoID: repoID})
+}
+
+// ChangeCollaborationAccessMode sets new access mode for the collaboration.
+func ChangeCollaborationAccessModeCtx(ctx context.Context, repo *Repository, uid int64, mode perm.AccessMode) error {
+	// Discard invalid input
+	if mode <= perm.AccessModeNone || mode > perm.AccessModeOwner {
+		return nil
+	}
+
+	e := db.GetEngine(ctx)
+
+	collaboration := &Collaboration{
+		RepoID: repo.ID,
+		UserID: uid,
+	}
+	has, err := e.Get(collaboration)
+	if err != nil {
+		return fmt.Errorf("get collaboration: %v", err)
+	} else if !has {
+		return nil
+	}
+
+	if collaboration.Mode == mode {
+		return nil
+	}
+	collaboration.Mode = mode
+
+	if _, err = e.
+		ID(collaboration.ID).
+		Cols("mode").
+		Update(collaboration); err != nil {
+		return fmt.Errorf("update collaboration: %v", err)
+	} else if _, err = e.Exec("UPDATE access SET mode = ? WHERE user_id = ? AND repo_id = ?", mode, uid, repo.ID); err != nil {
+		return fmt.Errorf("update access table: %v", err)
+	}
+
+	return nil
+}
+
+// ChangeCollaborationAccessMode sets new access mode for the collaboration.
+func ChangeCollaborationAccessMode(repo *Repository, uid int64, mode perm.AccessMode) error {
+	ctx, committer, err := db.TxContext()
+	if err != nil {
+		return err
+	}
+	defer committer.Close()
+
+	if err := ChangeCollaborationAccessModeCtx(ctx, repo, uid, mode); err != nil {
+		return err
+	}
+
+	return committer.Commit()
+}
diff --git a/models/repo/collaboration_test.go b/models/repo/collaboration_test.go
new file mode 100644
index 0000000000..a7d04498e9
--- /dev/null
+++ b/models/repo/collaboration_test.go
@@ -0,0 +1,49 @@
+// Copyright 2022 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 repo
+
+import (
+	"testing"
+
+	"code.gitea.io/gitea/models/db"
+	"code.gitea.io/gitea/models/unittest"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestRepository_GetCollaborators(t *testing.T) {
+	assert.NoError(t, unittest.PrepareTestDatabase())
+	test := func(repoID int64) {
+		repo := unittest.AssertExistsAndLoadBean(t, &Repository{ID: repoID}).(*Repository)
+		collaborators, err := GetCollaborators(db.DefaultContext, repo.ID, db.ListOptions{})
+		assert.NoError(t, err)
+		expectedLen, err := db.GetEngine(db.DefaultContext).Count(&Collaboration{RepoID: repoID})
+		assert.NoError(t, err)
+		assert.Len(t, collaborators, int(expectedLen))
+		for _, collaborator := range collaborators {
+			assert.EqualValues(t, collaborator.User.ID, collaborator.Collaboration.UserID)
+			assert.EqualValues(t, repoID, collaborator.Collaboration.RepoID)
+		}
+	}
+	test(1)
+	test(2)
+	test(3)
+	test(4)
+}
+
+func TestRepository_IsCollaborator(t *testing.T) {
+	assert.NoError(t, unittest.PrepareTestDatabase())
+
+	test := func(repoID, userID int64, expected bool) {
+		repo := unittest.AssertExistsAndLoadBean(t, &Repository{ID: repoID}).(*Repository)
+		actual, err := IsCollaborator(db.DefaultContext, repo.ID, userID)
+		assert.NoError(t, err)
+		assert.Equal(t, expected, actual)
+	}
+	test(3, 2, true)
+	test(3, unittest.NonexistentID, false)
+	test(4, 2, false)
+	test(4, 4, true)
+}
diff --git a/models/repo/main_test.go b/models/repo/main_test.go
index e375e8a9f3..375d0e0df1 100644
--- a/models/repo/main_test.go
+++ b/models/repo/main_test.go
@@ -26,6 +26,7 @@ func TestMain(m *testing.M) {
 			"topic.yml",
 			"repo_topic.yml",
 			"user.yml",
+			"collaboration.yml",
 		},
 	})
 }
diff --git a/models/repo_collaboration.go b/models/repo_collaboration.go
index d94b61b449..2069ce8cc3 100644
--- a/models/repo_collaboration.go
+++ b/models/repo_collaboration.go
@@ -12,31 +12,16 @@ import (
 	"code.gitea.io/gitea/models/db"
 	"code.gitea.io/gitea/models/organization"
 	"code.gitea.io/gitea/models/perm"
+	access_model "code.gitea.io/gitea/models/perm/access"
 	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/models/unit"
 	user_model "code.gitea.io/gitea/models/user"
-	"code.gitea.io/gitea/modules/log"
-	"code.gitea.io/gitea/modules/timeutil"
 
 	"xorm.io/builder"
 )
 
-// Collaboration represent the relation between an individual and a repository.
-type Collaboration struct {
-	ID          int64              `xorm:"pk autoincr"`
-	RepoID      int64              `xorm:"UNIQUE(s) INDEX NOT NULL"`
-	UserID      int64              `xorm:"UNIQUE(s) INDEX NOT NULL"`
-	Mode        perm.AccessMode    `xorm:"DEFAULT 2 NOT NULL"`
-	CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
-	UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
-}
-
-func init() {
-	db.RegisterModel(new(Collaboration))
-}
-
 func addCollaborator(ctx context.Context, repo *repo_model.Repository, u *user_model.User) error {
-	collaboration := &Collaboration{
+	collaboration := &repo_model.Collaboration{
 		RepoID: repo.ID,
 		UserID: u.ID,
 	}
@@ -54,7 +39,7 @@ func addCollaborator(ctx context.Context, repo *repo_model.Repository, u *user_m
 		return err
 	}
 
-	return recalculateUserAccess(ctx, repo, u.ID)
+	return access_model.RecalculateUserAccess(ctx, repo, u.ID)
 }
 
 // AddCollaborator adds new collaboration to a repository with default access mode.
@@ -72,132 +57,9 @@ func AddCollaborator(repo *repo_model.Repository, u *user_model.User) error {
 	return committer.Commit()
 }
 
-func getCollaborations(e db.Engine, repoID int64, listOptions db.ListOptions) ([]*Collaboration, error) {
-	if listOptions.Page == 0 {
-		collaborations := make([]*Collaboration, 0, 8)
-		return collaborations, e.Find(&collaborations, &Collaboration{RepoID: repoID})
-	}
-
-	e = db.SetEnginePagination(e, &listOptions)
-
-	collaborations := make([]*Collaboration, 0, listOptions.PageSize)
-	return collaborations, e.Find(&collaborations, &Collaboration{RepoID: repoID})
-}
-
-// Collaborator represents a user with collaboration details.
-type Collaborator struct {
-	*user_model.User
-	Collaboration *Collaboration
-}
-
-func getCollaborators(e db.Engine, repoID int64, listOptions db.ListOptions) ([]*Collaborator, error) {
-	collaborations, err := getCollaborations(e, repoID, listOptions)
-	if err != nil {
-		return nil, fmt.Errorf("getCollaborations: %v", err)
-	}
-
-	collaborators := make([]*Collaborator, 0, len(collaborations))
-	for _, c := range collaborations {
-		user, err := user_model.GetUserByIDEngine(e, c.UserID)
-		if err != nil {
-			if user_model.IsErrUserNotExist(err) {
-				log.Warn("Inconsistent DB: User: %d is listed as collaborator of %-v but does not exist", c.UserID, repoID)
-				user = user_model.NewGhostUser()
-			} else {
-				return nil, err
-			}
-		}
-		collaborators = append(collaborators, &Collaborator{
-			User:          user,
-			Collaboration: c,
-		})
-	}
-	return collaborators, nil
-}
-
-// GetCollaborators returns the collaborators for a repository
-func GetCollaborators(repoID int64, listOptions db.ListOptions) ([]*Collaborator, error) {
-	return getCollaborators(db.GetEngine(db.DefaultContext), repoID, listOptions)
-}
-
-// CountCollaborators returns total number of collaborators for a repository
-func CountCollaborators(repoID int64) (int64, error) {
-	return db.GetEngine(db.DefaultContext).Where("repo_id = ? ", repoID).Count(&Collaboration{})
-}
-
-func getCollaboration(e db.Engine, repoID, uid int64) (*Collaboration, error) {
-	collaboration := &Collaboration{
-		RepoID: repoID,
-		UserID: uid,
-	}
-	has, err := e.Get(collaboration)
-	if !has {
-		collaboration = nil
-	}
-	return collaboration, err
-}
-
-func isCollaborator(e db.Engine, repoID, userID int64) (bool, error) {
-	return e.Get(&Collaboration{RepoID: repoID, UserID: userID})
-}
-
-// IsCollaborator check if a user is a collaborator of a repository
-func IsCollaborator(repoID, userID int64) (bool, error) {
-	return isCollaborator(db.GetEngine(db.DefaultContext), repoID, userID)
-}
-
-func changeCollaborationAccessMode(e db.Engine, repo *repo_model.Repository, uid int64, mode perm.AccessMode) error {
-	// Discard invalid input
-	if mode <= perm.AccessModeNone || mode > perm.AccessModeOwner {
-		return nil
-	}
-
-	collaboration := &Collaboration{
-		RepoID: repo.ID,
-		UserID: uid,
-	}
-	has, err := e.Get(collaboration)
-	if err != nil {
-		return fmt.Errorf("get collaboration: %v", err)
-	} else if !has {
-		return nil
-	}
-
-	if collaboration.Mode == mode {
-		return nil
-	}
-	collaboration.Mode = mode
-
-	if _, err = e.
-		ID(collaboration.ID).
-		Cols("mode").
-		Update(collaboration); err != nil {
-		return fmt.Errorf("update collaboration: %v", err)
-	} else if _, err = e.Exec("UPDATE access SET mode = ? WHERE user_id = ? AND repo_id = ?", mode, uid, repo.ID); err != nil {
-		return fmt.Errorf("update access table: %v", err)
-	}
-
-	return nil
-}
-
-// ChangeCollaborationAccessMode sets new access mode for the collaboration.
-func ChangeCollaborationAccessMode(repo *repo_model.Repository, uid int64, mode perm.AccessMode) error {
-	ctx, committer, err := db.TxContext()
-	if err != nil {
-		return err
-	}
-	defer committer.Close()
-
-	if err := changeCollaborationAccessMode(db.GetEngine(ctx), repo, uid, mode); err != nil {
-		return err
-	}
-
-	return committer.Commit()
-}
-
 // DeleteCollaboration removes collaboration relation between the user and repository.
 func DeleteCollaboration(repo *repo_model.Repository, uid int64) (err error) {
-	collaboration := &Collaboration{
+	collaboration := &repo_model.Collaboration{
 		RepoID: repo.ID,
 		UserID: uid,
 	}
@@ -210,7 +72,7 @@ func DeleteCollaboration(repo *repo_model.Repository, uid int64) (err error) {
 
 	if has, err := db.GetEngine(ctx).Delete(collaboration); err != nil || has == 0 {
 		return err
-	} else if err = recalculateAccesses(ctx, repo); err != nil {
+	} else if err = access_model.RecalculateAccesses(ctx, repo); err != nil {
 		return err
 	}
 
@@ -236,7 +98,7 @@ func reconsiderRepoIssuesAssignee(ctx context.Context, repo *repo_model.Reposito
 		return err
 	}
 
-	if canAssigned, err := canBeAssigned(ctx, user, repo, true); err != nil || canAssigned {
+	if canAssigned, err := access_model.CanBeAssigned(ctx, user, repo, true); err != nil || canAssigned {
 		return err
 	}
 
@@ -249,7 +111,7 @@ func reconsiderRepoIssuesAssignee(ctx context.Context, repo *repo_model.Reposito
 }
 
 func reconsiderWatches(ctx context.Context, repo *repo_model.Repository, uid int64) error {
-	if has, err := hasAccess(ctx, uid, repo); err != nil || has {
+	if has, err := access_model.HasAccess(ctx, uid, repo); err != nil || has {
 		return err
 	}
 	if err := repo_model.WatchRepoCtx(ctx, uid, repo.ID, false); err != nil {
@@ -277,5 +139,5 @@ func IsOwnerMemberCollaborator(repo *repo_model.Repository, userID int64) (bool,
 		return true, nil
 	}
 
-	return db.GetEngine(db.DefaultContext).Get(&Collaboration{RepoID: repo.ID, UserID: userID})
+	return db.GetEngine(db.DefaultContext).Get(&repo_model.Collaboration{RepoID: repo.ID, UserID: userID})
 }
diff --git a/models/repo_collaboration_test.go b/models/repo_collaboration_test.go
index 8b4c712f07..b90490de92 100644
--- a/models/repo_collaboration_test.go
+++ b/models/repo_collaboration_test.go
@@ -9,6 +9,7 @@ import (
 
 	"code.gitea.io/gitea/models/db"
 	"code.gitea.io/gitea/models/perm"
+	access_model "code.gitea.io/gitea/models/perm/access"
 	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/models/unittest"
 	user_model "code.gitea.io/gitea/models/user"
@@ -31,56 +32,21 @@ func TestRepository_AddCollaborator(t *testing.T) {
 	testSuccess(3, 4)
 }
 
-func TestRepository_GetCollaborators(t *testing.T) {
-	assert.NoError(t, unittest.PrepareTestDatabase())
-	test := func(repoID int64) {
-		repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID}).(*repo_model.Repository)
-		collaborators, err := GetCollaborators(repo.ID, db.ListOptions{})
-		assert.NoError(t, err)
-		expectedLen, err := db.GetEngine(db.DefaultContext).Count(&Collaboration{RepoID: repoID})
-		assert.NoError(t, err)
-		assert.Len(t, collaborators, int(expectedLen))
-		for _, collaborator := range collaborators {
-			assert.EqualValues(t, collaborator.User.ID, collaborator.Collaboration.UserID)
-			assert.EqualValues(t, repoID, collaborator.Collaboration.RepoID)
-		}
-	}
-	test(1)
-	test(2)
-	test(3)
-	test(4)
-}
-
-func TestRepository_IsCollaborator(t *testing.T) {
-	assert.NoError(t, unittest.PrepareTestDatabase())
-
-	test := func(repoID, userID int64, expected bool) {
-		repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID}).(*repo_model.Repository)
-		actual, err := IsCollaborator(repo.ID, userID)
-		assert.NoError(t, err)
-		assert.Equal(t, expected, actual)
-	}
-	test(3, 2, true)
-	test(3, unittest.NonexistentID, false)
-	test(4, 2, false)
-	test(4, 4, true)
-}
-
 func TestRepository_ChangeCollaborationAccessMode(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 
 	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}).(*repo_model.Repository)
-	assert.NoError(t, ChangeCollaborationAccessMode(repo, 4, perm.AccessModeAdmin))
+	assert.NoError(t, repo_model.ChangeCollaborationAccessMode(repo, 4, perm.AccessModeAdmin))
 
-	collaboration := unittest.AssertExistsAndLoadBean(t, &Collaboration{RepoID: repo.ID, UserID: 4}).(*Collaboration)
+	collaboration := unittest.AssertExistsAndLoadBean(t, &repo_model.Collaboration{RepoID: repo.ID, UserID: 4}).(*repo_model.Collaboration)
 	assert.EqualValues(t, perm.AccessModeAdmin, collaboration.Mode)
 
-	access := unittest.AssertExistsAndLoadBean(t, &Access{UserID: 4, RepoID: repo.ID}).(*Access)
+	access := unittest.AssertExistsAndLoadBean(t, &access_model.Access{UserID: 4, RepoID: repo.ID}).(*access_model.Access)
 	assert.EqualValues(t, perm.AccessModeAdmin, access.Mode)
 
-	assert.NoError(t, ChangeCollaborationAccessMode(repo, 4, perm.AccessModeAdmin))
+	assert.NoError(t, repo_model.ChangeCollaborationAccessMode(repo, 4, perm.AccessModeAdmin))
 
-	assert.NoError(t, ChangeCollaborationAccessMode(repo, unittest.NonexistentID, perm.AccessModeAdmin))
+	assert.NoError(t, repo_model.ChangeCollaborationAccessMode(repo, unittest.NonexistentID, perm.AccessModeAdmin))
 
 	unittest.CheckConsistencyFor(t, &repo_model.Repository{ID: repo.ID})
 }
@@ -91,10 +57,10 @@ func TestRepository_DeleteCollaboration(t *testing.T) {
 	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}).(*repo_model.Repository)
 	assert.NoError(t, repo.GetOwner(db.DefaultContext))
 	assert.NoError(t, DeleteCollaboration(repo, 4))
-	unittest.AssertNotExistsBean(t, &Collaboration{RepoID: repo.ID, UserID: 4})
+	unittest.AssertNotExistsBean(t, &repo_model.Collaboration{RepoID: repo.ID, UserID: 4})
 
 	assert.NoError(t, DeleteCollaboration(repo, 4))
-	unittest.AssertNotExistsBean(t, &Collaboration{RepoID: repo.ID, UserID: 4})
+	unittest.AssertNotExistsBean(t, &repo_model.Collaboration{RepoID: repo.ID, UserID: 4})
 
 	unittest.CheckConsistencyFor(t, &repo_model.Repository{ID: repo.ID})
 }
diff --git a/models/repo_permission_test.go b/models/repo_permission_test.go
index 7e22437f99..80919e7cf1 100644
--- a/models/repo_permission_test.go
+++ b/models/repo_permission_test.go
@@ -10,6 +10,7 @@ import (
 	"code.gitea.io/gitea/models/db"
 	"code.gitea.io/gitea/models/organization"
 	perm_model "code.gitea.io/gitea/models/perm"
+	access_model "code.gitea.io/gitea/models/perm/access"
 	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/models/unit"
 	"code.gitea.io/gitea/models/unittest"
@@ -27,7 +28,7 @@ func TestRepoPermissionPublicNonOrgRepo(t *testing.T) {
 
 	// plain user
 	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
-	perm, err := GetUserRepoPermission(db.DefaultContext, repo, user)
+	perm, err := access_model.GetUserRepoPermission(db.DefaultContext, repo, user)
 	assert.NoError(t, err)
 	for _, unit := range repo.Units {
 		assert.True(t, perm.CanRead(unit.Type))
@@ -36,7 +37,7 @@ func TestRepoPermissionPublicNonOrgRepo(t *testing.T) {
 
 	// change to collaborator
 	assert.NoError(t, AddCollaborator(repo, user))
-	perm, err = GetUserRepoPermission(db.DefaultContext, repo, user)
+	perm, err = access_model.GetUserRepoPermission(db.DefaultContext, repo, user)
 	assert.NoError(t, err)
 	for _, unit := range repo.Units {
 		assert.True(t, perm.CanRead(unit.Type))
@@ -45,7 +46,7 @@ func TestRepoPermissionPublicNonOrgRepo(t *testing.T) {
 
 	// collaborator
 	collaborator := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}).(*user_model.User)
-	perm, err = GetUserRepoPermission(db.DefaultContext, repo, collaborator)
+	perm, err = access_model.GetUserRepoPermission(db.DefaultContext, repo, collaborator)
 	assert.NoError(t, err)
 	for _, unit := range repo.Units {
 		assert.True(t, perm.CanRead(unit.Type))
@@ -54,7 +55,7 @@ func TestRepoPermissionPublicNonOrgRepo(t *testing.T) {
 
 	// owner
 	owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}).(*user_model.User)
-	perm, err = GetUserRepoPermission(db.DefaultContext, repo, owner)
+	perm, err = access_model.GetUserRepoPermission(db.DefaultContext, repo, owner)
 	assert.NoError(t, err)
 	for _, unit := range repo.Units {
 		assert.True(t, perm.CanRead(unit.Type))
@@ -63,7 +64,7 @@ func TestRepoPermissionPublicNonOrgRepo(t *testing.T) {
 
 	// admin
 	admin := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}).(*user_model.User)
-	perm, err = GetUserRepoPermission(db.DefaultContext, repo, admin)
+	perm, err = access_model.GetUserRepoPermission(db.DefaultContext, repo, admin)
 	assert.NoError(t, err)
 	for _, unit := range repo.Units {
 		assert.True(t, perm.CanRead(unit.Type))
@@ -80,7 +81,7 @@ func TestRepoPermissionPrivateNonOrgRepo(t *testing.T) {
 
 	// plain user
 	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}).(*user_model.User)
-	perm, err := GetUserRepoPermission(db.DefaultContext, repo, user)
+	perm, err := access_model.GetUserRepoPermission(db.DefaultContext, repo, user)
 	assert.NoError(t, err)
 	for _, unit := range repo.Units {
 		assert.False(t, perm.CanRead(unit.Type))
@@ -89,15 +90,15 @@ func TestRepoPermissionPrivateNonOrgRepo(t *testing.T) {
 
 	// change to collaborator to default write access
 	assert.NoError(t, AddCollaborator(repo, user))
-	perm, err = GetUserRepoPermission(db.DefaultContext, repo, user)
+	perm, err = access_model.GetUserRepoPermission(db.DefaultContext, repo, user)
 	assert.NoError(t, err)
 	for _, unit := range repo.Units {
 		assert.True(t, perm.CanRead(unit.Type))
 		assert.True(t, perm.CanWrite(unit.Type))
 	}
 
-	assert.NoError(t, ChangeCollaborationAccessMode(repo, user.ID, perm_model.AccessModeRead))
-	perm, err = GetUserRepoPermission(db.DefaultContext, repo, user)
+	assert.NoError(t, repo_model.ChangeCollaborationAccessMode(repo, user.ID, perm_model.AccessModeRead))
+	perm, err = access_model.GetUserRepoPermission(db.DefaultContext, repo, user)
 	assert.NoError(t, err)
 	for _, unit := range repo.Units {
 		assert.True(t, perm.CanRead(unit.Type))
@@ -106,7 +107,7 @@ func TestRepoPermissionPrivateNonOrgRepo(t *testing.T) {
 
 	// owner
 	owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
-	perm, err = GetUserRepoPermission(db.DefaultContext, repo, owner)
+	perm, err = access_model.GetUserRepoPermission(db.DefaultContext, repo, owner)
 	assert.NoError(t, err)
 	for _, unit := range repo.Units {
 		assert.True(t, perm.CanRead(unit.Type))
@@ -115,7 +116,7 @@ func TestRepoPermissionPrivateNonOrgRepo(t *testing.T) {
 
 	// admin
 	admin := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}).(*user_model.User)
-	perm, err = GetUserRepoPermission(db.DefaultContext, repo, admin)
+	perm, err = access_model.GetUserRepoPermission(db.DefaultContext, repo, admin)
 	assert.NoError(t, err)
 	for _, unit := range repo.Units {
 		assert.True(t, perm.CanRead(unit.Type))
@@ -132,7 +133,7 @@ func TestRepoPermissionPublicOrgRepo(t *testing.T) {
 
 	// plain user
 	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}).(*user_model.User)
-	perm, err := GetUserRepoPermission(db.DefaultContext, repo, user)
+	perm, err := access_model.GetUserRepoPermission(db.DefaultContext, repo, user)
 	assert.NoError(t, err)
 	for _, unit := range repo.Units {
 		assert.True(t, perm.CanRead(unit.Type))
@@ -141,15 +142,15 @@ func TestRepoPermissionPublicOrgRepo(t *testing.T) {
 
 	// change to collaborator to default write access
 	assert.NoError(t, AddCollaborator(repo, user))
-	perm, err = GetUserRepoPermission(db.DefaultContext, repo, user)
+	perm, err = access_model.GetUserRepoPermission(db.DefaultContext, repo, user)
 	assert.NoError(t, err)
 	for _, unit := range repo.Units {
 		assert.True(t, perm.CanRead(unit.Type))
 		assert.True(t, perm.CanWrite(unit.Type))
 	}
 
-	assert.NoError(t, ChangeCollaborationAccessMode(repo, user.ID, perm_model.AccessModeRead))
-	perm, err = GetUserRepoPermission(db.DefaultContext, repo, user)
+	assert.NoError(t, repo_model.ChangeCollaborationAccessMode(repo, user.ID, perm_model.AccessModeRead))
+	perm, err = access_model.GetUserRepoPermission(db.DefaultContext, repo, user)
 	assert.NoError(t, err)
 	for _, unit := range repo.Units {
 		assert.True(t, perm.CanRead(unit.Type))
@@ -158,7 +159,7 @@ func TestRepoPermissionPublicOrgRepo(t *testing.T) {
 
 	// org member team owner
 	owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
-	perm, err = GetUserRepoPermission(db.DefaultContext, repo, owner)
+	perm, err = access_model.GetUserRepoPermission(db.DefaultContext, repo, owner)
 	assert.NoError(t, err)
 	for _, unit := range repo.Units {
 		assert.True(t, perm.CanRead(unit.Type))
@@ -167,7 +168,7 @@ func TestRepoPermissionPublicOrgRepo(t *testing.T) {
 
 	// org member team tester
 	member := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 15}).(*user_model.User)
-	perm, err = GetUserRepoPermission(db.DefaultContext, repo, member)
+	perm, err = access_model.GetUserRepoPermission(db.DefaultContext, repo, member)
 	assert.NoError(t, err)
 	for _, unit := range repo.Units {
 		assert.True(t, perm.CanRead(unit.Type))
@@ -177,7 +178,7 @@ func TestRepoPermissionPublicOrgRepo(t *testing.T) {
 
 	// admin
 	admin := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}).(*user_model.User)
-	perm, err = GetUserRepoPermission(db.DefaultContext, repo, admin)
+	perm, err = access_model.GetUserRepoPermission(db.DefaultContext, repo, admin)
 	assert.NoError(t, err)
 	for _, unit := range repo.Units {
 		assert.True(t, perm.CanRead(unit.Type))
@@ -194,7 +195,7 @@ func TestRepoPermissionPrivateOrgRepo(t *testing.T) {
 
 	// plain user
 	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}).(*user_model.User)
-	perm, err := GetUserRepoPermission(db.DefaultContext, repo, user)
+	perm, err := access_model.GetUserRepoPermission(db.DefaultContext, repo, user)
 	assert.NoError(t, err)
 	for _, unit := range repo.Units {
 		assert.False(t, perm.CanRead(unit.Type))
@@ -203,15 +204,15 @@ func TestRepoPermissionPrivateOrgRepo(t *testing.T) {
 
 	// change to collaborator to default write access
 	assert.NoError(t, AddCollaborator(repo, user))
-	perm, err = GetUserRepoPermission(db.DefaultContext, repo, user)
+	perm, err = access_model.GetUserRepoPermission(db.DefaultContext, repo, user)
 	assert.NoError(t, err)
 	for _, unit := range repo.Units {
 		assert.True(t, perm.CanRead(unit.Type))
 		assert.True(t, perm.CanWrite(unit.Type))
 	}
 
-	assert.NoError(t, ChangeCollaborationAccessMode(repo, user.ID, perm_model.AccessModeRead))
-	perm, err = GetUserRepoPermission(db.DefaultContext, repo, user)
+	assert.NoError(t, repo_model.ChangeCollaborationAccessMode(repo, user.ID, perm_model.AccessModeRead))
+	perm, err = access_model.GetUserRepoPermission(db.DefaultContext, repo, user)
 	assert.NoError(t, err)
 	for _, unit := range repo.Units {
 		assert.True(t, perm.CanRead(unit.Type))
@@ -220,7 +221,7 @@ func TestRepoPermissionPrivateOrgRepo(t *testing.T) {
 
 	// org member team owner
 	owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 15}).(*user_model.User)
-	perm, err = GetUserRepoPermission(db.DefaultContext, repo, owner)
+	perm, err = access_model.GetUserRepoPermission(db.DefaultContext, repo, owner)
 	assert.NoError(t, err)
 	for _, unit := range repo.Units {
 		assert.True(t, perm.CanRead(unit.Type))
@@ -231,7 +232,7 @@ func TestRepoPermissionPrivateOrgRepo(t *testing.T) {
 	team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 5}).(*organization.Team)
 	err = organization.UpdateTeamUnits(team, nil)
 	assert.NoError(t, err)
-	perm, err = GetUserRepoPermission(db.DefaultContext, repo, owner)
+	perm, err = access_model.GetUserRepoPermission(db.DefaultContext, repo, owner)
 	assert.NoError(t, err)
 	for _, unit := range repo.Units {
 		assert.True(t, perm.CanRead(unit.Type))
@@ -240,7 +241,7 @@ func TestRepoPermissionPrivateOrgRepo(t *testing.T) {
 
 	// org member team tester
 	tester := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
-	perm, err = GetUserRepoPermission(db.DefaultContext, repo, tester)
+	perm, err = access_model.GetUserRepoPermission(db.DefaultContext, repo, tester)
 	assert.NoError(t, err)
 	assert.True(t, perm.CanWrite(unit.TypeIssues))
 	assert.False(t, perm.CanWrite(unit.TypeCode))
@@ -248,7 +249,7 @@ func TestRepoPermissionPrivateOrgRepo(t *testing.T) {
 
 	// org member team reviewer
 	reviewer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 20}).(*user_model.User)
-	perm, err = GetUserRepoPermission(db.DefaultContext, repo, reviewer)
+	perm, err = access_model.GetUserRepoPermission(db.DefaultContext, repo, reviewer)
 	assert.NoError(t, err)
 	assert.False(t, perm.CanRead(unit.TypeIssues))
 	assert.False(t, perm.CanWrite(unit.TypeCode))
@@ -256,7 +257,7 @@ func TestRepoPermissionPrivateOrgRepo(t *testing.T) {
 
 	// admin
 	admin := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}).(*user_model.User)
-	perm, err = GetUserRepoPermission(db.DefaultContext, repo, admin)
+	perm, err = access_model.GetUserRepoPermission(db.DefaultContext, repo, admin)
 	assert.NoError(t, err)
 	for _, unit := range repo.Units {
 		assert.True(t, perm.CanRead(unit.Type))
diff --git a/models/repo_transfer.go b/models/repo_transfer.go
index f9a758a20b..b283bc8c74 100644
--- a/models/repo_transfer.go
+++ b/models/repo_transfer.go
@@ -11,6 +11,7 @@ import (
 
 	"code.gitea.io/gitea/models/db"
 	"code.gitea.io/gitea/models/organization"
+	access_model "code.gitea.io/gitea/models/perm/access"
 	repo_model "code.gitea.io/gitea/models/repo"
 	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/log"
@@ -280,13 +281,13 @@ func TransferOwnership(doer *user_model.User, newOwnerName string, repo *repo_mo
 	}
 
 	// Remove redundant collaborators.
-	collaborators, err := getCollaborators(sess, repo.ID, db.ListOptions{})
+	collaborators, err := repo_model.GetCollaborators(ctx, repo.ID, db.ListOptions{})
 	if err != nil {
 		return fmt.Errorf("getCollaborators: %v", err)
 	}
 
 	// Dummy object.
-	collaboration := &Collaboration{RepoID: repo.ID}
+	collaboration := &repo_model.Collaboration{RepoID: repo.ID}
 	for _, c := range collaborators {
 		if c.IsGhost() {
 			collaboration.ID = c.Collaboration.ID
@@ -330,7 +331,7 @@ func TransferOwnership(doer *user_model.User, newOwnerName string, repo *repo_mo
 				}
 			}
 		}
-	} else if err := recalculateAccesses(ctx, repo); err != nil {
+	} else if err := access_model.RecalculateAccesses(ctx, repo); err != nil {
 		// Organization called this in addRepository method.
 		return fmt.Errorf("recalculateAccesses: %v", err)
 	}
diff --git a/models/review.go b/models/review.go
index a9e29a10e0..8917ea714d 100644
--- a/models/review.go
+++ b/models/review.go
@@ -12,6 +12,7 @@ import (
 	"code.gitea.io/gitea/models/db"
 	"code.gitea.io/gitea/models/organization"
 	"code.gitea.io/gitea/models/perm"
+	access_model "code.gitea.io/gitea/models/perm/access"
 	"code.gitea.io/gitea/models/unit"
 	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/base"
@@ -891,7 +892,7 @@ func CanMarkConversation(issue *Issue, doer *user_model.User) (permResult bool,
 			return false, err
 		}
 
-		p, err := GetUserRepoPermission(db.DefaultContext, issue.Repo, doer)
+		p, err := access_model.GetUserRepoPermission(db.DefaultContext, issue.Repo, doer)
 		if err != nil {
 			return false, err
 		}
diff --git a/models/statistic.go b/models/statistic.go
index d858102be8..0f6359fb3e 100644
--- a/models/statistic.go
+++ b/models/statistic.go
@@ -10,6 +10,7 @@ import (
 	"code.gitea.io/gitea/models/db"
 	issues_model "code.gitea.io/gitea/models/issues"
 	"code.gitea.io/gitea/models/organization"
+	access_model "code.gitea.io/gitea/models/perm/access"
 	project_model "code.gitea.io/gitea/models/project"
 	repo_model "code.gitea.io/gitea/models/repo"
 	user_model "code.gitea.io/gitea/models/user"
@@ -56,7 +57,7 @@ func GetStatistic() (stats Statistic) {
 	stats.Counter.Watch, _ = e.Count(new(repo_model.Watch))
 	stats.Counter.Star, _ = e.Count(new(repo_model.Star))
 	stats.Counter.Action, _ = e.Count(new(Action))
-	stats.Counter.Access, _ = e.Count(new(Access))
+	stats.Counter.Access, _ = e.Count(new(access_model.Access))
 
 	type IssueCount struct {
 		Count    int64
diff --git a/models/user.go b/models/user.go
index 11234a881d..e805c746cb 100644
--- a/models/user.go
+++ b/models/user.go
@@ -16,6 +16,7 @@ import (
 	"code.gitea.io/gitea/models/db"
 	"code.gitea.io/gitea/models/issues"
 	"code.gitea.io/gitea/models/organization"
+	access_model "code.gitea.io/gitea/models/perm/access"
 	pull_model "code.gitea.io/gitea/models/pull"
 	repo_model "code.gitea.io/gitea/models/repo"
 	user_model "code.gitea.io/gitea/models/user"
@@ -68,8 +69,8 @@ func DeleteUser(ctx context.Context, u *user_model.User) (err error) {
 
 	if err = db.DeleteBeans(ctx,
 		&AccessToken{UID: u.ID},
-		&Collaboration{UserID: u.ID},
-		&Access{UserID: u.ID},
+		&repo_model.Collaboration{UserID: u.ID},
+		&access_model.Access{UserID: u.ID},
 		&repo_model.Watch{UserID: u.ID},
 		&repo_model.Star{UID: u.ID},
 		&user_model.Follow{UserID: u.ID},
@@ -80,7 +81,6 @@ func DeleteUser(ctx context.Context, u *user_model.User) (err error) {
 		&user_model.UserOpenID{UID: u.ID},
 		&issues.Reaction{UserID: u.ID},
 		&organization.TeamUser{UID: u.ID},
-		&Collaboration{UserID: u.ID},
 		&Stopwatch{UserID: u.ID},
 		&user_model.Setting{UserID: u.ID},
 		&pull_model.AutoMerge{DoerID: u.ID},
diff --git a/modules/context/permission.go b/modules/context/permission.go
index 8dc3b3cd46..fd2263c75f 100644
--- a/modules/context/permission.go
+++ b/modules/context/permission.go
@@ -32,7 +32,7 @@ func RequireRepoWriter(unitType unit.Type) func(ctx *Context) {
 // CanEnableEditor checks if the user is allowed to write to the branch of the repo
 func CanEnableEditor() func(ctx *Context) {
 	return func(ctx *Context) {
-		if !ctx.Repo.Permission.CanWriteToBranch(ctx.Doer, ctx.Repo.BranchName) {
+		if !ctx.Repo.CanWriteToBranch(ctx.Doer, ctx.Repo.BranchName) {
 			ctx.NotFound("CanWriteToBranch denies permission", nil)
 			return
 		}
diff --git a/modules/context/repo.go b/modules/context/repo.go
index 3dc8e51392..eb773dfb2e 100644
--- a/modules/context/repo.go
+++ b/modules/context/repo.go
@@ -16,6 +16,7 @@ import (
 	"strings"
 
 	"code.gitea.io/gitea/models"
+	access_model "code.gitea.io/gitea/models/perm/access"
 	repo_model "code.gitea.io/gitea/models/repo"
 	unit_model "code.gitea.io/gitea/models/unit"
 	user_model "code.gitea.io/gitea/models/user"
@@ -54,7 +55,7 @@ type PullRequest struct {
 
 // Repository contains information to operate a repository
 type Repository struct {
-	models.Permission
+	access_model.Permission
 	IsWatching   bool
 	IsViewBranch bool
 	IsViewTag    bool
@@ -77,9 +78,14 @@ type Repository struct {
 	PullRequest *PullRequest
 }
 
+// CanWriteToBranch checks if the branch is writable by the user
+func (r *Repository) CanWriteToBranch(user *user_model.User, branch string) bool {
+	return models.CanMaintainerWriteToBranch(r.Permission, branch, user)
+}
+
 // CanEnableEditor returns true if repository is editable and user has proper access level.
 func (r *Repository) CanEnableEditor(user *user_model.User) bool {
-	return r.IsViewBranch && r.Permission.CanWriteToBranch(user, r.BranchName) && r.Repository.CanEnableEditor() && !r.Repository.IsArchived
+	return r.IsViewBranch && r.CanWriteToBranch(user, r.BranchName) && r.Repository.CanEnableEditor() && !r.Repository.IsArchived
 }
 
 // CanCreateBranch returns true if repository is editable and user has proper access level.
@@ -285,7 +291,7 @@ func RetrieveTemplateRepo(ctx *Context, repo *repo_model.Repository) {
 		return
 	}
 
-	perm, err := models.GetUserRepoPermission(ctx, templateRepo, ctx.Doer)
+	perm, err := access_model.GetUserRepoPermission(ctx, templateRepo, ctx.Doer)
 	if err != nil {
 		ctx.ServerError("GetUserRepoPermission", err)
 		return
@@ -351,7 +357,7 @@ func repoAssignment(ctx *Context, repo *repo_model.Repository) {
 		return
 	}
 
-	ctx.Repo.Permission, err = models.GetUserRepoPermission(ctx, repo, ctx.Doer)
+	ctx.Repo.Permission, err = access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
 	if err != nil {
 		ctx.ServerError("GetUserRepoPermission", err)
 		return
diff --git a/modules/convert/convert.go b/modules/convert/convert.go
index 3a12ed8f1f..74ede47cef 100644
--- a/modules/convert/convert.go
+++ b/modules/convert/convert.go
@@ -17,6 +17,7 @@ import (
 	"code.gitea.io/gitea/models/db"
 	"code.gitea.io/gitea/models/organization"
 	"code.gitea.io/gitea/models/perm"
+	access_model "code.gitea.io/gitea/models/perm/access"
 	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/models/unit"
 	user_model "code.gitea.io/gitea/models/user"
@@ -44,16 +45,16 @@ func ToBranch(repo *repo_model.Repository, b *git.Branch, c *git.Commit, bp *mod
 		var canPush bool
 		var err error
 		if user != nil {
-			hasPerm, err = models.HasAccessUnit(user, repo, unit.TypeCode, perm.AccessModeWrite)
+			hasPerm, err = access_model.HasAccessUnit(db.DefaultContext, user, repo, unit.TypeCode, perm.AccessModeWrite)
 			if err != nil {
 				return nil, err
 			}
 
-			perms, err := models.GetUserRepoPermission(db.DefaultContext, repo, user)
+			perms, err := access_model.GetUserRepoPermission(db.DefaultContext, repo, user)
 			if err != nil {
 				return nil, err
 			}
-			canPush = perms.CanWriteToBranch(user, b.Name)
+			canPush = models.CanMaintainerWriteToBranch(perms, b.Name, user)
 		}
 
 		return &api.Branch{
@@ -82,7 +83,7 @@ func ToBranch(repo *repo_model.Repository, b *git.Branch, c *git.Commit, bp *mod
 	}
 
 	if user != nil {
-		permission, err := models.GetUserRepoPermission(db.DefaultContext, repo, user)
+		permission, err := access_model.GetUserRepoPermission(db.DefaultContext, repo, user)
 		if err != nil {
 			return nil, err
 		}
diff --git a/modules/convert/package.go b/modules/convert/package.go
index a4ea41d522..9713cda48b 100644
--- a/modules/convert/package.go
+++ b/modules/convert/package.go
@@ -7,8 +7,8 @@ package convert
 import (
 	"context"
 
-	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/models/packages"
+	access_model "code.gitea.io/gitea/models/perm/access"
 	user_model "code.gitea.io/gitea/models/user"
 	api "code.gitea.io/gitea/modules/structs"
 )
@@ -17,7 +17,7 @@ import (
 func ToPackage(ctx context.Context, pd *packages.PackageDescriptor, doer *user_model.User) (*api.Package, error) {
 	var repo *api.Repository
 	if pd.Repository != nil {
-		permission, err := models.GetUserRepoPermission(ctx, pd.Repository, doer)
+		permission, err := access_model.GetUserRepoPermission(ctx, pd.Repository, doer)
 		if err != nil {
 			return nil, err
 		}
diff --git a/modules/convert/pull.go b/modules/convert/pull.go
index a2f54270e4..310a7626c9 100644
--- a/modules/convert/pull.go
+++ b/modules/convert/pull.go
@@ -10,6 +10,7 @@ import (
 
 	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/models/perm"
+	access_model "code.gitea.io/gitea/models/perm/access"
 	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/git"
 	"code.gitea.io/gitea/modules/log"
@@ -43,7 +44,7 @@ func ToAPIPullRequest(ctx context.Context, pr *models.PullRequest, doer *user_mo
 		return nil
 	}
 
-	p, err := models.GetUserRepoPermission(ctx, pr.BaseRepo, doer)
+	p, err := access_model.GetUserRepoPermission(ctx, pr.BaseRepo, doer)
 	if err != nil {
 		log.Error("GetUserRepoPermission[%d]: %v", pr.BaseRepoID, err)
 		p.AccessMode = perm.AccessModeNone
@@ -132,7 +133,7 @@ func ToAPIPullRequest(ctx context.Context, pr *models.PullRequest, doer *user_mo
 	}
 
 	if pr.HeadRepo != nil && pr.Flow == models.PullRequestFlowGithub {
-		p, err := models.GetUserRepoPermission(ctx, pr.HeadRepo, doer)
+		p, err := access_model.GetUserRepoPermission(ctx, pr.HeadRepo, doer)
 		if err != nil {
 			log.Error("GetUserRepoPermission[%d]: %v", pr.HeadRepoID, err)
 			p.AccessMode = perm.AccessModeNone
diff --git a/modules/notification/webhook/webhook.go b/modules/notification/webhook/webhook.go
index c59e972ed6..38077f2180 100644
--- a/modules/notification/webhook/webhook.go
+++ b/modules/notification/webhook/webhook.go
@@ -11,6 +11,7 @@ import (
 	"code.gitea.io/gitea/models/db"
 	packages_model "code.gitea.io/gitea/models/packages"
 	"code.gitea.io/gitea/models/perm"
+	access_model "code.gitea.io/gitea/models/perm/access"
 	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/models/unit"
 	user_model "code.gitea.io/gitea/models/user"
@@ -52,7 +53,7 @@ func (m *webhookNotifier) NotifyIssueClearLabels(doer *user_model.User, issue *m
 		return
 	}
 
-	mode, _ := models.AccessLevel(issue.Poster, issue.Repo)
+	mode, _ := access_model.AccessLevel(issue.Poster, issue.Repo)
 	var err error
 	if issue.IsPull {
 		if err = issue.LoadPullRequest(); err != nil {
@@ -82,8 +83,8 @@ func (m *webhookNotifier) NotifyIssueClearLabels(doer *user_model.User, issue *m
 }
 
 func (m *webhookNotifier) NotifyForkRepository(doer *user_model.User, oldRepo, repo *repo_model.Repository) {
-	oldMode, _ := models.AccessLevel(doer, oldRepo)
-	mode, _ := models.AccessLevel(doer, repo)
+	oldMode, _ := access_model.AccessLevel(doer, oldRepo)
+	mode, _ := access_model.AccessLevel(doer, repo)
 
 	// forked webhook
 	if err := webhook_services.PrepareWebhooks(oldRepo, webhook.HookEventFork, &api.ForkPayload{
@@ -151,7 +152,7 @@ func (m *webhookNotifier) NotifyIssueChangeAssignee(doer *user_model.User, issue
 	defer finished()
 
 	if issue.IsPull {
-		mode, _ := models.AccessLevelUnit(doer, issue.Repo, unit.TypePullRequests)
+		mode, _ := access_model.AccessLevelUnit(doer, issue.Repo, unit.TypePullRequests)
 
 		if err := issue.LoadPullRequest(); err != nil {
 			log.Error("LoadPullRequest failed: %v", err)
@@ -175,7 +176,7 @@ func (m *webhookNotifier) NotifyIssueChangeAssignee(doer *user_model.User, issue
 			return
 		}
 	} else {
-		mode, _ := models.AccessLevelUnit(doer, issue.Repo, unit.TypeIssues)
+		mode, _ := access_model.AccessLevelUnit(doer, issue.Repo, unit.TypeIssues)
 		apiIssue := &api.IssuePayload{
 			Index:      issue.Index,
 			Issue:      convert.ToAPIIssue(issue),
@@ -199,7 +200,7 @@ func (m *webhookNotifier) NotifyIssueChangeTitle(doer *user_model.User, issue *m
 	ctx, _, finished := process.GetManager().AddContext(graceful.GetManager().HammerContext(), fmt.Sprintf("webhook.NotifyIssueChangeTitle User: %s[%d] Issue[%d] #%d in [%d]", doer.Name, doer.ID, issue.ID, issue.Index, issue.RepoID))
 	defer finished()
 
-	mode, _ := models.AccessLevel(issue.Poster, issue.Repo)
+	mode, _ := access_model.AccessLevel(issue.Poster, issue.Repo)
 	var err error
 	if issue.IsPull {
 		if err = issue.LoadPullRequest(); err != nil {
@@ -243,7 +244,7 @@ func (m *webhookNotifier) NotifyIssueChangeStatus(doer *user_model.User, issue *
 	ctx, _, finished := process.GetManager().AddContext(graceful.GetManager().HammerContext(), fmt.Sprintf("webhook.NotifyIssueChangeStatus User: %s[%d] Issue[%d] #%d in [%d]", doer.Name, doer.ID, issue.ID, issue.Index, issue.RepoID))
 	defer finished()
 
-	mode, _ := models.AccessLevel(issue.Poster, issue.Repo)
+	mode, _ := access_model.AccessLevel(issue.Poster, issue.Repo)
 	var err error
 	if issue.IsPull {
 		if err = issue.LoadPullRequest(); err != nil {
@@ -292,7 +293,7 @@ func (m *webhookNotifier) NotifyNewIssue(issue *models.Issue, mentions []*user_m
 		return
 	}
 
-	mode, _ := models.AccessLevel(issue.Poster, issue.Repo)
+	mode, _ := access_model.AccessLevel(issue.Poster, issue.Repo)
 	if err := webhook_services.PrepareWebhooks(issue.Repo, webhook.HookEventIssues, &api.IssuePayload{
 		Action:     api.HookIssueOpened,
 		Index:      issue.Index,
@@ -321,7 +322,7 @@ func (m *webhookNotifier) NotifyNewPullRequest(pull *models.PullRequest, mention
 		return
 	}
 
-	mode, _ := models.AccessLevel(pull.Issue.Poster, pull.Issue.Repo)
+	mode, _ := access_model.AccessLevel(pull.Issue.Poster, pull.Issue.Repo)
 	if err := webhook_services.PrepareWebhooks(pull.Issue.Repo, webhook.HookEventPullRequest, &api.PullRequestPayload{
 		Action:      api.HookIssueOpened,
 		Index:       pull.Issue.Index,
@@ -337,7 +338,7 @@ func (m *webhookNotifier) NotifyIssueChangeContent(doer *user_model.User, issue
 	ctx, _, finished := process.GetManager().AddContext(graceful.GetManager().HammerContext(), fmt.Sprintf("webhook.NotifyIssueChangeContent User: %s[%d] Issue[%d] #%d in [%d]", doer.Name, doer.ID, issue.ID, issue.Index, issue.RepoID))
 	defer finished()
 
-	mode, _ := models.AccessLevel(issue.Poster, issue.Repo)
+	mode, _ := access_model.AccessLevel(issue.Poster, issue.Repo)
 	var err error
 	if issue.IsPull {
 		issue.PullRequest.Issue = issue
@@ -389,7 +390,7 @@ func (m *webhookNotifier) NotifyUpdateComment(doer *user_model.User, c *models.C
 		return
 	}
 
-	mode, _ := models.AccessLevel(doer, c.Issue.Repo)
+	mode, _ := access_model.AccessLevel(doer, c.Issue.Repo)
 	if c.Issue.IsPull {
 		err = webhook_services.PrepareWebhooks(c.Issue.Repo, webhook.HookEventPullRequestComment, &api.IssueCommentPayload{
 			Action:  api.HookIssueCommentEdited,
@@ -428,7 +429,7 @@ func (m *webhookNotifier) NotifyUpdateComment(doer *user_model.User, c *models.C
 func (m *webhookNotifier) NotifyCreateIssueComment(doer *user_model.User, repo *repo_model.Repository,
 	issue *models.Issue, comment *models.Comment, mentions []*user_model.User,
 ) {
-	mode, _ := models.AccessLevel(doer, repo)
+	mode, _ := access_model.AccessLevel(doer, repo)
 
 	var err error
 	if issue.IsPull {
@@ -473,7 +474,7 @@ func (m *webhookNotifier) NotifyDeleteComment(doer *user_model.User, comment *mo
 		return
 	}
 
-	mode, _ := models.AccessLevel(doer, comment.Issue.Repo)
+	mode, _ := access_model.AccessLevel(doer, comment.Issue.Repo)
 
 	if comment.Issue.IsPull {
 		err = webhook_services.PrepareWebhooks(comment.Issue.Repo, webhook.HookEventPullRequestComment, &api.IssueCommentPayload{
@@ -518,7 +519,7 @@ func (m *webhookNotifier) NotifyIssueChangeLabels(doer *user_model.User, issue *
 		return
 	}
 
-	mode, _ := models.AccessLevel(issue.Poster, issue.Repo)
+	mode, _ := access_model.AccessLevel(issue.Poster, issue.Repo)
 	if issue.IsPull {
 		if err = issue.LoadPullRequest(); err != nil {
 			log.Error("loadPullRequest: %v", err)
@@ -566,7 +567,7 @@ func (m *webhookNotifier) NotifyIssueChangeMilestone(doer *user_model.User, issu
 		return
 	}
 
-	mode, _ := models.AccessLevel(doer, issue.Repo)
+	mode, _ := access_model.AccessLevel(doer, issue.Repo)
 	if issue.IsPull {
 		err = issue.PullRequest.LoadIssue()
 		if err != nil {
@@ -640,7 +641,7 @@ func (*webhookNotifier) NotifyMergePullRequest(pr *models.PullRequest, doer *use
 		return
 	}
 
-	mode, err := models.AccessLevel(doer, pr.Issue.Repo)
+	mode, err := access_model.AccessLevel(doer, pr.Issue.Repo)
 	if err != nil {
 		log.Error("models.AccessLevel: %v", err)
 		return
@@ -676,7 +677,7 @@ func (m *webhookNotifier) NotifyPullRequestChangeTargetBranch(doer *user_model.U
 		return
 	}
 	issue.PullRequest.Issue = issue
-	mode, _ := models.AccessLevel(issue.Poster, issue.Repo)
+	mode, _ := access_model.AccessLevel(issue.Poster, issue.Repo)
 	err = webhook_services.PrepareWebhooks(issue.Repo, webhook.HookEventPullRequest, &api.PullRequestPayload{
 		Action: api.HookIssueEdited,
 		Index:  issue.Index,
@@ -719,7 +720,7 @@ func (m *webhookNotifier) NotifyPullRequestReview(pr *models.PullRequest, review
 		return
 	}
 
-	mode, err := models.AccessLevel(review.Issue.Poster, review.Issue.Repo)
+	mode, err := access_model.AccessLevel(review.Issue.Poster, review.Issue.Repo)
 	if err != nil {
 		log.Error("models.AccessLevel: %v", err)
 		return
@@ -801,7 +802,7 @@ func sendReleaseHook(doer *user_model.User, rel *models.Release, action api.Hook
 		return
 	}
 
-	mode, _ := models.AccessLevel(doer, rel.Repo)
+	mode, _ := access_model.AccessLevel(doer, rel.Repo)
 	if err := webhook_services.PrepareWebhooks(rel.Repo, webhook.HookEventRelease, &api.ReleasePayload{
 		Action:     action,
 		Release:    convert.ToRelease(rel),
diff --git a/modules/templates/helper.go b/modules/templates/helper.go
index 1ee9cb00e0..cc0fed7442 100644
--- a/modules/templates/helper.go
+++ b/modules/templates/helper.go
@@ -574,7 +574,7 @@ func Avatar(item interface{}, others ...interface{}) template.HTML {
 		if src != "" {
 			return AvatarHTML(src, size, class, t.DisplayName())
 		}
-	case *models.Collaborator:
+	case *repo_model.Collaborator:
 		src := t.AvatarLinkWithSize(size * setting.Avatar.RenderedSizeFactor)
 		if src != "" {
 			return AvatarHTML(src, size, class, t.DisplayName())
diff --git a/modules/test/context_tests.go b/modules/test/context_tests.go
index c745a106c5..a08439e93f 100644
--- a/modules/test/context_tests.go
+++ b/modules/test/context_tests.go
@@ -13,7 +13,7 @@ import (
 	"net/url"
 	"testing"
 
-	"code.gitea.io/gitea/models"
+	access_model "code.gitea.io/gitea/models/perm/access"
 	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/models/unittest"
 	user_model "code.gitea.io/gitea/models/user"
@@ -61,7 +61,7 @@ func LoadRepo(t *testing.T, ctx *context.Context, repoID int64) {
 	ctx.Repo.Owner, err = user_model.GetUserByID(ctx.Repo.Repository.OwnerID)
 	assert.NoError(t, err)
 	ctx.Repo.RepoLink = ctx.Repo.Repository.Link()
-	ctx.Repo.Permission, err = models.GetUserRepoPermission(ctx, ctx.Repo.Repository, ctx.Doer)
+	ctx.Repo.Permission, err = access_model.GetUserRepoPermission(ctx, ctx.Repo.Repository, ctx.Doer)
 	assert.NoError(t, err)
 }
 
diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go
index 8fa9a0ed65..9db1d80f7f 100644
--- a/routers/api/v1/api.go
+++ b/routers/api/v1/api.go
@@ -70,9 +70,9 @@ import (
 	"reflect"
 	"strings"
 
-	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/models/organization"
 	"code.gitea.io/gitea/models/perm"
+	access_model "code.gitea.io/gitea/models/perm/access"
 	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/models/unit"
 	user_model "code.gitea.io/gitea/models/user"
@@ -183,7 +183,7 @@ func repoAssignment() func(ctx *context.APIContext) {
 		repo.Owner = owner
 		ctx.Repo.Repository = repo
 
-		ctx.Repo.Permission, err = models.GetUserRepoPermission(ctx, repo, ctx.Doer)
+		ctx.Repo.Permission, err = access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
 		if err != nil {
 			ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err)
 			return
diff --git a/routers/api/v1/org/team.go b/routers/api/v1/org/team.go
index b24c8a6235..d54ed5bb31 100644
--- a/routers/api/v1/org/team.go
+++ b/routers/api/v1/org/team.go
@@ -12,6 +12,7 @@ import (
 	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/models/organization"
 	"code.gitea.io/gitea/models/perm"
+	access_model "code.gitea.io/gitea/models/perm/access"
 	repo_model "code.gitea.io/gitea/models/repo"
 	unit_model "code.gitea.io/gitea/models/unit"
 	"code.gitea.io/gitea/modules/context"
@@ -547,7 +548,7 @@ func GetTeamRepos(ctx *context.APIContext) {
 	}
 	repos := make([]*api.Repository, len(teamRepos))
 	for i, repo := range teamRepos {
-		access, err := models.AccessLevel(ctx.Doer, repo)
+		access, err := access_model.AccessLevel(ctx.Doer, repo)
 		if err != nil {
 			ctx.Error(http.StatusInternalServerError, "GetTeamRepos", err)
 			return
@@ -598,7 +599,7 @@ func GetTeamRepo(ctx *context.APIContext) {
 		return
 	}
 
-	access, err := models.AccessLevel(ctx.Doer, repo)
+	access, err := access_model.AccessLevel(ctx.Doer, repo)
 	if err != nil {
 		ctx.Error(http.StatusInternalServerError, "GetTeamRepos", err)
 		return
@@ -655,7 +656,7 @@ func AddTeamRepository(ctx *context.APIContext) {
 	if ctx.Written() {
 		return
 	}
-	if access, err := models.AccessLevel(ctx.Doer, repo); err != nil {
+	if access, err := access_model.AccessLevel(ctx.Doer, repo); err != nil {
 		ctx.Error(http.StatusInternalServerError, "AccessLevel", err)
 		return
 	} else if access < perm.AccessModeAdmin {
@@ -705,7 +706,7 @@ func RemoveTeamRepository(ctx *context.APIContext) {
 	if ctx.Written() {
 		return
 	}
-	if access, err := models.AccessLevel(ctx.Doer, repo); err != nil {
+	if access, err := access_model.AccessLevel(ctx.Doer, repo); err != nil {
 		ctx.Error(http.StatusInternalServerError, "AccessLevel", err)
 		return
 	} else if access < perm.AccessModeAdmin {
diff --git a/routers/api/v1/repo/collaborators.go b/routers/api/v1/repo/collaborators.go
index 2db1724b2a..fed2d9f055 100644
--- a/routers/api/v1/repo/collaborators.go
+++ b/routers/api/v1/repo/collaborators.go
@@ -11,6 +11,8 @@ import (
 
 	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/models/perm"
+	access_model "code.gitea.io/gitea/models/perm/access"
+	repo_model "code.gitea.io/gitea/models/repo"
 	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/context"
 	"code.gitea.io/gitea/modules/convert"
@@ -49,13 +51,13 @@ func ListCollaborators(ctx *context.APIContext) {
 	//   "200":
 	//     "$ref": "#/responses/UserList"
 
-	count, err := models.CountCollaborators(ctx.Repo.Repository.ID)
+	count, err := repo_model.CountCollaborators(ctx.Repo.Repository.ID)
 	if err != nil {
 		ctx.InternalServerError(err)
 		return
 	}
 
-	collaborators, err := models.GetCollaborators(ctx.Repo.Repository.ID, utils.GetListOptions(ctx))
+	collaborators, err := repo_model.GetCollaborators(ctx, ctx.Repo.Repository.ID, utils.GetListOptions(ctx))
 	if err != nil {
 		ctx.Error(http.StatusInternalServerError, "ListCollaborators", err)
 		return
@@ -110,7 +112,7 @@ func IsCollaborator(ctx *context.APIContext) {
 		}
 		return
 	}
-	isColab, err := models.IsCollaborator(ctx.Repo.Repository.ID, user.ID)
+	isColab, err := repo_model.IsCollaborator(ctx, ctx.Repo.Repository.ID, user.ID)
 	if err != nil {
 		ctx.Error(http.StatusInternalServerError, "IsCollaborator", err)
 		return
@@ -178,7 +180,7 @@ func AddCollaborator(ctx *context.APIContext) {
 	}
 
 	if form.Permission != nil {
-		if err := models.ChangeCollaborationAccessMode(ctx.Repo.Repository, collaborator.ID, perm.ParseAccessMode(*form.Permission)); err != nil {
+		if err := repo_model.ChangeCollaborationAccessMode(ctx.Repo.Repository, collaborator.ID, perm.ParseAccessMode(*form.Permission)); err != nil {
 			ctx.Error(http.StatusInternalServerError, "ChangeCollaborationAccessMode", err)
 			return
 		}
@@ -279,7 +281,7 @@ func GetRepoPermissions(ctx *context.APIContext) {
 		return
 	}
 
-	permission, err := models.GetUserRepoPermission(ctx, ctx.Repo.Repository, collaborator)
+	permission, err := access_model.GetUserRepoPermission(ctx, ctx.Repo.Repository, collaborator)
 	if err != nil {
 		ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err)
 		return
diff --git a/routers/api/v1/repo/file.go b/routers/api/v1/repo/file.go
index 2a4c4ad979..1fdf70c13a 100644
--- a/routers/api/v1/repo/file.go
+++ b/routers/api/v1/repo/file.go
@@ -209,7 +209,7 @@ func GetEditorconfig(ctx *context.APIContext) {
 
 // canWriteFiles returns true if repository is editable and user has proper access level.
 func canWriteFiles(ctx *context.APIContext, branch string) bool {
-	return ctx.Repo.Permission.CanWriteToBranch(ctx.Doer, branch) &&
+	return ctx.Repo.CanWriteToBranch(ctx.Doer, branch) &&
 		!ctx.Repo.Repository.IsMirror &&
 		!ctx.Repo.Repository.IsArchived
 }
diff --git a/routers/api/v1/repo/fork.go b/routers/api/v1/repo/fork.go
index 10c05e5503..112a9562f0 100644
--- a/routers/api/v1/repo/fork.go
+++ b/routers/api/v1/repo/fork.go
@@ -9,9 +9,9 @@ import (
 	"fmt"
 	"net/http"
 
-	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/models/organization"
 	"code.gitea.io/gitea/models/perm"
+	access_model "code.gitea.io/gitea/models/perm/access"
 	repo_model "code.gitea.io/gitea/models/repo"
 	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/context"
@@ -59,7 +59,7 @@ func ListForks(ctx *context.APIContext) {
 	}
 	apiForks := make([]*api.Repository, len(forks))
 	for i, fork := range forks {
-		access, err := models.AccessLevel(ctx.Doer, fork)
+		access, err := access_model.AccessLevel(ctx.Doer, fork)
 		if err != nil {
 			ctx.Error(http.StatusInternalServerError, "AccessLevel", err)
 			return
diff --git a/routers/api/v1/repo/issue.go b/routers/api/v1/repo/issue.go
index 9654b270c0..b45069ad46 100644
--- a/routers/api/v1/repo/issue.go
+++ b/routers/api/v1/repo/issue.go
@@ -16,6 +16,7 @@ import (
 	"code.gitea.io/gitea/models/db"
 	issues_model "code.gitea.io/gitea/models/issues"
 	"code.gitea.io/gitea/models/organization"
+	access_model "code.gitea.io/gitea/models/perm/access"
 	"code.gitea.io/gitea/models/unit"
 	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/context"
@@ -629,7 +630,7 @@ func CreateIssue(ctx *context.APIContext) {
 				return
 			}
 
-			valid, err := models.CanBeAssigned(assignee, ctx.Repo.Repository, false)
+			valid, err := access_model.CanBeAssigned(ctx, assignee, ctx.Repo.Repository, false)
 			if err != nil {
 				ctx.Error(http.StatusInternalServerError, "canBeAssigned", err)
 				return
diff --git a/routers/api/v1/repo/issue_comment.go b/routers/api/v1/repo/issue_comment.go
index bc68cb396b..6065adc27f 100644
--- a/routers/api/v1/repo/issue_comment.go
+++ b/routers/api/v1/repo/issue_comment.go
@@ -11,6 +11,7 @@ import (
 	"net/http"
 
 	"code.gitea.io/gitea/models"
+	access_model "code.gitea.io/gitea/models/perm/access"
 	repo_model "code.gitea.io/gitea/models/repo"
 	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/context"
@@ -203,7 +204,7 @@ func isXRefCommentAccessible(ctx stdCtx.Context, user *user_model.User, c *model
 		if err != nil {
 			return false
 		}
-		perm, err := models.GetUserRepoPermission(ctx, c.RefRepo, user)
+		perm, err := access_model.GetUserRepoPermission(ctx, c.RefRepo, user)
 		if err != nil {
 			return false
 		}
diff --git a/routers/api/v1/repo/pull.go b/routers/api/v1/repo/pull.go
index f95bc6b16b..a01efda1bc 100644
--- a/routers/api/v1/repo/pull.go
+++ b/routers/api/v1/repo/pull.go
@@ -15,6 +15,7 @@ import (
 
 	"code.gitea.io/gitea/models"
 	issues_model "code.gitea.io/gitea/models/issues"
+	access_model "code.gitea.io/gitea/models/perm/access"
 	pull_model "code.gitea.io/gitea/models/pull"
 	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/models/unit"
@@ -402,7 +403,7 @@ func CreatePullRequest(ctx *context.APIContext) {
 			return
 		}
 
-		valid, err := models.CanBeAssigned(assignee, repo, true)
+		valid, err := access_model.CanBeAssigned(ctx, assignee, repo, true)
 		if err != nil {
 			ctx.Error(http.StatusInternalServerError, "canBeAssigned", err)
 			return
@@ -983,7 +984,7 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption)
 	}
 
 	// user should have permission to read baseRepo's codes and pulls, NOT headRepo's
-	permBase, err := models.GetUserRepoPermission(ctx, baseRepo, ctx.Doer)
+	permBase, err := access_model.GetUserRepoPermission(ctx, baseRepo, ctx.Doer)
 	if err != nil {
 		headGitRepo.Close()
 		ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err)
@@ -1002,7 +1003,7 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption)
 	}
 
 	// user should have permission to read headrepo's codes
-	permHead, err := models.GetUserRepoPermission(ctx, headRepo, ctx.Doer)
+	permHead, err := access_model.GetUserRepoPermission(ctx, headRepo, ctx.Doer)
 	if err != nil {
 		headGitRepo.Close()
 		ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err)
@@ -1197,7 +1198,7 @@ func CancelScheduledAutoMerge(ctx *context.APIContext) {
 	}
 
 	if ctx.Doer.ID != autoMerge.DoerID {
-		allowed, err := models.IsUserRepoAdminCtx(ctx, ctx.Repo.Repository, ctx.Doer)
+		allowed, err := access_model.IsUserRepoAdminCtx(ctx, ctx.Repo.Repository, ctx.Doer)
 		if err != nil {
 			ctx.InternalServerError(err)
 			return
diff --git a/routers/api/v1/repo/pull_review.go b/routers/api/v1/repo/pull_review.go
index b3ebe49bf5..0cf540ce7e 100644
--- a/routers/api/v1/repo/pull_review.go
+++ b/routers/api/v1/repo/pull_review.go
@@ -11,6 +11,7 @@ import (
 
 	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/models/organization"
+	access_model "code.gitea.io/gitea/models/perm/access"
 	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/context"
 	"code.gitea.io/gitea/modules/convert"
@@ -664,7 +665,7 @@ func apiReviewRequest(ctx *context.APIContext, opts api.PullReviewRequestOptions
 
 	reviewers := make([]*user_model.User, 0, len(opts.Reviewers))
 
-	permDoer, err := models.GetUserRepoPermission(ctx, pr.Issue.Repo, ctx.Doer)
+	permDoer, err := access_model.GetUserRepoPermission(ctx, pr.Issue.Repo, ctx.Doer)
 	if err != nil {
 		ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err)
 		return
diff --git a/routers/api/v1/repo/repo.go b/routers/api/v1/repo/repo.go
index 29e8352142..a11f579ee1 100644
--- a/routers/api/v1/repo/repo.go
+++ b/routers/api/v1/repo/repo.go
@@ -15,6 +15,7 @@ import (
 	"code.gitea.io/gitea/models/db"
 	"code.gitea.io/gitea/models/organization"
 	"code.gitea.io/gitea/models/perm"
+	access_model "code.gitea.io/gitea/models/perm/access"
 	repo_model "code.gitea.io/gitea/models/repo"
 	unit_model "code.gitea.io/gitea/models/unit"
 	user_model "code.gitea.io/gitea/models/user"
@@ -209,7 +210,7 @@ func Search(ctx *context.APIContext) {
 			})
 			return
 		}
-		accessMode, err := models.AccessLevel(ctx.Doer, repo)
+		accessMode, err := access_model.AccessLevel(ctx.Doer, repo)
 		if err != nil {
 			ctx.JSON(http.StatusInternalServerError, api.SearchError{
 				OK:    false,
@@ -555,7 +556,7 @@ func GetByID(ctx *context.APIContext) {
 		return
 	}
 
-	perm, err := models.GetUserRepoPermission(ctx, repo, ctx.Doer)
+	perm, err := access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
 	if err != nil {
 		ctx.Error(http.StatusInternalServerError, "AccessLevel", err)
 		return
diff --git a/routers/api/v1/user/repo.go b/routers/api/v1/user/repo.go
index 0231c8ccbc..05cecf508b 100644
--- a/routers/api/v1/user/repo.go
+++ b/routers/api/v1/user/repo.go
@@ -9,6 +9,7 @@ import (
 
 	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/models/perm"
+	access_model "code.gitea.io/gitea/models/perm/access"
 	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/context"
 	"code.gitea.io/gitea/modules/convert"
@@ -38,7 +39,7 @@ func listUserRepos(ctx *context.APIContext, u *user_model.User, private bool) {
 
 	apiRepos := make([]*api.Repository, 0, len(repos))
 	for i := range repos {
-		access, err := models.AccessLevel(ctx.Doer, repos[i])
+		access, err := access_model.AccessLevel(ctx.Doer, repos[i])
 		if err != nil {
 			ctx.Error(http.StatusInternalServerError, "AccessLevel", err)
 			return
@@ -123,7 +124,7 @@ func ListMyRepos(ctx *context.APIContext) {
 			ctx.Error(http.StatusInternalServerError, "GetOwner", err)
 			return
 		}
-		accessMode, err := models.AccessLevel(ctx.Doer, repo)
+		accessMode, err := access_model.AccessLevel(ctx.Doer, repo)
 		if err != nil {
 			ctx.Error(http.StatusInternalServerError, "AccessLevel", err)
 		}
diff --git a/routers/api/v1/user/star.go b/routers/api/v1/user/star.go
index cdbc35471b..d2e4f36cd7 100644
--- a/routers/api/v1/user/star.go
+++ b/routers/api/v1/user/star.go
@@ -8,8 +8,8 @@ package user
 import (
 	"net/http"
 
-	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/models/db"
+	access_model "code.gitea.io/gitea/models/perm/access"
 	repo_model "code.gitea.io/gitea/models/repo"
 	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/context"
@@ -28,7 +28,7 @@ func getStarredRepos(user *user_model.User, private bool, listOptions db.ListOpt
 
 	repos := make([]*api.Repository, len(starredRepos))
 	for i, starred := range starredRepos {
-		access, err := models.AccessLevel(user, starred)
+		access, err := access_model.AccessLevel(user, starred)
 		if err != nil {
 			return nil, err
 		}
diff --git a/routers/api/v1/user/watch.go b/routers/api/v1/user/watch.go
index e7c6837cb8..652ef3f8a6 100644
--- a/routers/api/v1/user/watch.go
+++ b/routers/api/v1/user/watch.go
@@ -7,8 +7,8 @@ package user
 import (
 	"net/http"
 
-	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/models/db"
+	access_model "code.gitea.io/gitea/models/perm/access"
 	repo_model "code.gitea.io/gitea/models/repo"
 	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/context"
@@ -26,7 +26,7 @@ func getWatchedRepos(user *user_model.User, private bool, listOptions db.ListOpt
 
 	repos := make([]*api.Repository, len(watchedRepos))
 	for i, watched := range watchedRepos {
-		access, err := models.AccessLevel(user, watched)
+		access, err := access_model.AccessLevel(user, watched)
 		if err != nil {
 			return nil, 0, err
 		}
diff --git a/routers/private/hook_pre_receive.go b/routers/private/hook_pre_receive.go
index d2203a1f99..9d3d665264 100644
--- a/routers/private/hook_pre_receive.go
+++ b/routers/private/hook_pre_receive.go
@@ -14,6 +14,7 @@ import (
 	"code.gitea.io/gitea/models"
 	asymkey_model "code.gitea.io/gitea/models/asymkey"
 	perm_model "code.gitea.io/gitea/models/perm"
+	access_model "code.gitea.io/gitea/models/perm/access"
 	"code.gitea.io/gitea/models/unit"
 	user_model "code.gitea.io/gitea/models/user"
 	gitea_context "code.gitea.io/gitea/modules/context"
@@ -30,7 +31,7 @@ type preReceiveContext struct {
 	// loadedPusher indicates that where the following information are loaded
 	loadedPusher        bool
 	user                *user_model.User // it's the org user if a DeployKey is used
-	userPerm            models.Permission
+	userPerm            access_model.Permission
 	deployKeyAccessMode perm_model.AccessMode
 
 	canCreatePullRequest        bool
@@ -55,7 +56,7 @@ func (ctx *preReceiveContext) CanWriteCode() bool {
 		if !ctx.loadPusherAndPermission() {
 			return false
 		}
-		ctx.canWriteCode = ctx.userPerm.CanWriteToBranch(ctx.user, ctx.branchName) || ctx.deployKeyAccessMode >= perm_model.AccessModeWrite
+		ctx.canWriteCode = models.CanMaintainerWriteToBranch(ctx.userPerm, ctx.branchName, ctx.user) || ctx.deployKeyAccessMode >= perm_model.AccessModeWrite
 		ctx.checkedCanWriteCode = true
 	}
 	return ctx.canWriteCode
@@ -472,7 +473,7 @@ func (ctx *preReceiveContext) loadPusherAndPermission() bool {
 	}
 	ctx.user = user
 
-	userPerm, err := models.GetUserRepoPermission(ctx, ctx.Repo.Repository, user)
+	userPerm, err := access_model.GetUserRepoPermission(ctx, ctx.Repo.Repository, user)
 	if err != nil {
 		log.Error("Unable to get Repo permission of repo %s/%s of User %s", ctx.Repo.Repository.OwnerName, ctx.Repo.Repository.Name, user.Name, err)
 		ctx.JSON(http.StatusInternalServerError, private.Response{
diff --git a/routers/private/serv.go b/routers/private/serv.go
index 6ef0079a2b..77877e1ad8 100644
--- a/routers/private/serv.go
+++ b/routers/private/serv.go
@@ -10,9 +10,9 @@ import (
 	"net/http"
 	"strings"
 
-	"code.gitea.io/gitea/models"
 	asymkey_model "code.gitea.io/gitea/models/asymkey"
 	"code.gitea.io/gitea/models/perm"
+	access_model "code.gitea.io/gitea/models/perm/access"
 	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/models/unit"
 	user_model "code.gitea.io/gitea/models/user"
@@ -320,7 +320,7 @@ func ServCommand(ctx *context.PrivateContext) {
 				mode = perm.AccessModeRead
 			}
 
-			perm, err := models.GetUserRepoPermission(ctx, repo, user)
+			perm, err := access_model.GetUserRepoPermission(ctx, repo, user)
 			if err != nil {
 				log.Error("Unable to get permissions for %-v with key %d in %-v Error: %v", user, key.ID, repo, err)
 				ctx.JSON(http.StatusInternalServerError, private.ErrServCommand{
diff --git a/routers/web/repo/attachment.go b/routers/web/repo/attachment.go
index c930311f70..b05d6448d9 100644
--- a/routers/web/repo/attachment.go
+++ b/routers/web/repo/attachment.go
@@ -9,6 +9,7 @@ import (
 	"net/http"
 
 	"code.gitea.io/gitea/models"
+	access_model "code.gitea.io/gitea/models/perm/access"
 	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/modules/context"
 	"code.gitea.io/gitea/modules/httpcache"
@@ -106,7 +107,7 @@ func GetAttachment(ctx *context.Context) {
 			return
 		}
 	} else { // If we have the repository we check access
-		perm, err := models.GetUserRepoPermission(ctx, repository, ctx.Doer)
+		perm, err := access_model.GetUserRepoPermission(ctx, repository, ctx.Doer)
 		if err != nil {
 			ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err.Error())
 			return
diff --git a/routers/web/repo/compare.go b/routers/web/repo/compare.go
index 31914c43ab..3ea888454c 100644
--- a/routers/web/repo/compare.go
+++ b/routers/web/repo/compare.go
@@ -18,6 +18,7 @@ import (
 	"strings"
 
 	"code.gitea.io/gitea/models"
+	access_model "code.gitea.io/gitea/models/perm/access"
 	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/models/unit"
 	user_model "code.gitea.io/gitea/models/user"
@@ -412,7 +413,7 @@ func ParseCompareInfo(ctx *context.Context) *CompareInfo {
 	// Now we need to assert that the ctx.Doer has permission to read
 	// the baseRepo's code and pulls
 	// (NOT headRepo's)
-	permBase, err := models.GetUserRepoPermission(ctx, baseRepo, ctx.Doer)
+	permBase, err := access_model.GetUserRepoPermission(ctx, baseRepo, ctx.Doer)
 	if err != nil {
 		ctx.ServerError("GetUserRepoPermission", err)
 		return nil
@@ -431,7 +432,7 @@ func ParseCompareInfo(ctx *context.Context) *CompareInfo {
 	// If we're not merging from the same repo:
 	if !isSameRepo {
 		// Assert ctx.Doer has permission to read headRepo's codes
-		permHead, err := models.GetUserRepoPermission(ctx, ci.HeadRepo, ctx.Doer)
+		permHead, err := access_model.GetUserRepoPermission(ctx, ci.HeadRepo, ctx.Doer)
 		if err != nil {
 			ctx.ServerError("GetUserRepoPermission", err)
 			return nil
diff --git a/routers/web/repo/http.go b/routers/web/repo/http.go
index a52d9b76c2..6a85bca16b 100644
--- a/routers/web/repo/http.go
+++ b/routers/web/repo/http.go
@@ -19,9 +19,9 @@ import (
 	"sync"
 	"time"
 
-	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/models/auth"
 	"code.gitea.io/gitea/models/perm"
+	access_model "code.gitea.io/gitea/models/perm/access"
 	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/models/unit"
 	"code.gitea.io/gitea/modules/context"
@@ -182,7 +182,7 @@ func httpBase(ctx *context.Context) (h *serviceHandler) {
 		}
 
 		if repoExist {
-			p, err := models.GetUserRepoPermission(ctx, repo, ctx.Doer)
+			p, err := access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
 			if err != nil {
 				ctx.ServerError("GetUserRepoPermission", err)
 				return
diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go
index 7ddeb05f71..f50b30da1c 100644
--- a/routers/web/repo/issue.go
+++ b/routers/web/repo/issue.go
@@ -23,6 +23,7 @@ import (
 	"code.gitea.io/gitea/models/db"
 	issues_model "code.gitea.io/gitea/models/issues"
 	"code.gitea.io/gitea/models/organization"
+	access_model "code.gitea.io/gitea/models/perm/access"
 	project_model "code.gitea.io/gitea/models/project"
 	pull_model "code.gitea.io/gitea/models/pull"
 	repo_model "code.gitea.io/gitea/models/repo"
@@ -959,7 +960,7 @@ func ValidateRepoMetas(ctx *context.Context, form forms.CreateIssueForm, isPull
 				return nil, nil, 0, 0
 			}
 
-			valid, err := models.CanBeAssigned(assignee, repo, isPull)
+			valid, err := access_model.CanBeAssigned(ctx, assignee, repo, isPull)
 			if err != nil {
 				ctx.ServerError("CanBeAssigned", err)
 				return nil, nil, 0, 0
@@ -1051,7 +1052,7 @@ func NewIssuePost(ctx *context.Context) {
 
 // roleDescriptor returns the Role Descriptor for a comment in/with the given repo, poster and issue
 func roleDescriptor(ctx stdCtx.Context, repo *repo_model.Repository, poster *user_model.User, issue *models.Issue) (models.RoleDescriptor, error) {
-	perm, err := models.GetUserRepoPermission(ctx, repo, poster)
+	perm, err := access_model.GetUserRepoPermission(ctx, repo, poster)
 	if err != nil {
 		return models.RoleDescriptorNone, err
 	}
@@ -1067,7 +1068,7 @@ func roleDescriptor(ctx stdCtx.Context, repo *repo_model.Repository, poster *use
 		} else {
 
 			// Otherwise check if poster is the real repo admin.
-			ok, err := models.IsUserRealRepoAdmin(repo, poster)
+			ok, err := access_model.IsUserRealRepoAdmin(repo, poster)
 			if err != nil {
 				return models.RoleDescriptorNone, err
 			}
@@ -1526,7 +1527,7 @@ func ViewIssue(ctx *context.Context) {
 			if err := pull.LoadHeadRepoCtx(ctx); err != nil {
 				log.Error("LoadHeadRepo: %v", err)
 			} else if pull.HeadRepo != nil {
-				perm, err := models.GetUserRepoPermission(ctx, pull.HeadRepo, ctx.Doer)
+				perm, err := access_model.GetUserRepoPermission(ctx, pull.HeadRepo, ctx.Doer)
 				if err != nil {
 					ctx.ServerError("GetUserRepoPermission", err)
 					return
@@ -1548,7 +1549,7 @@ func ViewIssue(ctx *context.Context) {
 			if err := pull.LoadBaseRepoCtx(ctx); err != nil {
 				log.Error("LoadBaseRepo: %v", err)
 			}
-			perm, err := models.GetUserRepoPermission(ctx, pull.BaseRepo, ctx.Doer)
+			perm, err := access_model.GetUserRepoPermission(ctx, pull.BaseRepo, ctx.Doer)
 			if err != nil {
 				ctx.ServerError("GetUserRepoPermission", err)
 				return
@@ -1978,7 +1979,7 @@ func UpdateIssueAssignee(ctx *context.Context) {
 				return
 			}
 
-			valid, err := models.CanBeAssigned(assignee, issue.Repo, issue.IsPull)
+			valid, err := access_model.CanBeAssigned(ctx, assignee, issue.Repo, issue.IsPull)
 			if err != nil {
 				ctx.ServerError("canBeAssigned", err)
 				return
@@ -2941,7 +2942,7 @@ func filterXRefComments(ctx *context.Context, issue *models.Issue) error {
 			if err != nil {
 				return err
 			}
-			perm, err := models.GetUserRepoPermission(ctx, c.RefRepo, ctx.Doer)
+			perm, err := access_model.GetUserRepoPermission(ctx, c.RefRepo, ctx.Doer)
 			if err != nil {
 				return err
 			}
diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go
index 27b61309a5..fd224a22ef 100644
--- a/routers/web/repo/pull.go
+++ b/routers/web/repo/pull.go
@@ -19,6 +19,7 @@ import (
 	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/models/db"
 	"code.gitea.io/gitea/models/organization"
+	access_model "code.gitea.io/gitea/models/perm/access"
 	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/models/unit"
 	user_model "code.gitea.io/gitea/models/user"
@@ -70,7 +71,7 @@ func getRepository(ctx *context.Context, repoID int64) *repo_model.Repository {
 		return nil
 	}
 
-	perm, err := models.GetUserRepoPermission(ctx, repo, ctx.Doer)
+	perm, err := access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
 	if err != nil {
 		ctx.ServerError("GetUserRepoPermission", err)
 		return nil
@@ -1247,7 +1248,7 @@ func CleanUpPullRequest(ctx *context.Context) {
 		return
 	}
 
-	perm, err := models.GetUserRepoPermission(ctx, pr.HeadRepo, ctx.Doer)
+	perm, err := access_model.GetUserRepoPermission(ctx, pr.HeadRepo, ctx.Doer)
 	if err != nil {
 		ctx.ServerError("GetUserRepoPermission", err)
 		return
diff --git a/routers/web/repo/setting.go b/routers/web/repo/setting.go
index ccb603833c..dd1cb412c1 100644
--- a/routers/web/repo/setting.go
+++ b/routers/web/repo/setting.go
@@ -833,7 +833,7 @@ func Collaboration(ctx *context.Context) {
 	ctx.Data["Title"] = ctx.Tr("repo.settings")
 	ctx.Data["PageIsSettingsCollaboration"] = true
 
-	users, err := models.GetCollaborators(ctx.Repo.Repository.ID, db.ListOptions{})
+	users, err := repo_model.GetCollaborators(ctx, ctx.Repo.Repository.ID, db.ListOptions{})
 	if err != nil {
 		ctx.ServerError("GetCollaborators", err)
 		return
@@ -887,7 +887,7 @@ func CollaborationPost(ctx *context.Context) {
 		return
 	}
 
-	if got, err := models.IsCollaborator(ctx.Repo.Repository.ID, u.ID); err == nil && got {
+	if got, err := repo_model.IsCollaborator(ctx, ctx.Repo.Repository.ID, u.ID); err == nil && got {
 		ctx.Flash.Error(ctx.Tr("repo.settings.add_collaborator_duplicate"))
 		ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration")
 		return
@@ -908,7 +908,7 @@ func CollaborationPost(ctx *context.Context) {
 
 // ChangeCollaborationAccessMode response for changing access of a collaboration
 func ChangeCollaborationAccessMode(ctx *context.Context) {
-	if err := models.ChangeCollaborationAccessMode(
+	if err := repo_model.ChangeCollaborationAccessMode(
 		ctx.Repo.Repository,
 		ctx.FormInt64("uid"),
 		perm.AccessMode(ctx.FormInt("mode"))); err != nil {
diff --git a/routers/web/repo/setting_protected_branch.go b/routers/web/repo/setting_protected_branch.go
index 1f6e2316e7..35f35163cb 100644
--- a/routers/web/repo/setting_protected_branch.go
+++ b/routers/web/repo/setting_protected_branch.go
@@ -13,6 +13,7 @@ import (
 	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/models/organization"
 	"code.gitea.io/gitea/models/perm"
+	access_model "code.gitea.io/gitea/models/perm/access"
 	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/context"
@@ -125,7 +126,7 @@ func SettingsProtectedBranch(c *context.Context) {
 		}
 	}
 
-	users, err := models.GetRepoReaders(c.Repo.Repository)
+	users, err := access_model.GetRepoReaders(c.Repo.Repository)
 	if err != nil {
 		c.ServerError("Repo.Repository.GetReaders", err)
 		return
diff --git a/routers/web/repo/settings_test.go b/routers/web/repo/settings_test.go
index 36d02de273..946220b4fc 100644
--- a/routers/web/repo/settings_test.go
+++ b/routers/web/repo/settings_test.go
@@ -130,7 +130,7 @@ func TestCollaborationPost(t *testing.T) {
 
 	assert.EqualValues(t, http.StatusSeeOther, ctx.Resp.Status())
 
-	exists, err := models.IsCollaborator(re.ID, 4)
+	exists, err := repo_model.IsCollaborator(ctx, re.ID, 4)
 	assert.NoError(t, err)
 	assert.True(t, exists)
 }
@@ -188,7 +188,7 @@ func TestCollaborationPost_AddCollaboratorTwice(t *testing.T) {
 
 	assert.EqualValues(t, http.StatusSeeOther, ctx.Resp.Status())
 
-	exists, err := models.IsCollaborator(re.ID, 4)
+	exists, err := repo_model.IsCollaborator(ctx, re.ID, 4)
 	assert.NoError(t, err)
 	assert.True(t, exists)
 
diff --git a/routers/web/repo/tag.go b/routers/web/repo/tag.go
index 7da1e36c81..3c47a0604f 100644
--- a/routers/web/repo/tag.go
+++ b/routers/web/repo/tag.go
@@ -12,6 +12,7 @@ import (
 	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/models/organization"
 	"code.gitea.io/gitea/models/perm"
+	access_model "code.gitea.io/gitea/models/perm/access"
 	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/context"
 	"code.gitea.io/gitea/modules/setting"
@@ -143,7 +144,7 @@ func setTagsContext(ctx *context.Context) error {
 	}
 	ctx.Data["ProtectedTags"] = protectedTags
 
-	users, err := models.GetRepoReaders(ctx.Repo.Repository)
+	users, err := access_model.GetRepoReaders(ctx.Repo.Repository)
 	if err != nil {
 		ctx.ServerError("Repo.Repository.GetReaders", err)
 		return err
diff --git a/routers/web/user/package.go b/routers/web/user/package.go
index 1c33998db9..c9aa2471ef 100644
--- a/routers/web/user/package.go
+++ b/routers/web/user/package.go
@@ -12,6 +12,7 @@ import (
 	packages_model "code.gitea.io/gitea/models/packages"
 	container_model "code.gitea.io/gitea/models/packages/container"
 	"code.gitea.io/gitea/models/perm"
+	access_model "code.gitea.io/gitea/models/perm/access"
 	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/context"
@@ -67,7 +68,7 @@ func ListPackages(ctx *context.Context) {
 			continue
 		}
 
-		permission, err := models.GetUserRepoPermission(ctx, pd.Repository, ctx.Doer)
+		permission, err := access_model.GetUserRepoPermission(ctx, pd.Repository, ctx.Doer)
 		if err != nil {
 			ctx.ServerError("GetUserRepoPermission", err)
 			return
@@ -177,7 +178,7 @@ func ViewPackageVersion(ctx *context.Context) {
 
 	hasRepositoryAccess := false
 	if pd.Repository != nil {
-		permission, err := models.GetUserRepoPermission(ctx, pd.Repository, ctx.Doer)
+		permission, err := access_model.GetUserRepoPermission(ctx, pd.Repository, ctx.Doer)
 		if err != nil {
 			ctx.ServerError("GetUserRepoPermission", err)
 			return
diff --git a/services/automerge/automerge.go b/services/automerge/automerge.go
index 85af2659c6..3ce4883aa7 100644
--- a/services/automerge/automerge.go
+++ b/services/automerge/automerge.go
@@ -13,6 +13,7 @@ import (
 
 	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/models/db"
+	access_model "code.gitea.io/gitea/models/perm/access"
 	pull_model "code.gitea.io/gitea/models/pull"
 	repo_model "code.gitea.io/gitea/models/repo"
 	user_model "code.gitea.io/gitea/models/user"
@@ -224,7 +225,7 @@ func handlePull(pullID int64, sha string) {
 		return
 	}
 
-	perm, err := models.GetUserRepoPermission(ctx, pr.HeadRepo, doer)
+	perm, err := access_model.GetUserRepoPermission(ctx, pr.HeadRepo, doer)
 	if err != nil {
 		log.Error("GetUserRepoPermission: %v", err)
 		return
diff --git a/services/issue/assignee.go b/services/issue/assignee.go
index e6169b9c7e..0b6d0045fd 100644
--- a/services/issue/assignee.go
+++ b/services/issue/assignee.go
@@ -11,6 +11,7 @@ import (
 	"code.gitea.io/gitea/models/db"
 	"code.gitea.io/gitea/models/organization"
 	"code.gitea.io/gitea/models/perm"
+	access_model "code.gitea.io/gitea/models/perm/access"
 	"code.gitea.io/gitea/models/unit"
 	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/log"
@@ -80,7 +81,7 @@ func ReviewRequest(issue *models.Issue, doer, reviewer *user_model.User, isAdd b
 }
 
 // IsValidReviewRequest Check permission for ReviewRequest
-func IsValidReviewRequest(ctx context.Context, reviewer, doer *user_model.User, isAdd bool, issue *models.Issue, permDoer *models.Permission) error {
+func IsValidReviewRequest(ctx context.Context, reviewer, doer *user_model.User, isAdd bool, issue *models.Issue, permDoer *access_model.Permission) error {
 	if reviewer.IsOrganization() {
 		return models.ErrNotValidReviewRequest{
 			Reason: "Organization can't be added as reviewer",
@@ -96,14 +97,14 @@ func IsValidReviewRequest(ctx context.Context, reviewer, doer *user_model.User,
 		}
 	}
 
-	permReviewer, err := models.GetUserRepoPermission(ctx, issue.Repo, reviewer)
+	permReviewer, err := access_model.GetUserRepoPermission(ctx, issue.Repo, reviewer)
 	if err != nil {
 		return err
 	}
 
 	if permDoer == nil {
-		permDoer = new(models.Permission)
-		*permDoer, err = models.GetUserRepoPermission(ctx, issue.Repo, doer)
+		permDoer = new(access_model.Permission)
+		*permDoer, err = access_model.GetUserRepoPermission(ctx, issue.Repo, doer)
 		if err != nil {
 			return err
 		}
@@ -179,7 +180,7 @@ func IsValidTeamReviewRequest(ctx context.Context, reviewer *organization.Team,
 		}
 	}
 
-	permission, err := models.GetUserRepoPermission(ctx, issue.Repo, doer)
+	permission, err := access_model.GetUserRepoPermission(ctx, issue.Repo, doer)
 	if err != nil {
 		log.Error("Unable to GetUserRepoPermission for %-v in %-v#%d", doer, issue.Repo, issue.Index)
 		return err
diff --git a/services/issue/commit.go b/services/issue/commit.go
index b5d97e12a8..5140eebed1 100644
--- a/services/issue/commit.go
+++ b/services/issue/commit.go
@@ -15,6 +15,7 @@ import (
 
 	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/models/db"
+	access_model "code.gitea.io/gitea/models/perm/access"
 	repo_model "code.gitea.io/gitea/models/repo"
 	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/references"
@@ -131,7 +132,7 @@ func UpdateIssuesCommit(doer *user_model.User, repo *repo_model.Repository, comm
 				continue
 			}
 
-			perm, err := models.GetUserRepoPermission(db.DefaultContext, refRepo, doer)
+			perm, err := access_model.GetUserRepoPermission(db.DefaultContext, refRepo, doer)
 			if err != nil {
 				return err
 			}
diff --git a/services/issue/issue.go b/services/issue/issue.go
index 6bc3959979..db304a46b7 100644
--- a/services/issue/issue.go
+++ b/services/issue/issue.go
@@ -9,6 +9,7 @@ import (
 
 	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/models/db"
+	access_model "code.gitea.io/gitea/models/perm/access"
 	repo_model "code.gitea.io/gitea/models/repo"
 	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/git"
@@ -172,7 +173,7 @@ func AddAssigneeIfNotAssigned(issue *models.Issue, doer *user_model.User, assign
 		return nil
 	}
 
-	valid, err := models.CanBeAssigned(assignee, issue.Repo, issue.IsPull)
+	valid, err := access_model.CanBeAssigned(db.DefaultContext, assignee, issue.Repo, issue.IsPull)
 	if err != nil {
 		return err
 	}
diff --git a/services/issue/label.go b/services/issue/label.go
index 62ccc0ad65..94e52482fb 100644
--- a/services/issue/label.go
+++ b/services/issue/label.go
@@ -7,6 +7,7 @@ package issue
 import (
 	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/models/db"
+	access_model "code.gitea.io/gitea/models/perm/access"
 	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/notification"
 )
@@ -54,7 +55,7 @@ func RemoveLabel(issue *models.Issue, doer *user_model.User, label *models.Label
 		return err
 	}
 
-	perm, err := models.GetUserRepoPermission(ctx, issue.Repo, doer)
+	perm, err := access_model.GetUserRepoPermission(ctx, issue.Repo, doer)
 	if err != nil {
 		return err
 	}
diff --git a/services/lfs/server.go b/services/lfs/server.go
index c095bbfab4..f5c57a7dab 100644
--- a/services/lfs/server.go
+++ b/services/lfs/server.go
@@ -20,6 +20,7 @@ import (
 
 	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/models/perm"
+	access_model "code.gitea.io/gitea/models/perm/access"
 	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/models/unit"
 	user_model "code.gitea.io/gitea/models/user"
@@ -488,7 +489,7 @@ func authenticate(ctx *context.Context, repository *repo_model.Repository, autho
 	}
 
 	// ctx.IsSigned is unnecessary here, this will be checked in perm.CanAccess
-	perm, err := models.GetUserRepoPermission(ctx, repository, ctx.Doer)
+	perm, err := access_model.GetUserRepoPermission(ctx, repository, ctx.Doer)
 	if err != nil {
 		log.Error("Unable to GetUserRepoPermission for user %-v in repo %-v Error: %v", ctx.Doer, repository)
 		return false
diff --git a/services/pull/check.go b/services/pull/check.go
index 6852940b22..d88dd3a550 100644
--- a/services/pull/check.go
+++ b/services/pull/check.go
@@ -15,6 +15,7 @@ import (
 
 	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/models/db"
+	access_model "code.gitea.io/gitea/models/perm/access"
 	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/models/unit"
 	user_model "code.gitea.io/gitea/models/user"
@@ -60,7 +61,7 @@ func AddToTaskQueue(pr *models.PullRequest) {
 }
 
 // CheckPullMergable check if the pull mergable based on all conditions (branch protection, merge options, ...)
-func CheckPullMergable(stdCtx context.Context, doer *user_model.User, perm *models.Permission, pr *models.PullRequest, manuallMerge, force bool) error {
+func CheckPullMergable(stdCtx context.Context, doer *user_model.User, perm *access_model.Permission, pr *models.PullRequest, manuallMerge, force bool) error {
 	return db.WithTx(func(ctx context.Context) error {
 		if pr.HasMerged {
 			return ErrHasMerged
@@ -98,7 +99,7 @@ func CheckPullMergable(stdCtx context.Context, doer *user_model.User, perm *mode
 		if err := CheckPullBranchProtections(ctx, pr, false); err != nil {
 			if models.IsErrDisallowedToMerge(err) {
 				if force {
-					if isRepoAdmin, err2 := models.IsUserRepoAdminCtx(ctx, pr.BaseRepo, doer); err2 != nil {
+					if isRepoAdmin, err2 := access_model.IsUserRepoAdminCtx(ctx, pr.BaseRepo, doer); err2 != nil {
 						return err2
 					} else if !isRepoAdmin {
 						return err
diff --git a/services/pull/edits.go b/services/pull/edits.go
index 68515ec141..11932d9ab8 100644
--- a/services/pull/edits.go
+++ b/services/pull/edits.go
@@ -10,6 +10,7 @@ import (
 	"errors"
 
 	"code.gitea.io/gitea/models"
+	access_model "code.gitea.io/gitea/models/perm/access"
 	unit_model "code.gitea.io/gitea/models/unit"
 	user_model "code.gitea.io/gitea/models/user"
 )
@@ -26,7 +27,7 @@ func SetAllowEdits(ctx context.Context, doer *user_model.User, pr *models.PullRe
 		return err
 	}
 
-	permission, err := models.GetUserRepoPermission(ctx, pr.HeadRepo, doer)
+	permission, err := access_model.GetUserRepoPermission(ctx, pr.HeadRepo, doer)
 	if err != nil {
 		return err
 	}
diff --git a/services/pull/merge.go b/services/pull/merge.go
index 0af3cc1613..fcced65cdf 100644
--- a/services/pull/merge.go
+++ b/services/pull/merge.go
@@ -19,6 +19,7 @@ import (
 
 	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/models/db"
+	access_model "code.gitea.io/gitea/models/perm/access"
 	pull_model "code.gitea.io/gitea/models/pull"
 	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/models/unit"
@@ -751,7 +752,7 @@ func getDiffTree(ctx context.Context, repoPath, baseBranch, headBranch string) (
 }
 
 // IsUserAllowedToMerge check if user is allowed to merge PR with given permissions and branch protections
-func IsUserAllowedToMerge(ctx context.Context, pr *models.PullRequest, p models.Permission, user *user_model.User) (bool, error) {
+func IsUserAllowedToMerge(ctx context.Context, pr *models.PullRequest, p access_model.Permission, user *user_model.User) (bool, error) {
 	if user == nil {
 		return false, nil
 	}
diff --git a/services/pull/update.go b/services/pull/update.go
index 3c5c1c048c..0ab8ffcd7d 100644
--- a/services/pull/update.go
+++ b/services/pull/update.go
@@ -9,6 +9,7 @@ import (
 	"fmt"
 
 	"code.gitea.io/gitea/models"
+	access_model "code.gitea.io/gitea/models/perm/access"
 	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/models/unit"
 	user_model "code.gitea.io/gitea/models/user"
@@ -83,7 +84,7 @@ func IsUserAllowedToUpdate(ctx context.Context, pull *models.PullRequest, user *
 	if user == nil {
 		return false, false, nil
 	}
-	headRepoPerm, err := models.GetUserRepoPermission(ctx, pull.HeadRepo, user)
+	headRepoPerm, err := access_model.GetUserRepoPermission(ctx, pull.HeadRepo, user)
 	if err != nil {
 		return false, false, err
 	}
@@ -115,7 +116,7 @@ func IsUserAllowedToUpdate(ctx context.Context, pull *models.PullRequest, user *
 		return false, false, nil
 	}
 
-	baseRepoPerm, err := models.GetUserRepoPermission(ctx, pull.BaseRepo, user)
+	baseRepoPerm, err := access_model.GetUserRepoPermission(ctx, pull.BaseRepo, user)
 	if err != nil {
 		return false, false, err
 	}
diff --git a/services/repository/transfer.go b/services/repository/transfer.go
index 3feeb68f22..ae15383240 100644
--- a/services/repository/transfer.go
+++ b/services/repository/transfer.go
@@ -11,6 +11,7 @@ import (
 	"code.gitea.io/gitea/models/db"
 	"code.gitea.io/gitea/models/organization"
 	"code.gitea.io/gitea/models/perm"
+	access_model "code.gitea.io/gitea/models/perm/access"
 	repo_model "code.gitea.io/gitea/models/repo"
 	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/log"
@@ -105,7 +106,7 @@ func StartRepositoryTransfer(doer, newOwner *user_model.User, repo *repo_model.R
 	}
 
 	// In case the new owner would not have sufficient access to the repo, give access rights for read
-	hasAccess, err := models.HasAccess(newOwner.ID, repo)
+	hasAccess, err := access_model.HasAccess(db.DefaultContext, newOwner.ID, repo)
 	if err != nil {
 		return err
 	}
@@ -113,7 +114,7 @@ func StartRepositoryTransfer(doer, newOwner *user_model.User, repo *repo_model.R
 		if err := models.AddCollaborator(repo, newOwner); err != nil {
 			return err
 		}
-		if err := models.ChangeCollaborationAccessMode(repo, newOwner.ID, perm.AccessModeRead); err != nil {
+		if err := repo_model.ChangeCollaborationAccessMode(repo, newOwner.ID, perm.AccessModeRead); err != nil {
 			return err
 		}
 	}
diff --git a/services/repository/transfer_test.go b/services/repository/transfer_test.go
index 1081c76c7e..8be8c5353d 100644
--- a/services/repository/transfer_test.go
+++ b/services/repository/transfer_test.go
@@ -9,7 +9,9 @@ import (
 	"testing"
 
 	"code.gitea.io/gitea/models"
+	"code.gitea.io/gitea/models/db"
 	"code.gitea.io/gitea/models/organization"
+	access_model "code.gitea.io/gitea/models/perm/access"
 	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/models/unittest"
 	user_model "code.gitea.io/gitea/models/user"
@@ -65,13 +67,13 @@ func TestStartRepositoryTransferSetPermission(t *testing.T) {
 	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}).(*repo_model.Repository)
 	repo.Owner = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}).(*user_model.User)
 
-	hasAccess, err := models.HasAccess(recipient.ID, repo)
+	hasAccess, err := access_model.HasAccess(db.DefaultContext, recipient.ID, repo)
 	assert.NoError(t, err)
 	assert.False(t, hasAccess)
 
 	assert.NoError(t, StartRepositoryTransfer(doer, recipient, repo, nil))
 
-	hasAccess, err = models.HasAccess(recipient.ID, repo)
+	hasAccess, err = access_model.HasAccess(db.DefaultContext, recipient.ID, repo)
 	assert.NoError(t, err)
 	assert.True(t, hasAccess)