mirror of
https://github.com/go-gitea/gitea.git
synced 2026-04-03 12:41:10 +02:00
Merge 76bb60215bbbd198721f395bd5b67d4404b7948a into 6eed75af248ae597d854a3c5e6b8831a5ff76290
This commit is contained in:
commit
5cf14bca66
@ -1623,6 +1623,9 @@ LEVEL = Info
|
||||
;; - change_username: a user cannot change their username
|
||||
;; - change_full_name: a user cannot change their full name
|
||||
;;EXTERNAL_USER_DISABLE_FEATURES =
|
||||
;; Disabled features for organizations, currently supported: danger_zone
|
||||
;; - danger_zone: only site administrators can delete, rename, or change organization visibility
|
||||
;ORG_DISABLED_FEATURES =
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
@ -14,6 +14,7 @@ var Admin struct {
|
||||
DefaultEmailNotification string
|
||||
UserDisabledFeatures container.Set[string]
|
||||
ExternalUserDisableFeatures container.Set[string]
|
||||
OrgDisabledFeatures container.Set[string]
|
||||
}
|
||||
|
||||
var validUserFeatures = container.SetOf(
|
||||
@ -26,12 +27,21 @@ var validUserFeatures = container.SetOf(
|
||||
UserFeatureChangeFullName,
|
||||
)
|
||||
|
||||
var validOrgFeatures = container.SetOf(
|
||||
OrgFeatureDangerZone,
|
||||
)
|
||||
|
||||
func CanManageOrgDangerZone(isAdmin bool) bool {
|
||||
return isAdmin || !Admin.OrgDisabledFeatures.Contains(OrgFeatureDangerZone)
|
||||
}
|
||||
|
||||
func loadAdminFrom(rootCfg ConfigProvider) {
|
||||
sec := rootCfg.Section("admin")
|
||||
Admin.DisableRegularOrgCreation = sec.Key("DISABLE_REGULAR_ORG_CREATION").MustBool(false)
|
||||
Admin.DefaultEmailNotification = sec.Key("DEFAULT_EMAIL_NOTIFICATIONS").MustString("enabled")
|
||||
Admin.UserDisabledFeatures = container.SetOf(sec.Key("USER_DISABLED_FEATURES").Strings(",")...)
|
||||
Admin.ExternalUserDisableFeatures = container.SetOf(sec.Key("EXTERNAL_USER_DISABLE_FEATURES").Strings(",")...).Union(Admin.UserDisabledFeatures)
|
||||
Admin.OrgDisabledFeatures = container.SetOf(sec.Key("ORG_DISABLED_FEATURES").Strings(",")...)
|
||||
|
||||
for feature := range Admin.UserDisabledFeatures {
|
||||
if !validUserFeatures.Contains(feature) {
|
||||
@ -43,6 +53,11 @@ func loadAdminFrom(rootCfg ConfigProvider) {
|
||||
log.Warn("EXTERNAL_USER_DISABLE_FEATURES contains unknown feature %q", feature)
|
||||
}
|
||||
}
|
||||
for feature := range Admin.OrgDisabledFeatures {
|
||||
if !validOrgFeatures.Contains(feature) {
|
||||
log.Warn("ORG_DISABLED_FEATURES contains unknown feature %q", feature)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
@ -53,4 +68,6 @@ const (
|
||||
UserFeatureManageCredentials = "manage_credentials"
|
||||
UserFeatureChangeUsername = "change_username"
|
||||
UserFeatureChangeFullName = "change_full_name"
|
||||
|
||||
OrgFeatureDangerZone = "danger_zone"
|
||||
)
|
||||
|
||||
40
modules/setting/admin_test.go
Normal file
40
modules/setting/admin_test.go
Normal file
@ -0,0 +1,40 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package setting
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestLoadAdminOrgDisabledFeatures(t *testing.T) {
|
||||
defer test.MockVariableValue(&Admin)()
|
||||
|
||||
cfg, err := NewConfigProviderFromData(`
|
||||
[admin]
|
||||
ORG_DISABLED_FEATURES = danger_zone
|
||||
`)
|
||||
assert.NoError(t, err)
|
||||
loadAdminFrom(cfg)
|
||||
|
||||
assert.True(t, Admin.OrgDisabledFeatures.Contains(OrgFeatureDangerZone))
|
||||
assert.False(t, CanManageOrgDangerZone(false))
|
||||
assert.True(t, CanManageOrgDangerZone(true))
|
||||
}
|
||||
|
||||
func TestLoadAdminOrgDisabledFeaturesDefault(t *testing.T) {
|
||||
defer test.MockVariableValue(&Admin)()
|
||||
|
||||
cfg, err := NewConfigProviderFromData(`
|
||||
[admin]
|
||||
`)
|
||||
assert.NoError(t, err)
|
||||
loadAdminFrom(cfg)
|
||||
|
||||
assert.False(t, Admin.OrgDisabledFeatures.Contains(OrgFeatureDangerZone))
|
||||
assert.True(t, CanManageOrgDangerZone(false))
|
||||
}
|
||||
@ -14,6 +14,7 @@ import (
|
||||
"code.gitea.io/gitea/models/perm"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
@ -340,6 +341,11 @@ func Rename(ctx *context.APIContext) {
|
||||
// "422":
|
||||
// "$ref": "#/responses/validationError"
|
||||
|
||||
if ctx.Doer == nil || !setting.CanManageOrgDangerZone(ctx.Doer.IsAdmin) {
|
||||
ctx.APIError(http.StatusForbidden, "Organization danger zone actions are restricted to site administrators")
|
||||
return
|
||||
}
|
||||
|
||||
form := web.GetForm(ctx).(*api.RenameOrgOption)
|
||||
orgUser := ctx.Org.Organization.AsUser()
|
||||
if err := user_service.RenameUser(ctx, orgUser, form.NewName, ctx.Doer); err != nil {
|
||||
@ -380,6 +386,10 @@ func Edit(ctx *context.APIContext) {
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
form := web.GetForm(ctx).(*api.EditOrgOption)
|
||||
if form.Visibility != nil && *form.Visibility != "" && ctx.Org.Organization.Visibility.String() != *form.Visibility && (ctx.Doer == nil || !setting.CanManageOrgDangerZone(ctx.Doer.IsAdmin)) {
|
||||
ctx.APIError(http.StatusForbidden, "Organization danger zone actions are restricted to site administrators")
|
||||
return
|
||||
}
|
||||
|
||||
if err := org.UpdateOrgEmailAddress(ctx, ctx.Org.Organization, form.Email); err != nil {
|
||||
if errors.Is(err, util.ErrInvalidArgument) {
|
||||
@ -425,6 +435,11 @@ func Delete(ctx *context.APIContext) {
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
if ctx.Doer == nil || !setting.CanManageOrgDangerZone(ctx.Doer.IsAdmin) {
|
||||
ctx.APIError(http.StatusForbidden, "Organization danger zone actions are restricted to site administrators")
|
||||
return
|
||||
}
|
||||
|
||||
if err := org.DeleteOrganization(ctx, ctx.Org.Organization, false); err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
|
||||
@ -47,6 +47,7 @@ func Settings(ctx *context.Context) {
|
||||
ctx.Data["CurrentVisibility"] = ctx.Org.Organization.Visibility
|
||||
ctx.Data["RepoAdminChangeTeamAccess"] = ctx.Org.Organization.RepoAdminChangeTeamAccess
|
||||
ctx.Data["ContextUser"] = ctx.ContextUser
|
||||
ctx.Data["CanManageOrgDangerZone"] = setting.CanManageOrgDangerZone(ctx.Doer.IsAdmin)
|
||||
|
||||
if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil {
|
||||
ctx.ServerError("RenderUserOrgHeader", err)
|
||||
@ -63,6 +64,7 @@ func SettingsPost(ctx *context.Context) {
|
||||
ctx.Data["PageIsOrgSettings"] = true
|
||||
ctx.Data["PageIsSettingsOptions"] = true
|
||||
ctx.Data["CurrentVisibility"] = ctx.Org.Organization.Visibility
|
||||
ctx.Data["CanManageOrgDangerZone"] = setting.CanManageOrgDangerZone(ctx.Doer.IsAdmin)
|
||||
|
||||
if ctx.HasError() {
|
||||
ctx.HTML(http.StatusOK, tplSettingsOptions)
|
||||
@ -125,6 +127,11 @@ func SettingsDeleteAvatar(ctx *context.Context) {
|
||||
|
||||
// SettingsDeleteOrgPost response for deleting an organization
|
||||
func SettingsDeleteOrgPost(ctx *context.Context) {
|
||||
if !setting.CanManageOrgDangerZone(ctx.Doer.IsAdmin) {
|
||||
ctx.HTTPError(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
if ctx.Org.Organization.Name != ctx.FormString("org_name") {
|
||||
ctx.JSONError(ctx.Tr("form.enterred_invalid_org_name"))
|
||||
return
|
||||
@ -198,6 +205,11 @@ func Labels(ctx *context.Context) {
|
||||
|
||||
// SettingsRenamePost response for renaming organization
|
||||
func SettingsRenamePost(ctx *context.Context) {
|
||||
if !setting.CanManageOrgDangerZone(ctx.Doer.IsAdmin) {
|
||||
ctx.HTTPError(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
form := web.GetForm(ctx).(*forms.RenameOrgForm)
|
||||
if ctx.HasError() {
|
||||
ctx.JSONError(ctx.GetErrMsg())
|
||||
@ -248,6 +260,11 @@ func SettingsChangeVisibilityPost(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if !setting.CanManageOrgDangerZone(ctx.Doer.IsAdmin) {
|
||||
ctx.HTTPError(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
if err := org_service.ChangeOrganizationVisibility(ctx, ctx.Org.Organization, visibility); err != nil {
|
||||
log.Error("ChangeOrganizationVisibility: %v", err)
|
||||
ctx.JSONError(ctx.Tr("error.occurred"))
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
{{if .CanManageOrgDangerZone}}
|
||||
<h4 class="ui top attached error header">
|
||||
{{ctx.Locale.Tr "repo.settings.danger_zone"}}
|
||||
</h4>
|
||||
@ -140,3 +141,4 @@
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user