aboutsummaryrefslogtreecommitdiffstats
path: root/models
diff options
context:
space:
mode:
authorManush Dodunekov <manush@stendahls.se>2020-01-13 19:33:46 +0200
committerAntoine GIRARD <sapk@users.noreply.github.com>2020-01-13 18:33:46 +0100
commit1751d5fcf200b7d78ec5543fa620174c69d2746a (patch)
treedfc9584c2c60ede1fcef436de02dc66d28fd9647 /models
parent0b3aaa61964faa85b8008b04487388cc362ab436 (diff)
downloadgitea-1751d5fcf200b7d78ec5543fa620174c69d2746a.tar.gz
gitea-1751d5fcf200b7d78ec5543fa620174c69d2746a.zip
Restricted users (#6274)
* Restricted users (#4334): initial implementation * Add User.IsRestricted & UI to edit it * Pass user object instead of user id to places where IsRestricted flag matters * Restricted users: maintain access rows for all referenced repos (incl public) * Take logged in user & IsRestricted flag into account in org/repo listings, searches and accesses * Add basic repo access tests for restricted users Signed-off-by: Manush Dodunekov <manush@stendahls.se> * Mention restricted users in the faq Signed-off-by: Manush Dodunekov <manush@stendahls.se> * Revert unnecessary change `.isUserPartOfOrg` -> `.IsUserPartOfOrg` Signed-off-by: Manush Dodunekov <manush@stendahls.se> * Remove unnecessary `org.IsOrganization()` call Signed-off-by: Manush Dodunekov <manush@stendahls.se> * Revert to an `int64` keyed `accessMap` * Add type `userAccess` * Add convenience func updateUserAccess() * Turn accessMap into a `map[int64]userAccess` Signed-off-by: Manush Dodunekov <manush@stendahls.se> * or even better: `map[int64]*userAccess` * updateUserAccess(): use tighter syntax as suggested by lafriks * even tighter * Avoid extra loop * Don't disclose limited orgs to unauthenticated users * Don't assume block only applies to orgs * Use an array of `VisibleType` for filtering * fix yet another thinko * Ok - no need for u * Revert "Ok - no need for u" This reverts commit 5c3e886aabd5acd997a3b35687d322439732c200. Co-authored-by: Antoine GIRARD <sapk@users.noreply.github.com> Co-authored-by: Lauris BH <lauris@nix.lv>
Diffstat (limited to 'models')
-rw-r--r--models/access.go49
-rw-r--r--models/access_test.go50
-rw-r--r--models/action.go20
-rw-r--r--models/action_test.go40
-rw-r--r--models/fixtures/access.yml14
-rw-r--r--models/fixtures/collaboration.yml8
-rw-r--r--models/fixtures/org_user.yml5
-rw-r--r--models/fixtures/team.yml2
-rw-r--r--models/fixtures/team_user.yml6
-rw-r--r--models/fixtures/user.yml17
-rw-r--r--models/lfs.go4
-rw-r--r--models/migrations/migrations.go2
-rw-r--r--models/migrations/v121.go17
-rw-r--r--models/org.go25
-rw-r--r--models/repo_list.go65
-rw-r--r--models/repo_permission.go8
-rw-r--r--models/user.go19
-rw-r--r--models/user_heatmap_test.go10
-rw-r--r--models/user_test.go4
19 files changed, 268 insertions, 97 deletions
diff --git a/models/access.go b/models/access.go
index 213efe08a6..94defbb196 100644
--- a/models/access.go
+++ b/models/access.go
@@ -71,9 +71,17 @@ type Access struct {
Mode AccessMode
}
-func accessLevel(e Engine, userID int64, repo *Repository) (AccessMode, error) {
+func accessLevel(e Engine, user *User, repo *Repository) (AccessMode, error) {
mode := AccessModeNone
- if !repo.IsPrivate {
+ var userID int64
+ restricted := false
+
+ if user != nil {
+ userID = user.ID
+ restricted = user.IsRestricted
+ }
+
+ if !restricted && !repo.IsPrivate {
mode = AccessModeRead
}
@@ -162,22 +170,37 @@ func maxAccessMode(modes ...AccessMode) AccessMode {
return max
}
+type userAccess struct {
+ User *User
+ Mode AccessMode
+}
+
+// updateUserAccess updates an access map so that user has at least mode
+func updateUserAccess(accessMap map[int64]*userAccess, user *User, mode AccessMode) {
+ if ua, ok := accessMap[user.ID]; ok {
+ ua.Mode = maxAccessMode(ua.Mode, mode)
+ } else {
+ accessMap[user.ID] = &userAccess{User: user, Mode: mode}
+ }
+}
+
// FIXME: do cross-comparison so reduce deletions and additions to the minimum?
-func (repo *Repository) refreshAccesses(e Engine, accessMap map[int64]AccessMode) (err error) {
+func (repo *Repository) refreshAccesses(e Engine, accessMap map[int64]*userAccess) (err error) {
minMode := AccessModeRead
if !repo.IsPrivate {
minMode = AccessModeWrite
}
newAccesses := make([]Access, 0, len(accessMap))
- for userID, mode := range accessMap {
- if mode < minMode {
+ for userID, ua := range accessMap {
+ if ua.Mode < minMode && !ua.User.IsRestricted {
continue
}
+
newAccesses = append(newAccesses, Access{
UserID: userID,
RepoID: repo.ID,
- Mode: mode,
+ Mode: ua.Mode,
})
}
@@ -191,13 +214,13 @@ func (repo *Repository) refreshAccesses(e Engine, accessMap map[int64]AccessMode
}
// refreshCollaboratorAccesses retrieves repository collaborations with their access modes.
-func (repo *Repository) refreshCollaboratorAccesses(e Engine, accessMap map[int64]AccessMode) error {
- collaborations, err := repo.getCollaborations(e)
+func (repo *Repository) refreshCollaboratorAccesses(e Engine, accessMap map[int64]*userAccess) error {
+ collaborators, err := repo.getCollaborators(e)
if err != nil {
return fmt.Errorf("getCollaborations: %v", err)
}
- for _, c := range collaborations {
- accessMap[c.UserID] = c.Mode
+ for _, c := range collaborators {
+ updateUserAccess(accessMap, c.User, c.Collaboration.Mode)
}
return nil
}
@@ -206,7 +229,7 @@ func (repo *Repository) refreshCollaboratorAccesses(e Engine, accessMap map[int6
// except the team whose ID is given. It is used to assign a team ID when
// remove repository from that team.
func (repo *Repository) recalculateTeamAccesses(e Engine, ignTeamID int64) (err error) {
- accessMap := make(map[int64]AccessMode, 20)
+ accessMap := make(map[int64]*userAccess, 20)
if err = repo.getOwner(e); err != nil {
return err
@@ -239,7 +262,7 @@ func (repo *Repository) recalculateTeamAccesses(e Engine, ignTeamID int64) (err
return fmt.Errorf("getMembers '%d': %v", t.ID, err)
}
for _, m := range t.Members {
- accessMap[m.ID] = maxAccessMode(accessMap[m.ID], t.Authorize)
+ updateUserAccess(accessMap, m, t.Authorize)
}
}
@@ -300,7 +323,7 @@ func (repo *Repository) recalculateAccesses(e Engine) error {
return repo.recalculateTeamAccesses(e, 0)
}
- accessMap := make(map[int64]AccessMode, 20)
+ accessMap := make(map[int64]*userAccess, 20)
if err := repo.refreshCollaboratorAccesses(e, accessMap); err != nil {
return fmt.Errorf("refreshCollaboratorAccesses: %v", err)
}
diff --git a/models/access_test.go b/models/access_test.go
index d0f0032547..103fe3a688 100644
--- a/models/access_test.go
+++ b/models/access_test.go
@@ -15,6 +15,7 @@ func TestAccessLevel(t *testing.T) {
user2 := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
user5 := AssertExistsAndLoadBean(t, &User{ID: 5}).(*User)
+ user29 := AssertExistsAndLoadBean(t, &User{ID: 29}).(*User)
// A public repository owned by User 2
repo1 := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository)
assert.False(t, repo1.IsPrivate)
@@ -22,6 +23,12 @@ func TestAccessLevel(t *testing.T) {
repo3 := AssertExistsAndLoadBean(t, &Repository{ID: 3}).(*Repository)
assert.True(t, repo3.IsPrivate)
+ // Another public repository
+ repo4 := AssertExistsAndLoadBean(t, &Repository{ID: 4}).(*Repository)
+ assert.False(t, repo4.IsPrivate)
+ // org. owned private repo
+ repo24 := AssertExistsAndLoadBean(t, &Repository{ID: 24}).(*Repository)
+
level, err := AccessLevel(user2, repo1)
assert.NoError(t, err)
assert.Equal(t, AccessModeOwner, level)
@@ -37,6 +44,21 @@ func TestAccessLevel(t *testing.T) {
level, err = AccessLevel(user5, repo3)
assert.NoError(t, err)
assert.Equal(t, AccessModeNone, level)
+
+ // restricted user has no access to a public repo
+ level, err = AccessLevel(user29, repo1)
+ assert.NoError(t, err)
+ assert.Equal(t, AccessModeNone, level)
+
+ // ... unless he's a collaborator
+ level, err = AccessLevel(user29, repo4)
+ assert.NoError(t, err)
+ assert.Equal(t, AccessModeWrite, level)
+
+ // ... or a team member
+ level, err = AccessLevel(user29, repo24)
+ assert.NoError(t, err)
+ assert.Equal(t, AccessModeRead, level)
}
func TestHasAccess(t *testing.T) {
@@ -72,6 +94,11 @@ func TestUser_GetRepositoryAccesses(t *testing.T) {
accesses, err := user1.GetRepositoryAccesses()
assert.NoError(t, err)
assert.Len(t, accesses, 0)
+
+ user29 := AssertExistsAndLoadBean(t, &User{ID: 29}).(*User)
+ accesses, err = user29.GetRepositoryAccesses()
+ assert.NoError(t, err)
+ assert.Len(t, accesses, 2)
}
func TestUser_GetAccessibleRepositories(t *testing.T) {
@@ -86,6 +113,11 @@ func TestUser_GetAccessibleRepositories(t *testing.T) {
repos, err = user2.GetAccessibleRepositories(0)
assert.NoError(t, err)
assert.Len(t, repos, 1)
+
+ user29 := AssertExistsAndLoadBean(t, &User{ID: 29}).(*User)
+ repos, err = user29.GetAccessibleRepositories(0)
+ assert.NoError(t, err)
+ assert.Len(t, repos, 2)
}
func TestRepository_RecalculateAccesses(t *testing.T) {
@@ -119,3 +151,21 @@ func TestRepository_RecalculateAccesses2(t *testing.T) {
assert.NoError(t, err)
assert.False(t, has)
}
+
+func TestRepository_RecalculateAccesses3(t *testing.T) {
+ assert.NoError(t, PrepareTestDatabase())
+ team5 := AssertExistsAndLoadBean(t, &Team{ID: 5}).(*Team)
+ user29 := AssertExistsAndLoadBean(t, &User{ID: 29}).(*User)
+
+ has, err := x.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 = x.Get(&Access{UserID: 29, RepoID: 23})
+ assert.NoError(t, err)
+ assert.True(t, has)
+}
diff --git a/models/action.go b/models/action.go
index 1754c2a353..1a6ff75603 100644
--- a/models/action.go
+++ b/models/action.go
@@ -284,11 +284,11 @@ func (a *Action) GetIssueContent() string {
// GetFeedsOptions options for retrieving feeds
type GetFeedsOptions struct {
- RequestedUser *User
- RequestingUserID int64
- IncludePrivate bool // include private actions
- OnlyPerformedBy bool // only actions performed by requested user
- IncludeDeleted bool // include deleted actions
+ RequestedUser *User // the user we want activity for
+ Actor *User // the user viewing the activity
+ IncludePrivate bool // include private actions
+ OnlyPerformedBy bool // only actions performed by requested user
+ IncludeDeleted bool // include deleted actions
}
// GetFeeds returns actions according to the provided options
@@ -296,8 +296,14 @@ func GetFeeds(opts GetFeedsOptions) ([]*Action, error) {
cond := builder.NewCond()
var repoIDs []int64
+ var actorID int64
+
+ if opts.Actor != nil {
+ actorID = opts.Actor.ID
+ }
+
if opts.RequestedUser.IsOrganization() {
- env, err := opts.RequestedUser.AccessibleReposEnv(opts.RequestingUserID)
+ env, err := opts.RequestedUser.AccessibleReposEnv(actorID)
if err != nil {
return nil, fmt.Errorf("AccessibleReposEnv: %v", err)
}
@@ -306,6 +312,8 @@ func GetFeeds(opts GetFeedsOptions) ([]*Action, error) {
}
cond = cond.And(builder.In("repo_id", repoIDs))
+ } else if opts.Actor != nil {
+ cond = cond.And(builder.In("repo_id", opts.Actor.AccessibleRepoIDsQuery()))
}
cond = cond.And(builder.Eq{"user_id": opts.RequestedUser.ID})
diff --git a/models/action_test.go b/models/action_test.go
index a4e224853c..ccdec8f532 100644
--- a/models/action_test.go
+++ b/models/action_test.go
@@ -33,11 +33,11 @@ func TestGetFeeds(t *testing.T) {
user := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
actions, err := GetFeeds(GetFeedsOptions{
- RequestedUser: user,
- RequestingUserID: user.ID,
- IncludePrivate: true,
- OnlyPerformedBy: false,
- IncludeDeleted: true,
+ RequestedUser: user,
+ Actor: user,
+ IncludePrivate: true,
+ OnlyPerformedBy: false,
+ IncludeDeleted: true,
})
assert.NoError(t, err)
if assert.Len(t, actions, 1) {
@@ -46,10 +46,10 @@ func TestGetFeeds(t *testing.T) {
}
actions, err = GetFeeds(GetFeedsOptions{
- RequestedUser: user,
- RequestingUserID: user.ID,
- IncludePrivate: false,
- OnlyPerformedBy: false,
+ RequestedUser: user,
+ Actor: user,
+ IncludePrivate: false,
+ OnlyPerformedBy: false,
})
assert.NoError(t, err)
assert.Len(t, actions, 0)
@@ -59,14 +59,14 @@ func TestGetFeeds2(t *testing.T) {
// test with an organization user
assert.NoError(t, PrepareTestDatabase())
org := AssertExistsAndLoadBean(t, &User{ID: 3}).(*User)
- const userID = 2 // user2 is an owner of the organization
+ user := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
actions, err := GetFeeds(GetFeedsOptions{
- RequestedUser: org,
- RequestingUserID: userID,
- IncludePrivate: true,
- OnlyPerformedBy: false,
- IncludeDeleted: true,
+ RequestedUser: org,
+ Actor: user,
+ IncludePrivate: true,
+ OnlyPerformedBy: false,
+ IncludeDeleted: true,
})
assert.NoError(t, err)
assert.Len(t, actions, 1)
@@ -76,11 +76,11 @@ func TestGetFeeds2(t *testing.T) {
}
actions, err = GetFeeds(GetFeedsOptions{
- RequestedUser: org,
- RequestingUserID: userID,
- IncludePrivate: false,
- OnlyPerformedBy: false,
- IncludeDeleted: true,
+ RequestedUser: org,
+ Actor: user,
+ IncludePrivate: false,
+ OnlyPerformedBy: false,
+ IncludeDeleted: true,
})
assert.NoError(t, err)
assert.Len(t, actions, 0)
diff --git a/models/fixtures/access.yml b/models/fixtures/access.yml
index af2c8a5293..811720c8e4 100644
--- a/models/fixtures/access.yml
+++ b/models/fixtures/access.yml
@@ -74,4 +74,16 @@
id: 13
user_id: 20
repo_id: 28
- mode: 4 # owner \ No newline at end of file
+ mode: 4 # owner
+
+-
+ id: 14
+ user_id: 29
+ repo_id: 4
+ mode: 2 # write (collaborator)
+
+-
+ id: 15
+ user_id: 29
+ repo_id: 24
+ mode: 1 # read
diff --git a/models/fixtures/collaboration.yml b/models/fixtures/collaboration.yml
index d32e288e4c..82d46f38f0 100644
--- a/models/fixtures/collaboration.yml
+++ b/models/fixtures/collaboration.yml
@@ -14,4 +14,10 @@
id: 3
repo_id: 40
user_id: 4
- mode: 2 # write \ No newline at end of file
+ mode: 2 # write
+
+-
+ id: 4
+ repo_id: 4
+ user_id: 29
+ mode: 2 # write
diff --git a/models/fixtures/org_user.yml b/models/fixtures/org_user.yml
index 0b6a5e60a7..a0bc4b9b43 100644
--- a/models/fixtures/org_user.yml
+++ b/models/fixtures/org_user.yml
@@ -58,3 +58,8 @@
org_id: 6
is_public: true
+-
+ id: 11
+ uid: 29
+ org_id: 17
+ is_public: true
diff --git a/models/fixtures/team.yml b/models/fixtures/team.yml
index b7e3856172..9a8b0aff76 100644
--- a/models/fixtures/team.yml
+++ b/models/fixtures/team.yml
@@ -77,7 +77,7 @@
name: review_team
authorize: 1 # read
num_repos: 1
- num_members: 1
+ num_members: 2
-
id: 10
diff --git a/models/fixtures/team_user.yml b/models/fixtures/team_user.yml
index d541156fe8..8f21164df4 100644
--- a/models/fixtures/team_user.yml
+++ b/models/fixtures/team_user.yml
@@ -81,3 +81,9 @@
org_id: 6
team_id: 13
uid: 28
+
+-
+ id: 15
+ org_id: 17
+ team_id: 9
+ uid: 29
diff --git a/models/fixtures/user.yml b/models/fixtures/user.yml
index 09a027de79..640fd65bff 100644
--- a/models/fixtures/user.yml
+++ b/models/fixtures/user.yml
@@ -275,7 +275,7 @@
avatar_email: user17@example.com
num_repos: 2
is_active: true
- num_members: 2
+ num_members: 3
num_teams: 3
-
@@ -463,3 +463,18 @@
num_following: 0
is_active: true
+-
+ id: 29
+ lower_name: user29
+ name: user29
+ full_name: User 29
+ email: user29@example.com
+ passwd: 7d93daa0d1e6f2305cc8fa496847d61dc7320bb16262f9c55dd753480207234cdd96a93194e408341971742f4701772a025a # password
+ type: 0 # individual
+ salt: ZogKvWdyEx
+ is_admin: false
+ is_restricted: true
+ avatar: avatar29
+ avatar_email: user29@example.com
+ num_repos: 0
+ is_active: true
diff --git a/models/lfs.go b/models/lfs.go
index 5f5fe2ccf4..854b715d5c 100644
--- a/models/lfs.go
+++ b/models/lfs.go
@@ -159,7 +159,7 @@ func LFSObjectAccessible(user *User, oid string) (bool, error) {
count, err := x.Count(&LFSMetaObject{Oid: oid})
return (count > 0), err
}
- cond := accessibleRepositoryCondition(user.ID)
+ cond := accessibleRepositoryCondition(user)
count, err := x.Where(cond).Join("INNER", "repository", "`lfs_meta_object`.repository_id = `repository`.id").Count(&LFSMetaObject{Oid: oid})
return (count > 0), err
}
@@ -182,7 +182,7 @@ func LFSAutoAssociate(metas []*LFSMetaObject, user *User, repoID int64) error {
cond := builder.NewCond()
if !user.IsAdmin {
cond = builder.In("`lfs_meta_object`.repository_id",
- builder.Select("`repository`.id").From("repository").Where(accessibleRepositoryCondition(user.ID)))
+ builder.Select("`repository`.id").From("repository").Where(accessibleRepositoryCondition(user)))
}
newMetas := make([]*LFSMetaObject, 0, len(metas))
if err := sess.Cols("oid").Where(cond).In("oid", oids...).GroupBy("oid").Find(&newMetas); err != nil {
diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go
index 703c168b00..6bdec1dfba 100644
--- a/models/migrations/migrations.go
+++ b/models/migrations/migrations.go
@@ -296,6 +296,8 @@ var migrations = []Migration{
NewMigration("Fix migrated repositories' git service type", fixMigratedRepositoryServiceType),
// v120 -> v121
NewMigration("Add owner_name on table repository", addOwnerNameOnRepository),
+ // v121 -> v122
+ NewMigration("add is_restricted column for users table", addIsRestricted),
}
// Migrate database to current version
diff --git a/models/migrations/v121.go b/models/migrations/v121.go
new file mode 100644
index 0000000000..c1ff7df3ad
--- /dev/null
+++ b/models/migrations/v121.go
@@ -0,0 +1,17 @@
+// 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 migrations
+
+import "xorm.io/xorm"
+
+func addIsRestricted(x *xorm.Engine) error {
+ // User see models/user.go
+ type User struct {
+ ID int64 `xorm:"pk autoincr"`
+ IsRestricted bool `xorm:"NOT NULL DEFAULT false"`
+ }
+
+ return x.Sync2(new(User))
+}
diff --git a/models/org.go b/models/org.go
index dbc71761f2..d79c0db84e 100644
--- a/models/org.go
+++ b/models/org.go
@@ -432,7 +432,7 @@ func hasOrgVisible(e Engine, org *User, user *User) bool {
return true
}
- if org.Visibility == structs.VisibleTypePrivate && !org.isUserPartOfOrg(e, user.ID) {
+ if (org.Visibility == structs.VisibleTypePrivate || user.IsRestricted) && !org.isUserPartOfOrg(e, user.ID) {
return false
}
return true
@@ -735,7 +735,7 @@ type AccessibleReposEnvironment interface {
type accessibleReposEnv struct {
org *User
- userID int64
+ user *User
teamIDs []int64
e Engine
keyword string
@@ -749,13 +749,23 @@ func (org *User) AccessibleReposEnv(userID int64) (AccessibleReposEnvironment, e
}
func (org *User) accessibleReposEnv(e Engine, userID int64) (AccessibleReposEnvironment, error) {
+ var user *User
+
+ if userID > 0 {
+ u, err := getUserByID(e, userID)
+ if err != nil {
+ return nil, err
+ }
+ user = u
+ }
+
teamIDs, err := org.getUserTeamIDs(e, userID)
if err != nil {
return nil, err
}
return &accessibleReposEnv{
org: org,
- userID: userID,
+ user: user,
teamIDs: teamIDs,
e: e,
orderBy: SearchOrderByRecentUpdated,
@@ -763,9 +773,12 @@ func (org *User) accessibleReposEnv(e Engine, userID int64) (AccessibleReposEnvi
}
func (env *accessibleReposEnv) cond() builder.Cond {
- var cond builder.Cond = builder.Eq{
- "`repository`.owner_id": env.org.ID,
- "`repository`.is_private": false,
+ var cond = builder.NewCond()
+ if env.user == nil || !env.user.IsRestricted {
+ cond = cond.Or(builder.Eq{
+ "`repository`.owner_id": env.org.ID,
+ "`repository`.is_private": false,
+ })
}
if len(env.teamIDs) > 0 {
cond = cond.Or(builder.In("team_repo.team_id", env.teamIDs))
diff --git a/models/repo_list.go b/models/repo_list.go
index 7b48834dba..45a506698a 100644
--- a/models/repo_list.go
+++ b/models/repo_list.go
@@ -111,8 +111,7 @@ func (repos MirrorRepositoryList) LoadAttributes() error {
// SearchRepoOptions holds the search options
type SearchRepoOptions struct {
- UserID int64
- UserIsAdmin bool
+ Actor *User
Keyword string
OwnerID int64
PriorityOwnerID int64
@@ -180,9 +179,9 @@ func SearchRepository(opts *SearchRepoOptions) (RepositoryList, int64, error) {
var cond = builder.NewCond()
if opts.Private {
- if !opts.UserIsAdmin && opts.UserID != 0 && opts.UserID != opts.OwnerID {
+ if opts.Actor != nil && !opts.Actor.IsAdmin && opts.Actor.ID != opts.OwnerID {
// OK we're in the context of a User
- cond = cond.And(accessibleRepositoryCondition(opts.UserID))
+ cond = cond.And(accessibleRepositoryCondition(opts.Actor))
}
} else {
// Not looking at private organisations
@@ -276,6 +275,10 @@ func SearchRepository(opts *SearchRepoOptions) (RepositoryList, int64, error) {
cond = cond.And(builder.Eq{"is_mirror": opts.Mirror == util.OptionalBoolTrue})
}
+ if opts.Actor != nil && opts.Actor.IsRestricted {
+ cond = cond.And(accessibleRepositoryCondition(opts.Actor))
+ }
+
if len(opts.OrderBy) == 0 {
opts.OrderBy = SearchOrderByAlphabetically
}
@@ -314,32 +317,43 @@ func SearchRepository(opts *SearchRepoOptions) (RepositoryList, int64, error) {
}
// accessibleRepositoryCondition takes a user a returns a condition for checking if a repository is accessible
-func accessibleRepositoryCondition(userID int64) builder.Cond {
- return builder.Or(
+func accessibleRepositoryCondition(user *User) builder.Cond {
+ var cond = builder.NewCond()
+
+ if user == nil || !user.IsRestricted {
+ orgVisibilityLimit := []structs.VisibleType{structs.VisibleTypePrivate}
+ if user == nil {
+ orgVisibilityLimit = append(orgVisibilityLimit, structs.VisibleTypeLimited)
+ }
// 1. Be able to see all non-private repositories that either:
- builder.And(
+ cond = cond.Or(builder.And(
builder.Eq{"`repository`.is_private": false},
builder.Or(
// A. Aren't in organisations __OR__
builder.NotIn("`repository`.owner_id", builder.Select("id").From("`user`").Where(builder.Eq{"type": UserTypeOrganization})),
- // B. Isn't a private organisation. (Limited is OK because we're logged in)
- builder.NotIn("`repository`.owner_id", builder.Select("id").From("`user`").Where(builder.Eq{"visibility": structs.VisibleTypePrivate}))),
- ),
+ // B. Isn't a private organisation. Limited is OK as long as we're logged in.
+ builder.NotIn("`repository`.owner_id", builder.Select("id").From("`user`").Where(builder.In("visibility", orgVisibilityLimit))))))
+ }
+
+ if user != nil {
// 2. Be able to see all repositories that we have access to
- builder.Or(
+ cond = cond.Or(builder.Or(
builder.In("`repository`.id", builder.Select("repo_id").
From("`access`").
Where(builder.And(
- builder.Eq{"user_id": userID},
+ builder.Eq{"user_id": user.ID},
builder.Gt{"mode": int(AccessModeNone)}))),
builder.In("`repository`.id", builder.Select("id").
From("`repository`").
- Where(builder.Eq{"owner_id": userID}))),
+ Where(builder.Eq{"owner_id": user.ID}))))
// 3. Be able to see all repositories that we are in a team
- builder.In("`repository`.id", builder.Select("`team_repo`.repo_id").
+ cond = cond.Or(builder.In("`repository`.id", builder.Select("`team_repo`.repo_id").
From("team_repo").
- Where(builder.Eq{"`team_user`.uid": userID}).
+ Where(builder.Eq{"`team_user`.uid": user.ID}).
Join("INNER", "team_user", "`team_user`.team_id = `team_repo`.team_id")))
+ }
+
+ return cond
}
// SearchRepositoryByName takes keyword and part of repository name to search,
@@ -349,25 +363,18 @@ func SearchRepositoryByName(opts *SearchRepoOptions) (RepositoryList, int64, err
return SearchRepository(opts)
}
-// FindUserAccessibleRepoIDs find all accessible repositories' ID by user's id
-func FindUserAccessibleRepoIDs(userID int64) ([]int64, error) {
- var accessCond builder.Cond = builder.Eq{"is_private": false}
-
- if userID > 0 {
- accessCond = accessCond.Or(
- builder.Eq{"owner_id": userID},
- builder.And(
- builder.Expr("id IN (SELECT repo_id FROM `access` WHERE access.user_id = ?)", userID),
- builder.Neq{"owner_id": userID},
- ),
- )
- }
+// AccessibleRepoIDsQuery queries accessible repository ids. Usable as a subquery wherever repo ids need to be filtered.
+func (user *User) AccessibleRepoIDsQuery() *builder.Builder {
+ return builder.Select("id").From("repository").Where(accessibleRepositoryCondition(user))
+}
+// FindUserAccessibleRepoIDs find all accessible repositories' ID by user's id
+func FindUserAccessibleRepoIDs(user *User) ([]int64, error) {
repoIDs := make([]int64, 0, 10)
if err := x.
Table("repository").
Cols("id").
- Where(accessCond).
+ Where(accessibleRepositoryCondition(user)).
Find(&repoIDs); err != nil {
return nil, fmt.Errorf("FindUserAccesibleRepoIDs: %v", err)
}
diff --git a/models/repo_permission.go b/models/repo_permission.go
index cd20224912..0b3e5b341a 100644
--- a/models/repo_permission.go
+++ b/models/repo_permission.go
@@ -202,7 +202,7 @@ func getUserRepoPermission(e Engine, repo *Repository, user *User) (perm Permiss
}
// plain user
- perm.AccessMode, err = accessLevel(e, user.ID, repo)
+ perm.AccessMode, err = accessLevel(e, user, repo)
if err != nil {
return
}
@@ -250,8 +250,8 @@ func getUserRepoPermission(e Engine, repo *Repository, user *User) (perm Permiss
}
}
- // for a public repo on an organization, user have read permission on non-team defined units.
- if !found && !repo.IsPrivate {
+ // for a public repo on an organization, a non-restricted user has read permission on non-team defined units.
+ if !found && !repo.IsPrivate && !user.IsRestricted {
if _, ok := perm.UnitsMode[u.Type]; !ok {
perm.UnitsMode[u.Type] = AccessModeRead
}
@@ -284,7 +284,7 @@ func isUserRepoAdmin(e Engine, repo *Repository, user *User) (bool, error) {
return true, nil
}
- mode, err := accessLevel(e, user.ID, repo)
+ mode, err := accessLevel(e, user, repo)
if err != nil {
return false, err
}
diff --git a/models/user.go b/models/user.go
index dc8ae7e0f8..ea1d110807 100644
--- a/models/user.go
+++ b/models/user.go
@@ -132,6 +132,7 @@ type User struct {
// Permissions
IsActive bool `xorm:"INDEX"` // Activate primary email
IsAdmin bool
+ IsRestricted bool `xorm:"NOT NULL DEFAULT false"`
AllowGitHook bool
AllowImportLocal bool // Allow migrate repository by local path
AllowCreateOrganization bool `xorm:"DEFAULT true"`
@@ -641,7 +642,7 @@ func (u *User) GetOrgRepositoryIDs(units ...UnitType) ([]int64, error) {
if err := x.Table("repository").
Cols("repository.id").
Join("INNER", "team_user", "repository.owner_id = team_user.org_id").
- Join("INNER", "team_repo", "repository.is_private != ? OR (team_user.team_id = team_repo.team_id AND repository.id = team_repo.repo_id)", true).
+ Join("INNER", "team_repo", "(? != ? and repository.is_private != ?) OR (team_user.team_id = team_repo.team_id AND repository.id = team_repo.repo_id)", true, u.IsRestricted, true).
Where("team_user.uid = ?", u.ID).
GroupBy("repository.id").Find(&ids); err != nil {
return nil, err
@@ -1470,7 +1471,7 @@ type SearchUserOptions struct {
OrderBy SearchOrderBy
Page int
Visible []structs.VisibleType
- OwnerID int64 // id of user for visibility calculation
+ Actor *User // The user doing the search
PageSize int // Can be smaller than or equal to setting.UI.ExplorePagingNum
IsActive util.OptionalBool
SearchByEmail bool // Search by email as well as username/full name
@@ -1498,7 +1499,7 @@ func (opts *SearchUserOptions) toConds() builder.Cond {
cond = cond.And(builder.In("visibility", structs.VisibleTypePublic))
}
- if opts.OwnerID > 0 {
+ if opts.Actor != nil {
var exprCond builder.Cond
if setting.Database.UseMySQL {
exprCond = builder.Expr("org_user.org_id = user.id")
@@ -1507,9 +1508,15 @@ func (opts *SearchUserOptions) toConds() builder.Cond {
} else {
exprCond = builder.Expr("org_user.org_id = \"user\".id")
}
- accessCond := builder.Or(
- builder.In("id", builder.Select("org_id").From("org_user").LeftJoin("`user`", exprCond).Where(builder.And(builder.Eq{"uid": opts.OwnerID}, builder.Eq{"visibility": structs.VisibleTypePrivate}))),
- builder.In("visibility", structs.VisibleTypePublic, structs.VisibleTypeLimited))
+ var accessCond = builder.NewCond()
+ if !opts.Actor.IsRestricted {
+ accessCond = builder.Or(
+ builder.In("id", builder.Select("org_id").From("org_user").LeftJoin("`user`", exprCond).Where(builder.And(builder.Eq{"uid": opts.Actor.ID}, builder.Eq{"visibility": structs.VisibleTypePrivate}))),
+ builder.In("visibility", structs.VisibleTypePublic, structs.VisibleTypeLimited))
+ } else {
+ // restricted users only see orgs they are a member of
+ accessCond = builder.In("id", builder.Select("org_id").From("org_user").LeftJoin("`user`", exprCond).Where(builder.And(builder.Eq{"uid": opts.Actor.ID})))
+ }
cond = cond.And(accessCond)
}
diff --git a/models/user_heatmap_test.go b/models/user_heatmap_test.go
index f882b35247..c2825d9ff0 100644
--- a/models/user_heatmap_test.go
+++ b/models/user_heatmap_test.go
@@ -30,11 +30,11 @@ func TestGetUserHeatmapDataByUser(t *testing.T) {
// get the action for comparison
actions, err := GetFeeds(GetFeedsOptions{
- RequestedUser: user,
- RequestingUserID: user.ID,
- IncludePrivate: true,
- OnlyPerformedBy: false,
- IncludeDeleted: true,
+ RequestedUser: user,
+ Actor: user,
+ IncludePrivate: true,
+ OnlyPerformedBy: false,
+ IncludeDeleted: true,
})
assert.NoError(t, err)
diff --git a/models/user_test.go b/models/user_test.go
index 95f4d5d363..2232d59963 100644
--- a/models/user_test.go
+++ b/models/user_test.go
@@ -153,13 +153,13 @@ func TestSearchUsers(t *testing.T) {
}
testUserSuccess(&SearchUserOptions{OrderBy: "id ASC", Page: 1},
- []int64{1, 2, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21, 24, 27, 28})
+ []int64{1, 2, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21, 24, 27, 28, 29})
testUserSuccess(&SearchUserOptions{Page: 1, IsActive: util.OptionalBoolFalse},
[]int64{9})
testUserSuccess(&SearchUserOptions{OrderBy: "id ASC", Page: 1, IsActive: util.OptionalBoolTrue},
- []int64{1, 2, 4, 5, 8, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21, 24, 28})
+ []int64{1, 2, 4, 5, 8, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21, 24, 28, 29})
testUserSuccess(&SearchUserOptions{Keyword: "user1", OrderBy: "id ASC", Page: 1, IsActive: util.OptionalBoolTrue},
[]int64{1, 10, 11, 12, 13, 14, 15, 16, 18})