This commit is contained in:
Rémy Boulanouar 2018-07-24 21:09:38 +00:00 committed by GitHub
commit b945906506
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 297 additions and 10 deletions

View File

@ -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

View File

@ -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

22
models/migrations/v71.go Normal file
View File

@ -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
}

View File

@ -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()

View File

@ -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
}

View File

@ -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)

View File

@ -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
}

View File

@ -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)

View File

@ -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

View File

@ -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))
}

View File

@ -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 {

View File

@ -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

View File

@ -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

View File

@ -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 {

View File

@ -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")

View File

@ -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

View File

@ -148,6 +148,9 @@
<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>
{{end}}
<dt>{{.i18n.Tr "admin.config.default_visibility_organization"}}</dt>
<dd>{{.Service.DefaultVisibility}}</dd>
<dt>{{.i18n.Tr "admin.config.no_reply_address"}}</dt>
<dd>{{if .Service.NoReplyAddress}}{{.Service.NoReplyAddress}}{{else}}-{{end}}</dd>
<dt>{{.i18n.Tr "admin.config.default_enable_dependencies"}}</dt>

View File

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

View File

@ -30,7 +30,12 @@
{{range .Repos}}
<tr>
<td>{{.ID}}</td>
<td><a href="{{AppSubUrl}}/{{.Owner.Name}}">{{.Owner.Name}}</a></td>
<td>
<a href="{{AppSubUrl}}/{{.Owner.Name}}">{{.Owner.Name}}</a>
{{if eq .Owner.Visibility 3}}
<span class="text gold"><i class="octicon octicon-lock"></i></span>
{{end}}
</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>{{.NumWatches}}</td>

View File

@ -6,10 +6,16 @@
<div class="ui user list">
{{range .Users}}
{{if (or (eq .Visibility 1) (and ($.SignedUser) (or (eq .Visibility 2) (and (.IsUserOrgPartOf $.SignedUserID) (eq .Visibility 3)) ($.IsAdmin))))}}
<div class="item">
<img class="ui avatar image" src="{{.RelAvatarLink}}">
<div class="content">
<span class="header"><a href="{{.HomeLink}}">{{.Name}}</a> {{.FullName}}</span>
<span class="header">
<a href="{{.HomeLink}}">{{.Name}}</a> {{.FullName}}
{{if eq .Visibility 3}}
<span class="text gold"><i class="octicon octicon-lock"></i></span>
{{end}}
</span>
<div class="description">
{{if .Location}}
<i class="octicon octicon-location"></i> {{.Location}}
@ -22,6 +28,7 @@
</div>
</div>
</div>
{{end}}
{{else}}
<div>{{$.i18n.Tr "explore.org_no_results"}}</div>
{{end}}

View File

@ -9,12 +9,19 @@
<span><i class="octicon octicon-repo-forked"></i></span>
{{else if .IsMirror}}
<span><i class="octicon octicon-repo-clone"></i></span>
{{else if .Owner}}
{{if eq .Owner.Visibility 3}}
<span class="text gold"><i class="octicon octicon-lock"></i></span>
{{end}}
{{end}}
<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-git-branch"></i> {{.NumForks}}</span>
<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-git-branch"></i> {{.NumForks}}</span>
</div>
</div>
{{if .DescriptionHTML}}<p class="has-emoji">{{.DescriptionHTML}}</p>{{end}}
<p class="time">{{$.i18n.Tr "org.repo_updated"}} {{TimeSinceUnix .UpdatedUnix $.i18n.Lang}}</p>
</div>
{{if .DescriptionHTML}}<p class="has-emoji">{{.DescriptionHTML}}</p>{{end}}
{{if .Topics }}

View File

@ -15,6 +15,28 @@
<span class="help">{{.i18n.Tr "org.org_name_helper"}}</span>
</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="1" {{if eq .DefaultVisibilityMode 1}}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="2" {{if eq .DefaultVisibilityMode 2}}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="3" {{if eq .DefaultVisibilityMode 3}}checked{{end}}/>
<label>{{.i18n.Tr "org.settings.visibility.private"}}</label>
</div>
</div>
</div>
<div class="inline field">
<label></label>
<button class="ui green button">

View File

@ -33,6 +33,29 @@
<input id="location" name="location" value="{{.Org.Location}}">
</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="1" {{if eq .CurrentVisibility 1}}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="2" {{if eq .CurrentVisibility 2}}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="3" {{if eq .CurrentVisibility 3}}checked{{end}}/>
<label>{{.i18n.Tr "org.settings.visibility.private"}}</label>
</div>
</div>
</div>
{{if .SignedUser.IsAdmin}}
<div class="ui divider"></div>

View File

@ -61,10 +61,12 @@
</a>
</li>
*/}}
{{if .Orgs}}
{{if and .Orgs .HasOrgsVisible}}
<li>
{{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 (eq .Visibility 1) (and ($.SignedUser) (or (eq .Visibility 2) (and (.IsUserOrgPartOf $.SignedUserID) (eq .Visibility 3)) ($.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}}
</li>
{{end}}