Allow to set organization visibility (public, internal, private) (#1763)

release/v1.15
Rémy Boulanouar 2019-02-18 17:00:27 +01:00 committed by Lauris BH
parent ae3a913122
commit 64ce159a6e
27 changed files with 388 additions and 28 deletions

View File

@ -351,6 +351,11 @@ DEFAULT_KEEP_EMAIL_PRIVATE = false
; Default value for AllowCreateOrganization ; Default value for AllowCreateOrganization
; Every new user will have rights set to create organizations depending on this setting ; Every new user will have rights set to create organizations depending on this setting
DEFAULT_ALLOW_CREATE_ORGANIZATION = true 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 ; Default value for EnableDependencies
; Repositories will use dependencies by default depending on this setting ; Repositories will use dependencies by default depending on this setting
DEFAULT_ENABLE_DEPENDENCIES = true DEFAULT_ENABLE_DEPENDENCIES = true

View File

@ -147,7 +147,7 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
- `PATH`: **data/gitea.db**: For SQLite3 only, the database file path. - `PATH`: **data/gitea.db**: For SQLite3 only, the database file path.
- `LOG_SQL`: **true**: Log the executed SQL. - `LOG_SQL`: **true**: Log the executed SQL.
- `DB_RETRIES`: **10**: How many ORM init / DB connect attempts allowed. - `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`) ## Indexer (`indexer`)
@ -203,12 +203,13 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
- `CAPTCHA_TYPE`: **image**: \[image, recaptcha\] - `CAPTCHA_TYPE`: **image**: \[image, recaptcha\]
- `RECAPTCHA_SECRET`: **""**: Go to https://www.google.com/recaptcha/admin to get a secret for 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. - `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. - `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. - `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 - `EMAIL_DOMAIN_WHITELIST`: **\<empty\>**: If non-empty, list of domain names that can only be used to register
on this instance. on this instance.
- `SHOW_REGISTRATION_BUTTON`: **! DISABLE\_REGISTRATION**: Show Registration Button - `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`) ## Webhook (`webhook`)

View File

@ -1,4 +1,5 @@
// Copyright 2015 The Gogs Authors. All rights reserved. // 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 // Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.

View File

@ -1,4 +1,5 @@
// Copyright 2014 The Gogs Authors. All rights reserved. // 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 // Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -11,6 +12,7 @@ import (
"strings" "strings"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/structs"
"github.com/Unknwon/com" "github.com/Unknwon/com"
"github.com/go-xorm/builder" "github.com/go-xorm/builder"
@ -366,6 +368,40 @@ func getOwnedOrgsByUserID(sess *xorm.Session, userID int64) ([]*User, error) {
Find(&orgs) 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. // GetOwnedOrgsByUserID returns a list of organizations are owned by given user ID.
func GetOwnedOrgsByUserID(userID int64) ([]*User, error) { func GetOwnedOrgsByUserID(userID int64) ([]*User, error) {
sess := x.NewSession() sess := x.NewSession()

View File

@ -7,6 +7,8 @@ package models
import ( import (
"testing" "testing"
"code.gitea.io/gitea/modules/structs"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -545,3 +547,72 @@ func TestAccessibleReposEnv_MirrorRepos(t *testing.T) {
testSuccess(2, []int64{5}) testSuccess(2, []int64{5})
testSuccess(4, []int64{}) 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
}

View File

@ -8,9 +8,11 @@ import (
"fmt" "fmt"
"strings" "strings"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"github.com/go-xorm/builder" "github.com/go-xorm/builder"
"github.com/go-xorm/core"
) )
// RepositoryListDefaultPageSize is the default number of repositories // RepositoryListDefaultPageSize is the default number of repositories
@ -171,6 +173,10 @@ func SearchRepositoryByName(opts *SearchRepoOptions) (RepositoryList, int64, err
if !opts.Private { if !opts.Private {
cond = cond.And(builder.Eq{"is_private": false}) 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 { if opts.OwnerID > 0 {
@ -193,6 +199,35 @@ func SearchRepositoryByName(opts *SearchRepoOptions) (RepositoryList, int64, err
accessCond = accessCond.Or(collaborateCond) 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 { if opts.AllPublic {
accessCond = accessCond.Or(builder.Eq{"is_private": false}) accessCond = accessCond.Or(builder.Eq{"is_private": false})
} }

View File

@ -25,22 +25,23 @@ import (
"time" "time"
"unicode/utf8" "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" "code.gitea.io/git"
api "code.gitea.io/sdk/gitea"
"code.gitea.io/gitea/modules/avatar" "code.gitea.io/gitea/modules/avatar"
"code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/generate" "code.gitea.io/gitea/modules/generate"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util" "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 // UserType defines the user type
@ -136,8 +137,9 @@ type User struct {
Description string Description string
NumTeams int NumTeams int
NumMembers int NumMembers int
Teams []*Team `xorm:"-"` Teams []*Team `xorm:"-"`
Members []*User `xorm:"-"` Members []*User `xorm:"-"`
Visibility structs.VisibleType `xorm:"NOT NULL DEFAULT 0"`
// Preferences // Preferences
DiffViewStyle string `xorm:"NOT NULL DEFAULT ''"` DiffViewStyle string `xorm:"NOT NULL DEFAULT ''"`
@ -526,6 +528,16 @@ func (u *User) IsUserOrgOwner(orgID int64) bool {
return isOwner 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. // IsPublicMember returns true if user public his/her membership in given organization.
func (u *User) IsPublicMember(orgID int64) bool { func (u *User) IsPublicMember(orgID int64) bool {
isMember, err := IsPublicMembership(orgID, u.ID) isMember, err := IsPublicMembership(orgID, u.ID)
@ -1341,13 +1353,18 @@ type SearchUserOptions struct {
UID int64 UID int64
OrderBy SearchOrderBy OrderBy SearchOrderBy
Page int 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 IsActive util.OptionalBool
SearchByEmail bool // Search by email as well as username/full name SearchByEmail bool // Search by email as well as username/full name
} }
func (opts *SearchUserOptions) toConds() builder.Cond { 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 { if len(opts.Keyword) > 0 {
lowerKeyword := strings.ToLower(opts.Keyword) lowerKeyword := strings.ToLower(opts.Keyword)
keywordCond := builder.Or( keywordCond := builder.Or(
@ -1361,6 +1378,27 @@ func (opts *SearchUserOptions) toConds() builder.Cond {
cond = cond.And(keywordCond) 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 { if opts.UID > 0 {
cond = cond.And(builder.Eq{"id": opts.UID}) cond = cond.And(builder.Eq{"id": opts.UID})
} }

View File

@ -1,4 +1,5 @@
// Copyright 2014 The Gogs Authors. All rights reserved. // 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 // Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -6,6 +7,7 @@ package auth
import ( import (
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/structs"
"github.com/go-macaron/binding" "github.com/go-macaron/binding"
"gopkg.in/macaron.v1" "gopkg.in/macaron.v1"
@ -20,7 +22,8 @@ import (
// CreateOrgForm form for creating organization // CreateOrgForm form for creating organization
type CreateOrgForm struct { 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 // Validate validates the fields
@ -35,6 +38,7 @@ type UpdateOrgSettingForm struct {
Description string `binding:"MaxSize(255)"` Description string `binding:"MaxSize(255)"`
Website string `binding:"ValidUrl;MaxSize(255)"` Website string `binding:"ValidUrl;MaxSize(255)"`
Location string `binding:"MaxSize(50)"` Location string `binding:"MaxSize(50)"`
Visibility structs.VisibleType
MaxRepoCreation int MaxRepoCreation int
} }

View File

@ -4,10 +4,16 @@
package setting package setting
import "regexp" import (
"regexp"
"code.gitea.io/gitea/modules/structs"
)
// Service settings // Service settings
var Service struct { var Service struct {
DefaultOrgVisibility string
DefaultOrgVisibilityMode structs.VisibleType
ActiveCodeLives int ActiveCodeLives int
ResetPwdCodeLives int ResetPwdCodeLives int
RegisterEmailConfirm bool RegisterEmailConfirm bool
@ -68,6 +74,8 @@ func newService() {
Service.NoReplyAddress = sec.Key("NO_REPLY_ADDRESS").MustString("noreply.example.org") Service.NoReplyAddress = sec.Key("NO_REPLY_ADDRESS").MustString("noreply.example.org")
Service.EnableUserHeatmap = sec.Key("ENABLE_USER_HEATMAP").MustBool(true) Service.EnableUserHeatmap = sec.Key("ENABLE_USER_HEATMAP").MustBool(true)
Service.AutoWatchNewRepos = sec.Key("AUTO_WATCH_NEW_REPOS").MustBool(true) Service.AutoWatchNewRepos = sec.Key("AUTO_WATCH_NEW_REPOS").MustBool(true)
Service.DefaultOrgVisibility = sec.Key("DEFAULT_ORG_VISIBILITY").In("public", structs.ExtractKeysFromMapString(structs.VisibilityModes))
Service.DefaultOrgVisibilityMode = structs.VisibilityModes[Service.DefaultOrgVisibility]
sec = Cfg.Section("openid") sec = Cfg.Section("openid")
Service.EnableOpenIDSignIn = sec.Key("ENABLE_OPENID_SIGNIN").MustBool(!InstallLock) Service.EnableOpenIDSignIn = sec.Key("ENABLE_OPENID_SIGNIN").MustBool(!InstallLock)

View File

@ -0,0 +1,49 @@
// 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 structs
// VisibleType defines the visibility (Organization only)
type VisibleType int
const (
// VisibleTypePublic Visible for everyone
VisibleTypePublic VisibleType = iota
// VisibleTypeLimited Visible for every connected user
VisibleTypeLimited
// VisibleTypePrivate Visible only for organization's members
VisibleTypePrivate
)
// VisibilityModes is a map of org Visibility types
var VisibilityModes = map[string]VisibleType{
"public": VisibleTypePublic,
"limited": VisibleTypeLimited,
"private": VisibleTypePrivate,
}
// IsPublic returns true if VisibleType is public
func (vt VisibleType) IsPublic() bool {
return vt == VisibleTypePublic
}
// IsLimited returns true if VisibleType is limited
func (vt VisibleType) IsLimited() bool {
return vt == VisibleTypeLimited
}
// IsPrivate returns true if VisibleType is private
func (vt VisibleType) IsPrivate() bool {
return vt == VisibleTypePrivate
}
// ExtractKeysFromMapString provides a slice of keys from map
func ExtractKeysFromMapString(in map[string]VisibleType) (keys []string) {
for k := range in {
keys = append(keys, k)
}
return
}

View File

@ -1314,6 +1314,11 @@ settings.options = Organization
settings.full_name = Full Name settings.full_name = Full Name
settings.website = Website settings.website = Website
settings.location = Location settings.location = Location
settings.visibility = Visibility
settings.visibility.public = Public
settings.visibility.limited = Limited (Visible to logged in users only)
settings.visibility.private = Private (Visible only to organization members)
settings.update_settings = Update Settings settings.update_settings = Update Settings
settings.update_setting_success = Organization settings have been updated. settings.update_setting_success = Organization settings have been updated.
settings.change_orgname_prompt = Note: changing the organization name also changes the organization's URL. settings.change_orgname_prompt = Note: changing the organization name also changes the organization's URL.
@ -1617,6 +1622,7 @@ config.enable_timetracking = Enable Time Tracking
config.default_enable_timetracking = Enable Time Tracking by Default config.default_enable_timetracking = Enable Time Tracking by Default
config.default_allow_only_contributors_to_track_time = Let Only Contributors Track Time config.default_allow_only_contributors_to_track_time = Let Only Contributors Track Time
config.no_reply_address = Hidden Email Domain config.no_reply_address = Hidden Email Domain
config.default_visibility_organization = Default visibility for new Organizations
config.default_enable_dependencies = Enable Issue Dependencies by Default config.default_enable_dependencies = Enable Issue Dependencies by Default
config.webhook_config = Webhook Configuration config.webhook_config = Webhook Configuration

View File

@ -129,6 +129,10 @@ func Get(ctx *context.APIContext) {
// responses: // responses:
// "200": // "200":
// "$ref": "#/responses/Organization" // "$ref": "#/responses/Organization"
if !models.HasOrgVisible(ctx.Org.Organization, ctx.User) {
ctx.NotFound("HasOrgVisible", nil)
return
}
ctx.JSON(200, convert.ToOrganization(ctx.Org.Organization)) ctx.JSON(200, convert.ToOrganization(ctx.Org.Organization))
} }

View File

@ -302,6 +302,11 @@ func CreateOrgRepo(ctx *context.APIContext, opt api.CreateRepoOption) {
return return
} }
if !models.HasOrgVisible(org, ctx.User) {
ctx.NotFound("HasOrgVisible", nil)
return
}
if !ctx.User.IsAdmin { if !ctx.User.IsAdmin {
isOwner, err := org.IsOwnedBy(ctx.User.ID) isOwner, err := org.IsOwnedBy(ctx.User.ID)
if err != nil { if err != nil {

View File

@ -1,4 +1,5 @@
// Copyright 2014 The Gogs Authors. All rights reserved. // 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 // Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -230,6 +231,7 @@ func ExploreUsers(ctx *context.Context) {
Type: models.UserTypeIndividual, Type: models.UserTypeIndividual,
PageSize: setting.UI.ExplorePagingNum, PageSize: setting.UI.ExplorePagingNum,
IsActive: util.OptionalBoolTrue, IsActive: util.OptionalBoolTrue,
Private: true,
}, tplExploreUsers) }, tplExploreUsers)
} }
@ -240,9 +242,16 @@ func ExploreOrganizations(ctx *context.Context) {
ctx.Data["PageIsExploreOrganizations"] = true ctx.Data["PageIsExploreOrganizations"] = true
ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled
var ownerID int64
if ctx.User != nil && !ctx.User.IsAdmin {
ownerID = ctx.User.ID
}
RenderUserSearch(ctx, &models.SearchUserOptions{ RenderUserSearch(ctx, &models.SearchUserOptions{
Type: models.UserTypeOrganization, Type: models.UserTypeOrganization,
PageSize: setting.UI.ExplorePagingNum, PageSize: setting.UI.ExplorePagingNum,
Private: ctx.User != nil,
OwnerID: ownerID,
}, tplExploreOrganizations) }, tplExploreOrganizations)
} }

View File

@ -1,4 +1,5 @@
// Copyright 2014 The Gogs Authors. All rights reserved. // Copyright 2014 The Gogs Authors. All rights reserved.
// Copyright 2018 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style // Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -23,6 +24,7 @@ const (
// Create render the page for create organization // Create render the page for create organization
func Create(ctx *context.Context) { func Create(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("new_org") ctx.Data["Title"] = ctx.Tr("new_org")
ctx.Data["DefaultOrgVisibilityMode"] = setting.Service.DefaultOrgVisibilityMode
if !ctx.User.CanCreateOrganization() { if !ctx.User.CanCreateOrganization() {
ctx.ServerError("Not allowed", errors.New(ctx.Tr("org.form.create_org_not_allowed"))) ctx.ServerError("Not allowed", errors.New(ctx.Tr("org.form.create_org_not_allowed")))
return return
@ -45,9 +47,10 @@ func CreatePost(ctx *context.Context, form auth.CreateOrgForm) {
} }
org := &models.User{ org := &models.User{
Name: form.OrgName, Name: form.OrgName,
IsActive: true, IsActive: true,
Type: models.UserTypeOrganization, Type: models.UserTypeOrganization,
Visibility: form.Visibility,
} }
if err := models.CreateOrganization(org, ctx.User); err != nil { if err := models.CreateOrganization(org, ctx.User); err != nil {

View File

@ -1,4 +1,5 @@
// Copyright 2014 The Gogs Authors. All rights reserved. // 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 // Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -13,6 +14,7 @@ import (
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs"
userSetting "code.gitea.io/gitea/routers/user/setting" userSetting "code.gitea.io/gitea/routers/user/setting"
) )
@ -29,6 +31,7 @@ const (
func Settings(ctx *context.Context) { func Settings(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("org.settings") ctx.Data["Title"] = ctx.Tr("org.settings")
ctx.Data["PageIsSettingsOptions"] = true ctx.Data["PageIsSettingsOptions"] = true
ctx.Data["CurrentVisibility"] = structs.VisibleType(ctx.Org.Organization.Visibility)
ctx.HTML(200, tplSettingsOptions) ctx.HTML(200, tplSettingsOptions)
} }
@ -79,6 +82,7 @@ func SettingsPost(ctx *context.Context, form auth.UpdateOrgSettingForm) {
org.Description = form.Description org.Description = form.Description
org.Website = form.Website org.Website = form.Website
org.Location = form.Location org.Location = form.Location
org.Visibility = form.Visibility
if err := models.UpdateUser(org); err != nil { if err := models.UpdateUser(org); err != nil {
ctx.ServerError("UpdateUser", err) ctx.ServerError("UpdateUser", err)
return return

View File

@ -303,6 +303,11 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st
// Home render repository home page // Home render repository home page
func Home(ctx *context.Context) { func Home(ctx *context.Context) {
if !models.HasOrgVisible(ctx.Repo.Repository.Owner, ctx.User) {
ctx.NotFound("HasOrgVisible", nil)
return
}
if len(ctx.Repo.Units) > 0 { if len(ctx.Repo.Units) > 0 {
var firstUnit *models.Unit var firstUnit *models.Unit
for _, repoUnit := range ctx.Repo.Units { for _, repoUnit := range ctx.Repo.Units {

View File

@ -386,6 +386,12 @@ func showOrgProfile(ctx *context.Context) {
} }
org := ctx.Org.Organization org := ctx.Org.Organization
if !models.HasOrgVisible(org, ctx.User) {
ctx.NotFound("HasOrgVisible", nil)
return
}
ctx.Data["Title"] = org.DisplayName() ctx.Data["Title"] = org.DisplayName()
var orderBy models.SearchOrderBy var orderBy models.SearchOrderBy

View File

@ -1,4 +1,5 @@
// Copyright 2015 The Gogs Authors. All rights reserved. // Copyright 2015 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 // Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -98,6 +99,7 @@ func Profile(ctx *context.Context) {
} }
ctx.Data["Orgs"] = orgs ctx.Data["Orgs"] = orgs
ctx.Data["HasOrgsVisible"] = models.HasOrgsVisible(orgs, ctx.User)
tab := ctx.Query("tab") tab := ctx.Query("tab")
ctx.Data["TabName"] = tab ctx.Data["TabName"] = tab

View File

@ -148,6 +148,9 @@
<dt>{{.i18n.Tr "admin.config.default_allow_only_contributors_to_track_time"}}</dt> <dt>{{.i18n.Tr "admin.config.default_allow_only_contributors_to_track_time"}}</dt>
<dd><i class="fa fa{{if .Service.DefaultAllowOnlyContributorsToTrackTime}}-check{{end}}-square-o"></i></dd> <dd><i class="fa fa{{if .Service.DefaultAllowOnlyContributorsToTrackTime}}-check{{end}}-square-o"></i></dd>
{{end}} {{end}}
<dt>{{.i18n.Tr "admin.config.default_visibility_organization"}}</dt>
<dd>{{.Service.DefaultOrgVisibility}}</dd>
<dt>{{.i18n.Tr "admin.config.no_reply_address"}}</dt> <dt>{{.i18n.Tr "admin.config.no_reply_address"}}</dt>
<dd>{{if .Service.NoReplyAddress}}{{.Service.NoReplyAddress}}{{else}}-{{end}}</dd> <dd>{{if .Service.NoReplyAddress}}{{.Service.NoReplyAddress}}{{else}}-{{end}}</dd>
<dt>{{.i18n.Tr "admin.config.default_enable_dependencies"}}</dt> <dt>{{.i18n.Tr "admin.config.default_enable_dependencies"}}</dt>

View File

@ -29,7 +29,12 @@
{{range .Users}} {{range .Users}}
<tr> <tr>
<td>{{.ID}}</td> <td>{{.ID}}</td>
<td><a href="{{.HomeLink}}">{{.Name}}</a></td> <td>
<a href="{{.HomeLink}}">{{.Name}}</a>
{{if .Visibility.IsPrivate}}
<span class="text gold"><i class="octicon octicon-lock"></i></span>
{{end}}
</td>
<td>{{.NumTeams}}</td> <td>{{.NumTeams}}</td>
<td>{{.NumMembers}}</td> <td>{{.NumMembers}}</td>
<td>{{.NumRepos}}</td> <td>{{.NumRepos}}</td>

View File

@ -30,7 +30,12 @@
{{range .Repos}} {{range .Repos}}
<tr> <tr>
<td>{{.ID}}</td> <td>{{.ID}}</td>
<td><a href="{{AppSubUrl}}/{{.Owner.Name}}">{{.Owner.Name}}</a></td> <td>
<a href="{{AppSubUrl}}/{{.Owner.Name}}">{{.Owner.Name}}</a>
{{if .Owner.Visibility.IsPrivate}}
<span class="text gold"><i class="octicon octicon-lock"></i></span>
{{end}}
</td>
<td><a href="{{AppSubUrl}}/{{.Owner.Name}}/{{.Name}}">{{.Name}}</a></td> <td><a href="{{AppSubUrl}}/{{.Owner.Name}}/{{.Name}}">{{.Name}}</a></td>
<td><i class="fa fa{{if .IsPrivate}}-check{{end}}-square-o"></i></td> <td><i class="fa fa{{if .IsPrivate}}-check{{end}}-square-o"></i></td>
<td>{{.NumWatches}}</td> <td>{{.NumWatches}}</td>

View File

@ -9,7 +9,12 @@
<div class="item"> <div class="item">
<img class="ui avatar image" src="{{.RelAvatarLink}}"> <img class="ui avatar image" src="{{.RelAvatarLink}}">
<div class="content"> <div class="content">
<span class="header"><a href="{{.HomeLink}}">{{.Name}}</a> {{.FullName}}</span> <span class="header">
<a href="{{.HomeLink}}">{{.Name}}</a> {{.FullName}}
{{if .Visibility.IsPrivate}}
<span class="text gold"><i class="octicon octicon-lock"></i></span>
{{end}}
</span>
<div class="description"> <div class="description">
{{if .Location}} {{if .Location}}
<i class="octicon octicon-location"></i> {{.Location}} <i class="octicon octicon-location"></i> {{.Location}}

View File

@ -12,8 +12,11 @@
<span><i class="octicon octicon-repo-forked"></i></span> <span><i class="octicon octicon-repo-forked"></i></span>
{{else if .IsMirror}} {{else if .IsMirror}}
<span><i class="octicon octicon-repo-clone"></i></span> <span><i class="octicon octicon-repo-clone"></i></span>
{{else if .Owner}}
{{if .Owner.Visibility.IsPrivate}}
<span class="text gold"><i class="octicon octicon-lock"></i></span>
{{end}}
{{end}} {{end}}
<div class="ui right metas"> <div class="ui right metas">
<span class="text grey"><i class="octicon octicon-star"></i> {{.NumStars}}</span> <span class="text grey"><i class="octicon octicon-star"></i> {{.NumStars}}</span>
<span class="text grey"><i class="octicon octicon-git-branch"></i> {{.NumForks}}</span> <span class="text grey"><i class="octicon octicon-git-branch"></i> {{.NumForks}}</span>

View File

@ -15,6 +15,28 @@
<span class="help">{{.i18n.Tr "org.org_name_helper"}}</span> <span class="help">{{.i18n.Tr "org.org_name_helper"}}</span>
</div> </div>
<div class="inline required field {{if .Err_OrgVisibility}}error{{end}}">
<label for="visibility">{{.i18n.Tr "org.settings.visibility"}}</label>
<div class="field">
<div class="ui radio checkbox">
<input class="hidden enable-system-radio" tabindex="0" name="visibility" type="radio" value="0" {{if .DefaultOrgVisibilityMode.IsPublic}}checked{{end}}/>
<label>{{.i18n.Tr "org.settings.visibility.public"}}</label>
</div>
</div>
<div class="field">
<div class="ui radio checkbox">
<input class="hidden enable-system-radio" tabindex="0" name="visibility" type="radio" value="1" {{if .DefaultOrgVisibilityMode.IsLimited}}checked{{end}}/>
<label>{{.i18n.Tr "org.settings.visibility.limited"}}</label>
</div>
</div>
<div class="field">
<div class="ui radio checkbox">
<input class="hidden enable-system-radio" tabindex="0" name="visibility" type="radio" value="2" {{if .DefaultOrgVisibilityMode.IsPrivate}}checked{{end}}/>
<label>{{.i18n.Tr "org.settings.visibility.private"}}</label>
</div>
</div>
</div>
<div class="inline field"> <div class="inline field">
<label></label> <label></label>
<button class="ui green button"> <button class="ui green button">

View File

@ -33,6 +33,29 @@
<input id="location" name="location" value="{{.Org.Location}}"> <input id="location" name="location" value="{{.Org.Location}}">
</div> </div>
<div class="ui divider"></div>
<div class="field" id="visibility_box">
<label for="visibility">{{.i18n.Tr "org.settings.visibility"}}</label>
<div class="field">
<div class="ui radio checkbox">
<input class="hidden enable-system-radio" tabindex="0" name="visibility" type="radio" value="0" {{if eq .CurrentVisibility 0}}checked{{end}}/>
<label>{{.i18n.Tr "org.settings.visibility.public"}}</label>
</div>
</div>
<div class="field">
<div class="ui radio checkbox">
<input class="hidden enable-system-radio" tabindex="0" name="visibility" type="radio" value="1" {{if eq .CurrentVisibility 1}}checked{{end}}/>
<label>{{.i18n.Tr "org.settings.visibility.limited"}}</label>
</div>
</div>
<div class="field">
<div class="ui radio checkbox">
<input class="hidden enable-system-radio" tabindex="0" name="visibility" type="radio" value="2" {{if eq .CurrentVisibility 2}}checked{{end}}/>
<label>{{.i18n.Tr "org.settings.visibility.private"}}</label>
</div>
</div>
</div>
{{if .SignedUser.IsAdmin}} {{if .SignedUser.IsAdmin}}
<div class="ui divider"></div> <div class="ui divider"></div>

View File

@ -61,10 +61,12 @@
</a> </a>
</li> </li>
*/}} */}}
{{if .Orgs}} {{if and .Orgs .HasOrgsVisible}}
<li> <li>
{{range .Orgs}} {{range .Orgs}}
<a href="{{.HomeLink}}"><img class="ui mini image poping up" src="{{.RelAvatarLink}}" data-content="{{.Name}}" data-position="top center" data-variation="tiny inverted"></a> {{if (or .Visibility.IsPublic (and ($.SignedUser) (or .Visibility.IsLimited (and (.IsUserPartOfOrg $.SignedUserID) .Visibility.IsPrivate) ($.IsAdmin))))}}
<a href="{{.HomeLink}}"><img class="ui mini image poping up" src="{{.RelAvatarLink}}" data-content="{{.Name}}" data-position="top center" data-variation="tiny inverted"></a>
{{end}}
{{end}} {{end}}
</li> </li>
{{end}} {{end}}