From 487d12a5d14cd415731bf35a20e04e53019daab2 Mon Sep 17 00:00:00 2001 From: Sergey Dryabzhinsky Date: Sat, 5 Jun 2021 01:00:21 +0300 Subject: [PATCH 01/67] First take for hiding users from explore page --- models/migrations/v181.go | 21 +++++++++++++++++++++ models/user.go | 2 ++ options/locale/locale_en-US.ini | 4 ++++ routers/user/setting/profile.go | 1 + services/forms/user_form.go | 1 + templates/admin/user/edit.tmpl | 7 +++++++ templates/explore/users.tmpl | 2 ++ templates/user/settings/profile.tmpl | 7 +++++++ 8 files changed, 45 insertions(+) create mode 100644 models/migrations/v181.go diff --git a/models/migrations/v181.go b/models/migrations/v181.go new file mode 100644 index 0000000000000..6df1ef84221d5 --- /dev/null +++ b/models/migrations/v181.go @@ -0,0 +1,21 @@ +// Copyright 2021 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" + + "xorm.io/xorm" +) + +func addHideFromExplorePageUserColumn(x *xorm.Engine) error { + type User struct { + HideFromExplorePage bool `xorm:"NOT NULL DEFAULT false"` + } + if err := x.Sync2(new(User)); err != nil { + return fmt.Errorf("Sync2: %v", err) + } + return nil +} diff --git a/models/user.go b/models/user.go index c7b44bdb73216..a99fe6735006a 100644 --- a/models/user.go +++ b/models/user.go @@ -166,6 +166,8 @@ type User struct { DiffViewStyle string `xorm:"NOT NULL DEFAULT ''"` Theme string `xorm:"NOT NULL DEFAULT ''"` KeepActivityPrivate bool `xorm:"NOT NULL DEFAULT false"` + // Users may don't want to appear on /explore/users page. + HideFromExplorePage bool `xorm:"NOT NULL DEFAULT false"` } // SearchOrganizationsOptions options to filter organizations diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 4df9965bcb07a..471ee7e66d8a0 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -473,6 +473,8 @@ ui = Theme privacy = Privacy keep_activity_private = Hide the activity from the profile page keep_activity_private_popup = Makes the activity visible only for you and the admins +hide_from_explore_page = Hide user from explorabale service users page +hide_from_explore_page_popup = Makes user visible only if mentioned in repo, commit, etc. lookup_avatar_by_mail = Look Up Avatar by Email Address federated_avatar_lookup = Federated Avatar Lookup @@ -2236,6 +2238,8 @@ users.still_own_repo = This user still owns one or more repositories. Delete or users.still_has_org = This user is a member of an organization. Remove the user from any organizations first. users.deletion_success = The user account has been deleted. users.reset_2fa = Reset 2FA +users.hide_from_explore_page = User do not appear on service explore page +users.hide_from_explore_page_tooltip = Users may do not want to be anounced publicly. So they can be hided from that page. But they profile will be accesible by mentioning in commit, issue, etc. emails.email_manage_panel = User Email Management emails.primary = Primary diff --git a/routers/user/setting/profile.go b/routers/user/setting/profile.go index 8cde81f2954b9..f2d0542367e69 100644 --- a/routers/user/setting/profile.go +++ b/routers/user/setting/profile.go @@ -113,6 +113,7 @@ func ProfilePost(ctx *context.Context) { } ctx.User.Description = form.Description ctx.User.KeepActivityPrivate = form.KeepActivityPrivate + ctx.User.HideFromExplorePage = form.HideFromExplorePage if err := models.UpdateUserSetting(ctx.User); err != nil { if _, ok := err.(models.ErrEmailAlreadyUsed); ok { ctx.Flash.Error(ctx.Tr("form.email_been_used")) diff --git a/services/forms/user_form.go b/services/forms/user_form.go index 2c065dc5116a8..6378666dc01db 100644 --- a/services/forms/user_form.go +++ b/services/forms/user_form.go @@ -231,6 +231,7 @@ type UpdateProfileForm struct { Language string Description string `binding:"MaxSize(255)"` KeepActivityPrivate bool + HideFromExplorePage bool } // Validate validates the fields diff --git a/templates/admin/user/edit.tmpl b/templates/admin/user/edit.tmpl index af01489c0af26..92eb2c9b3b8b5 100644 --- a/templates/admin/user/edit.tmpl +++ b/templates/admin/user/edit.tmpl @@ -120,6 +120,13 @@ {{end}} +
+
+ + +
+
+
diff --git a/templates/explore/users.tmpl b/templates/explore/users.tmpl index dff4e8c8be717..28cbd6d653b5a 100644 --- a/templates/explore/users.tmpl +++ b/templates/explore/users.tmpl @@ -6,6 +6,7 @@
{{range .Users}} + {{if not .HideFromExplorePage}}
{{avatar .}}
@@ -22,6 +23,7 @@
+ {{end}} {{else}}
{{$.i18n.Tr "explore.user_no_results"}}
{{end}} diff --git a/templates/user/settings/profile.tmpl b/templates/user/settings/profile.tmpl index 9f07226632fcd..11c0c35291ccc 100644 --- a/templates/user/settings/profile.tmpl +++ b/templates/user/settings/profile.tmpl @@ -68,6 +68,13 @@
+
+ +
+ + +
+
From 4d194d6ab7d37cfa7d755e379fae3564ea509337 Mon Sep 17 00:00:00 2001 From: Sergey Dryabzhinsky Date: Sat, 5 Jun 2021 01:12:33 +0300 Subject: [PATCH 02/67] Update admin user forms --- modules/structs/admin_user.go | 2 ++ routers/api/v1/admin/user.go | 7 +++++++ services/forms/admin.go | 4 +++- 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/modules/structs/admin_user.go b/modules/structs/admin_user.go index 5da4e9608bea7..7896e02ae2ee7 100644 --- a/modules/structs/admin_user.go +++ b/modules/structs/admin_user.go @@ -19,6 +19,7 @@ type CreateUserOption struct { Password string `json:"password" binding:"Required;MaxSize(255)"` MustChangePassword *bool `json:"must_change_password"` SendNotify bool `json:"send_notify"` + HideFromExplorePage *bool `json:"hide_from_explore_page"` } // EditUserOption edit user options @@ -43,4 +44,5 @@ type EditUserOption struct { ProhibitLogin *bool `json:"prohibit_login"` AllowCreateOrganization *bool `json:"allow_create_organization"` Restricted *bool `json:"restricted"` + HideFromExplorePage *bool `json:"hide_from_explore_page"` } diff --git a/routers/api/v1/admin/user.go b/routers/api/v1/admin/user.go index 4bbe7f77ba2ea..719cb70e461c0 100644 --- a/routers/api/v1/admin/user.go +++ b/routers/api/v1/admin/user.go @@ -74,10 +74,14 @@ func CreateUser(ctx *context.APIContext) { MustChangePassword: true, IsActive: true, LoginType: models.LoginPlain, + HideFromExplorePage: false, } if form.MustChangePassword != nil { u.MustChangePassword = *form.MustChangePassword } + if form.HideFromExplorePage != nil { + u.HideFromExplorePage = *form.HideFromExplorePage + } parseLoginSource(ctx, u, form.SourceID, form.LoginName) if ctx.Written() { @@ -230,6 +234,9 @@ func EditUser(ctx *context.APIContext) { if form.Restricted != nil { u.IsRestricted = *form.Restricted } + if form.HideFromExplorePage != nil { + u.HideFromExplorePage = *form.HideFromExplorePage + } if err := models.UpdateUser(u); err != nil { if models.IsErrEmailAlreadyUsed(err) || models.IsErrEmailInvalid(err) { diff --git a/services/forms/admin.go b/services/forms/admin.go index 2e6bbaf172019..3a250a32a5290 100644 --- a/services/forms/admin.go +++ b/services/forms/admin.go @@ -21,7 +21,8 @@ type AdminCreateUserForm struct { Email string `binding:"Required;Email;MaxSize(254)"` Password string `binding:"MaxSize(255)"` SendNotify bool - MustChangePassword bool + MustChangePassword bool + HideFromExplorePage bool } // Validate validates form fields @@ -49,6 +50,7 @@ type AdminEditUserForm struct { AllowCreateOrganization bool ProhibitLogin bool Reset2FA bool `form:"reset_2fa"` + HideFromExplorePage bool } // Validate validates form fields From 62f2bd9d65017e481533e7c2391c7f0193fef92b Mon Sep 17 00:00:00 2001 From: Sergey Dryabzhinsky Date: Sat, 5 Jun 2021 01:16:04 +0300 Subject: [PATCH 03/67] Update template for new user by admin --- templates/admin/user/new.tmpl | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/templates/admin/user/new.tmpl b/templates/admin/user/new.tmpl index 885045dd02708..bcd4f9f52c082 100644 --- a/templates/admin/user/new.tmpl +++ b/templates/admin/user/new.tmpl @@ -59,6 +59,13 @@ {{end}} +
+
+ + +
+
+
From 1258a6e6d2d8e2c591da6003f1cefab6d1739e68 Mon Sep 17 00:00:00 2001 From: Sergey Dryabzhinsky Date: Sat, 5 Jun 2021 01:21:40 +0300 Subject: [PATCH 04/67] Add migration to list to process --- models/migrations/migrations.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index d440722c96f8c..e20d2cccffaf6 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -311,6 +311,8 @@ var migrations = []Migration{ NewMigration("Convert avatar url to text", convertAvatarURLToText), // v180 -> v181 NewMigration("Delete credentials from past migrations", deleteMigrationCredentials), + // v181 -> v182 + NewMigration("Add column to User to allow them hide self from explore/users page", addHideFromExplorePageUserColumn), } // GetCurrentDBVersion returns the current db version From 51520a362803ebb5c2280dfd7254e1c023cda40e Mon Sep 17 00:00:00 2001 From: Sergey Dryabzhinsky Date: Sat, 5 Jun 2021 01:37:08 +0300 Subject: [PATCH 05/67] Format files --- modules/structs/admin_user.go | 8 ++++---- routers/api/v1/admin/user.go | 14 +++++++------- services/forms/admin.go | 12 ++++++------ 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/modules/structs/admin_user.go b/modules/structs/admin_user.go index 7896e02ae2ee7..8fd80bc3d2034 100644 --- a/modules/structs/admin_user.go +++ b/modules/structs/admin_user.go @@ -16,10 +16,10 @@ type CreateUserOption struct { // swagger:strfmt email Email string `json:"email" binding:"Required;Email;MaxSize(254)"` // required: true - Password string `json:"password" binding:"Required;MaxSize(255)"` - MustChangePassword *bool `json:"must_change_password"` - SendNotify bool `json:"send_notify"` - HideFromExplorePage *bool `json:"hide_from_explore_page"` + Password string `json:"password" binding:"Required;MaxSize(255)"` + MustChangePassword *bool `json:"must_change_password"` + SendNotify bool `json:"send_notify"` + HideFromExplorePage *bool `json:"hide_from_explore_page"` } // EditUserOption edit user options diff --git a/routers/api/v1/admin/user.go b/routers/api/v1/admin/user.go index 719cb70e461c0..87d483e5ec7db 100644 --- a/routers/api/v1/admin/user.go +++ b/routers/api/v1/admin/user.go @@ -67,13 +67,13 @@ func CreateUser(ctx *context.APIContext) { // "$ref": "#/responses/validationError" form := web.GetForm(ctx).(*api.CreateUserOption) u := &models.User{ - Name: form.Username, - FullName: form.FullName, - Email: form.Email, - Passwd: form.Password, - MustChangePassword: true, - IsActive: true, - LoginType: models.LoginPlain, + Name: form.Username, + FullName: form.FullName, + Email: form.Email, + Passwd: form.Password, + MustChangePassword: true, + IsActive: true, + LoginType: models.LoginPlain, HideFromExplorePage: false, } if form.MustChangePassword != nil { diff --git a/services/forms/admin.go b/services/forms/admin.go index 3a250a32a5290..58979237f1682 100644 --- a/services/forms/admin.go +++ b/services/forms/admin.go @@ -15,12 +15,12 @@ import ( // AdminCreateUserForm form for admin to create user type AdminCreateUserForm struct { - LoginType string `binding:"Required"` - LoginName string - UserName string `binding:"Required;AlphaDashDot;MaxSize(40)"` - Email string `binding:"Required;Email;MaxSize(254)"` - Password string `binding:"MaxSize(255)"` - SendNotify bool + LoginType string `binding:"Required"` + LoginName string + UserName string `binding:"Required;AlphaDashDot;MaxSize(40)"` + Email string `binding:"Required;Email;MaxSize(254)"` + Password string `binding:"MaxSize(255)"` + SendNotify bool MustChangePassword bool HideFromExplorePage bool } From 8064e02326a313a5ca62625da704d92d6687f00f Mon Sep 17 00:00:00 2001 From: Sergey Dryabzhinsky Date: Sat, 5 Jun 2021 02:15:14 +0300 Subject: [PATCH 06/67] Update swagger template --- templates/swagger/v1_json.tmpl | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index e3ac4a4c8a68c..66a9bd8a74594 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -13014,6 +13014,10 @@ "type": "string", "x-go-name": "FullName" }, + "hide_from_explore_page": { + "type": "boolean", + "x-go-name": "HideFromExplorePage" + }, "login_name": { "type": "string", "x-go-name": "LoginName" @@ -13813,6 +13817,10 @@ "type": "string", "x-go-name": "FullName" }, + "hide_from_explore_page": { + "type": "boolean", + "x-go-name": "HideFromExplorePage" + }, "location": { "type": "string", "x-go-name": "Location" From 9d5447fbb8ce54845221058e3791500a859020f9 Mon Sep 17 00:00:00 2001 From: Sergey Dryabzhinsky Date: Sat, 5 Jun 2021 07:27:07 +0300 Subject: [PATCH 07/67] Filter users via query, not in template --- models/user.go | 21 +++++++++++++-------- routers/home.go | 11 ++++++----- templates/explore/users.tmpl | 2 -- 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/models/user.go b/models/user.go index a99fe6735006a..3054ecd9f5686 100644 --- a/models/user.go +++ b/models/user.go @@ -1558,14 +1558,15 @@ func GetUser(user *User) (bool, error) { // SearchUserOptions contains the options for searching type SearchUserOptions struct { ListOptions - Keyword string - Type UserType - UID int64 - OrderBy SearchOrderBy - Visible []structs.VisibleType - Actor *User // The user doing the search - IsActive util.OptionalBool - SearchByEmail bool // Search by email as well as username/full name + Keyword string + Type UserType + UID int64 + OrderBy SearchOrderBy + Visible []structs.VisibleType + Actor *User // The user doing the search + IsActive util.OptionalBool + HideFromExplorePage util.OptionalBool + SearchByEmail bool // Search by email as well as username/full name } func (opts *SearchUserOptions) toConds() builder.Cond { @@ -1619,6 +1620,10 @@ func (opts *SearchUserOptions) toConds() builder.Cond { cond = cond.And(builder.Eq{"is_active": opts.IsActive.IsTrue()}) } + if !opts.HideFromExplorePage.IsNone() { + cond = cond.And(builder.Eq{"hide_from_explore_page": opts.HideFromExplorePage.IsTrue()}) + } + return cond } diff --git a/routers/home.go b/routers/home.go index 7eaebc081fd4e..c402eebfac557 100644 --- a/routers/home.go +++ b/routers/home.go @@ -259,11 +259,12 @@ func ExploreUsers(ctx *context.Context) { ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled RenderUserSearch(ctx, &models.SearchUserOptions{ - Actor: ctx.User, - Type: models.UserTypeIndividual, - ListOptions: models.ListOptions{PageSize: setting.UI.ExplorePagingNum}, - IsActive: util.OptionalBoolTrue, - Visible: []structs.VisibleType{structs.VisibleTypePublic, structs.VisibleTypeLimited, structs.VisibleTypePrivate}, + Actor: ctx.User, + Type: models.UserTypeIndividual, + ListOptions: models.ListOptions{PageSize: setting.UI.ExplorePagingNum}, + IsActive: util.OptionalBoolTrue, + HideFromExplorePage: util.OptionalBoolFalse, + Visible: []structs.VisibleType{structs.VisibleTypePublic, structs.VisibleTypeLimited, structs.VisibleTypePrivate}, }, tplExploreUsers) } diff --git a/templates/explore/users.tmpl b/templates/explore/users.tmpl index 28cbd6d653b5a..dff4e8c8be717 100644 --- a/templates/explore/users.tmpl +++ b/templates/explore/users.tmpl @@ -6,7 +6,6 @@
{{range .Users}} - {{if not .HideFromExplorePage}}
{{avatar .}}
@@ -23,7 +22,6 @@
- {{end}} {{else}}
{{$.i18n.Tr "explore.user_no_results"}}
{{end}} From f0b582cfed84fe06cfc7fc7c5520a75d49652202 Mon Sep 17 00:00:00 2001 From: Sergey Dryabzhinsky Date: Sat, 5 Jun 2021 07:32:02 +0300 Subject: [PATCH 08/67] Fix user edit by admin - store new field --- routers/admin/users.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/routers/admin/users.go b/routers/admin/users.go index a71a11dd8a225..e16d4dc1330ca 100644 --- a/routers/admin/users.go +++ b/routers/admin/users.go @@ -126,6 +126,9 @@ func NewUserPost(ctx *context.Context) { } u.MustChangePassword = form.MustChangePassword } + + u.HideFromExplorePage = form.HideFromExplorePage + if err := models.CreateUser(u); err != nil { switch { case models.IsErrUserAlreadyExist(err): @@ -311,6 +314,7 @@ func EditUserPost(ctx *context.Context) { u.AllowGitHook = form.AllowGitHook u.AllowImportLocal = form.AllowImportLocal u.AllowCreateOrganization = form.AllowCreateOrganization + u.HideFromExplorePage = form.HideFromExplorePage // skip self Prohibit Login if ctx.User.ID == u.ID { From 266bee8be43ab8831f239331aacd8c106a17829e Mon Sep 17 00:00:00 2001 From: Sergey Dryabzhinsky Date: Sat, 5 Jun 2021 07:36:48 +0300 Subject: [PATCH 09/67] Update locale strings for user profile page --- options/locale/locale_en-US.ini | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 471ee7e66d8a0..992c96951a1fb 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -473,8 +473,8 @@ ui = Theme privacy = Privacy keep_activity_private = Hide the activity from the profile page keep_activity_private_popup = Makes the activity visible only for you and the admins -hide_from_explore_page = Hide user from explorabale service users page -hide_from_explore_page_popup = Makes user visible only if mentioned in repo, commit, etc. +hide_from_explore_page = Hide me from public "explore users" page +hide_from_explore_page_popup = Make me visible only by mention in repo, commit, etc. And hide from public "explore users" page. lookup_avatar_by_mail = Look Up Avatar by Email Address federated_avatar_lookup = Federated Avatar Lookup @@ -2238,7 +2238,7 @@ users.still_own_repo = This user still owns one or more repositories. Delete or users.still_has_org = This user is a member of an organization. Remove the user from any organizations first. users.deletion_success = The user account has been deleted. users.reset_2fa = Reset 2FA -users.hide_from_explore_page = User do not appear on service explore page +users.hide_from_explore_page = User do not want to appear on service explore page users.hide_from_explore_page_tooltip = Users may do not want to be anounced publicly. So they can be hided from that page. But they profile will be accesible by mentioning in commit, issue, etc. emails.email_manage_panel = User Email Management From b419594c30968b924c6ed202ec79e15202b16696 Mon Sep 17 00:00:00 2001 From: Sergey Dryabzhinsky Date: Sun, 6 Jun 2021 23:14:50 +0300 Subject: [PATCH 10/67] Hide user from public api user/userSearch by swagger, only there --- routers/api/v1/user/user.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/routers/api/v1/user/user.go b/routers/api/v1/user/user.go index 6e811bf0f8a4d..95fadc60c6b19 100644 --- a/routers/api/v1/user/user.go +++ b/routers/api/v1/user/user.go @@ -14,6 +14,7 @@ import ( "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/convert" api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/routers/api/v1/utils" ) @@ -58,10 +59,11 @@ func Search(ctx *context.APIContext) { listOptions := utils.GetListOptions(ctx) opts := &models.SearchUserOptions{ - Keyword: strings.Trim(ctx.Query("q"), " "), - UID: ctx.QueryInt64("uid"), - Type: models.UserTypeIndividual, - ListOptions: listOptions, + Keyword: strings.Trim(ctx.Query("q"), " "), + UID: ctx.QueryInt64("uid"), + Type: models.UserTypeIndividual, + ListOptions: listOptions, + HideFromExplorePage: util.OptionalBoolFalse, // As api is public - respect user privacy } users, maxResults, err := models.SearchUsers(opts) From 0194b9dcbf53afd5a712b5e68f770252b45f0170 Mon Sep 17 00:00:00 2001 From: Sergey Dryabzhinsky Date: Mon, 7 Jun 2021 00:06:08 +0300 Subject: [PATCH 11/67] Simplify userSearch limits if hide_from_explore_page set - check for Admin actor - first test for new column value --- models/fixtures/user.yml | 1 + models/user.go | 27 ++++++++++++++------------- modules/convert/user.go | 1 + modules/convert/user_test.go | 7 +++++++ modules/structs/user.go | 2 ++ routers/api/v1/user/user.go | 9 ++++----- 6 files changed, 29 insertions(+), 18 deletions(-) diff --git a/models/fixtures/user.yml b/models/fixtures/user.yml index d903a7942f81b..0fcdf747c811a 100644 --- a/models/fixtures/user.yml +++ b/models/fixtures/user.yml @@ -50,6 +50,7 @@ type: 1 # organization salt: ZogKvWdyEx is_admin: false + hide_from_explode_page: true avatar: avatar3 avatar_email: user3@example.com num_repos: 3 diff --git a/models/user.go b/models/user.go index 3054ecd9f5686..3be2294e54190 100644 --- a/models/user.go +++ b/models/user.go @@ -1558,15 +1558,14 @@ func GetUser(user *User) (bool, error) { // SearchUserOptions contains the options for searching type SearchUserOptions struct { ListOptions - Keyword string - Type UserType - UID int64 - OrderBy SearchOrderBy - Visible []structs.VisibleType - Actor *User // The user doing the search - IsActive util.OptionalBool - HideFromExplorePage util.OptionalBool - SearchByEmail bool // Search by email as well as username/full name + Keyword string + Type UserType + UID int64 + OrderBy SearchOrderBy + Visible []structs.VisibleType + Actor *User // The user doing the search + IsActive util.OptionalBool + SearchByEmail bool // Search by email as well as username/full name } func (opts *SearchUserOptions) toConds() builder.Cond { @@ -1610,6 +1609,12 @@ func (opts *SearchUserOptions) toConds() builder.Cond { accessCond = builder.In("id", builder.Select("org_id").From("org_user").LeftJoin("`user`", exprCond).Where(builder.And(builder.Eq{"uid": opts.Actor.ID}))) } cond = cond.And(accessCond) + + if !opts.Actor.IsAdmin { + cond = cond.And(builder.Eq{"hide_from_explore_page": false}.Or(builder.Eq{"id": opts.Actor.ID})) + } + } else { + cond = cond.And(builder.Eq{"hide_from_explore_page": false}) } if opts.UID > 0 { @@ -1620,10 +1625,6 @@ func (opts *SearchUserOptions) toConds() builder.Cond { cond = cond.And(builder.Eq{"is_active": opts.IsActive.IsTrue()}) } - if !opts.HideFromExplorePage.IsNone() { - cond = cond.And(builder.Eq{"hide_from_explore_page": opts.HideFromExplorePage.IsTrue()}) - } - return cond } diff --git a/modules/convert/user.go b/modules/convert/user.go index 088ede5add507..dede9f32925f8 100644 --- a/modules/convert/user.go +++ b/modules/convert/user.go @@ -56,6 +56,7 @@ func toUser(user *models.User, signed, authed bool) *api.User { // only site admin will get these information and possibly user himself if authed { result.IsAdmin = user.IsAdmin + result.HideFromExplorePage = user.HideFromExplorePage result.LastLogin = user.LastLoginUnix.AsTime() result.Language = user.Language result.IsActive = user.IsActive diff --git a/modules/convert/user_test.go b/modules/convert/user_test.go index 7837910ffecb3..9db23bb22e133 100644 --- a/modules/convert/user_test.go +++ b/modules/convert/user_test.go @@ -27,4 +27,11 @@ func TestUser_ToUser(t *testing.T) { apiUser = toUser(user1, false, false) assert.False(t, apiUser.IsAdmin) + + user3 := models.AssertExistsAndLoadBean(t, &models.User{ID: 3, IsAdmin: false}).(*models.User) + + apiUser = toUser(user3, true, true) + assert.False(t, apiUser.IsAdmin) + assert.True(t, apiUser.HideFromExplorePage) + } diff --git a/modules/structs/user.go b/modules/structs/user.go index 2dbc5305382ab..6bbb8dc3d6ac8 100644 --- a/modules/structs/user.go +++ b/modules/structs/user.go @@ -43,6 +43,8 @@ type User struct { Website string `json:"website"` // the user's description Description string `json:"description"` + // user hides from public + HideFromExplorePage bool `json:"hide_from_explore_page"` } // MarshalJSON implements the json.Marshaler interface for User, adding field(s) for backward compatibility diff --git a/routers/api/v1/user/user.go b/routers/api/v1/user/user.go index 95fadc60c6b19..73aa3d71aba4b 100644 --- a/routers/api/v1/user/user.go +++ b/routers/api/v1/user/user.go @@ -59,11 +59,10 @@ func Search(ctx *context.APIContext) { listOptions := utils.GetListOptions(ctx) opts := &models.SearchUserOptions{ - Keyword: strings.Trim(ctx.Query("q"), " "), - UID: ctx.QueryInt64("uid"), - Type: models.UserTypeIndividual, - ListOptions: listOptions, - HideFromExplorePage: util.OptionalBoolFalse, // As api is public - respect user privacy + Keyword: strings.Trim(ctx.Query("q"), " "), + UID: ctx.QueryInt64("uid"), + Type: models.UserTypeIndividual, + ListOptions: listOptions, } users, maxResults, err := models.SearchUsers(opts) From 1fe1a41e4cb1b70fc5019f2e370a596bc21066d8 Mon Sep 17 00:00:00 2001 From: Sergey Dryabzhinsky Date: Mon, 7 Jun 2021 00:07:43 +0300 Subject: [PATCH 12/67] Check user2 too --- modules/convert/user_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/convert/user_test.go b/modules/convert/user_test.go index 9db23bb22e133..55207b96cb674 100644 --- a/modules/convert/user_test.go +++ b/modules/convert/user_test.go @@ -27,6 +27,7 @@ func TestUser_ToUser(t *testing.T) { apiUser = toUser(user1, false, false) assert.False(t, apiUser.IsAdmin) + assert.False(t, apiUser.HideFromExplorePage) user3 := models.AssertExistsAndLoadBean(t, &models.User{ID: 3, IsAdmin: false}).(*models.User) From 9e68bb7c23a2e1dc668742015da4bc3a7cb117ba Mon Sep 17 00:00:00 2001 From: Sergey Dryabzhinsky Date: Mon, 7 Jun 2021 00:08:32 +0300 Subject: [PATCH 13/67] And add new colum for select --- modules/convert/user_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/convert/user_test.go b/modules/convert/user_test.go index 55207b96cb674..a5637fb8feee4 100644 --- a/modules/convert/user_test.go +++ b/modules/convert/user_test.go @@ -29,7 +29,7 @@ func TestUser_ToUser(t *testing.T) { assert.False(t, apiUser.IsAdmin) assert.False(t, apiUser.HideFromExplorePage) - user3 := models.AssertExistsAndLoadBean(t, &models.User{ID: 3, IsAdmin: false}).(*models.User) + user3 := models.AssertExistsAndLoadBean(t, &models.User{ID: 3, IsAdmin: false, HideFromExplorePage: true}).(*models.User) apiUser = toUser(user3, true, true) assert.False(t, apiUser.IsAdmin) From f6fbef737e38313632d47fa3558357bcb0622c23 Mon Sep 17 00:00:00 2001 From: Sergey Dryabzhinsky Date: Mon, 7 Jun 2021 00:12:43 +0300 Subject: [PATCH 14/67] Add 2 tests for api_user_search --- integrations/api_user_search_test.go | 30 ++++++++++++++++++++++++++++ routers/api/v1/user/user.go | 1 - 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/integrations/api_user_search_test.go b/integrations/api_user_search_test.go index c5295fbba5dad..0ec97a486c48c 100644 --- a/integrations/api_user_search_test.go +++ b/integrations/api_user_search_test.go @@ -59,3 +59,33 @@ func TestAPIUserSearchNotLoggedIn(t *testing.T) { } } } + +func TestAPIUserSearchAdminLoggedInUserHidden(t *testing.T) { + defer prepareTestEnv(t)() + adminUsername := "user1" + session := loginUser(t, adminUsername) + token := getTokenForLoggedInUser(t, session) + query := "user3" + req := NewRequestf(t, "GET", "/api/v1/users/search?token=%s&q=%s", token, query) + resp := session.MakeRequest(t, req, http.StatusOK) + + var results SearchResults + DecodeJSON(t, resp, &results) + assert.NotEmpty(t, results.Data) + for _, user := range results.Data { + assert.Contains(t, user.UserName, query) + assert.NotEmpty(t, user.Email) + assert.True(t, user.HideFromExplorePage) + } +} + +func TestAPIUserSearchNotLoggedInUserHidden(t *testing.T) { + defer prepareTestEnv(t)() + query := "user3" + req := NewRequestf(t, "GET", "/api/v1/users/search?token=%s&q=%s", token, query) + resp := session.MakeRequest(t, req, http.StatusOK) + + var results SearchResults + DecodeJSON(t, resp, &results) + assert.Empty(t, results.Data) +} diff --git a/routers/api/v1/user/user.go b/routers/api/v1/user/user.go index 73aa3d71aba4b..6e811bf0f8a4d 100644 --- a/routers/api/v1/user/user.go +++ b/routers/api/v1/user/user.go @@ -14,7 +14,6 @@ import ( "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/convert" api "code.gitea.io/gitea/modules/structs" - "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/routers/api/v1/utils" ) From 71bf39746fe3065a18745d428f2ef228d0e5a1e0 Mon Sep 17 00:00:00 2001 From: Sergey Dryabzhinsky Date: Mon, 7 Jun 2021 00:15:40 +0300 Subject: [PATCH 15/67] Remove useles field --- routers/home.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/routers/home.go b/routers/home.go index c402eebfac557..7eaebc081fd4e 100644 --- a/routers/home.go +++ b/routers/home.go @@ -259,12 +259,11 @@ func ExploreUsers(ctx *context.Context) { ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled RenderUserSearch(ctx, &models.SearchUserOptions{ - Actor: ctx.User, - Type: models.UserTypeIndividual, - ListOptions: models.ListOptions{PageSize: setting.UI.ExplorePagingNum}, - IsActive: util.OptionalBoolTrue, - HideFromExplorePage: util.OptionalBoolFalse, - Visible: []structs.VisibleType{structs.VisibleTypePublic, structs.VisibleTypeLimited, structs.VisibleTypePrivate}, + Actor: ctx.User, + Type: models.UserTypeIndividual, + ListOptions: models.ListOptions{PageSize: setting.UI.ExplorePagingNum}, + IsActive: util.OptionalBoolTrue, + Visible: []structs.VisibleType{structs.VisibleTypePublic, structs.VisibleTypeLimited, structs.VisibleTypePrivate}, }, tplExploreUsers) } From 99257245269d7c94e4e750acfbf09fa3f34fd5c1 Mon Sep 17 00:00:00 2001 From: Sergey Dryabzhinsky Date: Mon, 7 Jun 2021 00:19:18 +0300 Subject: [PATCH 16/67] Fix test unused/undeclared vars --- integrations/api_user_search_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integrations/api_user_search_test.go b/integrations/api_user_search_test.go index 0ec97a486c48c..e7e076486858c 100644 --- a/integrations/api_user_search_test.go +++ b/integrations/api_user_search_test.go @@ -83,7 +83,7 @@ func TestAPIUserSearchNotLoggedInUserHidden(t *testing.T) { defer prepareTestEnv(t)() query := "user3" req := NewRequestf(t, "GET", "/api/v1/users/search?token=%s&q=%s", token, query) - resp := session.MakeRequest(t, req, http.StatusOK) + resp := MakeRequest(t, req, http.StatusOK) var results SearchResults DecodeJSON(t, resp, &results) From 6580f93e5d727d0d97e914f5992c741bbc52327b Mon Sep 17 00:00:00 2001 From: Sergey Dryabzhinsky Date: Mon, 7 Jun 2021 00:20:55 +0300 Subject: [PATCH 17/67] Fix request string formating --- integrations/api_user_search_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integrations/api_user_search_test.go b/integrations/api_user_search_test.go index e7e076486858c..3784f16f32b1c 100644 --- a/integrations/api_user_search_test.go +++ b/integrations/api_user_search_test.go @@ -82,7 +82,7 @@ func TestAPIUserSearchAdminLoggedInUserHidden(t *testing.T) { func TestAPIUserSearchNotLoggedInUserHidden(t *testing.T) { defer prepareTestEnv(t)() query := "user3" - req := NewRequestf(t, "GET", "/api/v1/users/search?token=%s&q=%s", token, query) + req := NewRequestf(t, "GET", "/api/v1/users/search?q=%s", query) resp := MakeRequest(t, req, http.StatusOK) var results SearchResults From 132a7277ee9de2e764665b6cce255f8d82bf6002 Mon Sep 17 00:00:00 2001 From: Sergey Dryabzhinsky Date: Mon, 7 Jun 2021 00:29:55 +0300 Subject: [PATCH 18/67] Update swapper template --- templates/swagger/v1_json.tmpl | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index 66a9bd8a74594..c4c2bea757563 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -16288,6 +16288,11 @@ "type": "string", "x-go-name": "FullName" }, + "hide_from_explore_page": { + "description": "user hides from public", + "type": "boolean", + "x-go-name": "HideFromExplorePage" + }, "id": { "description": "the user's id", "type": "integer", From 3d74a8482cb8504b0dec0744a5d664f5dd6d05d0 Mon Sep 17 00:00:00 2001 From: Sergey Dryabzhinsky Date: Mon, 7 Jun 2021 00:57:46 +0300 Subject: [PATCH 19/67] Remove whitespace --- templates/swagger/v1_json.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index c4c2bea757563..ebc2889eab8df 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -16292,7 +16292,7 @@ "description": "user hides from public", "type": "boolean", "x-go-name": "HideFromExplorePage" - }, + }, "id": { "description": "the user's id", "type": "integer", From 5cb908c9d965c3e4c7499d88301a9a31b8f57a5e Mon Sep 17 00:00:00 2001 From: Sergey Dryabzhinsky Date: Mon, 7 Jun 2021 02:38:46 +0300 Subject: [PATCH 20/67] Typo in fixture --- models/fixtures/user.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/fixtures/user.yml b/models/fixtures/user.yml index 0fcdf747c811a..fd0fc648d4a62 100644 --- a/models/fixtures/user.yml +++ b/models/fixtures/user.yml @@ -50,7 +50,7 @@ type: 1 # organization salt: ZogKvWdyEx is_admin: false - hide_from_explode_page: true + hide_from_explore_page: true avatar: avatar3 avatar_email: user3@example.com num_repos: 3 From 39df5f335ec02409fe9f12bd5dd594b4f148b6c3 Mon Sep 17 00:00:00 2001 From: Sergey Dryabzhinsky Date: Mon, 7 Jun 2021 02:41:43 +0300 Subject: [PATCH 21/67] Don't break old tests, use new user31 --- integrations/api_user_search_test.go | 4 ++-- models/fixtures/user.yml | 19 ++++++++++++++++++- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/integrations/api_user_search_test.go b/integrations/api_user_search_test.go index 3784f16f32b1c..4ae6d113a2401 100644 --- a/integrations/api_user_search_test.go +++ b/integrations/api_user_search_test.go @@ -65,7 +65,7 @@ func TestAPIUserSearchAdminLoggedInUserHidden(t *testing.T) { adminUsername := "user1" session := loginUser(t, adminUsername) token := getTokenForLoggedInUser(t, session) - query := "user3" + query := "user31" req := NewRequestf(t, "GET", "/api/v1/users/search?token=%s&q=%s", token, query) resp := session.MakeRequest(t, req, http.StatusOK) @@ -81,7 +81,7 @@ func TestAPIUserSearchAdminLoggedInUserHidden(t *testing.T) { func TestAPIUserSearchNotLoggedInUserHidden(t *testing.T) { defer prepareTestEnv(t)() - query := "user3" + query := "user31" req := NewRequestf(t, "GET", "/api/v1/users/search?q=%s", query) resp := MakeRequest(t, req, http.StatusOK) diff --git a/models/fixtures/user.yml b/models/fixtures/user.yml index fd0fc648d4a62..f6b91fdb28267 100644 --- a/models/fixtures/user.yml +++ b/models/fixtures/user.yml @@ -50,7 +50,6 @@ type: 1 # organization salt: ZogKvWdyEx is_admin: false - hide_from_explore_page: true avatar: avatar3 avatar_email: user3@example.com num_repos: 3 @@ -526,3 +525,21 @@ avatar_email: user30@example.com num_repos: 2 is_active: true + + - + id: 31 + lower_name: user31 + name: user31 + full_name: " <<<< >> >> > >> > >>> >> " + email: user31@example.com + email_notifications_preference: onmention + passwd_hash_algo: argon2 + passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b # password + type: 0 # individual + salt: ZogKvWdyEx + is_admin: false + hide_from_explore_page: true + avatar: avatar31 + avatar_email: user31@example.com + is_active: true + num_repos: 0 From c38ed8506a8b027d9d2c833f6fb714bf22793a31 Mon Sep 17 00:00:00 2001 From: Sergey Dryabzhinsky Date: Mon, 7 Jun 2021 03:09:56 +0300 Subject: [PATCH 22/67] Fix unit-tests - use user31, fix fixture typo --- models/fixtures/user.yml | 5 ++--- modules/convert/user_test.go | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/models/fixtures/user.yml b/models/fixtures/user.yml index f6b91fdb28267..b7a324fcde98d 100644 --- a/models/fixtures/user.yml +++ b/models/fixtures/user.yml @@ -508,7 +508,6 @@ num_repos: 0 is_active: true - - id: 30 lower_name: user30 @@ -526,7 +525,7 @@ num_repos: 2 is_active: true - - +- id: 31 lower_name: user31 name: user31 @@ -541,5 +540,5 @@ hide_from_explore_page: true avatar: avatar31 avatar_email: user31@example.com - is_active: true num_repos: 0 + is_active: true diff --git a/modules/convert/user_test.go b/modules/convert/user_test.go index a5637fb8feee4..0fcde5f922ffe 100644 --- a/modules/convert/user_test.go +++ b/modules/convert/user_test.go @@ -29,9 +29,9 @@ func TestUser_ToUser(t *testing.T) { assert.False(t, apiUser.IsAdmin) assert.False(t, apiUser.HideFromExplorePage) - user3 := models.AssertExistsAndLoadBean(t, &models.User{ID: 3, IsAdmin: false, HideFromExplorePage: true}).(*models.User) + user31 := models.AssertExistsAndLoadBean(t, &models.User{ID: 31, IsAdmin: false, HideFromExplorePage: true}).(*models.User) - apiUser = toUser(user3, true, true) + apiUser = toUser(user31, true, true) assert.False(t, apiUser.IsAdmin) assert.True(t, apiUser.HideFromExplorePage) From 5c03ff1e59f005604d0ad9f20f21c6e4c92f7fd1 Mon Sep 17 00:00:00 2001 From: Sergey Dryabzhinsky Date: Mon, 7 Jun 2021 05:38:01 +0300 Subject: [PATCH 23/67] Pass Actor to api user search --- routers/api/v1/admin/user.go | 1 + 1 file changed, 1 insertion(+) diff --git a/routers/api/v1/admin/user.go b/routers/api/v1/admin/user.go index 87d483e5ec7db..da29f3477a6c1 100644 --- a/routers/api/v1/admin/user.go +++ b/routers/api/v1/admin/user.go @@ -402,6 +402,7 @@ func GetAllUsers(ctx *context.APIContext) { listOptions := utils.GetListOptions(ctx) users, maxResults, err := models.SearchUsers(&models.SearchUserOptions{ + Actor: ctx.User, Type: models.UserTypeIndividual, OrderBy: models.SearchOrderByAlphabetically, ListOptions: listOptions, From 0655b1f5cfe5d0f95d63f997363d19f84848ef26 Mon Sep 17 00:00:00 2001 From: Sergey Dryabzhinsky Date: Mon, 7 Jun 2021 06:40:29 +0300 Subject: [PATCH 24/67] Token auth is basic auth? Trace request from who --- integrations/api_user_search_test.go | 1 + routers/api/v1/admin/user.go | 2 ++ 2 files changed, 3 insertions(+) diff --git a/integrations/api_user_search_test.go b/integrations/api_user_search_test.go index 4ae6d113a2401..3ebd5938721fe 100644 --- a/integrations/api_user_search_test.go +++ b/integrations/api_user_search_test.go @@ -67,6 +67,7 @@ func TestAPIUserSearchAdminLoggedInUserHidden(t *testing.T) { token := getTokenForLoggedInUser(t, session) query := "user31" req := NewRequestf(t, "GET", "/api/v1/users/search?token=%s&q=%s", token, query) + req.SetBasicAuth(token, "x-oauth-basic") resp := session.MakeRequest(t, req, http.StatusOK) var results SearchResults diff --git a/routers/api/v1/admin/user.go b/routers/api/v1/admin/user.go index da29f3477a6c1..a83c15fe6b55c 100644 --- a/routers/api/v1/admin/user.go +++ b/routers/api/v1/admin/user.go @@ -399,6 +399,8 @@ func GetAllUsers(ctx *context.APIContext) { // "403": // "$ref": "#/responses/forbidden" + log.Trace("GetAllUsers requested by admin(%s): is admin?= %ui", ctx.User.Name, ctx.User.IsAdmin) + listOptions := utils.GetListOptions(ctx) users, maxResults, err := models.SearchUsers(&models.SearchUserOptions{ From a1bae9ce56ffd7c59bd39c44f7ed1d57b6736055 Mon Sep 17 00:00:00 2001 From: Sergey Dryabzhinsky Date: Mon, 7 Jun 2021 08:14:00 +0300 Subject: [PATCH 25/67] Fixes for user search: - ctx.User was not passed by - add 2 more tests for user creation --- routers/admin/users.go | 3 +- routers/admin/users_test.go | 77 ++++++++++++++++++++++++++++++++++++ routers/api/v1/admin/user.go | 2 - routers/api/v1/user/user.go | 1 + 4 files changed, 80 insertions(+), 3 deletions(-) diff --git a/routers/admin/users.go b/routers/admin/users.go index e16d4dc1330ca..092e3dcb35ce5 100644 --- a/routers/admin/users.go +++ b/routers/admin/users.go @@ -37,7 +37,8 @@ func Users(ctx *context.Context) { ctx.Data["PageIsAdminUsers"] = true routers.RenderUserSearch(ctx, &models.SearchUserOptions{ - Type: models.UserTypeIndividual, + Actor: ctx.User, + Type: models.UserTypeIndividual, ListOptions: models.ListOptions{ PageSize: setting.UI.Admin.UserPagingNum, }, diff --git a/routers/admin/users_test.go b/routers/admin/users_test.go index b19dcb886bde1..1d198f8789420 100644 --- a/routers/admin/users_test.go +++ b/routers/admin/users_test.go @@ -121,3 +121,80 @@ func TestNewUserPost_InvalidEmail(t *testing.T) { assert.NotEmpty(t, ctx.Flash.ErrorMsg) } + +func TestNewUserPost_HideFromExplorePageDefaultFalse(t *testing.T) { + + models.PrepareTestEnv(t) + ctx := test.MockContext(t, "admin/users/new") + + u := models.AssertExistsAndLoadBean(t, &models.User{ + IsAdmin: true, + ID: 2, + }).(*models.User) + + ctx.User = u + + username := "gitea" + email := "gitea@gitea.io" + + form := forms.AdminCreateUserForm{ + LoginType: "local", + LoginName: "local", + UserName: username, + Email: email, + Password: "abc123ABC!=$", + SendNotify: false, + MustChangePassword: false, + } + + web.SetForm(ctx, &form) + NewUserPost(ctx) + + assert.NotEmpty(t, ctx.Flash.SuccessMsg) + + u, err := models.GetUserByName(username) + + assert.NoError(t, err) + assert.Equal(t, username, u.Name) + assert.Equal(t, email, u.Email) + assert.False(t, u.HideFromExplorePage) +} + +func TestNewUserPost_HideFromExplorePageTrue(t *testing.T) { + + models.PrepareTestEnv(t) + ctx := test.MockContext(t, "admin/users/new") + + u := models.AssertExistsAndLoadBean(t, &models.User{ + IsAdmin: true, + ID: 2, + }).(*models.User) + + ctx.User = u + + username := "gitea" + email := "gitea@gitea.io" + + form := forms.AdminCreateUserForm{ + LoginType: "local", + LoginName: "local", + UserName: username, + Email: email, + Password: "abc123ABC!=$", + SendNotify: false, + MustChangePassword: false, + HideFromExplorePage: true, + } + + web.SetForm(ctx, &form) + NewUserPost(ctx) + + assert.NotEmpty(t, ctx.Flash.SuccessMsg) + + u, err := models.GetUserByName(username) + + assert.NoError(t, err) + assert.Equal(t, username, u.Name) + assert.Equal(t, email, u.Email) + assert.True(t, u.HideFromExplorePage) +} diff --git a/routers/api/v1/admin/user.go b/routers/api/v1/admin/user.go index a83c15fe6b55c..da29f3477a6c1 100644 --- a/routers/api/v1/admin/user.go +++ b/routers/api/v1/admin/user.go @@ -399,8 +399,6 @@ func GetAllUsers(ctx *context.APIContext) { // "403": // "$ref": "#/responses/forbidden" - log.Trace("GetAllUsers requested by admin(%s): is admin?= %ui", ctx.User.Name, ctx.User.IsAdmin) - listOptions := utils.GetListOptions(ctx) users, maxResults, err := models.SearchUsers(&models.SearchUserOptions{ diff --git a/routers/api/v1/user/user.go b/routers/api/v1/user/user.go index 6e811bf0f8a4d..df6acbd1050e3 100644 --- a/routers/api/v1/user/user.go +++ b/routers/api/v1/user/user.go @@ -58,6 +58,7 @@ func Search(ctx *context.APIContext) { listOptions := utils.GetListOptions(ctx) opts := &models.SearchUserOptions{ + Actor: ctx.User, Keyword: strings.Trim(ctx.Query("q"), " "), UID: ctx.QueryInt64("uid"), Type: models.UserTypeIndividual, From e1d99350ece566ebe7623ee677880fb7b5c08c0b Mon Sep 17 00:00:00 2001 From: Sergey Dryabzhinsky Date: Tue, 8 Jun 2021 14:57:27 +0300 Subject: [PATCH 26/67] Rename migration --- models/migrations/v182.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 models/migrations/v182.go diff --git a/models/migrations/v182.go b/models/migrations/v182.go new file mode 100644 index 0000000000000..44ef44f18267c --- /dev/null +++ b/models/migrations/v182.go @@ -0,0 +1,20 @@ +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package migrations + +import ( + "fmt" + + "xorm.io/xorm" +) + +func addHideFromExplorePageUserColumn(x *xorm.Engine) error { + type User struct { + HideFromExplorePage bool `xorm:"NOT NULL DEFAULT false"` + } + if err := x.Sync2(new(User)); err != nil { + return fmt.Errorf("Sync2: %v", err) + } + return nil +} From f9ba4f789e216b8e7c01a4643b95ad0f4eea8903 Mon Sep 17 00:00:00 2001 From: Sergey Dryabzhinsky Date: Wed, 9 Jun 2021 05:46:32 +0300 Subject: [PATCH 27/67] Mention API searches in new locale strings --- options/locale/locale_en-US.ini | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 992c96951a1fb..c3fb9052130a5 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -473,8 +473,8 @@ ui = Theme privacy = Privacy keep_activity_private = Hide the activity from the profile page keep_activity_private_popup = Makes the activity visible only for you and the admins -hide_from_explore_page = Hide me from public "explore users" page -hide_from_explore_page_popup = Make me visible only by mention in repo, commit, etc. And hide from public "explore users" page. +hide_from_explore_page = Hide me from public "explore users" and API. +hide_from_explore_page_popup = Make me visible only by mention in repo, commit, etc. And hide from public "explore users" page and public API searches. lookup_avatar_by_mail = Look Up Avatar by Email Address federated_avatar_lookup = Federated Avatar Lookup @@ -2238,8 +2238,8 @@ users.still_own_repo = This user still owns one or more repositories. Delete or users.still_has_org = This user is a member of an organization. Remove the user from any organizations first. users.deletion_success = The user account has been deleted. users.reset_2fa = Reset 2FA -users.hide_from_explore_page = User do not want to appear on service explore page -users.hide_from_explore_page_tooltip = Users may do not want to be anounced publicly. So they can be hided from that page. But they profile will be accesible by mentioning in commit, issue, etc. +users.hide_from_explore_page = User do not want to appear on service explore page and API. +users.hide_from_explore_page_tooltip = Users may do not want to be anounced publicly. So they can be hided from that page and API searches. But they profile will be accesible by mentioning in commit, issue, etc. emails.email_manage_panel = User Email Management emails.primary = Primary From 6bfa28e5d2555aa30d83a5cee02dacba41e8e632 Mon Sep 17 00:00:00 2001 From: Sergey Dryabzhinsky Date: Wed, 9 Jun 2021 05:56:55 +0300 Subject: [PATCH 28/67] Add note about visibility by admins --- options/locale/locale_en-US.ini | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index c3fb9052130a5..9a31a4841675c 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -473,8 +473,8 @@ ui = Theme privacy = Privacy keep_activity_private = Hide the activity from the profile page keep_activity_private_popup = Makes the activity visible only for you and the admins -hide_from_explore_page = Hide me from public "explore users" and API. -hide_from_explore_page_popup = Make me visible only by mention in repo, commit, etc. And hide from public "explore users" page and public API searches. +hide_from_explore_page = Hide me from public "explore users" page and API. +hide_from_explore_page_popup = Make me visible only by mention in repo, commit, etc. And to service admins. Hide from public "explore users" page and public API searches. lookup_avatar_by_mail = Look Up Avatar by Email Address federated_avatar_lookup = Federated Avatar Lookup @@ -2239,7 +2239,7 @@ users.still_has_org = This user is a member of an organization. Remove the user users.deletion_success = The user account has been deleted. users.reset_2fa = Reset 2FA users.hide_from_explore_page = User do not want to appear on service explore page and API. -users.hide_from_explore_page_tooltip = Users may do not want to be anounced publicly. So they can be hided from that page and API searches. But they profile will be accesible by mentioning in commit, issue, etc. +users.hide_from_explore_page_tooltip = Users may do not want to be anounced publicly. So they can be hided from that page and API searches. But they profiles will be accesible by mentioning in commit, issue, etc. And they stay visible for service admins. emails.email_manage_panel = User Email Management emails.primary = Primary From 7066c1f688d631b785cb5424620f1da928d65749 Mon Sep 17 00:00:00 2001 From: Sergey Dryabzhinsky Date: Thu, 10 Jun 2021 04:52:59 +0300 Subject: [PATCH 29/67] Rewrote user hiding based on Visibility field: - drop migration 182 - dont need - add app option DEFAULT_USER_VISIBILITY - update localization - rewrite many files to use Visibility field --- custom/conf/app.example.ini | 12 ++++-- .../doc/advanced/config-cheat-sheet.en-us.md | 5 ++- integrations/api_user_search_test.go | 2 +- models/fixtures/user.yml | 2 +- models/migrations/migrations.go | 2 - models/migrations/v182.go | 20 ---------- models/user.go | 37 ++++++++++--------- modules/convert/user.go | 3 +- modules/convert/user_test.go | 8 ++-- modules/setting/service.go | 4 ++ modules/structs/admin_user.go | 10 ++--- modules/structs/user.go | 2 +- options/locale/locale_en-US.ini | 14 +++++-- routers/api/v1/admin/user.go | 34 ++++++++++------- routers/web/admin/users.go | 18 +++++---- routers/web/admin/users_test.go | 24 ++++++------ routers/web/user/setting/profile.go | 2 +- services/forms/admin.go | 19 +++++----- services/forms/user_form.go | 3 +- templates/admin/user/edit.tmpl | 19 ++++++++-- templates/admin/user/new.tmpl | 19 ++++++++-- templates/swagger/v1_json.tmpl | 20 +++++----- templates/user/settings/profile.tmpl | 25 +++++++++---- 23 files changed, 173 insertions(+), 131 deletions(-) delete mode 100644 models/migrations/v182.go diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 2f3fc1f1d8504..5cb4b3aaaf713 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -631,6 +631,12 @@ PATH = ;; Limited is for signed user only ;; Private is only for member of the organization ;; Public is for everyone +;DEFAULT_USER_VISIBILITY = public +;; +;; 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 DefaultOrgMemberVisible @@ -1145,8 +1151,8 @@ PATH = ;; ;; When ISSUE_INDEXER_QUEUE_TYPE is levelqueue, this will be the path where the queue will be saved. ;; This can be overridden by `ISSUE_INDEXER_QUEUE_CONN_STR`. -;; default is queues/common -;ISSUE_INDEXER_QUEUE_DIR = queues/common +;; default is queues/common +;ISSUE_INDEXER_QUEUE_DIR = queues/common ;; ;; When `ISSUE_INDEXER_QUEUE_TYPE` is `redis`, this will store the redis connection string. ;; When `ISSUE_INDEXER_QUEUE_TYPE` is `levelqueue`, this is a directory or additional options of @@ -1201,7 +1207,7 @@ PATH = ;; default to persistable-channel ;TYPE = persistable-channel ;; -;; data-dir for storing persistable queues and level queues, individual queues will default to `queues/common` meaning the queue is shared. +;; data-dir for storing persistable queues and level queues, individual queues will default to `queues/common` meaning the queue is shared. ;DATADIR = queues/ ;; ;; Default queue length before a channel queue will block 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 28ab922c439eb..01b161650b621 100644 --- a/docs/content/doc/advanced/config-cheat-sheet.en-us.md +++ b/docs/content/doc/advanced/config-cheat-sheet.en-us.md @@ -350,7 +350,7 @@ relation to port exhaustion. - `ISSUE_INDEXER_PATH`: **indexers/issues.bleve**: Index file used for issue search; available when ISSUE_INDEXER_TYPE is bleve and elasticsearch. - The next 4 configuration values are deprecated and should be set in `queue.issue_indexer` however are kept for backwards compatibility: - `ISSUE_INDEXER_QUEUE_TYPE`: **levelqueue**: Issue indexer queue, currently supports:`channel`, `levelqueue`, `redis`. -- `ISSUE_INDEXER_QUEUE_DIR`: **queues/common**: When `ISSUE_INDEXER_QUEUE_TYPE` is `levelqueue`, this will be the path where the queue will be saved. (Previously this was `indexers/issues.queue`.) +- `ISSUE_INDEXER_QUEUE_DIR`: **queues/common**: When `ISSUE_INDEXER_QUEUE_TYPE` is `levelqueue`, this will be the path where the queue will be saved. (Previously this was `indexers/issues.queue`.) - `ISSUE_INDEXER_QUEUE_CONN_STR`: **addrs=127.0.0.1:6379 db=0**: When `ISSUE_INDEXER_QUEUE_TYPE` is `redis`, this will store the redis connection string. When `ISSUE_INDEXER_QUEUE_TYPE` is `levelqueue`, this is a directory or additional options of the form `leveldb://path/to/db?option=value&....`, and overrides `ISSUE_INDEXER_QUEUE_DIR`. - `ISSUE_INDEXER_QUEUE_BATCH_NUMBER`: **20**: Batch queue number. @@ -370,7 +370,7 @@ relation to port exhaustion. ## Queue (`queue` and `queue.*`) - `TYPE`: **persistable-channel**: General queue type, currently support: `persistable-channel` (uses a LevelDB internally), `channel`, `level`, `redis`, `dummy` -- `DATADIR`: **queues/**: Base DataDir for storing persistent and level queues. `DATADIR` for individual queues can be set in `queue.name` sections but will default to `DATADIR/`**`common`**. (Previously each queue would default to `DATADIR/`**`name`**.) +- `DATADIR`: **queues/**: Base DataDir for storing persistent and level queues. `DATADIR` for individual queues can be set in `queue.name` sections but will default to `DATADIR/`**`common`**. (Previously each queue would default to `DATADIR/`**`name`**.) - `LENGTH`: **20**: Maximal queue size before channel queues block - `BATCH_LENGTH`: **20**: Batch data before passing to the handler - `CONN_STR`: **redis://127.0.0.1:6379/0**: Connection string for the redis queue type. Options can be set using query params. Similarly LevelDB options can also be set using: **leveldb://relative/path?option=value** or **leveldb:///absolute/path?option=value**, and will override `DATADIR` @@ -504,6 +504,7 @@ relation to port exhaustion. - `SHOW_MILESTONES_DASHBOARD_PAGE`: **true** Enable this to show the milestones dashboard page - a view of all the user's milestones - `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". - `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/integrations/api_user_search_test.go b/integrations/api_user_search_test.go index 3ebd5938721fe..dc40ecea74fd2 100644 --- a/integrations/api_user_search_test.go +++ b/integrations/api_user_search_test.go @@ -76,7 +76,7 @@ func TestAPIUserSearchAdminLoggedInUserHidden(t *testing.T) { for _, user := range results.Data { assert.Contains(t, user.UserName, query) assert.NotEmpty(t, user.Email) - assert.True(t, user.HideFromExplorePage) + assert.True(t, user.Visibility == structs.VisibleTypePrivate.String()) } } diff --git a/models/fixtures/user.yml b/models/fixtures/user.yml index b7a324fcde98d..ae42d1f98e1a5 100644 --- a/models/fixtures/user.yml +++ b/models/fixtures/user.yml @@ -537,7 +537,7 @@ type: 0 # individual salt: ZogKvWdyEx is_admin: false - hide_from_explore_page: true + visibility: 2 avatar: avatar31 avatar_email: user31@example.com num_repos: 0 diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index fc33a6f48413c..df1bac4a13f3b 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -313,8 +313,6 @@ var migrations = []Migration{ NewMigration("Delete credentials from past migrations", deleteMigrationCredentials), // v181 -> v182 NewMigration("Always save primary email on email address table", addPrimaryEmail2EmailAddress), - // v182 -> v183 - NewMigration("Add column to User to allow them hide self from explore/users page", addHideFromExplorePageUserColumn), } // GetCurrentDBVersion returns the current db version diff --git a/models/migrations/v182.go b/models/migrations/v182.go deleted file mode 100644 index 44ef44f18267c..0000000000000 --- a/models/migrations/v182.go +++ /dev/null @@ -1,20 +0,0 @@ -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package migrations - -import ( - "fmt" - - "xorm.io/xorm" -) - -func addHideFromExplorePageUserColumn(x *xorm.Engine) error { - type User struct { - HideFromExplorePage bool `xorm:"NOT NULL DEFAULT false"` - } - if err := x.Sync2(new(User)); err != nil { - return fmt.Errorf("Sync2: %v", err) - } - return nil -} diff --git a/models/user.go b/models/user.go index b1adc825fdfc0..a20eb4b84cd24 100644 --- a/models/user.go +++ b/models/user.go @@ -163,8 +163,6 @@ type User struct { DiffViewStyle string `xorm:"NOT NULL DEFAULT ''"` Theme string `xorm:"NOT NULL DEFAULT ''"` KeepActivityPrivate bool `xorm:"NOT NULL DEFAULT false"` - // Users may don't want to appear on /explore/users page. - HideFromExplorePage bool `xorm:"NOT NULL DEFAULT false"` } // SearchOrganizationsOptions options to filter organizations @@ -887,6 +885,7 @@ func CreateUser(u *User) (err error) { } u.KeepEmailPrivate = setting.Service.DefaultKeepEmailPrivate + u.Visibility = setting.Service.DefaultUserVisibilityMode u.LowerName = strings.ToLower(u.Name) u.AvatarEmail = u.Email @@ -1582,10 +1581,9 @@ func (opts *SearchUserOptions) toConds() builder.Cond { cond = cond.And(keywordCond) } + // If visibility filtered if len(opts.Visible) > 0 { cond = cond.And(builder.In("visibility", opts.Visible)) - } else { - cond = cond.And(builder.In("visibility", structs.VisibleTypePublic)) } if opts.Actor != nil { @@ -1598,22 +1596,27 @@ func (opts *SearchUserOptions) toConds() builder.Cond { exprCond = builder.Expr("org_user.org_id = \"user\".id") } - var accessCond builder.Cond - if !opts.Actor.IsRestricted { - accessCond = builder.Or( - builder.In("id", builder.Select("org_id").From("org_user").LeftJoin("`user`", exprCond).Where(builder.And(builder.Eq{"uid": opts.Actor.ID}, builder.Eq{"visibility": structs.VisibleTypePrivate}))), - builder.In("visibility", structs.VisibleTypePublic, structs.VisibleTypeLimited)) - } else { - // restricted users only see orgs they are a member of - accessCond = builder.In("id", builder.Select("org_id").From("org_user").LeftJoin("`user`", exprCond).Where(builder.And(builder.Eq{"uid": opts.Actor.ID}))) - } - cond = cond.And(accessCond) - + // If Admin - they see all users! if !opts.Actor.IsAdmin { - cond = cond.And(builder.Eq{"hide_from_explore_page": false}.Or(builder.Eq{"id": opts.Actor.ID})) + // Force visiblity for privacy + var accessCond builder.Cond + if !opts.Actor.IsRestricted { + accessCond = builder.Or( + builder.In("id", builder.Select("org_id").From("org_user").LeftJoin("`user`", exprCond).Where(builder.And(builder.Eq{"uid": opts.Actor.ID}, builder.Eq{"visibility": structs.VisibleTypePrivate}))), + builder.In("visibility", structs.VisibleTypePublic, structs.VisibleTypeLimited)) + } else { + // restricted users only see orgs they are a member of + accessCond = builder.In("id", builder.Select("org_id").From("org_user").LeftJoin("`user`", exprCond).Where(builder.And(builder.Eq{"uid": opts.Actor.ID}))) + } + // Don't forget about self + accessCond = accessCond.Or(builder.Eq{"id": opts.Actor.ID}) + cond = cond.And(accessCond) } + } else { - cond = cond.And(builder.Eq{"hide_from_explore_page": false}) + // Force visiblity for privacy + // Not logged in - only public users + cond = cond.And(builder.In("visibility", structs.VisibleTypePublic)) } if opts.UID > 0 { diff --git a/modules/convert/user.go b/modules/convert/user.go index dede9f32925f8..1b2efa5d94389 100644 --- a/modules/convert/user.go +++ b/modules/convert/user.go @@ -53,10 +53,11 @@ func toUser(user *models.User, signed, authed bool) *api.User { if signed && (!user.KeepEmailPrivate || authed) { result.Email = user.Email } + // only site admin will get these information and possibly user himself if authed { result.IsAdmin = user.IsAdmin - result.HideFromExplorePage = user.HideFromExplorePage + result.Visibility = user.Visibility.String() result.LastLogin = user.LastLoginUnix.AsTime() result.Language = user.Language result.IsActive = user.IsActive diff --git a/modules/convert/user_test.go b/modules/convert/user_test.go index 0fcde5f922ffe..0f56140d4bad0 100644 --- a/modules/convert/user_test.go +++ b/modules/convert/user_test.go @@ -8,6 +8,7 @@ import ( "testing" "code.gitea.io/gitea/models" + api "code.gitea.io/gitea/modules/structs" "github.com/stretchr/testify/assert" ) @@ -27,12 +28,11 @@ func TestUser_ToUser(t *testing.T) { apiUser = toUser(user1, false, false) assert.False(t, apiUser.IsAdmin) - assert.False(t, apiUser.HideFromExplorePage) + assert.False(t, apiUser.Visibility == api.VisibleTypePublic.String()) - user31 := models.AssertExistsAndLoadBean(t, &models.User{ID: 31, IsAdmin: false, HideFromExplorePage: true}).(*models.User) + user31 := models.AssertExistsAndLoadBean(t, &models.User{ID: 31, IsAdmin: false, Visibility: api.VisibleTypePrivate}).(*models.User) apiUser = toUser(user31, true, true) assert.False(t, apiUser.IsAdmin) - assert.True(t, apiUser.HideFromExplorePage) - + assert.True(t, apiUser.Visibility == api.VisibleTypePrivate.String()) } diff --git a/modules/setting/service.go b/modules/setting/service.go index 41e834e8e61ef..66e1701d427e8 100644 --- a/modules/setting/service.go +++ b/modules/setting/service.go @@ -14,6 +14,8 @@ import ( // Service settings var Service struct { + DefaultUserVisibility string + DefaultUserVisibilityMode structs.VisibleType DefaultOrgVisibility string DefaultOrgVisibilityMode structs.VisibleType ActiveCodeLives int @@ -116,6 +118,8 @@ func newService() { Service.EnableUserHeatmap = sec.Key("ENABLE_USER_HEATMAP").MustBool(true) Service.AutoWatchNewRepos = sec.Key("AUTO_WATCH_NEW_REPOS").MustBool(true) 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.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/modules/structs/admin_user.go b/modules/structs/admin_user.go index 8fd80bc3d2034..facf16a39552a 100644 --- a/modules/structs/admin_user.go +++ b/modules/structs/admin_user.go @@ -16,10 +16,10 @@ type CreateUserOption struct { // swagger:strfmt email Email string `json:"email" binding:"Required;Email;MaxSize(254)"` // required: true - Password string `json:"password" binding:"Required;MaxSize(255)"` - MustChangePassword *bool `json:"must_change_password"` - SendNotify bool `json:"send_notify"` - HideFromExplorePage *bool `json:"hide_from_explore_page"` + Password string `json:"password" binding:"Required;MaxSize(255)"` + MustChangePassword *bool `json:"must_change_password"` + SendNotify bool `json:"send_notify"` + Visibility string `json:"visibility" binding:"In(,public,limited,private)"` } // EditUserOption edit user options @@ -44,5 +44,5 @@ type EditUserOption struct { ProhibitLogin *bool `json:"prohibit_login"` AllowCreateOrganization *bool `json:"allow_create_organization"` Restricted *bool `json:"restricted"` - HideFromExplorePage *bool `json:"hide_from_explore_page"` + Visibility string `json:"visibility" binding:"In(,public,limited,private)"` } diff --git a/modules/structs/user.go b/modules/structs/user.go index 6bbb8dc3d6ac8..08caf5827eeae 100644 --- a/modules/structs/user.go +++ b/modules/structs/user.go @@ -44,7 +44,7 @@ type User struct { // the user's description Description string `json:"description"` // user hides from public - HideFromExplorePage bool `json:"hide_from_explore_page"` + Visibility string `json:"visibility" binding:"In(,public,limited,private)"` } // MarshalJSON implements the json.Marshaler interface for User, adding field(s) for backward compatibility diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 9a31a4841675c..e0669c3b1d8ce 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -434,6 +434,11 @@ form.name_reserved = The username '%s' is reserved. form.name_pattern_not_allowed = The pattern '%s' is not allowed in a username. form.name_chars_not_allowed = User name '%s' contains invalid characters. +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] profile = Profile account = Account @@ -473,8 +478,6 @@ ui = Theme privacy = Privacy keep_activity_private = Hide the activity from the profile page keep_activity_private_popup = Makes the activity visible only for you and the admins -hide_from_explore_page = Hide me from public "explore users" page and API. -hide_from_explore_page_popup = Make me visible only by mention in repo, commit, etc. And to service admins. Hide from public "explore users" page and public API searches. lookup_avatar_by_mail = Look Up Avatar by Email Address federated_avatar_lookup = Federated Avatar Lookup @@ -2238,8 +2241,11 @@ users.still_own_repo = This user still owns one or more repositories. Delete or users.still_has_org = This user is a member of an organization. Remove the user from any organizations first. users.deletion_success = The user account has been deleted. users.reset_2fa = Reset 2FA -users.hide_from_explore_page = User do not want to appear on service explore page and API. -users.hide_from_explore_page_tooltip = Users may do not want to be anounced publicly. So they can be hided from that page and API searches. But they profiles will be accesible by mentioning in commit, issue, etc. And they stay visible for service admins. +users.settings.visibility = Visibility +users.settings.visibility.public = Public +users.settings.visibility.limited = Limited (Visible to logged in users only) +users.settings.visibility.private = Private (Visible only to organization members) + emails.email_manage_panel = User Email Management emails.primary = Primary diff --git a/routers/api/v1/admin/user.go b/routers/api/v1/admin/user.go index da29f3477a6c1..f09276427e6c2 100644 --- a/routers/api/v1/admin/user.go +++ b/routers/api/v1/admin/user.go @@ -66,22 +66,25 @@ func CreateUser(ctx *context.APIContext) { // "422": // "$ref": "#/responses/validationError" form := web.GetForm(ctx).(*api.CreateUserOption) + + visibility := api.VisibleTypePublic + if form.Visibility != "" { + visibility = api.VisibilityModes[form.Visibility] + } + u := &models.User{ - Name: form.Username, - FullName: form.FullName, - Email: form.Email, - Passwd: form.Password, - MustChangePassword: true, - IsActive: true, - LoginType: models.LoginPlain, - HideFromExplorePage: false, + Name: form.Username, + FullName: form.FullName, + Email: form.Email, + Passwd: form.Password, + MustChangePassword: true, + IsActive: true, + LoginType: models.LoginPlain, + Visibility: visibility, } if form.MustChangePassword != nil { u.MustChangePassword = *form.MustChangePassword } - if form.HideFromExplorePage != nil { - u.HideFromExplorePage = *form.HideFromExplorePage - } parseLoginSource(ctx, u, form.SourceID, form.LoginName) if ctx.Written() { @@ -160,6 +163,12 @@ func EditUser(ctx *context.APIContext) { return } + visibility := api.VisibleTypePublic + if form.Visibility != "" { + visibility = api.VisibilityModes[form.Visibility] + } + u.Visibility = visibility + if len(form.Password) != 0 { if !password.IsComplexEnough(form.Password) { err := errors.New("PasswordComplexity") @@ -234,9 +243,6 @@ func EditUser(ctx *context.APIContext) { if form.Restricted != nil { u.IsRestricted = *form.Restricted } - if form.HideFromExplorePage != nil { - u.HideFromExplorePage = *form.HideFromExplorePage - } if err := models.UpdateUser(u); err != nil { if models.IsErrEmailAlreadyUsed(err) || models.IsErrEmailInvalid(err) { diff --git a/routers/web/admin/users.go b/routers/web/admin/users.go index 6476ac8ecc7e5..56cd53eef0f94 100644 --- a/routers/web/admin/users.go +++ b/routers/web/admin/users.go @@ -51,6 +51,7 @@ func NewUser(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("admin.users.new_account") ctx.Data["PageIsAdmin"] = true ctx.Data["PageIsAdminUsers"] = true + ctx.Data["DefaultUserVisibilityMode"] = setting.Service.DefaultUserVisibilityMode ctx.Data["login_type"] = "0-0" @@ -71,6 +72,7 @@ func NewUserPost(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("admin.users.new_account") ctx.Data["PageIsAdmin"] = true ctx.Data["PageIsAdminUsers"] = true + ctx.Data["DefaultUserVisibilityMode"] = setting.Service.DefaultUserVisibilityMode sources, err := models.LoginSources() if err != nil { @@ -87,11 +89,12 @@ func NewUserPost(ctx *context.Context) { } u := &models.User{ - Name: form.UserName, - Email: form.Email, - Passwd: form.Password, - IsActive: true, - LoginType: models.LoginPlain, + Name: form.UserName, + Email: form.Email, + Passwd: form.Password, + IsActive: true, + LoginType: models.LoginPlain, + Visibility: form.Visibility, } if len(form.LoginType) > 0 { @@ -128,8 +131,6 @@ func NewUserPost(ctx *context.Context) { u.MustChangePassword = form.MustChangePassword } - u.HideFromExplorePage = form.HideFromExplorePage - if err := models.CreateUser(u); err != nil { switch { case models.IsErrUserAlreadyExist(err): @@ -315,7 +316,8 @@ func EditUserPost(ctx *context.Context) { u.AllowGitHook = form.AllowGitHook u.AllowImportLocal = form.AllowImportLocal u.AllowCreateOrganization = form.AllowCreateOrganization - u.HideFromExplorePage = form.HideFromExplorePage + + u.Visibility = form.Visibility // skip self Prohibit Login if ctx.User.ID == u.ID { diff --git a/routers/web/admin/users_test.go b/routers/web/admin/users_test.go index 1d198f8789420..d3db9b6a08c51 100644 --- a/routers/web/admin/users_test.go +++ b/routers/web/admin/users_test.go @@ -122,7 +122,7 @@ func TestNewUserPost_InvalidEmail(t *testing.T) { assert.NotEmpty(t, ctx.Flash.ErrorMsg) } -func TestNewUserPost_HideFromExplorePageDefaultFalse(t *testing.T) { +func TestNewUserPost_VisiblityDefaultPublic(t *testing.T) { models.PrepareTestEnv(t) ctx := test.MockContext(t, "admin/users/new") @@ -157,10 +157,10 @@ func TestNewUserPost_HideFromExplorePageDefaultFalse(t *testing.T) { assert.NoError(t, err) assert.Equal(t, username, u.Name) assert.Equal(t, email, u.Email) - assert.False(t, u.HideFromExplorePage) + assert.False(t, u.Visibility == api.VisibleTypePublic) } -func TestNewUserPost_HideFromExplorePageTrue(t *testing.T) { +func TestNewUserPost_VisibilityPrivate(t *testing.T) { models.PrepareTestEnv(t) ctx := test.MockContext(t, "admin/users/new") @@ -176,14 +176,14 @@ func TestNewUserPost_HideFromExplorePageTrue(t *testing.T) { email := "gitea@gitea.io" form := forms.AdminCreateUserForm{ - LoginType: "local", - LoginName: "local", - UserName: username, - Email: email, - Password: "abc123ABC!=$", - SendNotify: false, - MustChangePassword: false, - HideFromExplorePage: true, + LoginType: "local", + LoginName: "local", + UserName: username, + Email: email, + Password: "abc123ABC!=$", + SendNotify: false, + MustChangePassword: false, + Visibility: api.VisibleTypePrivate, } web.SetForm(ctx, &form) @@ -196,5 +196,5 @@ func TestNewUserPost_HideFromExplorePageTrue(t *testing.T) { assert.NoError(t, err) assert.Equal(t, username, u.Name) assert.Equal(t, email, u.Email) - assert.True(t, u.HideFromExplorePage) + assert.True(t, u.Visibility == api.VisibleTypePrivate) } diff --git a/routers/web/user/setting/profile.go b/routers/web/user/setting/profile.go index 5dc54b5ece5f5..463c4ec2038c8 100644 --- a/routers/web/user/setting/profile.go +++ b/routers/web/user/setting/profile.go @@ -114,7 +114,7 @@ func ProfilePost(ctx *context.Context) { } ctx.User.Description = form.Description ctx.User.KeepActivityPrivate = form.KeepActivityPrivate - ctx.User.HideFromExplorePage = form.HideFromExplorePage + ctx.User.Visibility = form.Visibility if err := models.UpdateUserSetting(ctx.User); err != nil { if _, ok := err.(models.ErrEmailAlreadyUsed); ok { ctx.Flash.Error(ctx.Tr("form.email_been_used")) diff --git a/services/forms/admin.go b/services/forms/admin.go index 58979237f1682..5abef0550e39a 100644 --- a/services/forms/admin.go +++ b/services/forms/admin.go @@ -8,6 +8,7 @@ import ( "net/http" "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/web/middleware" "gitea.com/go-chi/binding" @@ -15,14 +16,14 @@ import ( // AdminCreateUserForm form for admin to create user type AdminCreateUserForm struct { - LoginType string `binding:"Required"` - LoginName string - UserName string `binding:"Required;AlphaDashDot;MaxSize(40)"` - Email string `binding:"Required;Email;MaxSize(254)"` - Password string `binding:"MaxSize(255)"` - SendNotify bool - MustChangePassword bool - HideFromExplorePage bool + LoginType string `binding:"Required"` + LoginName string + UserName string `binding:"Required;AlphaDashDot;MaxSize(40)"` + Email string `binding:"Required;Email;MaxSize(254)"` + Password string `binding:"MaxSize(255)"` + SendNotify bool + MustChangePassword bool + Visibility structs.VisibleType } // Validate validates form fields @@ -50,7 +51,7 @@ type AdminEditUserForm struct { AllowCreateOrganization bool ProhibitLogin bool Reset2FA bool `form:"reset_2fa"` - HideFromExplorePage bool + Visibility structs.VisibleType } // Validate validates form fields diff --git a/services/forms/user_form.go b/services/forms/user_form.go index 6378666dc01db..d7db3f70f4a4b 100644 --- a/services/forms/user_form.go +++ b/services/forms/user_form.go @@ -12,6 +12,7 @@ import ( "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/web/middleware" "gitea.com/go-chi/binding" @@ -230,8 +231,8 @@ type UpdateProfileForm struct { Location string `binding:"MaxSize(50)"` Language string Description string `binding:"MaxSize(255)"` + Visibility structs.VisibleType KeepActivityPrivate bool - HideFromExplorePage bool } // Validate validates the fields diff --git a/templates/admin/user/edit.tmpl b/templates/admin/user/edit.tmpl index 92eb2c9b3b8b5..3b86eff9146a4 100644 --- a/templates/admin/user/edit.tmpl +++ b/templates/admin/user/edit.tmpl @@ -120,10 +120,21 @@ {{end}} -
-
- - +
+ +
+
+ + +
+
+ + +
+
+ + +
diff --git a/templates/admin/user/new.tmpl b/templates/admin/user/new.tmpl index bcd4f9f52c082..01271fab16bed 100644 --- a/templates/admin/user/new.tmpl +++ b/templates/admin/user/new.tmpl @@ -59,10 +59,21 @@
{{end}} -
-
- - +
+ +
+
+ + +
+
+ + +
+
+ + +
diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index ebc2889eab8df..08df1a6519431 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -13014,9 +13014,9 @@ "type": "string", "x-go-name": "FullName" }, - "hide_from_explore_page": { - "type": "boolean", - "x-go-name": "HideFromExplorePage" + "visibility": { + "type": "string", + "x-go-name": "Visibility" }, "login_name": { "type": "string", @@ -13817,9 +13817,9 @@ "type": "string", "x-go-name": "FullName" }, - "hide_from_explore_page": { - "type": "boolean", - "x-go-name": "HideFromExplorePage" + "visibility": { + "type": "string", + "x-go-name": "Visibility" }, "location": { "type": "string", @@ -16288,10 +16288,10 @@ "type": "string", "x-go-name": "FullName" }, - "hide_from_explore_page": { - "description": "user hides from public", - "type": "boolean", - "x-go-name": "HideFromExplorePage" + "visibility": { + "description": "prefer user visibility option", + "type": "string", + "x-go-name": "Visibility" }, "id": { "description": "the user's id", diff --git a/templates/user/settings/profile.tmpl b/templates/user/settings/profile.tmpl index 11c0c35291ccc..01b8ddced4c41 100644 --- a/templates/user/settings/profile.tmpl +++ b/templates/user/settings/profile.tmpl @@ -61,6 +61,24 @@
+
+ +
+
+ + +
+
+ + +
+
+ + +
+
+
+
@@ -68,13 +86,6 @@
-
- -
- - -
-
From fdf1f8e2c8e98d33dea456e5ffc993253b763cdc Mon Sep 17 00:00:00 2001 From: Sergey Dryabzhinsky Date: Thu, 10 Jun 2021 05:31:06 +0300 Subject: [PATCH 30/67] Fix lint errors --- integrations/api_user_search_test.go | 2 +- routers/web/admin/users_test.go | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/integrations/api_user_search_test.go b/integrations/api_user_search_test.go index dc40ecea74fd2..49a073b800759 100644 --- a/integrations/api_user_search_test.go +++ b/integrations/api_user_search_test.go @@ -76,7 +76,7 @@ func TestAPIUserSearchAdminLoggedInUserHidden(t *testing.T) { for _, user := range results.Data { assert.Contains(t, user.UserName, query) assert.NotEmpty(t, user.Email) - assert.True(t, user.Visibility == structs.VisibleTypePrivate.String()) + assert.True(t, user.Visibility == api.VisibleTypePrivate.String()) } } diff --git a/routers/web/admin/users_test.go b/routers/web/admin/users_test.go index d3db9b6a08c51..d6cb835400dc6 100644 --- a/routers/web/admin/users_test.go +++ b/routers/web/admin/users_test.go @@ -8,6 +8,7 @@ import ( "testing" "code.gitea.io/gitea/models" + api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/services/forms" From c80d1c8464a1394d97930f4654ef1182a70807cf Mon Sep 17 00:00:00 2001 From: Sergey Dryabzhinsky Date: Thu, 10 Jun 2021 06:29:02 +0300 Subject: [PATCH 31/67] One more step to use `Visibility` field: - change radio-inputs to select - update swagger template --- modules/structs/user.go | 2 +- options/locale/locale_en-US.ini | 18 ++++++--- routers/web/user/setting/profile.go | 5 ++- services/forms/user_form.go | 3 +- templates/admin/user/edit.tmpl | 37 ++++++++++--------- templates/admin/user/new.tmpl | 37 ++++++++++--------- templates/swagger/v1_json.tmpl | 26 ++++++------- templates/user/settings/profile.tmpl | 55 ++++++++++++++++------------ 8 files changed, 101 insertions(+), 82 deletions(-) diff --git a/modules/structs/user.go b/modules/structs/user.go index 08caf5827eeae..ffed947d068f6 100644 --- a/modules/structs/user.go +++ b/modules/structs/user.go @@ -43,7 +43,7 @@ type User struct { Website string `json:"website"` // the user's description Description string `json:"description"` - // user hides from public + // User visibility level option Visibility string `json:"visibility" binding:"In(,public,limited,private)"` } diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index e0669c3b1d8ce..09b76c2b9157c 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -434,10 +434,13 @@ form.name_reserved = The username '%s' is reserved. form.name_pattern_not_allowed = The pattern '%s' is not allowed in a username. form.name_chars_not_allowed = User name '%s' contains invalid characters. -settings.visibility = Visibility +settings.visibility = User 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.visibility.public_tooltip = Visible to all users +settings.visibility.limited = Limited +settings.visibility.limited_tooltip = Visible to logged in users only +settings.visibility.private = Private +settings.visibility.private_tooltip = Visible only to organization members [settings] profile = Profile @@ -2241,10 +2244,13 @@ users.still_own_repo = This user still owns one or more repositories. Delete or users.still_has_org = This user is a member of an organization. Remove the user from any organizations first. users.deletion_success = The user account has been deleted. users.reset_2fa = Reset 2FA -users.settings.visibility = Visibility +users.settings.visibility = User visibility users.settings.visibility.public = Public -users.settings.visibility.limited = Limited (Visible to logged in users only) -users.settings.visibility.private = Private (Visible only to organization members) +users.settings.visibility.public_tooltip = Visible to all users +users.settings.visibility.limited = Limited +users.settings.visibility.limited_tooltip = Visible to logged in users only +users.settings.visibility.private = Private +users.settings.visibility.private_tooltip = Visible only to organization members emails.email_manage_panel = User Email Management diff --git a/routers/web/user/setting/profile.go b/routers/web/user/setting/profile.go index 463c4ec2038c8..7947c93e2fba5 100644 --- a/routers/web/user/setting/profile.go +++ b/routers/web/user/setting/profile.go @@ -19,6 +19,7 @@ import ( "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" + api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/typesniffer" "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/web" @@ -114,7 +115,9 @@ func ProfilePost(ctx *context.Context) { } ctx.User.Description = form.Description ctx.User.KeepActivityPrivate = form.KeepActivityPrivate - ctx.User.Visibility = form.Visibility + if form.Visibility != "" { + ctx.User.Visibility = api.VisibilityModes[form.Visibility] + } if err := models.UpdateUserSetting(ctx.User); err != nil { if _, ok := err.(models.ErrEmailAlreadyUsed); ok { ctx.Flash.Error(ctx.Tr("form.email_been_used")) diff --git a/services/forms/user_form.go b/services/forms/user_form.go index d7db3f70f4a4b..727c637d545ef 100644 --- a/services/forms/user_form.go +++ b/services/forms/user_form.go @@ -12,7 +12,6 @@ import ( "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/web/middleware" "gitea.com/go-chi/binding" @@ -231,7 +230,7 @@ type UpdateProfileForm struct { Location string `binding:"MaxSize(50)"` Language string Description string `binding:"MaxSize(255)"` - Visibility structs.VisibleType + Visibility string KeepActivityPrivate bool } diff --git a/templates/admin/user/edit.tmpl b/templates/admin/user/edit.tmpl index 3b86eff9146a4..403750a7d978e 100644 --- a/templates/admin/user/edit.tmpl +++ b/templates/admin/user/edit.tmpl @@ -28,6 +28,25 @@
+ +
+ + +
+
@@ -120,24 +139,6 @@
{{end}} -
- -
-
- - -
-
- - -
-
- - -
-
-
-
diff --git a/templates/admin/user/new.tmpl b/templates/admin/user/new.tmpl index 01271fab16bed..2e844a7982b06 100644 --- a/templates/admin/user/new.tmpl +++ b/templates/admin/user/new.tmpl @@ -24,6 +24,25 @@
+ +
+ + +
+
@@ -59,24 +78,6 @@
{{end}} -
- -
-
- - -
-
- - -
-
- - -
-
-
-
diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index 08df1a6519431..35e5961cb9992 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -13014,10 +13014,6 @@ "type": "string", "x-go-name": "FullName" }, - "visibility": { - "type": "string", - "x-go-name": "Visibility" - }, "login_name": { "type": "string", "x-go-name": "LoginName" @@ -13042,6 +13038,10 @@ "username": { "type": "string", "x-go-name": "Username" + }, + "visibility": { + "type": "string", + "x-go-name": "Visibility" } }, "x-go-package": "code.gitea.io/gitea/modules/structs" @@ -13817,10 +13817,6 @@ "type": "string", "x-go-name": "FullName" }, - "visibility": { - "type": "string", - "x-go-name": "Visibility" - }, "location": { "type": "string", "x-go-name": "Location" @@ -13855,6 +13851,10 @@ "format": "int64", "x-go-name": "SourceID" }, + "visibility": { + "type": "string", + "x-go-name": "Visibility" + }, "website": { "type": "string", "x-go-name": "Website" @@ -16288,11 +16288,6 @@ "type": "string", "x-go-name": "FullName" }, - "visibility": { - "description": "prefer user visibility option", - "type": "string", - "x-go-name": "Visibility" - }, "id": { "description": "the user's id", "type": "integer", @@ -16334,6 +16329,11 @@ "type": "boolean", "x-go-name": "Restricted" }, + "visibility": { + "description": "User visibility level option", + "type": "string", + "x-go-name": "Visibility" + }, "website": { "description": "the user's website", "type": "string", diff --git a/templates/user/settings/profile.tmpl b/templates/user/settings/profile.tmpl index 01b8ddced4c41..319a5d3304ea5 100644 --- a/templates/user/settings/profile.tmpl +++ b/templates/user/settings/profile.tmpl @@ -47,45 +47,54 @@ -
- -