Skip to content

Commit

Permalink
Allow to set organization visibility (public, internal, private) (#1763)
Browse files Browse the repository at this point in the history
  • Loading branch information
DblK authored and lafriks committed Feb 18, 2019
1 parent ae3a913 commit 64ce159
Show file tree
Hide file tree
Showing 27 changed files with 388 additions and 28 deletions.
5 changes: 5 additions & 0 deletions custom/conf/app.ini.sample
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,11 @@ DEFAULT_KEEP_EMAIL_PRIVATE = false
; Default value for AllowCreateOrganization
; Every new user will have rights set to create organizations depending on this setting
DEFAULT_ALLOW_CREATE_ORGANIZATION = true
; Either "public", "limited" or "private", default is "public"
; Limited is for signed user only
; Private is only for member of the organization
; Public is for everyone
DEFAULT_ORG_VISIBILITY = public
; Default value for EnableDependencies
; Repositories will use dependencies by default depending on this setting
DEFAULT_ENABLE_DEPENDENCIES = true
Expand Down
9 changes: 5 additions & 4 deletions docs/content/doc/advanced/config-cheat-sheet.en-us.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
- `PATH`: **data/gitea.db**: For SQLite3 only, the database file path.
- `LOG_SQL`: **true**: Log the executed SQL.
- `DB_RETRIES`: **10**: How many ORM init / DB connect attempts allowed.
- `DB_RETRY_BACKOFF`: **3s*: time.Duration to wait before trying another ORM init / DB connect attempt, if failure occured.
- `DB_RETRY_BACKOFF`: **3s**: time.Duration to wait before trying another ORM init / DB connect attempt, if failure occured.

## Indexer (`indexer`)

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

## Webhook (`webhook`)

Expand Down
1 change: 1 addition & 0 deletions models/migrations/migrations.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// Copyright 2015 The Gogs Authors. All rights reserved.
// Copyright 2017 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

Expand Down
36 changes: 36 additions & 0 deletions models/org.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// Copyright 2014 The Gogs Authors. All rights reserved.
// Copyright 2019 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.

Expand All @@ -11,6 +12,7 @@ import (
"strings"

"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/structs"

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

// HasOrgVisible tells if the given user can see the given org
func HasOrgVisible(org *User, user *User) bool {
// Not SignedUser
if user == nil {
if org.Visibility == structs.VisibleTypePublic {
return true
}
return false
}

if user.IsAdmin {
return true
}

if org.Visibility == structs.VisibleTypePrivate && !org.IsUserPartOfOrg(user.ID) {
return false
}
return true
}

// HasOrgsVisible tells if the given user can see at least one of the orgs provided
func HasOrgsVisible(orgs []*User, user *User) bool {
if len(orgs) == 0 {
return false
}

for _, org := range orgs {
if HasOrgVisible(org, user) {
return true
}
}
return false
}

// GetOwnedOrgsByUserID returns a list of organizations are owned by given user ID.
func GetOwnedOrgsByUserID(userID int64) ([]*User, error) {
sess := x.NewSession()
Expand Down
71 changes: 71 additions & 0 deletions models/org_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ package models
import (
"testing"

"code.gitea.io/gitea/modules/structs"

"github.com/stretchr/testify/assert"
)

Expand Down Expand Up @@ -545,3 +547,72 @@ func TestAccessibleReposEnv_MirrorRepos(t *testing.T) {
testSuccess(2, []int64{5})
testSuccess(4, []int64{})
}

func TestHasOrgVisibleTypePublic(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
owner := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
user3 := AssertExistsAndLoadBean(t, &User{ID: 3}).(*User)

const newOrgName = "test-org-public"
org := &User{
Name: newOrgName,
Visibility: structs.VisibleTypePublic,
}

AssertNotExistsBean(t, &User{Name: org.Name, Type: UserTypeOrganization})
assert.NoError(t, CreateOrganization(org, owner))
org = AssertExistsAndLoadBean(t,
&User{Name: org.Name, Type: UserTypeOrganization}).(*User)
test1 := HasOrgVisible(org, owner)
test2 := HasOrgVisible(org, user3)
test3 := HasOrgVisible(org, nil)
assert.Equal(t, test1, true) // owner of org
assert.Equal(t, test2, true) // user not a part of org
assert.Equal(t, test3, true) // logged out user
}

func TestHasOrgVisibleTypeLimited(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
owner := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
user3 := AssertExistsAndLoadBean(t, &User{ID: 3}).(*User)

const newOrgName = "test-org-limited"
org := &User{
Name: newOrgName,
Visibility: structs.VisibleTypeLimited,
}

AssertNotExistsBean(t, &User{Name: org.Name, Type: UserTypeOrganization})
assert.NoError(t, CreateOrganization(org, owner))
org = AssertExistsAndLoadBean(t,
&User{Name: org.Name, Type: UserTypeOrganization}).(*User)
test1 := HasOrgVisible(org, owner)
test2 := HasOrgVisible(org, user3)
test3 := HasOrgVisible(org, nil)
assert.Equal(t, test1, true) // owner of org
assert.Equal(t, test2, true) // user not a part of org
assert.Equal(t, test3, false) // logged out user
}

func TestHasOrgVisibleTypePrivate(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
owner := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
user3 := AssertExistsAndLoadBean(t, &User{ID: 3}).(*User)

const newOrgName = "test-org-private"
org := &User{
Name: newOrgName,
Visibility: structs.VisibleTypePrivate,
}

AssertNotExistsBean(t, &User{Name: org.Name, Type: UserTypeOrganization})
assert.NoError(t, CreateOrganization(org, owner))
org = AssertExistsAndLoadBean(t,
&User{Name: org.Name, Type: UserTypeOrganization}).(*User)
test1 := HasOrgVisible(org, owner)
test2 := HasOrgVisible(org, user3)
test3 := HasOrgVisible(org, nil)
assert.Equal(t, test1, true) // owner of org
assert.Equal(t, test2, false) // user not a part of org
assert.Equal(t, test3, false) // logged out user
}
35 changes: 35 additions & 0 deletions models/repo_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ import (
"fmt"
"strings"

"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"

"github.com/go-xorm/builder"
"github.com/go-xorm/core"
)

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

if !opts.Private {
cond = cond.And(builder.Eq{"is_private": false})
accessCond := builder.Or(
builder.NotIn("owner_id", builder.Select("id").From("`user`").Where(builder.Or(builder.Eq{"visibility": structs.VisibleTypeLimited}, builder.Eq{"visibility": structs.VisibleTypePrivate}))),
builder.NotIn("owner_id", builder.Select("id").From("`user`").Where(builder.Eq{"type": UserTypeOrganization})))
cond = cond.And(accessCond)
}

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

var exprCond builder.Cond
if DbCfg.Type == core.POSTGRES {
exprCond = builder.Expr("org_user.org_id = \"user\".id")
} else if DbCfg.Type == core.MSSQL {
exprCond = builder.Expr("org_user.org_id = [user].id")
} else {
exprCond = builder.Eq{"org_user.org_id": "user.id"}
}

visibilityCond := builder.Or(
builder.In("owner_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("owner_id",
builder.Select("id").From("`user`").
Where(
builder.Or(
builder.Eq{"visibility": structs.VisibleTypePublic},
builder.Eq{"visibility": structs.VisibleTypeLimited})),
),
builder.NotIn("owner_id", builder.Select("id").From("`user`").Where(builder.Eq{"type": UserTypeOrganization})),
)
cond = cond.And(visibilityCond)

if opts.AllPublic {
accessCond = accessCond.Or(builder.Eq{"is_private": false})
}
Expand Down
64 changes: 51 additions & 13 deletions models/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,22 +25,23 @@ import (
"time"
"unicode/utf8"

"github.com/Unknwon/com"
"github.com/go-xorm/builder"
"github.com/go-xorm/xorm"
"github.com/nfnt/resize"
"golang.org/x/crypto/pbkdf2"
"golang.org/x/crypto/ssh"

"code.gitea.io/git"
api "code.gitea.io/sdk/gitea"

"code.gitea.io/gitea/modules/avatar"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/generate"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
api "code.gitea.io/sdk/gitea"

"github.com/Unknwon/com"
"github.com/go-xorm/builder"
"github.com/go-xorm/core"
"github.com/go-xorm/xorm"
"github.com/nfnt/resize"
"golang.org/x/crypto/pbkdf2"
"golang.org/x/crypto/ssh"
)

// UserType defines the user type
Expand Down Expand Up @@ -136,8 +137,9 @@ type User struct {
Description string
NumTeams int
NumMembers int
Teams []*Team `xorm:"-"`
Members []*User `xorm:"-"`
Teams []*Team `xorm:"-"`
Members []*User `xorm:"-"`
Visibility structs.VisibleType `xorm:"NOT NULL DEFAULT 0"`

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

// IsUserPartOfOrg returns true if user with userID is part of the u organisation.
func (u *User) IsUserPartOfOrg(userID int64) bool {
isMember, err := IsOrganizationMember(u.ID, userID)
if err != nil {
log.Error(4, "IsOrganizationMember: %v", err)
return false
}
return isMember
}

// IsPublicMember returns true if user public his/her membership in given organization.
func (u *User) IsPublicMember(orgID int64) bool {
isMember, err := IsPublicMembership(orgID, u.ID)
Expand Down Expand Up @@ -1341,13 +1353,18 @@ type SearchUserOptions struct {
UID int64
OrderBy SearchOrderBy
Page int
PageSize int // Can be smaller than or equal to setting.UI.ExplorePagingNum
Private bool // Include private orgs in search
OwnerID int64 // id of user for visibility calculation
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
}

func (opts *SearchUserOptions) toConds() builder.Cond {
var cond builder.Cond = builder.Eq{"type": opts.Type}

var cond = builder.NewCond()
cond = cond.And(builder.Eq{"type": opts.Type})

if len(opts.Keyword) > 0 {
lowerKeyword := strings.ToLower(opts.Keyword)
keywordCond := builder.Or(
Expand All @@ -1361,6 +1378,27 @@ func (opts *SearchUserOptions) toConds() builder.Cond {
cond = cond.And(keywordCond)
}

if !opts.Private {
// user not logged in and so they won't be allowed to see non-public orgs
cond = cond.And(builder.In("visibility", structs.VisibleTypePublic))
}

if opts.OwnerID > 0 {
var exprCond builder.Cond
if DbCfg.Type == core.MYSQL {
exprCond = builder.Expr("org_user.org_id = user.id")
} else if DbCfg.Type == core.MSSQL {
exprCond = builder.Expr("org_user.org_id = [user].id")
} else {
exprCond = builder.Expr("org_user.org_id = \"user\".id")
}
var accessCond = builder.NewCond()
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))
cond = cond.And(accessCond)
}

if opts.UID > 0 {
cond = cond.And(builder.Eq{"id": opts.UID})
}
Expand Down
6 changes: 5 additions & 1 deletion modules/auth/org.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
// Copyright 2014 The Gogs Authors. All rights reserved.
// Copyright 2019 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 auth

import (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/structs"

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

// CreateOrgForm form for creating organization
type CreateOrgForm struct {
OrgName string `binding:"Required;AlphaDashDot;MaxSize(35)" locale:"org.org_name_holder"`
OrgName string `binding:"Required;AlphaDashDot;MaxSize(35)" locale:"org.org_name_holder"`
Visibility structs.VisibleType
}

// Validate validates the fields
Expand All @@ -35,6 +38,7 @@ type UpdateOrgSettingForm struct {
Description string `binding:"MaxSize(255)"`
Website string `binding:"ValidUrl;MaxSize(255)"`
Location string `binding:"MaxSize(50)"`
Visibility structs.VisibleType
MaxRepoCreation int
}

Expand Down
Loading

0 comments on commit 64ce159

Please sign in to comment.