diff --git a/custom/conf/app.ini.sample b/custom/conf/app.ini.sample index 12e50e8cb..e8381e783 100644 --- a/custom/conf/app.ini.sample +++ b/custom/conf/app.ini.sample @@ -318,6 +318,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_VISIBILITY = public ; Default value for EnableDependencies ; Repositories will use depencies by default depending on this setting DEFAULT_ENABLE_DEPENDENCIES = true diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 48c4228fe..577ed2f12 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -194,6 +194,8 @@ var migrations = []Migration{ NewMigration("move team units to team_unit table", moveTeamUnitsToTeamUnitTable), // v70 -> v71 NewMigration("add issue_dependencies", addIssueDependencies), + // v71 -> v72 + NewMigration("add visibility for user and org", addVisibilityForUserAndOrg), } // Migrate database to current version diff --git a/models/migrations/v71.go b/models/migrations/v71.go new file mode 100644 index 000000000..48261b399 --- /dev/null +++ b/models/migrations/v71.go @@ -0,0 +1,22 @@ +// Copyright 2018 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 ( + "fmt" + + "github.com/go-xorm/xorm" +) + +func addVisibilityForUserAndOrg(x *xorm.Engine) error { + type User struct { + Visibility int `xorm:"NOT NULL DEFAULT 1"` + } + + if err := x.Sync2(new(User)); err != nil { + return fmt.Errorf("Sync2: %v", err) + } + return nil +} diff --git a/models/org.go b/models/org.go index bd5fc825b..6f3ffc9c3 100644 --- a/models/org.go +++ b/models/org.go @@ -1,4 +1,5 @@ // 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 // license that can be found in the LICENSE file. @@ -366,6 +367,42 @@ func getOwnedOrgsByUserID(sess *xorm.Session, userID int64) ([]*User, error) { Find(&orgs) } +// HasOrgVisible tell if the given user can see one of the given orgs +func HasOrgVisible(orgs []*User, user *User) bool { + if len(orgs) == 0 { + return false + } + + // Not SignedUser + if user == nil { + for _, org := range orgs { + if org.Visibility == 1 { + return true + } + } + return false + } + + if user.IsAdmin { + return true + } + for _, org := range orgs { + switch org.Visibility { + case VisibleTypePublic: + return true + case VisibleTypeLimited: + return true + case VisibleTypePrivate: + if org.IsUserOrgPartOf(user.ID) { + return true + } + default: + } + } + + return false +} + // GetOwnedOrgsByUserID returns a list of organizations are owned by given user ID. func GetOwnedOrgsByUserID(userID int64) ([]*User, error) { sess := x.NewSession() diff --git a/models/org_test.go b/models/org_test.go index c54e7a93b..9f6cbbbfd 100644 --- a/models/org_test.go +++ b/models/org_test.go @@ -544,3 +544,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: 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([]*User{org}, owner) + test2 := HasOrgVisible([]*User{org}, user3) + test3 := HasOrgVisible([]*User{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: 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([]*User{org}, owner) + test2 := HasOrgVisible([]*User{org}, user3) + test3 := HasOrgVisible([]*User{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: 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([]*User{org}, owner) + test2 := HasOrgVisible([]*User{org}, user3) + test3 := HasOrgVisible([]*User{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 +} diff --git a/models/user.go b/models/user.go index 32b9bfec9..bf6a1f345 100644 --- a/models/user.go +++ b/models/user.go @@ -1,4 +1,5 @@ // 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 // license that can be found in the LICENSE file. @@ -73,6 +74,20 @@ var ( ErrUnsupportedLoginType = errors.New("Login source is unknown") ) +// VisibleType define the visibility (Organization only) +type VisibleType int + +const ( + // VisibleTypePublic Visible for everyone + VisibleTypePublic VisibleType = iota + 1 + + // VisibleTypeLimited Visible for every connected user + VisibleTypeLimited + + // VisibleTypePrivate Visible only for organization's members + VisibleTypePrivate +) + // User represents the object of individual and member of organization. type User struct { ID int64 `xorm:"pk autoincr"` @@ -128,8 +143,9 @@ type User struct { Description string NumTeams int NumMembers int - Teams []*Team `xorm:"-"` - Members []*User `xorm:"-"` + Teams []*Team `xorm:"-"` + Members []*User `xorm:"-"` + Visibility VisibleType `xorm:"DEFAULT 1"` // Preferences DiffViewStyle string `xorm:"NOT NULL DEFAULT ''"` @@ -530,6 +546,16 @@ func (u *User) IsUserOrgOwner(orgID int64) bool { return isOwner } +// IsUserOrgPartOf returns true if user is part of the organization +func (u *User) IsUserOrgPartOf(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) diff --git a/modules/auth/org.go b/modules/auth/org.go index d6c26b633..987eb00e4 100644 --- a/modules/auth/org.go +++ b/modules/auth/org.go @@ -1,4 +1,5 @@ // 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 // license that can be found in the LICENSE file. @@ -35,6 +36,7 @@ type UpdateOrgSettingForm struct { Description string `binding:"MaxSize(255)"` Website string `binding:"ValidUrl;MaxSize(255)"` Location string `binding:"MaxSize(50)"` + Visibility models.VisibleType MaxRepoCreation int } diff --git a/modules/setting/setting.go b/modules/setting/setting.go index 396dec254..6c124867b 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -1164,6 +1164,8 @@ func NewContext() { // Service settings var Service struct { + DefaultVisibility string + DefaultVisibilityMode int ActiveCodeLives int ResetPwdCodeLives int RegisterEmailConfirm bool @@ -1193,6 +1195,12 @@ var Service struct { OpenIDBlacklist []*regexp.Regexp } +var visibilityModes = map[string]int{ + "public": 1, + "limited": 2, + "private": 3, +} + func newService() { sec := Cfg.Section("service") Service.ActiveCodeLives = sec.Key("ACTIVE_CODE_LIVE_MINUTES").MustInt(180) @@ -1216,6 +1224,8 @@ func newService() { Service.DefaultEnableDependencies = sec.Key("DEFAULT_ENABLE_DEPENDENCIES").MustBool(true) Service.DefaultAllowOnlyContributorsToTrackTime = sec.Key("DEFAULT_ALLOW_ONLY_CONTRIBUTORS_TO_TRACK_TIME").MustBool(true) Service.NoReplyAddress = sec.Key("NO_REPLY_ADDRESS").MustString("noreply.example.org") + Service.DefaultVisibility = sec.Key("DEFAULT_VISIBILITY").In("public", []string{"public", "limited", "private"}) + Service.DefaultVisibilityMode = visibilityModes[Service.DefaultVisibility] sec = Cfg.Section("openid") Service.EnableOpenIDSignIn = sec.Key("ENABLE_OPENID_SIGNIN").MustBool(!InstallLock) diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 629d84e05..b877a4d77 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -1227,6 +1227,11 @@ settings.options = Organization settings.full_name = Full Name settings.website = Website 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_setting_success = Organization settings have been updated. settings.change_orgname_prompt = Note: changing the organization name also changes the organization's URL. @@ -1526,6 +1531,7 @@ config.enable_timetracking = Enable Time Tracking config.default_enable_timetracking = Enable Time Tracking by Default config.default_allow_only_contributors_to_track_time = Let Only Contributors Track Time 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.webhook_config = Webhook Configuration diff --git a/routers/api/v1/org/org.go b/routers/api/v1/org/org.go index 29d45d2f2..5247b02e8 100644 --- a/routers/api/v1/org/org.go +++ b/routers/api/v1/org/org.go @@ -1,4 +1,5 @@ // Copyright 2015 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 // license that can be found in the LICENSE file. @@ -78,6 +79,11 @@ func Get(ctx *context.APIContext) { // responses: // "200": // "$ref": "#/responses/Organization" + canSeeOrg := models.HasOrgVisible([]*models.User{ctx.Org.Organization}, ctx.User) + if !canSeeOrg { + ctx.NotFound("HasOrgVisible", nil) + return + } ctx.JSON(200, convert.ToOrganization(ctx.Org.Organization)) } diff --git a/routers/api/v1/repo/repo.go b/routers/api/v1/repo/repo.go index 044b1e9c1..7abea25c9 100644 --- a/routers/api/v1/repo/repo.go +++ b/routers/api/v1/repo/repo.go @@ -1,4 +1,5 @@ // 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 // license that can be found in the LICENSE file. @@ -257,6 +258,12 @@ func CreateOrgRepo(ctx *context.APIContext, opt api.CreateRepoOption) { return } + canSeeOrg := models.HasOrgVisible([]*models.User{org}, ctx.User) + if !canSeeOrg { + ctx.NotFound("HasOrgVisible", nil) + return + } + if !ctx.User.IsAdmin { isOwner, err := org.IsOwnedBy(ctx.User.ID) if err != nil { diff --git a/routers/org/org.go b/routers/org/org.go index bb7540277..d13955003 100644 --- a/routers/org/org.go +++ b/routers/org/org.go @@ -1,4 +1,5 @@ // 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 // license that can be found in the LICENSE file. @@ -23,6 +24,7 @@ const ( // Create render the page for create organization func Create(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("new_org") + ctx.Data["DefaultVisibilityMode"] = setting.Service.DefaultVisibilityMode if !ctx.User.CanCreateOrganization() { ctx.ServerError("Not allowed", errors.New(ctx.Tr("org.form.create_org_not_allowed"))) return diff --git a/routers/org/setting.go b/routers/org/setting.go index 7f652c11d..38bb0ee17 100644 --- a/routers/org/setting.go +++ b/routers/org/setting.go @@ -1,4 +1,5 @@ // 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 // license that can be found in the LICENSE file. @@ -29,6 +30,7 @@ const ( func Settings(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("org.settings") ctx.Data["PageIsSettingsOptions"] = true + ctx.Data["CurrentVisibility"] = models.VisibleType(ctx.Org.Organization.Visibility) ctx.HTML(200, tplSettingsOptions) } @@ -79,6 +81,7 @@ func SettingsPost(ctx *context.Context, form auth.UpdateOrgSettingForm) { org.Description = form.Description org.Website = form.Website org.Location = form.Location + org.Visibility = form.Visibility if err := models.UpdateUser(org); err != nil { ctx.ServerError("UpdateUser", err) return diff --git a/routers/repo/view.go b/routers/repo/view.go index 4f1deeae4..d6e276b15 100644 --- a/routers/repo/view.go +++ b/routers/repo/view.go @@ -276,6 +276,12 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st // Home render repository home page func Home(ctx *context.Context) { + + canSeeOrg := models.HasOrgVisible([]*models.User{ctx.Repo.Repository.Owner}, ctx.User) + if !canSeeOrg { + ctx.NotFound("HasOrgVisible", nil) + return + } if len(ctx.Repo.Repository.Units) > 0 { var firstUnit *models.Unit for _, repoUnit := range ctx.Repo.Repository.Units { diff --git a/routers/user/home.go b/routers/user/home.go index 0c84b2498..a0142c2bb 100644 --- a/routers/user/home.go +++ b/routers/user/home.go @@ -1,4 +1,5 @@ // 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 // license that can be found in the LICENSE file. @@ -362,6 +363,13 @@ func showOrgProfile(ctx *context.Context) { } org := ctx.Org.Organization + + canSeeOrg := models.HasOrgVisible([]*models.User{org}, ctx.User) + if !canSeeOrg { + ctx.NotFound("HasOrgVisible", nil) + return + } + ctx.Data["Title"] = org.DisplayName() page := ctx.QueryInt("page") diff --git a/routers/user/profile.go b/routers/user/profile.go index fb731e715..716bed296 100644 --- a/routers/user/profile.go +++ b/routers/user/profile.go @@ -1,4 +1,5 @@ // Copyright 2015 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 // license that can be found in the LICENSE file. @@ -96,6 +97,7 @@ func Profile(ctx *context.Context) { } ctx.Data["Orgs"] = orgs + ctx.Data["HasOrgsVisible"] = models.HasOrgVisible(orgs, ctx.User) tab := ctx.Query("tab") ctx.Data["TabName"] = tab diff --git a/templates/admin/config.tmpl b/templates/admin/config.tmpl index c3d76687f..ba699fd8a 100644 --- a/templates/admin/config.tmpl +++ b/templates/admin/config.tmpl @@ -148,6 +148,9 @@
{{.i18n.Tr "admin.config.default_allow_only_contributors_to_track_time"}}
{{end}} +
{{.i18n.Tr "admin.config.default_visibility_organization"}}
+
{{.Service.DefaultVisibility}}
+
{{.i18n.Tr "admin.config.no_reply_address"}}
{{if .Service.NoReplyAddress}}{{.Service.NoReplyAddress}}{{else}}-{{end}}
{{.i18n.Tr "admin.config.default_enable_dependencies"}}
diff --git a/templates/admin/org/list.tmpl b/templates/admin/org/list.tmpl index 141628c5b..3ea7790af 100644 --- a/templates/admin/org/list.tmpl +++ b/templates/admin/org/list.tmpl @@ -29,7 +29,12 @@ {{range .Users}} {{.ID}} - {{.Name}} + + {{.Name}} + {{if eq .Visibility 3}} + + {{end}} + {{.NumTeams}} {{.NumMembers}} {{.NumRepos}} diff --git a/templates/admin/repo/list.tmpl b/templates/admin/repo/list.tmpl index f706eaa1c..9ccac3932 100644 --- a/templates/admin/repo/list.tmpl +++ b/templates/admin/repo/list.tmpl @@ -30,7 +30,12 @@ {{range .Repos}} {{.ID}} - {{.Owner.Name}} + + {{.Owner.Name}} + {{if eq .Owner.Visibility 3}} + + {{end}} + {{.Name}} {{.NumWatches}} diff --git a/templates/explore/organizations.tmpl b/templates/explore/organizations.tmpl index b977da4e4..4b30331a0 100644 --- a/templates/explore/organizations.tmpl +++ b/templates/explore/organizations.tmpl @@ -6,10 +6,16 @@
{{range .Users}} + {{if (or (eq .Visibility 1) (and ($.SignedUser) (or (eq .Visibility 2) (and (.IsUserOrgPartOf $.SignedUserID) (eq .Visibility 3)) ($.IsAdmin))))}}
- {{.Name}} {{.FullName}} + + {{.Name}} {{.FullName}} + {{if eq .Visibility 3}} + + {{end}} +
{{if .Location}} {{.Location}} @@ -22,6 +28,7 @@
+ {{end}} {{else}}
{{$.i18n.Tr "explore.org_no_results"}}
{{end}} diff --git a/templates/explore/repo_list.tmpl b/templates/explore/repo_list.tmpl index b8f4490c1..c04d8dfee 100644 --- a/templates/explore/repo_list.tmpl +++ b/templates/explore/repo_list.tmpl @@ -9,12 +9,19 @@ {{else if .IsMirror}} + {{else if .Owner}} + {{if eq .Owner.Visibility 3}} + + {{end}} {{end}} -
- {{.NumStars}} - {{.NumForks}} +
+ {{.NumStars}} + {{.NumForks}} +
+ {{if .DescriptionHTML}}

{{.DescriptionHTML}}

{{end}} +

{{$.i18n.Tr "org.repo_updated"}} {{TimeSinceUnix .UpdatedUnix $.i18n.Lang}}

{{if .DescriptionHTML}}

{{.DescriptionHTML}}

{{end}} {{if .Topics }} diff --git a/templates/org/create.tmpl b/templates/org/create.tmpl index 765ef240e..39412b16a 100644 --- a/templates/org/create.tmpl +++ b/templates/org/create.tmpl @@ -15,6 +15,28 @@ {{.i18n.Tr "org.org_name_helper"}} +
+ +
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+
+
+ +
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+ {{if .SignedUser.IsAdmin}}
diff --git a/templates/user/profile.tmpl b/templates/user/profile.tmpl index 6cac30040..927667a7d 100644 --- a/templates/user/profile.tmpl +++ b/templates/user/profile.tmpl @@ -61,10 +61,12 @@ */}} - {{if .Orgs}} + {{if and .Orgs .HasOrgsVisible}}
  • {{range .Orgs}} - + {{if (or (eq .Visibility 1) (and ($.SignedUser) (or (eq .Visibility 2) (and (.IsUserOrgPartOf $.SignedUserID) (eq .Visibility 3)) ($.IsAdmin))))}} + + {{end}} {{end}}
  • {{end}}