Skip to content

Commit 64ce159

Browse files
DblKlafriks
authored andcommitted
Allow to set organization visibility (public, internal, private) (#1763)
1 parent ae3a913 commit 64ce159

File tree

27 files changed

+388
-28
lines changed

27 files changed

+388
-28
lines changed

custom/conf/app.ini.sample

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,11 @@ DEFAULT_KEEP_EMAIL_PRIVATE = false
351351
; Default value for AllowCreateOrganization
352352
; Every new user will have rights set to create organizations depending on this setting
353353
DEFAULT_ALLOW_CREATE_ORGANIZATION = true
354+
; Either "public", "limited" or "private", default is "public"
355+
; Limited is for signed user only
356+
; Private is only for member of the organization
357+
; Public is for everyone
358+
DEFAULT_ORG_VISIBILITY = public
354359
; Default value for EnableDependencies
355360
; Repositories will use dependencies by default depending on this setting
356361
DEFAULT_ENABLE_DEPENDENCIES = true

docs/content/doc/advanced/config-cheat-sheet.en-us.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
147147
- `PATH`: **data/gitea.db**: For SQLite3 only, the database file path.
148148
- `LOG_SQL`: **true**: Log the executed SQL.
149149
- `DB_RETRIES`: **10**: How many ORM init / DB connect attempts allowed.
150-
- `DB_RETRY_BACKOFF`: **3s*: time.Duration to wait before trying another ORM init / DB connect attempt, if failure occured.
150+
- `DB_RETRY_BACKOFF`: **3s**: time.Duration to wait before trying another ORM init / DB connect attempt, if failure occured.
151151

152152
## Indexer (`indexer`)
153153

@@ -203,12 +203,13 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
203203
- `CAPTCHA_TYPE`: **image**: \[image, recaptcha\]
204204
- `RECAPTCHA_SECRET`: **""**: Go to https://www.google.com/recaptcha/admin to get a secret for recaptcha.
205205
- `RECAPTCHA_SITEKEY`: **""**: Go to https://www.google.com/recaptcha/admin to get a sitekey for recaptcha.
206-
- `DEFAULT_ENABLE_DEPENDENCIES`: **true** Enable this to have dependencies enabled by default.
207-
- `ENABLE_USER_HEATMAP`: **true** Enable this to display the heatmap on users profiles.
206+
- `DEFAULT_ENABLE_DEPENDENCIES`: **true**: Enable this to have dependencies enabled by default.
207+
- `ENABLE_USER_HEATMAP`: **true**: Enable this to display the heatmap on users profiles.
208208
- `EMAIL_DOMAIN_WHITELIST`: **\<empty\>**: If non-empty, list of domain names that can only be used to register
209209
on this instance.
210210
- `SHOW_REGISTRATION_BUTTON`: **! DISABLE\_REGISTRATION**: Show Registration Button
211-
- `AUTO_WATCH_NEW_REPOS`: **true** Enable this to let all organisation users watch new repos when they are created
211+
- `AUTO_WATCH_NEW_REPOS`: **true**: Enable this to let all organisation users watch new repos when they are created
212+
- `DEFAULT_ORG_VISIBILITY`: **public**: Set default visibility mode for organisations, either "public", "limited" or "private".
212213

213214
## Webhook (`webhook`)
214215

models/migrations/migrations.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
// Copyright 2015 The Gogs Authors. All rights reserved.
2+
// Copyright 2017 The Gitea Authors. All rights reserved.
23
// Use of this source code is governed by a MIT-style
34
// license that can be found in the LICENSE file.
45

models/org.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
// Copyright 2014 The Gogs Authors. All rights reserved.
2+
// Copyright 2019 The Gitea Authors. All rights reserved.
23
// Use of this source code is governed by a MIT-style
34
// license that can be found in the LICENSE file.
45

@@ -11,6 +12,7 @@ import (
1112
"strings"
1213

1314
"code.gitea.io/gitea/modules/log"
15+
"code.gitea.io/gitea/modules/structs"
1416

1517
"github.com/Unknwon/com"
1618
"github.com/go-xorm/builder"
@@ -366,6 +368,40 @@ func getOwnedOrgsByUserID(sess *xorm.Session, userID int64) ([]*User, error) {
366368
Find(&orgs)
367369
}
368370

371+
// HasOrgVisible tells if the given user can see the given org
372+
func HasOrgVisible(org *User, user *User) bool {
373+
// Not SignedUser
374+
if user == nil {
375+
if org.Visibility == structs.VisibleTypePublic {
376+
return true
377+
}
378+
return false
379+
}
380+
381+
if user.IsAdmin {
382+
return true
383+
}
384+
385+
if org.Visibility == structs.VisibleTypePrivate && !org.IsUserPartOfOrg(user.ID) {
386+
return false
387+
}
388+
return true
389+
}
390+
391+
// HasOrgsVisible tells if the given user can see at least one of the orgs provided
392+
func HasOrgsVisible(orgs []*User, user *User) bool {
393+
if len(orgs) == 0 {
394+
return false
395+
}
396+
397+
for _, org := range orgs {
398+
if HasOrgVisible(org, user) {
399+
return true
400+
}
401+
}
402+
return false
403+
}
404+
369405
// GetOwnedOrgsByUserID returns a list of organizations are owned by given user ID.
370406
func GetOwnedOrgsByUserID(userID int64) ([]*User, error) {
371407
sess := x.NewSession()

models/org_test.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ package models
77
import (
88
"testing"
99

10+
"code.gitea.io/gitea/modules/structs"
11+
1012
"github.com/stretchr/testify/assert"
1113
)
1214

@@ -545,3 +547,72 @@ func TestAccessibleReposEnv_MirrorRepos(t *testing.T) {
545547
testSuccess(2, []int64{5})
546548
testSuccess(4, []int64{})
547549
}
550+
551+
func TestHasOrgVisibleTypePublic(t *testing.T) {
552+
assert.NoError(t, PrepareTestDatabase())
553+
owner := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
554+
user3 := AssertExistsAndLoadBean(t, &User{ID: 3}).(*User)
555+
556+
const newOrgName = "test-org-public"
557+
org := &User{
558+
Name: newOrgName,
559+
Visibility: structs.VisibleTypePublic,
560+
}
561+
562+
AssertNotExistsBean(t, &User{Name: org.Name, Type: UserTypeOrganization})
563+
assert.NoError(t, CreateOrganization(org, owner))
564+
org = AssertExistsAndLoadBean(t,
565+
&User{Name: org.Name, Type: UserTypeOrganization}).(*User)
566+
test1 := HasOrgVisible(org, owner)
567+
test2 := HasOrgVisible(org, user3)
568+
test3 := HasOrgVisible(org, nil)
569+
assert.Equal(t, test1, true) // owner of org
570+
assert.Equal(t, test2, true) // user not a part of org
571+
assert.Equal(t, test3, true) // logged out user
572+
}
573+
574+
func TestHasOrgVisibleTypeLimited(t *testing.T) {
575+
assert.NoError(t, PrepareTestDatabase())
576+
owner := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
577+
user3 := AssertExistsAndLoadBean(t, &User{ID: 3}).(*User)
578+
579+
const newOrgName = "test-org-limited"
580+
org := &User{
581+
Name: newOrgName,
582+
Visibility: structs.VisibleTypeLimited,
583+
}
584+
585+
AssertNotExistsBean(t, &User{Name: org.Name, Type: UserTypeOrganization})
586+
assert.NoError(t, CreateOrganization(org, owner))
587+
org = AssertExistsAndLoadBean(t,
588+
&User{Name: org.Name, Type: UserTypeOrganization}).(*User)
589+
test1 := HasOrgVisible(org, owner)
590+
test2 := HasOrgVisible(org, user3)
591+
test3 := HasOrgVisible(org, nil)
592+
assert.Equal(t, test1, true) // owner of org
593+
assert.Equal(t, test2, true) // user not a part of org
594+
assert.Equal(t, test3, false) // logged out user
595+
}
596+
597+
func TestHasOrgVisibleTypePrivate(t *testing.T) {
598+
assert.NoError(t, PrepareTestDatabase())
599+
owner := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
600+
user3 := AssertExistsAndLoadBean(t, &User{ID: 3}).(*User)
601+
602+
const newOrgName = "test-org-private"
603+
org := &User{
604+
Name: newOrgName,
605+
Visibility: structs.VisibleTypePrivate,
606+
}
607+
608+
AssertNotExistsBean(t, &User{Name: org.Name, Type: UserTypeOrganization})
609+
assert.NoError(t, CreateOrganization(org, owner))
610+
org = AssertExistsAndLoadBean(t,
611+
&User{Name: org.Name, Type: UserTypeOrganization}).(*User)
612+
test1 := HasOrgVisible(org, owner)
613+
test2 := HasOrgVisible(org, user3)
614+
test3 := HasOrgVisible(org, nil)
615+
assert.Equal(t, test1, true) // owner of org
616+
assert.Equal(t, test2, false) // user not a part of org
617+
assert.Equal(t, test3, false) // logged out user
618+
}

models/repo_list.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@ import (
88
"fmt"
99
"strings"
1010

11+
"code.gitea.io/gitea/modules/structs"
1112
"code.gitea.io/gitea/modules/util"
1213

1314
"github.com/go-xorm/builder"
15+
"github.com/go-xorm/core"
1416
)
1517

1618
// RepositoryListDefaultPageSize is the default number of repositories
@@ -171,6 +173,10 @@ func SearchRepositoryByName(opts *SearchRepoOptions) (RepositoryList, int64, err
171173

172174
if !opts.Private {
173175
cond = cond.And(builder.Eq{"is_private": false})
176+
accessCond := builder.Or(
177+
builder.NotIn("owner_id", builder.Select("id").From("`user`").Where(builder.Or(builder.Eq{"visibility": structs.VisibleTypeLimited}, builder.Eq{"visibility": structs.VisibleTypePrivate}))),
178+
builder.NotIn("owner_id", builder.Select("id").From("`user`").Where(builder.Eq{"type": UserTypeOrganization})))
179+
cond = cond.And(accessCond)
174180
}
175181

176182
if opts.OwnerID > 0 {
@@ -193,6 +199,35 @@ func SearchRepositoryByName(opts *SearchRepoOptions) (RepositoryList, int64, err
193199
accessCond = accessCond.Or(collaborateCond)
194200
}
195201

202+
var exprCond builder.Cond
203+
if DbCfg.Type == core.POSTGRES {
204+
exprCond = builder.Expr("org_user.org_id = \"user\".id")
205+
} else if DbCfg.Type == core.MSSQL {
206+
exprCond = builder.Expr("org_user.org_id = [user].id")
207+
} else {
208+
exprCond = builder.Eq{"org_user.org_id": "user.id"}
209+
}
210+
211+
visibilityCond := builder.Or(
212+
builder.In("owner_id",
213+
builder.Select("org_id").From("org_user").
214+
LeftJoin("`user`", exprCond).
215+
Where(
216+
builder.And(
217+
builder.Eq{"uid": opts.OwnerID},
218+
builder.Eq{"visibility": structs.VisibleTypePrivate})),
219+
),
220+
builder.In("owner_id",
221+
builder.Select("id").From("`user`").
222+
Where(
223+
builder.Or(
224+
builder.Eq{"visibility": structs.VisibleTypePublic},
225+
builder.Eq{"visibility": structs.VisibleTypeLimited})),
226+
),
227+
builder.NotIn("owner_id", builder.Select("id").From("`user`").Where(builder.Eq{"type": UserTypeOrganization})),
228+
)
229+
cond = cond.And(visibilityCond)
230+
196231
if opts.AllPublic {
197232
accessCond = accessCond.Or(builder.Eq{"is_private": false})
198233
}

models/user.go

Lines changed: 51 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,22 +25,23 @@ import (
2525
"time"
2626
"unicode/utf8"
2727

28-
"github.com/Unknwon/com"
29-
"github.com/go-xorm/builder"
30-
"github.com/go-xorm/xorm"
31-
"github.com/nfnt/resize"
32-
"golang.org/x/crypto/pbkdf2"
33-
"golang.org/x/crypto/ssh"
34-
3528
"code.gitea.io/git"
36-
api "code.gitea.io/sdk/gitea"
37-
3829
"code.gitea.io/gitea/modules/avatar"
3930
"code.gitea.io/gitea/modules/base"
4031
"code.gitea.io/gitea/modules/generate"
4132
"code.gitea.io/gitea/modules/log"
4233
"code.gitea.io/gitea/modules/setting"
34+
"code.gitea.io/gitea/modules/structs"
4335
"code.gitea.io/gitea/modules/util"
36+
api "code.gitea.io/sdk/gitea"
37+
38+
"github.com/Unknwon/com"
39+
"github.com/go-xorm/builder"
40+
"github.com/go-xorm/core"
41+
"github.com/go-xorm/xorm"
42+
"github.com/nfnt/resize"
43+
"golang.org/x/crypto/pbkdf2"
44+
"golang.org/x/crypto/ssh"
4445
)
4546

4647
// UserType defines the user type
@@ -136,8 +137,9 @@ type User struct {
136137
Description string
137138
NumTeams int
138139
NumMembers int
139-
Teams []*Team `xorm:"-"`
140-
Members []*User `xorm:"-"`
140+
Teams []*Team `xorm:"-"`
141+
Members []*User `xorm:"-"`
142+
Visibility structs.VisibleType `xorm:"NOT NULL DEFAULT 0"`
141143

142144
// Preferences
143145
DiffViewStyle string `xorm:"NOT NULL DEFAULT ''"`
@@ -526,6 +528,16 @@ func (u *User) IsUserOrgOwner(orgID int64) bool {
526528
return isOwner
527529
}
528530

531+
// IsUserPartOfOrg returns true if user with userID is part of the u organisation.
532+
func (u *User) IsUserPartOfOrg(userID int64) bool {
533+
isMember, err := IsOrganizationMember(u.ID, userID)
534+
if err != nil {
535+
log.Error(4, "IsOrganizationMember: %v", err)
536+
return false
537+
}
538+
return isMember
539+
}
540+
529541
// IsPublicMember returns true if user public his/her membership in given organization.
530542
func (u *User) IsPublicMember(orgID int64) bool {
531543
isMember, err := IsPublicMembership(orgID, u.ID)
@@ -1341,13 +1353,18 @@ type SearchUserOptions struct {
13411353
UID int64
13421354
OrderBy SearchOrderBy
13431355
Page int
1344-
PageSize int // Can be smaller than or equal to setting.UI.ExplorePagingNum
1356+
Private bool // Include private orgs in search
1357+
OwnerID int64 // id of user for visibility calculation
1358+
PageSize int // Can be smaller than or equal to setting.UI.ExplorePagingNum
13451359
IsActive util.OptionalBool
13461360
SearchByEmail bool // Search by email as well as username/full name
13471361
}
13481362

13491363
func (opts *SearchUserOptions) toConds() builder.Cond {
1350-
var cond builder.Cond = builder.Eq{"type": opts.Type}
1364+
1365+
var cond = builder.NewCond()
1366+
cond = cond.And(builder.Eq{"type": opts.Type})
1367+
13511368
if len(opts.Keyword) > 0 {
13521369
lowerKeyword := strings.ToLower(opts.Keyword)
13531370
keywordCond := builder.Or(
@@ -1361,6 +1378,27 @@ func (opts *SearchUserOptions) toConds() builder.Cond {
13611378
cond = cond.And(keywordCond)
13621379
}
13631380

1381+
if !opts.Private {
1382+
// user not logged in and so they won't be allowed to see non-public orgs
1383+
cond = cond.And(builder.In("visibility", structs.VisibleTypePublic))
1384+
}
1385+
1386+
if opts.OwnerID > 0 {
1387+
var exprCond builder.Cond
1388+
if DbCfg.Type == core.MYSQL {
1389+
exprCond = builder.Expr("org_user.org_id = user.id")
1390+
} else if DbCfg.Type == core.MSSQL {
1391+
exprCond = builder.Expr("org_user.org_id = [user].id")
1392+
} else {
1393+
exprCond = builder.Expr("org_user.org_id = \"user\".id")
1394+
}
1395+
var accessCond = builder.NewCond()
1396+
accessCond = builder.Or(
1397+
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}))),
1398+
builder.In("visibility", structs.VisibleTypePublic, structs.VisibleTypeLimited))
1399+
cond = cond.And(accessCond)
1400+
}
1401+
13641402
if opts.UID > 0 {
13651403
cond = cond.And(builder.Eq{"id": opts.UID})
13661404
}

modules/auth/org.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
// Copyright 2014 The Gogs Authors. All rights reserved.
2+
// Copyright 2019 The Gitea Authors. All rights reserved.
23
// Use of this source code is governed by a MIT-style
34
// license that can be found in the LICENSE file.
45

56
package auth
67

78
import (
89
"code.gitea.io/gitea/models"
10+
"code.gitea.io/gitea/modules/structs"
911

1012
"github.com/go-macaron/binding"
1113
"gopkg.in/macaron.v1"
@@ -20,7 +22,8 @@ import (
2022

2123
// CreateOrgForm form for creating organization
2224
type CreateOrgForm struct {
23-
OrgName string `binding:"Required;AlphaDashDot;MaxSize(35)" locale:"org.org_name_holder"`
25+
OrgName string `binding:"Required;AlphaDashDot;MaxSize(35)" locale:"org.org_name_holder"`
26+
Visibility structs.VisibleType
2427
}
2528

2629
// Validate validates the fields
@@ -35,6 +38,7 @@ type UpdateOrgSettingForm struct {
3538
Description string `binding:"MaxSize(255)"`
3639
Website string `binding:"ValidUrl;MaxSize(255)"`
3740
Location string `binding:"MaxSize(50)"`
41+
Visibility structs.VisibleType
3842
MaxRepoCreation int
3943
}
4044

0 commit comments

Comments
 (0)