From 0b27b93728fd3cf2ecc82ac6a2b5859270543ef2 Mon Sep 17 00:00:00 2001
From: 6543 <6543@obermui.de>
Date: Sun, 27 Jun 2021 20:47:35 +0200
Subject: [PATCH] Make allowed Visiblity modes configurable for Users (#16271)

Now that #16069 is merged, some sites may wish to enforce that users are all public, limited or private, and/or disallow users from becoming private.

This PR adds functionality and settings to constrain a user's ability to change their visibility.

Co-authored-by: zeripath <art27@cantab.net>
---
 custom/conf/app.example.ini                   |  3 +
 .../doc/advanced/config-cheat-sheet.en-us.md  |  1 +
 models/user.go                                | 64 ++++++++++++-------
 models/user_test.go                           | 22 +++++++
 modules/setting/service.go                    | 34 +++++++++-
 routers/web/admin/users.go                    |  2 +
 routers/web/admin/users_test.go               |  4 --
 routers/web/user/setting/profile.go           |  1 +
 templates/admin/user/edit.tmpl                | 30 ++++-----
 templates/admin/user/new.tmpl                 | 18 ++++--
 templates/user/settings/profile.tmpl          | 30 ++++-----
 11 files changed, 146 insertions(+), 63 deletions(-)

diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini
index e7fe9206ed..33ff7a62c5 100644
--- a/custom/conf/app.example.ini
+++ b/custom/conf/app.example.ini
@@ -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
diff --git a/docs/content/doc/advanced/config-cheat-sheet.en-us.md b/docs/content/doc/advanced/config-cheat-sheet.en-us.md
index 21359dcab1..d1d47bc893 100644
--- a/docs/content/doc/advanced/config-cheat-sheet.en-us.md
+++ b/docs/content/doc/advanced/config-cheat-sheet.en-us.md
@@ -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.
diff --git a/models/user.go b/models/user.go
index 221c840a7f..47d24aefd6 100644
--- a/models/user.go
+++ b/models/user.go
@@ -863,12 +863,31 @@ 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
 	}
 
+	// validate data
+
+	if err := validateUser(u); err != nil {
+		return err
+	}
+
 	isExist, err := isUserExist(sess, 0, u.Name)
 	if err != nil {
 		return err
@@ -876,15 +895,6 @@ func CreateUser(u *User, overwriteDefault ...*CreateUserOverwriteOptions) (err e
 		return ErrUserAlreadyExist{u.Name}
 	}
 
-	if err = deleteUserRedirect(sess, u.Name); err != nil {
-		return err
-	}
-
-	u.Email = strings.ToLower(u.Email)
-	if err = ValidateEmail(u.Email); err != nil {
-		return err
-	}
-
 	isExist, err = isEmailUsed(sess, u.Email)
 	if err != nil {
 		return err
@@ -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 {
@@ -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 {
@@ -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
 }
 
@@ -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
 }
diff --git a/models/user_test.go b/models/user_test.go
index 39a1b3c989..34c465c586 100644
--- a/models/user_test.go
+++ b/models/user_test.go
@@ -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"
@@ -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
@@ -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))
+}
diff --git a/modules/setting/service.go b/modules/setting/service.go
index 3f689212f3..dbabfb8400 100644
--- a/modules/setting/service.go
+++ b/modules/setting/service.go
@@ -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
@@ -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},
+}
+
+// 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() {
@@ -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()
diff --git a/routers/web/admin/users.go b/routers/web/admin/users.go
index dc2a97e526..e1903ab1df 100644
--- a/routers/web/admin/users.go
+++ b/routers/web/admin/users.go
@@ -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"
 
@@ -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() {
diff --git a/routers/web/admin/users_test.go b/routers/web/admin/users_test.go
index 17c5a309b4..5ce20d8fa7 100644
--- a/routers/web/admin/users_test.go
+++ b/routers/web/admin/users_test.go
@@ -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")
 
@@ -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")
 
@@ -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")
 
@@ -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")
 
diff --git a/routers/web/user/setting/profile.go b/routers/web/user/setting/profile.go
index 463c4ec203..682f920578 100644
--- a/routers/web/user/setting/profile.go
+++ b/routers/web/user/setting/profile.go
@@ -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)
 }
diff --git a/templates/admin/user/edit.tmpl b/templates/admin/user/edit.tmpl
index dba24d9837..5e5bc75c96 100644
--- a/templates/admin/user/edit.tmpl
+++ b/templates/admin/user/edit.tmpl
@@ -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>
diff --git a/templates/admin/user/new.tmpl b/templates/admin/user/new.tmpl
index 2e39172535..a433c5a7cc 100644
--- a/templates/admin/user/new.tmpl
+++ b/templates/admin/user/new.tmpl
@@ -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>
diff --git a/templates/user/settings/profile.tmpl b/templates/user/settings/profile.tmpl
index 4b860049d8..1f1585a787 100644
--- a/templates/user/settings/profile.tmpl
+++ b/templates/user/settings/profile.tmpl
@@ -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>