Add Visible modes function from Organisation to Users too (#16069)

You can limit or hide organisations. This pull make it also posible for users

- new strings to translte
- add checkbox to user profile form
- add checkbox to admin user.edit form
- filter explore page user search
- filter api admin and public user searches
- allow admins view "hidden" users
- add app option DEFAULT_USER_VISIBILITY
- rewrite many files to use Visibility field
- check for teams intersection
- fix context output
- right fake 404 if not visible

Co-authored-by: 6543 <6543@obermui.de>
Co-authored-by: Andrew Thornton <art27@cantab.net>
This commit is contained in:
Sergey Dryabzhinsky 2021-06-26 22:53:14 +03:00 committed by GitHub
parent 19ac575d57
commit 22a0636544
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
32 changed files with 440 additions and 68 deletions

View file

@ -66,6 +66,7 @@ func CreateUser(ctx *context.APIContext) {
// "422":
// "$ref": "#/responses/validationError"
form := web.GetForm(ctx).(*api.CreateUserOption)
u := &models.User{
Name: form.Username,
FullName: form.FullName,
@ -97,7 +98,15 @@ func CreateUser(ctx *context.APIContext) {
ctx.Error(http.StatusBadRequest, "PasswordPwned", errors.New("PasswordPwned"))
return
}
if err := models.CreateUser(u); err != nil {
var overwriteDefault *models.CreateUserOverwriteOptions
if form.Visibility != "" {
overwriteDefault = &models.CreateUserOverwriteOptions{
Visibility: api.VisibilityModes[form.Visibility],
}
}
if err := models.CreateUser(u, overwriteDefault); err != nil {
if models.IsErrUserAlreadyExist(err) ||
models.IsErrEmailAlreadyUsed(err) ||
models.IsErrNameReserved(err) ||
@ -209,6 +218,9 @@ func EditUser(ctx *context.APIContext) {
if form.Active != nil {
u.IsActive = *form.Active
}
if len(form.Visibility) != 0 {
u.Visibility = api.VisibilityModes[form.Visibility]
}
if form.Admin != nil {
u.IsAdmin = *form.Admin
}
@ -395,6 +407,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,

View file

@ -225,8 +225,8 @@ func Get(ctx *context.APIContext) {
// "200":
// "$ref": "#/responses/Organization"
if !models.HasOrgVisible(ctx.Org.Organization, ctx.User) {
ctx.NotFound("HasOrgVisible", nil)
if !models.HasOrgOrUserVisible(ctx.Org.Organization, ctx.User) {
ctx.NotFound("HasOrgOrUserVisible", nil)
return
}
ctx.JSON(http.StatusOK, convert.ToOrganization(ctx.Org.Organization))

View file

@ -375,8 +375,8 @@ func CreateOrgRepo(ctx *context.APIContext) {
return
}
if !models.HasOrgVisible(org, ctx.User) {
ctx.NotFound("HasOrgVisible", nil)
if !models.HasOrgOrUserVisible(org, ctx.User) {
ctx.NotFound("HasOrgOrUserVisible", nil)
return
}

View file

@ -17,7 +17,7 @@ func GetUserByParamsName(ctx *context.APIContext, name string) *models.User {
user, err := models.GetUserByName(username)
if err != nil {
if models.IsErrUserNotExist(err) {
if redirectUserID, err := models.LookupUserRedirect(username); err == nil {
if redirectUserID, err2 := models.LookupUserRedirect(username); err2 == nil {
context.RedirectToUser(ctx.Context, username, redirectUserID)
} else {
ctx.NotFound("GetUserByName", err)

View file

@ -57,6 +57,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,
@ -102,10 +103,16 @@ func GetInfo(ctx *context.APIContext) {
// "$ref": "#/responses/notFound"
u := GetUserByParams(ctx)
if ctx.Written() {
return
}
if !u.IsVisibleToUser(ctx.User) {
// fake ErrUserNotExist error message to not leak information about existence
ctx.NotFound("GetUserByName", models.ErrUserNotExist{Name: ctx.Params(":username")})
return
}
ctx.JSON(http.StatusOK, convert.ToUser(u, ctx.User))
}

View file

@ -25,7 +25,8 @@ func Organizations(ctx *context.Context) {
ctx.Data["PageIsAdminOrganizations"] = true
explore.RenderUserSearch(ctx, &models.SearchUserOptions{
Type: models.UserTypeOrganization,
Actor: ctx.User,
Type: models.UserTypeOrganization,
ListOptions: models.ListOptions{
PageSize: setting.UI.Admin.OrgPagingNum,
},

View file

@ -37,7 +37,8 @@ func Users(ctx *context.Context) {
ctx.Data["PageIsAdminUsers"] = true
explore.RenderUserSearch(ctx, &models.SearchUserOptions{
Type: models.UserTypeIndividual,
Actor: ctx.User,
Type: models.UserTypeIndividual,
ListOptions: models.ListOptions{
PageSize: setting.UI.Admin.UserPagingNum,
},
@ -50,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"
@ -70,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 {
@ -126,7 +129,8 @@ func NewUserPost(ctx *context.Context) {
}
u.MustChangePassword = form.MustChangePassword
}
if err := models.CreateUser(u); err != nil {
if err := models.CreateUser(u, &models.CreateUserOverwriteOptions{Visibility: form.Visibility}); err != nil {
switch {
case models.IsErrUserAlreadyExist(err):
ctx.Data["Err_UserName"] = true
@ -312,6 +316,8 @@ func EditUserPost(ctx *context.Context) {
u.AllowImportLocal = form.AllowImportLocal
u.AllowCreateOrganization = form.AllowCreateOrganization
u.Visibility = form.Visibility
// skip self Prohibit Login
if ctx.User.ID == u.ID {
u.ProhibitLogin = false

View file

@ -8,6 +8,8 @@ import (
"testing"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/setting"
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"
@ -121,3 +123,82 @@ func TestNewUserPost_InvalidEmail(t *testing.T) {
assert.NotEmpty(t, ctx.Flash.ErrorMsg)
}
func TestNewUserPost_VisiblityDefaultPublic(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)
// As default user visibility
assert.Equal(t, setting.Service.DefaultUserVisibilityMode, u.Visibility)
}
func TestNewUserPost_VisibilityPrivate(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,
Visibility: api.VisibleTypePrivate,
}
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)
// As default user visibility
assert.True(t, u.Visibility.IsPrivate())
}

View file

@ -30,8 +30,8 @@ func Home(ctx *context.Context) {
org := ctx.Org.Organization
if !models.HasOrgVisible(org, ctx.User) {
ctx.NotFound("HasOrgVisible", nil)
if !models.HasOrgOrUserVisible(org, ctx.User) {
ctx.NotFound("HasOrgOrUserVisible", nil)
return
}

View file

@ -75,6 +75,17 @@ func Profile(ctx *context.Context) {
return
}
if ctxUser.IsOrganization() {
org.Home(ctx)
return
}
// check view permissions
if !ctxUser.IsVisibleToUser(ctx.User) {
ctx.NotFound("user", fmt.Errorf(uname))
return
}
// Show SSH keys.
if isShowKeys {
ShowSSHKeys(ctx, ctxUser.ID)
@ -87,11 +98,6 @@ func Profile(ctx *context.Context) {
return
}
if ctxUser.IsOrganization() {
org.Home(ctx)
return
}
// Show OpenID URIs
openIDs, err := models.GetUserOpenIDs(ctxUser.ID)
if err != nil {

View file

@ -114,6 +114,7 @@ func ProfilePost(ctx *context.Context) {
}
ctx.User.Description = form.Description
ctx.User.KeepActivityPrivate = form.KeepActivityPrivate
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"))