Compare commits

...

9 Commits

Author SHA1 Message Date
Mark Wylde
4ee9e17cec
Merge a8efed8c627f876c8e6112f4978844763dccd4bc into 97fc87af89a03470999a1a68e293417e36bd9b52 2025-07-02 20:24:34 -05:00
GiteaBot
97fc87af89 [skip ci] Updated translations via Crowdin 2025-07-03 00:37:58 +00:00
silverwind
6fe5c4c4d9
Exclude devtest.ts from tailwindcss (#34935)
Fix this leftover from the typescript migration.
2025-07-02 18:00:16 -04:00
GiteaBot
dd1fd89185 [skip ci] Updated translations via Crowdin 2025-07-02 00:37:55 +00:00
Mark Wylde
a8efed8c62
Merge upstream/main and resolve conflicts 2025-06-21 23:13:51 +01:00
Mark Wylde
4211016c85
refactor for dangerzone 2025-06-21 23:12:09 +01:00
Mark Wylde
b711e5fd91
Merge branch 'main' into add-allowed-org-visibility-modes 2025-04-26 18:10:19 +01:00
Mark Wylde
93dd2042ab
Add ALLOWED_ORG_VISIBILITY_MODES setting
Similar to ALLOWED_USER_VISIBILITY_MODES, this setting restricts which visibility
modes (public, limited, private) can be selected for organizations.

- Added new settings in service.go
- Updated settings loading to parse the config
- Modified org templates to filter visibility options
- Added validation in controllers and service layer
- Added translation for error message
- Added tests for configuration loading and validation

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-04-26 12:43:01 +01:00
Mark Wylde
a1420340dd
Add ALLOWED_ORG_VISIBILITY_MODES setting
Similar to ALLOWED_USER_VISIBILITY_MODES, this setting restricts which visibility
modes (public, limited, private) can be selected for organizations.

- Added new settings in service.go
- Updated settings loading to parse the config
- Modified org templates to filter visibility options
- Added validation in controllers and service layer
- Added translation for error message

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-04-26 12:18:24 +01:00
14 changed files with 286 additions and 39 deletions

View File

@ -32,6 +32,8 @@ var Service = struct {
AllowedUserVisibilityModesSlice AllowedVisibility `ini:"-"` AllowedUserVisibilityModesSlice AllowedVisibility `ini:"-"`
DefaultOrgVisibility string DefaultOrgVisibility string
DefaultOrgVisibilityMode structs.VisibleType DefaultOrgVisibilityMode structs.VisibleType
AllowedOrgVisibilityModes []string
AllowedOrgVisibilityModesSlice AllowedVisibility `ini:"-"`
ActiveCodeLives int ActiveCodeLives int
ResetPwdCodeLives int ResetPwdCodeLives int
RegisterEmailConfirm bool RegisterEmailConfirm bool
@ -108,6 +110,7 @@ var Service = struct {
} }
}{ }{
AllowedUserVisibilityModesSlice: []bool{true, true, true}, AllowedUserVisibilityModesSlice: []bool{true, true, true},
AllowedOrgVisibilityModesSlice: []bool{true, true, true},
} }
// AllowedVisibility store in a 3 item bool array what is allowed // AllowedVisibility store in a 3 item bool array what is allowed
@ -245,7 +248,34 @@ func loadServiceFrom(rootCfg ConfigProvider) {
Service.DefaultUserVisibility = Service.AllowedUserVisibilityModes[0] Service.DefaultUserVisibility = Service.AllowedUserVisibilityModes[0]
} }
Service.DefaultUserVisibilityMode = structs.VisibilityModes[Service.DefaultUserVisibility] Service.DefaultUserVisibilityMode = structs.VisibilityModes[Service.DefaultUserVisibility]
Service.DefaultOrgVisibility = sec.Key("DEFAULT_ORG_VISIBILITY").In("public", structs.ExtractKeysFromMapString(structs.VisibilityModes))
// Process allowed organization visibility modes
modes = sec.Key("ALLOWED_ORG_VISIBILITY_MODES").Strings(",")
if len(modes) != 0 {
Service.AllowedOrgVisibilityModes = []string{}
Service.AllowedOrgVisibilityModesSlice = []bool{false, false, false}
for _, sMode := range modes {
if tp, ok := structs.VisibilityModes[sMode]; ok { // remove unsupported modes
Service.AllowedOrgVisibilityModes = append(Service.AllowedOrgVisibilityModes, sMode)
Service.AllowedOrgVisibilityModesSlice[tp] = true
} else {
log.Warn("ALLOWED_ORG_VISIBILITY_MODES %s is unsupported", sMode)
}
}
}
if len(Service.AllowedOrgVisibilityModes) == 0 {
Service.AllowedOrgVisibilityModes = []string{"public", "limited", "private"}
Service.AllowedOrgVisibilityModesSlice = []bool{true, true, true}
}
Service.DefaultOrgVisibility = sec.Key("DEFAULT_ORG_VISIBILITY").String()
if Service.DefaultOrgVisibility == "" {
Service.DefaultOrgVisibility = Service.AllowedOrgVisibilityModes[0]
} else if !Service.AllowedOrgVisibilityModesSlice[structs.VisibilityModes[Service.DefaultOrgVisibility]] {
log.Warn("DEFAULT_ORG_VISIBILITY %s is wrong or not in ALLOWED_ORG_VISIBILITY_MODES, using first allowed", Service.DefaultOrgVisibility)
Service.DefaultOrgVisibility = Service.AllowedOrgVisibilityModes[0]
}
Service.DefaultOrgVisibilityMode = structs.VisibilityModes[Service.DefaultOrgVisibility] Service.DefaultOrgVisibilityMode = structs.VisibilityModes[Service.DefaultOrgVisibility]
Service.DefaultOrgMemberVisible = sec.Key("DEFAULT_ORG_MEMBER_VISIBLE").MustBool() Service.DefaultOrgMemberVisible = sec.Key("DEFAULT_ORG_MEMBER_VISIBLE").MustBool()
Service.UserDeleteWithCommentsMaxTime = sec.Key("USER_DELETE_WITH_COMMENTS_MAX_TIME").MustDuration(0) Service.UserDeleteWithCommentsMaxTime = sec.Key("USER_DELETE_WITH_COMMENTS_MAX_TIME").MustDuration(0)

View File

@ -126,6 +126,87 @@ ALLOWED_USER_VISIBILITY_MODES = public, limit, privated
} }
} }
func TestLoadServiceOrgVisibilityModes(t *testing.T) {
defer test.MockVariableValue(&Service)()
kases := map[string]func(){
`
[service]
DEFAULT_ORG_VISIBILITY = public
ALLOWED_ORG_VISIBILITY_MODES = public,limited,private
`: func() {
assert.Equal(t, "public", Service.DefaultOrgVisibility)
assert.Equal(t, structs.VisibleTypePublic, Service.DefaultOrgVisibilityMode)
assert.Equal(t, []string{"public", "limited", "private"}, Service.AllowedOrgVisibilityModes)
},
`
[service]
DEFAULT_ORG_VISIBILITY = public
`: func() {
assert.Equal(t, "public", Service.DefaultOrgVisibility)
assert.Equal(t, structs.VisibleTypePublic, Service.DefaultOrgVisibilityMode)
assert.Equal(t, []string{"public", "limited", "private"}, Service.AllowedOrgVisibilityModes)
},
`
[service]
DEFAULT_ORG_VISIBILITY = limited
`: func() {
assert.Equal(t, "limited", Service.DefaultOrgVisibility)
assert.Equal(t, structs.VisibleTypeLimited, Service.DefaultOrgVisibilityMode)
assert.Equal(t, []string{"public", "limited", "private"}, Service.AllowedOrgVisibilityModes)
},
`
[service]
ALLOWED_ORG_VISIBILITY_MODES = public,limited,private
`: func() {
assert.Equal(t, "public", Service.DefaultOrgVisibility)
assert.Equal(t, structs.VisibleTypePublic, Service.DefaultOrgVisibilityMode)
assert.Equal(t, []string{"public", "limited", "private"}, Service.AllowedOrgVisibilityModes)
},
`
[service]
DEFAULT_ORG_VISIBILITY = public
ALLOWED_ORG_VISIBILITY_MODES = limited,private
`: func() {
assert.Equal(t, "limited", Service.DefaultOrgVisibility)
assert.Equal(t, structs.VisibleTypeLimited, Service.DefaultOrgVisibilityMode)
assert.Equal(t, []string{"limited", "private"}, Service.AllowedOrgVisibilityModes)
},
`
[service]
DEFAULT_ORG_VISIBILITY = my_type
ALLOWED_ORG_VISIBILITY_MODES = limited,private
`: func() {
assert.Equal(t, "limited", Service.DefaultOrgVisibility)
assert.Equal(t, structs.VisibleTypeLimited, Service.DefaultOrgVisibilityMode)
assert.Equal(t, []string{"limited", "private"}, Service.AllowedOrgVisibilityModes)
},
`
[service]
DEFAULT_ORG_VISIBILITY = public
ALLOWED_ORG_VISIBILITY_MODES = public, limit, privated
`: func() {
assert.Equal(t, "public", Service.DefaultOrgVisibility)
assert.Equal(t, structs.VisibleTypePublic, Service.DefaultOrgVisibilityMode)
assert.Equal(t, []string{"public"}, Service.AllowedOrgVisibilityModes)
},
}
for kase, fun := range kases {
t.Run(kase, func(t *testing.T) {
cfg, err := NewConfigProviderFromData(kase)
assert.NoError(t, err)
loadServiceFrom(cfg)
fun()
// reset
Service.AllowedOrgVisibilityModesSlice = []bool{true, true, true}
Service.AllowedOrgVisibilityModes = []string{}
Service.DefaultOrgVisibility = ""
Service.DefaultOrgVisibilityMode = structs.VisibleTypePublic
})
}
}
func TestLoadServiceRequireSignInView(t *testing.T) { func TestLoadServiceRequireSignInView(t *testing.T) {
defer test.MockVariableValue(&Service)() defer test.MockVariableValue(&Service)()

View File

@ -2829,6 +2829,7 @@ form.name_been_taken = The organisation name "%s" has already been taken.
form.name_reserved = The organization name "%s" is reserved. form.name_reserved = The organization name "%s" is reserved.
form.name_pattern_not_allowed = The pattern "%s" is not allowed in an organization name. form.name_pattern_not_allowed = The pattern "%s" is not allowed in an organization name.
form.create_org_not_allowed = You are not allowed to create an organization. form.create_org_not_allowed = You are not allowed to create an organization.
form.visibility_not_allowed = The selected visibility mode is not allowed.
settings = Settings settings = Settings
settings.options = Organization settings.options = Organization

View File

@ -1969,6 +1969,7 @@ pulls.cmd_instruction_checkout_title=Basculer
pulls.cmd_instruction_checkout_desc=Depuis votre dépôt, basculer sur une nouvelle branche et tester des modifications. pulls.cmd_instruction_checkout_desc=Depuis votre dépôt, basculer sur une nouvelle branche et tester des modifications.
pulls.cmd_instruction_merge_title=Fusionner pulls.cmd_instruction_merge_title=Fusionner
pulls.cmd_instruction_merge_desc=Fusionner les modifications et mettre à jour sur Gitea. pulls.cmd_instruction_merge_desc=Fusionner les modifications et mettre à jour sur Gitea.
pulls.cmd_instruction_merge_warning=Attention : cette opération ne peut pas fusionner la demande dajout car la « détection automatique de fusion manuelle » na pas été activée
pulls.clear_merge_message=Effacer le message de fusion pulls.clear_merge_message=Effacer le message de fusion
pulls.clear_merge_message_hint=Effacer le message de fusion ne supprimera que le message de la révision, mais pas les pieds de révision générés tels que "Co-Authored-By:". pulls.clear_merge_message_hint=Effacer le message de fusion ne supprimera que le message de la révision, mais pas les pieds de révision générés tels que "Co-Authored-By:".
@ -2768,6 +2769,8 @@ branch.new_branch_from=`Créer une nouvelle branche à partir de "%s"`
branch.renamed=La branche %s à été renommée en %s. branch.renamed=La branche %s à été renommée en %s.
branch.rename_default_or_protected_branch_error=Seuls les administrateurs peuvent renommer les branches par défaut ou protégées. branch.rename_default_or_protected_branch_error=Seuls les administrateurs peuvent renommer les branches par défaut ou protégées.
branch.rename_protected_branch_failed=Cette branche est protégée par des règles de protection basées sur des globs. branch.rename_protected_branch_failed=Cette branche est protégée par des règles de protection basées sur des globs.
branch.commits_divergence_from=Divergence de révisions : %[1]d en retard et %[2]d en avance sur %[3]s
branch.commits_no_divergence=Identique à la branche %[1]s
tag.create_tag=Créer l'étiquette %s tag.create_tag=Créer l'étiquette %s
tag.create_tag_operation=Créer une étiquette tag.create_tag_operation=Créer une étiquette

View File

@ -2769,6 +2769,8 @@ branch.new_branch_from=`Cruthaigh brainse nua ó "%s"`
branch.renamed=Ainmníodh brainse %s go %s. branch.renamed=Ainmníodh brainse %s go %s.
branch.rename_default_or_protected_branch_error=Ní féidir ach le riarthóirí brainsí réamhshocraithe nó cosanta a athainmniú. branch.rename_default_or_protected_branch_error=Ní féidir ach le riarthóirí brainsí réamhshocraithe nó cosanta a athainmniú.
branch.rename_protected_branch_failed=Tá an brainse seo faoi chosaint ag rialacha cosanta domhanda. branch.rename_protected_branch_failed=Tá an brainse seo faoi chosaint ag rialacha cosanta domhanda.
branch.commits_divergence_from=Déanann sé dialltacht a thiomnú: %[1]d taobh thiar agus %[2]d chun tosaigh ar %[3]s
branch.commits_no_divergence=Mar an gcéanna le brainse %[1]s
tag.create_tag=Cruthaigh clib %s tag.create_tag=Cruthaigh clib %s
tag.create_tag_operation=Cruthaigh clib tag.create_tag_operation=Cruthaigh clib
@ -2782,6 +2784,7 @@ topic.done=Déanta
topic.count_prompt=Ní féidir leat níos mó ná 25 topaicí a roghnú topic.count_prompt=Ní féidir leat níos mó ná 25 topaicí a roghnú
topic.format_prompt=Ní mór do thopaicí tosú le litir nó uimhir, is féidir daiseanna ('-') agus poncanna ('.') a áireamh, a bheith suas le 35 carachtar ar fad. Ní mór litreacha a bheith i litreacha beaga. topic.format_prompt=Ní mór do thopaicí tosú le litir nó uimhir, is féidir daiseanna ('-') agus poncanna ('.') a áireamh, a bheith suas le 35 carachtar ar fad. Ní mór litreacha a bheith i litreacha beaga.
find_file.follow_symlink=Lean an nasc siombalach seo go dtí an áit a bhfuil sé ag pointeáil air
find_file.go_to_file=Téigh go dtí an comhad find_file.go_to_file=Téigh go dtí an comhad
find_file.no_matching=Níl aon chomhad meaitseála le fáil find_file.no_matching=Níl aon chomhad meaitseála le fáil

View File

@ -1562,8 +1562,8 @@ issues.filter_project=Planeamento
issues.filter_project_all=Todos os planeamentos issues.filter_project_all=Todos os planeamentos
issues.filter_project_none=Nenhum planeamento issues.filter_project_none=Nenhum planeamento
issues.filter_assignee=Encarregado issues.filter_assignee=Encarregado
issues.filter_assignee_no_assignee=Não atribuído issues.filter_assignee_no_assignee=Não atribuída
issues.filter_assignee_any_assignee=Atribuído a qualquer pessoa issues.filter_assignee_any_assignee=Atribuída a alguém
issues.filter_poster=Autor(a) issues.filter_poster=Autor(a)
issues.filter_user_placeholder=Procurar utilizadores issues.filter_user_placeholder=Procurar utilizadores
issues.filter_user_no_select=Todos os utilizadores issues.filter_user_no_select=Todos os utilizadores
@ -1969,6 +1969,7 @@ pulls.cmd_instruction_checkout_title=Checkout
pulls.cmd_instruction_checkout_desc=A partir do seu repositório, crie um novo ramo e teste nele as modificações. pulls.cmd_instruction_checkout_desc=A partir do seu repositório, crie um novo ramo e teste nele as modificações.
pulls.cmd_instruction_merge_title=Integrar pulls.cmd_instruction_merge_title=Integrar
pulls.cmd_instruction_merge_desc=Integrar as modificações e enviar para o Gitea. pulls.cmd_instruction_merge_desc=Integrar as modificações e enviar para o Gitea.
pulls.cmd_instruction_merge_warning=Aviso: Esta operação não pode executar pedidos de integração porque a opção "auto-identificar integração manual" não está habilitada.
pulls.clear_merge_message=Apagar mensagem de integração pulls.clear_merge_message=Apagar mensagem de integração
pulls.clear_merge_message_hint=Apagar a mensagem de integração apenas remove o conteúdo da mensagem de cometimento e mantém os rodapés do git, tais como "Co-Autorado-Por …". pulls.clear_merge_message_hint=Apagar a mensagem de integração apenas remove o conteúdo da mensagem de cometimento e mantém os rodapés do git, tais como "Co-Autorado-Por …".
@ -2768,6 +2769,8 @@ branch.new_branch_from=`Criar um novo ramo a partir do ramo "%s"`
branch.renamed=O ramo %s foi renomeado para %s. branch.renamed=O ramo %s foi renomeado para %s.
branch.rename_default_or_protected_branch_error=Só os administradores é que podem renomear o ramo principal ou ramos protegidos. branch.rename_default_or_protected_branch_error=Só os administradores é que podem renomear o ramo principal ou ramos protegidos.
branch.rename_protected_branch_failed=Este ramo está protegido por regras de salvaguarda baseadas em padrões glob. branch.rename_protected_branch_failed=Este ramo está protegido por regras de salvaguarda baseadas em padrões glob.
branch.commits_divergence_from=Divergência nos cometimentos: %[1]d atrás e %[2]d à frente de %[3]s
branch.commits_no_divergence=Idêntico ao ramo %[1]s
tag.create_tag=Criar etiqueta %s tag.create_tag=Criar etiqueta %s
tag.create_tag_operation=Criar etiqueta tag.create_tag_operation=Criar etiqueta
@ -2781,6 +2784,7 @@ topic.done=Concluído
topic.count_prompt=Não pode escolher mais do que 25 tópicos topic.count_prompt=Não pode escolher mais do que 25 tópicos
topic.format_prompt=Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') ou pontos ('.') e podem ter até 35 caracteres. As letras têm que ser minúsculas. topic.format_prompt=Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') ou pontos ('.') e podem ter até 35 caracteres. As letras têm que ser minúsculas.
find_file.follow_symlink=Seguir esta ligação simbólica para onde ela está apontando
find_file.go_to_file=Ir para o ficheiro find_file.go_to_file=Ir para o ficheiro
find_file.no_matching=Não foi encontrado qualquer ficheiro correspondente find_file.no_matching=Não foi encontrado qualquer ficheiro correspondente

View File

@ -33,6 +33,7 @@ func Create(ctx *context.Context) {
} }
ctx.Data["visibility"] = setting.Service.DefaultOrgVisibilityMode ctx.Data["visibility"] = setting.Service.DefaultOrgVisibilityMode
ctx.Data["AllowedOrgVisibilityModes"] = setting.Service.AllowedOrgVisibilityModesSlice.ToVisibleTypeSlice()
ctx.Data["repo_admin_change_team_access"] = true ctx.Data["repo_admin_change_team_access"] = true
ctx.HTML(http.StatusOK, tplCreateOrg) ctx.HTML(http.StatusOK, tplCreateOrg)
@ -48,6 +49,13 @@ func CreatePost(ctx *context.Context) {
return return
} }
// Check if the visibility is allowed
if !setting.Service.AllowedOrgVisibilityModesSlice.IsAllowedVisibility(form.Visibility) {
ctx.Data["Err_OrgVisibility"] = true
ctx.RenderWithErr(ctx.Tr("org.form.visibility_not_allowed"), tplCreateOrg, &form)
return
}
if ctx.HasError() { if ctx.HasError() {
ctx.HTML(http.StatusOK, tplCreateOrg) ctx.HTML(http.StatusOK, tplCreateOrg)
return return

View File

@ -46,6 +46,7 @@ func Settings(ctx *context.Context) {
ctx.Data["CurrentVisibility"] = ctx.Org.Organization.Visibility ctx.Data["CurrentVisibility"] = ctx.Org.Organization.Visibility
ctx.Data["RepoAdminChangeTeamAccess"] = ctx.Org.Organization.RepoAdminChangeTeamAccess ctx.Data["RepoAdminChangeTeamAccess"] = ctx.Org.Organization.RepoAdminChangeTeamAccess
ctx.Data["ContextUser"] = ctx.ContextUser ctx.Data["ContextUser"] = ctx.ContextUser
ctx.Data["AllowedOrgVisibilityModes"] = setting.Service.AllowedOrgVisibilityModesSlice.ToVisibleTypeSlice()
if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil { if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil {
ctx.ServerError("RenderUserOrgHeader", err) ctx.ServerError("RenderUserOrgHeader", err)
@ -62,6 +63,14 @@ func SettingsPost(ctx *context.Context) {
ctx.Data["PageIsOrgSettings"] = true ctx.Data["PageIsOrgSettings"] = true
ctx.Data["PageIsSettingsOptions"] = true ctx.Data["PageIsSettingsOptions"] = true
ctx.Data["CurrentVisibility"] = ctx.Org.Organization.Visibility ctx.Data["CurrentVisibility"] = ctx.Org.Organization.Visibility
ctx.Data["AllowedOrgVisibilityModes"] = setting.Service.AllowedOrgVisibilityModesSlice.ToVisibleTypeSlice()
// Check if the visibility is allowed
if !setting.Service.AllowedOrgVisibilityModesSlice.IsAllowedVisibility(form.Visibility) {
ctx.Data["Err_Visibility"] = true
ctx.RenderWithErr(ctx.Tr("org.form.visibility_not_allowed"), tplSettingsOptions, form)
return
}
if ctx.HasError() { if ctx.HasError() {
ctx.HTML(http.StatusOK, tplSettingsOptions) ctx.HTML(http.StatusOK, tplSettingsOptions)

View File

@ -149,6 +149,9 @@ func UpdateUser(ctx context.Context, u *user_model.User, opts *UpdateOptions) er
if !u.IsOrganization() && !setting.Service.AllowedUserVisibilityModesSlice.IsAllowedVisibility(opts.Visibility.Value()) { if !u.IsOrganization() && !setting.Service.AllowedUserVisibilityModesSlice.IsAllowedVisibility(opts.Visibility.Value()) {
return fmt.Errorf("visibility mode not allowed: %s", opts.Visibility.Value().String()) return fmt.Errorf("visibility mode not allowed: %s", opts.Visibility.Value().String())
} }
if u.IsOrganization() && !setting.Service.AllowedOrgVisibilityModesSlice.IsAllowedVisibility(opts.Visibility.Value()) {
return fmt.Errorf("visibility mode not allowed for organization: %s", opts.Visibility.Value().String())
}
u.Visibility = opts.Visibility.Value() u.Visibility = opts.Visibility.Value()
cols = append(cols, "visibility") cols = append(cols, "visibility")

View File

@ -11,7 +11,9 @@ import (
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
password_module "code.gitea.io/gitea/modules/auth/password" password_module "code.gitea.io/gitea/modules/auth/password"
"code.gitea.io/gitea/modules/optional" "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/test"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -122,3 +124,68 @@ func TestUpdateAuth(t *testing.T) {
Password: optional.Some("aaaa"), Password: optional.Some("aaaa"),
}), password_module.ErrMinLength) }), password_module.ErrMinLength)
} }
func TestVisibilityModeValidation(t *testing.T) {
// Mock testing setup
defer test.MockVariableValue(&setting.Service)()
assert.NoError(t, unittest.PrepareTestDatabase())
// Organization user
org := &user_model.User{
ID: 500,
Type: user_model.UserTypeOrganization,
Name: "test-org",
LowerName: "test-org",
}
// Regular user
user := &user_model.User{
ID: 501,
Type: user_model.UserTypeIndividual,
Name: "test-user",
LowerName: "test-user",
}
// Test case 1: Allow only limited and private visibility for organizations
setting.Service.AllowedOrgVisibilityModesSlice = []bool{false, true, true}
setting.Service.AllowedOrgVisibilityModes = []string{"limited", "private"}
// Should fail when trying to set public visibility for organization
err := UpdateUser(db.DefaultContext, org, &UpdateOptions{
Visibility: optional.Some(structs.VisibleTypePublic),
})
assert.Error(t, err)
assert.Contains(t, err.Error(), "visibility mode not allowed for organization")
// Should succeed when setting limited visibility for organization
err = UpdateUser(db.DefaultContext, org, &UpdateOptions{
Visibility: optional.Some(structs.VisibleTypeLimited),
})
assert.NoError(t, err)
assert.Equal(t, structs.VisibleTypeLimited, org.Visibility)
// Test case 2: Allow only public and limited visibility for users
setting.Service.AllowedUserVisibilityModesSlice = []bool{true, true, false}
setting.Service.AllowedUserVisibilityModes = []string{"public", "limited"}
// Should fail when trying to set private visibility for user
err = UpdateUser(db.DefaultContext, user, &UpdateOptions{
Visibility: optional.Some(structs.VisibleTypePrivate),
})
assert.Error(t, err)
assert.Contains(t, err.Error(), "visibility mode not allowed")
// Should succeed when setting public visibility for user
err = UpdateUser(db.DefaultContext, user, &UpdateOptions{
Visibility: optional.Some(structs.VisibleTypePublic),
})
assert.NoError(t, err)
assert.Equal(t, structs.VisibleTypePublic, user.Visibility)
// Reset to default settings
setting.Service.AllowedOrgVisibilityModesSlice = []bool{true, true, true}
setting.Service.AllowedOrgVisibilityModes = []string{"public", "limited", "private"}
setting.Service.AllowedUserVisibilityModesSlice = []bool{true, true, true}
setting.Service.AllowedUserVisibilityModes = []string{"public", "limited", "private"}
}

View File

@ -29,7 +29,7 @@ export default {
important: true, // the frameworks are mixed together, so tailwind needs to override other framework's styles important: true, // the frameworks are mixed together, so tailwind needs to override other framework's styles
content: [ content: [
isProduction && '!./templates/devtest/**/*', isProduction && '!./templates/devtest/**/*',
isProduction && '!./web_src/js/standalone/devtest.js', isProduction && '!./web_src/js/standalone/devtest.ts',
'!./templates/swagger/v1_json.tmpl', '!./templates/swagger/v1_json.tmpl',
'!./templates/user/auth/oidc_wellknown.tmpl', '!./templates/user/auth/oidc_wellknown.tmpl',
'!**/*_test.go', '!**/*_test.go',

View File

@ -17,18 +17,24 @@
<div class="inline field required {{if .Err_OrgVisibility}}error{{end}}"> <div class="inline field required {{if .Err_OrgVisibility}}error{{end}}">
<label for="visibility">{{ctx.Locale.Tr "org.settings.visibility"}}</label> <label for="visibility">{{ctx.Locale.Tr "org.settings.visibility"}}</label>
<div class="inline-right"> <div class="inline-right">
<div class="ui radio checkbox"> {{range $mode := .AllowedOrgVisibilityModes}}
<input class="enable-system-radio" name="visibility" type="radio" value="0" {{if .visibility.IsPublic}}checked{{end}}> {{if $mode.IsPublic}}
<label>{{ctx.Locale.Tr "org.settings.visibility.public"}}</label> <div class="ui radio checkbox">
</div> <input class="enable-system-radio" name="visibility" type="radio" value="0" {{if $.visibility.IsPublic}}checked{{end}}>
<div class="ui radio checkbox"> <label>{{ctx.Locale.Tr "org.settings.visibility.public"}}</label>
<input class="enable-system-radio" name="visibility" type="radio" value="1" {{if .visibility.IsLimited}}checked{{end}}> </div>
<label>{{ctx.Locale.Tr "org.settings.visibility.limited"}}</label> {{else if $mode.IsLimited}}
</div> <div class="ui radio checkbox">
<div class="ui radio checkbox"> <input class="enable-system-radio" name="visibility" type="radio" value="1" {{if $.visibility.IsLimited}}checked{{end}}>
<input class="enable-system-radio" name="visibility" type="radio" value="2" {{if .visibility.IsPrivate}}checked{{end}}> <label>{{ctx.Locale.Tr "org.settings.visibility.limited"}}</label>
<label>{{ctx.Locale.Tr "org.settings.visibility.private"}}</label> </div>
</div> {{else if $mode.IsPrivate}}
<div class="ui radio checkbox">
<input class="enable-system-radio" name="visibility" type="radio" value="2" {{if $.visibility.IsPrivate}}checked{{end}}>
<label>{{ctx.Locale.Tr "org.settings.visibility.private"}}</label>
</div>
{{end}}
{{end}}
</div> </div>
</div> </div>

View File

@ -30,27 +30,6 @@
</div> </div>
<div class="divider"></div> <div class="divider"></div>
<div class="field" id="visibility_box">
<label for="visibility">{{ctx.Locale.Tr "org.settings.visibility"}}</label>
<div class="field">
<div class="ui radio checkbox">
<input class="enable-system-radio" name="visibility" type="radio" value="0" {{if eq .CurrentVisibility 0}}checked{{end}}>
<label>{{ctx.Locale.Tr "org.settings.visibility.public"}}</label>
</div>
</div>
<div class="field">
<div class="ui radio checkbox">
<input class="enable-system-radio" name="visibility" type="radio" value="1" {{if eq .CurrentVisibility 1}}checked{{end}}>
<label>{{ctx.Locale.Tr "org.settings.visibility.limited"}}</label>
</div>
</div>
<div class="field">
<div class="ui radio checkbox">
<input class="enable-system-radio" name="visibility" type="radio" value="2" {{if eq .CurrentVisibility 2}}checked{{end}}>
<label>{{ctx.Locale.Tr "org.settings.visibility.private"}}</label>
</div>
</div>
</div>
<div class="field" id="permission_box"> <div class="field" id="permission_box">
<label>{{ctx.Locale.Tr "org.settings.permission"}}</label> <label>{{ctx.Locale.Tr "org.settings.permission"}}</label>

View File

@ -3,6 +3,16 @@
</h4> </h4>
<div class="ui attached error danger segment"> <div class="ui attached error danger segment">
<div class="flex-list"> <div class="flex-list">
<div class="flex-item">
<div class="flex-item-main">
<div class="flex-item-title">{{ctx.Locale.Tr "org.settings.visibility"}}</div>
<div class="flex-item-body">{{ctx.Locale.Tr "org.settings.visibility_helper"}}</div>
</div>
<div class="flex-item-trailing">
<button class="ui basic red show-modal button" data-modal="#change-visibility-modal">{{ctx.Locale.Tr "org.settings.change_visibility"}}</button>
</div>
</div>
<div class="flex-item tw-items-center"> <div class="flex-item tw-items-center">
<div class="flex-item-main"> <div class="flex-item-main">
<div class="flex-item-title">{{ctx.Locale.Tr "org.settings.rename"}}</div> <div class="flex-item-title">{{ctx.Locale.Tr "org.settings.rename"}}</div>
@ -25,6 +35,49 @@
</div> </div>
</div> </div>
<div class="ui small modal" id="change-visibility-modal">
<div class="header">
{{ctx.Locale.Tr "org.settings.change_visibility"}}
</div>
<div class="content">
<p>{{ctx.Locale.Tr "org.settings.visibility_change_warning"}}</p>
<form class="ui form form-fetch-action" action="{{.Link}}" method="post">
{{.CsrfTokenHtml}}
<div class="field" id="visibility_box">
<label for="visibility">{{ctx.Locale.Tr "org.settings.visibility"}}</label>
{{range $mode := .AllowedOrgVisibilityModes}}
{{if $mode.IsPublic}}
<div class="field">
<div class="ui radio checkbox">
<input class="enable-system-radio" name="visibility" type="radio" value="0" {{if eq $.CurrentVisibility 0}}checked{{end}}>
<label>{{ctx.Locale.Tr "org.settings.visibility.public"}}</label>
</div>
</div>
{{else if $mode.IsLimited}}
<div class="field">
<div class="ui radio checkbox">
<input class="enable-system-radio" name="visibility" type="radio" value="1" {{if eq $.CurrentVisibility 1}}checked{{end}}>
<label>{{ctx.Locale.Tr "org.settings.visibility.limited"}}</label>
</div>
</div>
{{else if $mode.IsPrivate}}
<div class="field">
<div class="ui radio checkbox">
<input class="enable-system-radio" name="visibility" type="radio" value="2" {{if eq $.CurrentVisibility 2}}checked{{end}}>
<label>{{ctx.Locale.Tr "org.settings.visibility.private"}}</label>
</div>
</div>
{{end}}
{{end}}
</div>
<div class="actions">
<button class="ui cancel button">{{ctx.Locale.Tr "settings.cancel"}}</button>
<button class="ui red button">{{ctx.Locale.Tr "org.settings.update_settings"}}</button>
</div>
</form>
</div>
</div>
<div class="ui small modal" id="rename-org-modal"> <div class="ui small modal" id="rename-org-modal">
<div class="header"> <div class="header">
{{ctx.Locale.Tr "org.settings.rename"}} {{ctx.Locale.Tr "org.settings.rename"}}