Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add configuration to restrict allowed user visibility modes #16271

Merged
3 changes: 3 additions & 0 deletions custom/conf/app.example.ini
Original file line number Diff line number Diff line change
Expand Up @@ -656,6 +656,9 @@ PATH =
;; Public is for users visible for everyone
;DEFAULT_USER_VISIBILITY = public
;;
;; Set whitch visibibilty modes a user can have
;ALLOWED_USER_VISIBILITY_MODES = public,limited,private
;;
;; Either "public", "limited" or "private", default is "public"
;; Limited is for organizations visible only to signed users
;; Private is for organizations visible only to members of the organization
Expand Down
1 change: 1 addition & 0 deletions docs/content/doc/advanced/config-cheat-sheet.en-us.md
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,7 @@ relation to port exhaustion.
- `AUTO_WATCH_NEW_REPOS`: **true**: Enable this to let all organisation users watch new repos when they are created
- `AUTO_WATCH_ON_CHANGES`: **false**: Enable this to make users watch a repository after their first commit to it
- `DEFAULT_USER_VISIBILITY`: **public**: Set default visibility mode for users, either "public", "limited" or "private".
- `ALLOWED_USER_VISIBILITY_MODES`: **public,limited,private**: Set whitch visibibilty modes a user can have
- `DEFAULT_ORG_VISIBILITY`: **public**: Set default visibility mode for organisations, either "public", "limited" or "private".
- `DEFAULT_ORG_MEMBER_VISIBLE`: **false** True will make the membership of the users visible when added to the organisation.
- `ALLOW_ONLY_INTERNAL_REGISTRATION`: **false** Set to true to force registration only via gitea.
Expand Down
64 changes: 42 additions & 22 deletions models/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -863,26 +863,36 @@ func CreateUser(u *User, overwriteDefault ...*CreateUserOverwriteOptions) (err e
return err
}

// set system defaults
u.KeepEmailPrivate = setting.Service.DefaultKeepEmailPrivate
u.Visibility = setting.Service.DefaultUserVisibilityMode
u.AllowCreateOrganization = setting.Service.DefaultAllowCreateOrganization && !setting.Admin.DisableRegularOrgCreation
u.EmailNotificationsPreference = setting.Admin.DefaultEmailNotification
u.MaxRepoCreation = -1
u.Theme = setting.UI.DefaultTheme

// overwrite defaults if set
if len(overwriteDefault) != 0 && overwriteDefault[0] != nil {
u.Visibility = overwriteDefault[0].Visibility
}

sess := x.NewSession()
defer sess.Close()
if err = sess.Begin(); err != nil {
return err
}

isExist, err := isUserExist(sess, 0, u.Name)
if err != nil {
return err
} else if isExist {
return ErrUserAlreadyExist{u.Name}
}
// validate data

if err = deleteUserRedirect(sess, u.Name); err != nil {
if err := validateUser(u); err != nil {
return err
}

u.Email = strings.ToLower(u.Email)
if err = ValidateEmail(u.Email); err != nil {
isExist, err := isUserExist(sess, 0, u.Name)
if err != nil {
return err
} else if isExist {
return ErrUserAlreadyExist{u.Name}
}

isExist, err = isEmailUsed(sess, u.Email)
Expand All @@ -892,6 +902,8 @@ func CreateUser(u *User, overwriteDefault ...*CreateUserOverwriteOptions) (err e
return ErrEmailAlreadyUsed{u.Email}
}

// prepare for database

u.LowerName = strings.ToLower(u.Name)
u.AvatarEmail = u.Email
if u.Rands, err = GetUserSalt(); err != nil {
Expand All @@ -901,16 +913,10 @@ func CreateUser(u *User, overwriteDefault ...*CreateUserOverwriteOptions) (err e
return err
}

// set system defaults
u.KeepEmailPrivate = setting.Service.DefaultKeepEmailPrivate
u.Visibility = setting.Service.DefaultUserVisibilityMode
u.AllowCreateOrganization = setting.Service.DefaultAllowCreateOrganization && !setting.Admin.DisableRegularOrgCreation
u.EmailNotificationsPreference = setting.Admin.DefaultEmailNotification
u.MaxRepoCreation = -1
u.Theme = setting.UI.DefaultTheme
// overwrite defaults if set
if len(overwriteDefault) != 0 && overwriteDefault[0] != nil {
u.Visibility = overwriteDefault[0].Visibility
// save changes to database

if err = deleteUserRedirect(sess, u.Name); err != nil {
return err
}

if _, err = sess.Insert(u); err != nil {
Expand Down Expand Up @@ -1056,12 +1062,22 @@ func checkDupEmail(e Engine, u *User) error {
return nil
}

func updateUser(e Engine, u *User) (err error) {
// validateUser check if user is valide to insert / update into database
func validateUser(u *User) error {
if !setting.Service.AllowedUserVisibilityModesSlice.IsAllowedVisibility(u.Visibility) {
return fmt.Errorf("visibility Mode not allowed: %s", u.Visibility.String())
}

u.Email = strings.ToLower(u.Email)
if err = ValidateEmail(u.Email); err != nil {
return ValidateEmail(u.Email)
}

func updateUser(e Engine, u *User) error {
if err := validateUser(u); err != nil {
return err
}
_, err = e.ID(u.ID).AllCols().Update(u)

_, err := e.ID(u.ID).AllCols().Update(u)
return err
}

Expand All @@ -1076,6 +1092,10 @@ func UpdateUserCols(u *User, cols ...string) error {
}

func updateUserCols(e Engine, u *User, cols ...string) error {
if err := validateUser(u); err != nil {
return err
}

_, err := e.ID(u.ID).Cols(cols...).Update(u)
return err
}
Expand Down
22 changes: 22 additions & 0 deletions models/user_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"testing"

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

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -189,6 +190,7 @@ func TestDeleteUser(t *testing.T) {

func TestEmailNotificationPreferences(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())

for _, test := range []struct {
expected string
userID int64
Expand Down Expand Up @@ -467,3 +469,23 @@ ssh-dss AAAAB3NzaC1kc3MAAACBAOChCC7lf6Uo9n7BmZ6M8St19PZf4Tn59NriyboW2x/DZuYAz3ib
}
}
}

func TestUpdateUser(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
user := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)

user.KeepActivityPrivate = true
assert.NoError(t, UpdateUser(user))
user = AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
assert.True(t, user.KeepActivityPrivate)

setting.Service.AllowedUserVisibilityModesSlice = []bool{true, false, false}
user.KeepActivityPrivate = false
user.Visibility = structs.VisibleTypePrivate
assert.Error(t, UpdateUser(user))
user = AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
assert.True(t, user.KeepActivityPrivate)

user.Email = "no mail@mail.org"
assert.Error(t, UpdateUser(user))
}
34 changes: 33 additions & 1 deletion modules/setting/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@ import (
)

// Service settings
var Service struct {
var Service = struct {
DefaultUserVisibility string
DefaultUserVisibilityMode structs.VisibleType
AllowedUserVisibilityModes []string
AllowedUserVisibilityModesSlice AllowedVisibility `ini:"-"`
DefaultOrgVisibility string
DefaultOrgVisibilityMode structs.VisibleType
ActiveCodeLives int
Expand Down Expand Up @@ -71,6 +73,29 @@ var Service struct {
RequireSigninView bool `ini:"REQUIRE_SIGNIN_VIEW"`
DisableUsersPage bool `ini:"DISABLE_USERS_PAGE"`
} `ini:"service.explore"`
}{
AllowedUserVisibilityModesSlice: []bool{true, true, true},
}
6543 marked this conversation as resolved.
Show resolved Hide resolved

// AllowedVisibility store in a 3 item bool array what is allowed
type AllowedVisibility []bool

// IsAllowedVisibility check if a AllowedVisibility allow a specific VisibleType
func (a AllowedVisibility) IsAllowedVisibility(t structs.VisibleType) bool {
if int(t) >= len(a) {
return false
}
return a[t]
}

// ToVisibleTypeSlice convert a AllowedVisibility into a VisibleType slice
func (a AllowedVisibility) ToVisibleTypeSlice() (result []structs.VisibleType) {
for i, v := range a {
if v {
result = append(result, structs.VisibleType(i))
}
}
return
}

func newService() {
Expand Down Expand Up @@ -122,6 +147,13 @@ func newService() {
Service.AutoWatchOnChanges = sec.Key("AUTO_WATCH_ON_CHANGES").MustBool(false)
Service.DefaultUserVisibility = sec.Key("DEFAULT_USER_VISIBILITY").In("public", structs.ExtractKeysFromMapString(structs.VisibilityModes))
Service.DefaultUserVisibilityMode = structs.VisibilityModes[Service.DefaultUserVisibility]
Service.AllowedUserVisibilityModes = sec.Key("ALLOWED_USER_VISIBILITY_MODES").Strings(",")
if len(Service.AllowedUserVisibilityModes) != 0 {
Service.AllowedUserVisibilityModesSlice = []bool{false, false, false}
for _, sMode := range Service.AllowedUserVisibilityModes {
Service.AllowedUserVisibilityModesSlice[structs.VisibilityModes[sMode]] = true
}
}
Service.DefaultOrgVisibility = sec.Key("DEFAULT_ORG_VISIBILITY").In("public", structs.ExtractKeysFromMapString(structs.VisibilityModes))
Service.DefaultOrgVisibilityMode = structs.VisibilityModes[Service.DefaultOrgVisibility]
Service.DefaultOrgMemberVisible = sec.Key("DEFAULT_ORG_MEMBER_VISIBLE").MustBool()
Expand Down
2 changes: 2 additions & 0 deletions routers/web/admin/users.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ func NewUser(ctx *context.Context) {
ctx.Data["PageIsAdmin"] = true
ctx.Data["PageIsAdminUsers"] = true
ctx.Data["DefaultUserVisibilityMode"] = setting.Service.DefaultUserVisibilityMode
ctx.Data["AllowedUserVisibilityModes"] = setting.Service.AllowedUserVisibilityModesSlice.ToVisibleTypeSlice()

ctx.Data["login_type"] = "0-0"

Expand Down Expand Up @@ -211,6 +212,7 @@ func EditUser(ctx *context.Context) {
ctx.Data["PageIsAdminUsers"] = true
ctx.Data["DisableRegularOrgCreation"] = setting.Admin.DisableRegularOrgCreation
ctx.Data["DisableMigrations"] = setting.Repository.DisableMigrations
ctx.Data["AllowedUserVisibilityModes"] = setting.Service.AllowedUserVisibilityModesSlice.ToVisibleTypeSlice()

prepareUserInfo(ctx)
if ctx.Written() {
Expand Down
4 changes: 0 additions & 4 deletions routers/web/admin/users_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ func TestNewUserPost_MustChangePassword(t *testing.T) {
}

func TestNewUserPost_MustChangePasswordFalse(t *testing.T) {

models.PrepareTestEnv(t)
ctx := test.MockContext(t, "admin/users/new")

Expand Down Expand Up @@ -94,7 +93,6 @@ func TestNewUserPost_MustChangePasswordFalse(t *testing.T) {
}

func TestNewUserPost_InvalidEmail(t *testing.T) {

models.PrepareTestEnv(t)
ctx := test.MockContext(t, "admin/users/new")

Expand Down Expand Up @@ -125,7 +123,6 @@ func TestNewUserPost_InvalidEmail(t *testing.T) {
}

func TestNewUserPost_VisiblityDefaultPublic(t *testing.T) {

models.PrepareTestEnv(t)
ctx := test.MockContext(t, "admin/users/new")

Expand Down Expand Up @@ -164,7 +161,6 @@ func TestNewUserPost_VisiblityDefaultPublic(t *testing.T) {
}

func TestNewUserPost_VisibilityPrivate(t *testing.T) {

models.PrepareTestEnv(t)
ctx := test.MockContext(t, "admin/users/new")

Expand Down
1 change: 1 addition & 0 deletions routers/web/user/setting/profile.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const (
func Profile(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("settings")
ctx.Data["PageIsSettingsProfile"] = true
ctx.Data["AllowedUserVisibilityModes"] = setting.Service.AllowedUserVisibilityModesSlice.ToVisibleTypeSlice()

ctx.HTML(http.StatusOK, tplSettingsProfile)
}
Expand Down
30 changes: 15 additions & 15 deletions templates/admin/user/edit.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -32,25 +32,25 @@
<div class="inline field {{if .Err_Visibility}}error{{end}}">
<span class="inline required field"><label for="visibility">{{.i18n.Tr "settings.visibility"}}</label></span>
<div class="ui selection type dropdown">
{{if .User.Visibility.IsPublic}}
<input type="hidden" id="visibility" name="visibility" value="0">
{{end}}
{{if .User.Visibility.IsLimited}}
<input type="hidden" id="visibility" name="visibility" value="1">
{{end}}
{{if .User.Visibility.IsPrivate}}
<input type="hidden" id="visibility" name="visibility" value="2">
{{end}}
{{if .User.Visibility.IsPublic}}<input type="hidden" id="visibility" name="visibility" value="0">{{end}}
{{if .User.Visibility.IsLimited}}<input type="hidden" id="visibility" name="visibility" value="1">{{end}}
{{if .User.Visibility.IsPrivate}}<input type="hidden" id="visibility" name="visibility" value="2">{{end}}
<div class="text">
{{if .User.Visibility.IsPublic}}{{.i18n.Tr "settings.visibility.public"}}{{end}}
{{if .User.Visibility.IsLimited}}{{.i18n.Tr "settings.visibility.limited"}}{{end}}
{{if .User.Visibility.IsPrivate}}{{.i18n.Tr "settings.visibility.private"}}{{end}}
{{if .User.Visibility.IsPublic}}{{.i18n.Tr "settings.visibility.public"}}{{end}}
{{if .User.Visibility.IsLimited}}{{.i18n.Tr "settings.visibility.limited"}}{{end}}
{{if .User.Visibility.IsPrivate}}{{.i18n.Tr "settings.visibility.private"}}{{end}}
</div>
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
<div class="menu">
<div class="item poping up" data-content="{{.i18n.Tr "settings.visibility.public_tooltip"}}" data-value="0">{{.i18n.Tr "settings.visibility.public"}}</div>
<div class="item poping up" data-content="{{.i18n.Tr "settings.visibility.limited_tooltip"}}" data-value="1">{{.i18n.Tr "settings.visibility.limited"}}</div>
<div class="item poping up" data-content="{{.i18n.Tr "settings.visibility.private_tooltip"}}" data-value="2">{{.i18n.Tr "settings.visibility.private"}}</div>
{{range $mode := .AllowedUserVisibilityModes}}
{{if $mode.IsPublic}}
<div class="item poping up" data-content="{{$.i18n.Tr "settings.visibility.public_tooltip"}}" data-value="0">{{$.i18n.Tr "settings.visibility.public"}}</div>
{{else if $mode.IsLimited}}
<div class="item poping up" data-content="{{$.i18n.Tr "settings.visibility.limited_tooltip"}}" data-value="1">{{$.i18n.Tr "settings.visibility.limited"}}</div>
{{else if $mode.IsPrivate}}
<div class="item poping up" data-content="{{$.i18n.Tr "settings.visibility.private_tooltip"}}" data-value="2">{{$.i18n.Tr "settings.visibility.private"}}</div>
{{end}}
{{end}}
</div>
</div>
</div>
Expand Down
18 changes: 12 additions & 6 deletions templates/admin/user/new.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,21 @@
<div class="ui selection type dropdown">
<input type="hidden" id="visibility" name="visibility" value="{{.visibility}}">
<div class="text">
{{if .DefaultUserVisibilityMode.IsPublic}}{{.i18n.Tr "settings.visibility.public"}}{{end}}
{{if .DefaultUserVisibilityMode.IsLimited}}{{.i18n.Tr "settings.visibility.limited"}}{{end}}
{{if .DefaultUserVisibilityMode.IsPrivate}}{{.i18n.Tr "settings.visibility.private"}}{{end}}
{{if .DefaultUserVisibilityMode.IsPublic}}{{.i18n.Tr "settings.visibility.public"}}{{end}}
{{if .DefaultUserVisibilityMode.IsLimited}}{{.i18n.Tr "settings.visibility.limited"}}{{end}}
{{if .DefaultUserVisibilityMode.IsPrivate}}{{.i18n.Tr "settings.visibility.private"}}{{end}}
</div>
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
<div class="menu">
<div class="item poping up" data-content="{{.i18n.Tr "settings.visibility.public_tooltip"}}" data-value="0">{{.i18n.Tr "settings.visibility.public"}}</div>
<div class="item poping up" data-content="{{.i18n.Tr "settings.visibility.limited_tooltip"}}" data-value="1">{{.i18n.Tr "settings.visibility.limited"}}</div>
<div class="item poping up" data-content="{{.i18n.Tr "settings.visibility.private_tooltip"}}" data-value="2">{{.i18n.Tr "settings.visibility.private"}}</div>
{{range $mode := .AllowedUserVisibilityModes}}
{{if $mode.IsPublic}}
<div class="item poping up" data-content="{{$.i18n.Tr "settings.visibility.public_tooltip"}}" data-value="0">{{$.i18n.Tr "settings.visibility.public"}}</div>
{{else if $mode.IsLimited}}
<div class="item poping up" data-content="{{$.i18n.Tr "settings.visibility.limited_tooltip"}}" data-value="1">{{$.i18n.Tr "settings.visibility.limited"}}</div>
{{else if $mode.IsPrivate}}
<div class="item poping up" data-content="{{$.i18n.Tr "settings.visibility.private_tooltip"}}" data-value="2">{{$.i18n.Tr "settings.visibility.private"}}</div>
{{end}}
{{end}}
</div>
</div>
</div>
Expand Down
30 changes: 15 additions & 15 deletions templates/user/settings/profile.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -71,25 +71,25 @@
<div class="inline field {{if .Err_Visibility}}error{{end}}">
<span class="inline required field"><label for="visibility">{{.i18n.Tr "settings.visibility"}}</label></span>
<div class="ui selection type dropdown">
{{if .SignedUser.Visibility.IsPublic}}
<input type="hidden" id="visibility" name="visibility" value="0">
{{end}}
{{if .SignedUser.Visibility.IsLimited}}
<input type="hidden" id="visibility" name="visibility" value="1">
{{end}}
{{if .SignedUser.Visibility.IsPrivate}}
<input type="hidden" id="visibility" name="visibility" value="2">
{{end}}
{{if .SignedUser.Visibility.IsPublic}}<input type="hidden" id="visibility" name="visibility" value="0">{{end}}
{{if .SignedUser.Visibility.IsLimited}}<input type="hidden" id="visibility" name="visibility" value="1">{{end}}
{{if .SignedUser.Visibility.IsPrivate}}<input type="hidden" id="visibility" name="visibility" value="2">{{end}}
<div class="text">
{{if .SignedUser.Visibility.IsPublic}}{{.i18n.Tr "settings.visibility.public"}}{{end}}
{{if .SignedUser.Visibility.IsLimited}}{{.i18n.Tr "settings.visibility.limited"}}{{end}}
{{if .SignedUser.Visibility.IsPrivate}}{{.i18n.Tr "settings.visibility.private"}}{{end}}
{{if .SignedUser.Visibility.IsPublic}}{{.i18n.Tr "settings.visibility.public"}}{{end}}
{{if .SignedUser.Visibility.IsLimited}}{{.i18n.Tr "settings.visibility.limited"}}{{end}}
{{if .SignedUser.Visibility.IsPrivate}}{{.i18n.Tr "settings.visibility.private"}}{{end}}
</div>
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
<div class="menu">
<div class="item poping up" data-content="{{.i18n.Tr "settings.visibility.public_tooltip"}}" data-value="0">{{.i18n.Tr "settings.visibility.public"}}</div>
<div class="item poping up" data-content="{{.i18n.Tr "settings.visibility.limited_tooltip"}}" data-value="1">{{.i18n.Tr "settings.visibility.limited"}}</div>
<div class="item poping up" data-content="{{.i18n.Tr "settings.visibility.private_tooltip"}}" data-value="2">{{.i18n.Tr "settings.visibility.private"}}</div>
{{range $mode := .AllowedUserVisibilityModes}}
{{if $mode.IsPublic}}
<div class="item poping up" data-content="{{$.i18n.Tr "settings.visibility.public_tooltip"}}" data-value="0">{{$.i18n.Tr "settings.visibility.public"}}</div>
{{else if $mode.IsLimited}}
<div class="item poping up" data-content="{{$.i18n.Tr "settings.visibility.limited_tooltip"}}" data-value="1">{{$.i18n.Tr "settings.visibility.limited"}}</div>
{{else if $mode.IsPrivate}}
<div class="item poping up" data-content="{{$.i18n.Tr "settings.visibility.private_tooltip"}}" data-value="2">{{$.i18n.Tr "settings.visibility.private"}}</div>
{{end}}
{{end}}
</div>
</div>
</div>
Expand Down