mirror of
https://github.com/go-gitea/gitea.git
synced 2026-11-05 00:09:33 +09:00
refactor: remove UnmarshalHandleDoubleEncode patch and fix repo unit TODO (#39998)
Remove the `UnmarshalHandleDoubleEncode` runtime workaround for an old xorm bug, and normalize legacy double-encoded JSON in `repo_unit.config` and `login_source.cfg` with a migration instead. - Config `FromDB` no longer fails on corrupted values. A broken auth source is deactivated, so admins can still fix it in the admin panel - Remove `ProjectsConfig.GetProjectsMode`, `FromDB` already defaults `ProjectsMode` Actually it doesn't need so many changes, just because AI thinks it should handle "corrupted database value" for auth sources. --------- Signed-off-by: wxiaoguang <wxiaoguang@gmail.com> Co-authored-by: silverwind <me@silverwind.io>
This commit is contained in:
1 parent
666e6e2d17
commit
d919301d36
17 files changed
+318
-145
No files matched your search
@@ -441,6 +441,7 @@ func prepareMigrationTasks() []*migration {
|
||||
newMigration(362, "Add reusable_workflow_ref column to action_run_job", v29.AddReusableWorkflowRefToActionRunJob),
|
||||
newMigration(363, "Add release_id to reaction table", v29.AddReleaseIDToReaction),
|
||||
newMigration(364, "Add new table project_workflow", v29.AddProjectWorkflow),
|
||||
newMigration(365, "Normalize legacy repository unit and login source JSON", v29.NormalizeLegacyConfigJSON),
|
||||
}
|
||||
return preparedMigrations
|
||||
}
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v29
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
|
||||
"gitea.dev/modelmigration/base"
|
||||
"gitea.dev/modules/json"
|
||||
"gitea.dev/modules/util"
|
||||
)
|
||||
|
||||
// unmarshalHandleDoubleEncode - due to a bug in xorm (see https://gitea.com/xorm/xorm/pulls/1957) - it's
|
||||
// possible that a Blob may be double encoded or gain an unwanted prefix of 0xff 0xfe.
|
||||
func unmarshalHandleDoubleEncode(bs []byte, v any) error {
|
||||
if len(bs) == 0 {
|
||||
// json.Unmarshal will report errors if input is empty (nil or zero-length)
|
||||
// It seems that XORM ignores the nil but still passes zero-length string into this function
|
||||
// To be consistent, we should treat all empty inputs as success
|
||||
return nil
|
||||
}
|
||||
err := json.Unmarshal(bs, v)
|
||||
if err != nil {
|
||||
ok := true
|
||||
rs := []byte{}
|
||||
temp := make([]byte, 2)
|
||||
for _, rn := range string(bs) {
|
||||
if rn > 0xffff {
|
||||
ok = false
|
||||
break
|
||||
}
|
||||
binary.LittleEndian.PutUint16(temp, uint16(rn))
|
||||
rs = append(rs, temp...)
|
||||
}
|
||||
if ok {
|
||||
if len(rs) > 1 && rs[0] == 0xff && rs[1] == 0xfe {
|
||||
rs = rs[2:]
|
||||
}
|
||||
err = json.Unmarshal(rs, v)
|
||||
}
|
||||
}
|
||||
if err != nil && len(bs) > 2 && bs[0] == 0xff && bs[1] == 0xfe {
|
||||
err = json.Unmarshal(bs[2:], v)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func normalizeLegacyConfigJSON(config string) string {
|
||||
if config == "" || json.Valid(util.UnsafeStringToBytes(config)) {
|
||||
return config
|
||||
}
|
||||
|
||||
m := map[any]any{}
|
||||
err := unmarshalHandleDoubleEncode(util.UnsafeStringToBytes(config), &m)
|
||||
if err != nil {
|
||||
return config
|
||||
}
|
||||
if bs, err := json.Marshal(m); err == nil {
|
||||
return string(bs)
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
func NormalizeLegacyConfigJSON(ctx context.Context, x base.EngineMigration) error {
|
||||
type RepoUnit struct {
|
||||
ID int64
|
||||
Config string
|
||||
}
|
||||
type LoginSource struct {
|
||||
ID int64
|
||||
Cfg string
|
||||
}
|
||||
return base.EngineTransaction(x, func(sess base.Session) error {
|
||||
if err := base.IterateTableByColumn(ctx, x, sess, "id", nil, func(_ context.Context, unit *RepoUnit) error {
|
||||
config := normalizeLegacyConfigJSON(unit.Config)
|
||||
if config == unit.Config {
|
||||
return nil
|
||||
}
|
||||
_, err := sess.ID(unit.ID).Cols("config").Update(&RepoUnit{Config: config})
|
||||
return err
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
return base.IterateTableByColumn(ctx, x, sess, "id", nil, func(_ context.Context, source *LoginSource) error {
|
||||
config := normalizeLegacyConfigJSON(source.Cfg)
|
||||
if config == source.Cfg {
|
||||
return nil
|
||||
}
|
||||
_, err := sess.ID(source.ID).Cols("cfg").Update(&LoginSource{Cfg: config})
|
||||
return err
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v29
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"gitea.dev/modelmigration/migrationtest"
|
||||
"gitea.dev/modules/setting"
|
||||
"gitea.dev/modules/test"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNormalizeLegacyConfigJSONValue(t *testing.T) {
|
||||
const (
|
||||
config = `{"Host":"smtp.example.com"}`
|
||||
packed = "≻潈瑳㨢猢瑭硥浡汰潣≭⁽"
|
||||
)
|
||||
for _, tc := range []struct {
|
||||
name, input, want string
|
||||
}{
|
||||
{"empty", "", ""},
|
||||
{"valid", config + " ", config + " "},
|
||||
{"unicode and large number", ` {"name":"例😀","id":9007199254740993} `, ` {"name":"例😀","id":9007199254740993} `},
|
||||
{"double encoded", packed, config},
|
||||
{"double encoded with BOM", "\ufeff" + packed, config},
|
||||
{"BOM", "\xff\xfe" + config, config},
|
||||
{"BOM with non-BMP rune", "\xff\xfe" + `{"name":"😀"}`, `{"name":"😀"}`},
|
||||
{"corrupt", "invalid", "invalid"},
|
||||
{"corrupt non-BMP rune", "invalid😀", "invalid😀"},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got := normalizeLegacyConfigJSON(tc.input)
|
||||
assert.Equal(t, tc.want, got)
|
||||
assert.Equal(t, got, normalizeLegacyConfigJSON(got))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeLegacyConfigJSON(t *testing.T) {
|
||||
type RepoUnit struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
RepoID int64
|
||||
Config string `xorm:"TEXT"`
|
||||
}
|
||||
type LoginSource struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
Name string
|
||||
Cfg string `xorm:"TEXT"`
|
||||
}
|
||||
x, cleanup := migrationtest.PrepareTestEnv(t, 0, new(RepoUnit), new(LoginSource))
|
||||
defer cleanup()
|
||||
if x == nil || t.Failed() {
|
||||
return
|
||||
}
|
||||
defer test.MockVariableValue(&setting.Database.IterateBufferSize, 2)()
|
||||
|
||||
inputs := []string{
|
||||
` {"id":9007199254740993,"name":"例😀"} `,
|
||||
"≻潈瑳㨢猢瑭硥浡汰潣≭⁽",
|
||||
"\ufeff≻潈瑳㨢猢瑭硥浡汰潣≭⁽",
|
||||
"",
|
||||
"invalid",
|
||||
}
|
||||
want := []string{inputs[0], `{"Host":"smtp.example.com"}`, `{"Host":"smtp.example.com"}`, "", "invalid", ""}
|
||||
for _, input := range inputs {
|
||||
_, err := x.Insert(&RepoUnit{RepoID: 42, Config: input})
|
||||
require.NoError(t, err)
|
||||
_, err = x.Insert(&LoginSource{Name: "test", Cfg: input})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
_, err := x.Exec("INSERT INTO `repo_unit` (`repo_id`, `config`) VALUES (?, NULL)", 42)
|
||||
require.NoError(t, err)
|
||||
_, err = x.Exec("INSERT INTO `login_source` (`name`, `cfg`) VALUES (?, NULL)", "test")
|
||||
require.NoError(t, err)
|
||||
|
||||
for range 2 {
|
||||
require.NoError(t, NormalizeLegacyConfigJSON(t.Context(), x))
|
||||
var units []RepoUnit
|
||||
require.NoError(t, x.Asc("id").Find(&units))
|
||||
require.Len(t, units, len(want))
|
||||
var sources []LoginSource
|
||||
require.NoError(t, x.Asc("id").Find(&sources))
|
||||
require.Len(t, sources, len(want))
|
||||
for i, config := range want {
|
||||
assert.Equal(t, config, units[i].Config)
|
||||
assert.EqualValues(t, 42, units[i].RepoID)
|
||||
assert.Equal(t, config, sources[i].Cfg)
|
||||
assert.Equal(t, "test", sources[i].Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,10 +6,10 @@ package actions
|
||||
import (
|
||||
"context"
|
||||
|
||||
"gitea.dev/models/db"
|
||||
"gitea.dev/models/perm"
|
||||
repo_model "gitea.dev/models/repo"
|
||||
user_model "gitea.dev/models/user"
|
||||
"gitea.dev/modules/json"
|
||||
"gitea.dev/modules/util"
|
||||
|
||||
"xorm.io/xorm/convert"
|
||||
@@ -30,7 +30,7 @@ type OwnerActionsConfig struct {
|
||||
var _ convert.ConversionFrom = (*OwnerActionsConfig)(nil)
|
||||
|
||||
func (cfg *OwnerActionsConfig) FromDB(bytes []byte) error {
|
||||
_ = json.Unmarshal(bytes, cfg)
|
||||
db.UnmarshalFromDB(bytes, cfg)
|
||||
cfg.TokenPermissionMode, _ = util.EnumValue(cfg.TokenPermissionMode)
|
||||
return nil
|
||||
}
|
||||
|
||||
+30
-3
@@ -10,6 +10,7 @@ import (
|
||||
"reflect"
|
||||
|
||||
"gitea.dev/models/db"
|
||||
"gitea.dev/modules/graceful"
|
||||
"gitea.dev/modules/log"
|
||||
"gitea.dev/modules/optional"
|
||||
"gitea.dev/modules/setting"
|
||||
@@ -60,14 +61,40 @@ var Names = map[Type]string{
|
||||
type Config interface {
|
||||
convert.Conversion
|
||||
SetAuthSource(*Source)
|
||||
GetAuthSource() *Source
|
||||
}
|
||||
|
||||
type ConfigBase struct {
|
||||
AuthSource *Source
|
||||
AuthSource *Source `json:"-"`
|
||||
}
|
||||
|
||||
func (p *ConfigBase) SetAuthSource(s *Source) {
|
||||
p.AuthSource = s
|
||||
func (c *ConfigBase) SetAuthSource(source *Source) {
|
||||
c.AuthSource = source
|
||||
}
|
||||
|
||||
func (c *ConfigBase) GetAuthSource() *Source {
|
||||
return c.AuthSource
|
||||
}
|
||||
|
||||
func UnmarshalAuthSourceConfig(bs []byte, v Config) {
|
||||
if db.UnmarshalFromDB(bs, &v) {
|
||||
return
|
||||
}
|
||||
// This is only a workaround because we don't know whether this unmarshal happens on sign-in page or admin panel.
|
||||
// * Admin should have the chance to edit the auth source in admin panel, so FromDB should not return an error
|
||||
// * The sign-in page should not use the auth source in the next login attempt, but we don't have a clear way to disable it immediately (acceptable)
|
||||
// Actually, corrupted database value is a really rare case, just because AI thinks it should be handled.
|
||||
authSourceID := v.GetAuthSource().ID
|
||||
log.Error("Unable to unmarshal login source id=%d, will mark it as inactive to avoid unexpected sign-in issues", authSourceID)
|
||||
go graceful.GetManager().RunWithShutdownContext(func(ctx context.Context) {
|
||||
// A small time window to avoid race condition with admin's editing because there is no other way.
|
||||
// The UpdateUnix field is not filled when this function is called.
|
||||
_, err := db.GetEngine(ctx).ID(authSourceID).And("is_active = ? AND updated_unix < ?", true, timeutil.TimeStampNow()-5).
|
||||
Cols("is_active").Update(&Source{IsActive: false})
|
||||
if err != nil {
|
||||
log.Error("Unable to set login source id=%d to inactive: %v", authSourceID, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// SkipVerifiable configurations provide a IsSkipVerify to check if SkipVerify is set
|
||||
|
||||
@@ -6,10 +6,15 @@ package auth_test
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
auth_model "gitea.dev/models/auth"
|
||||
"gitea.dev/models/db"
|
||||
"gitea.dev/models/unittest"
|
||||
"gitea.dev/modules/json"
|
||||
"gitea.dev/modules/setting"
|
||||
"gitea.dev/modules/test"
|
||||
"gitea.dev/modules/timeutil"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -24,7 +29,8 @@ type TestSource struct {
|
||||
|
||||
// FromDB fills up a LDAPConfig from serialized format.
|
||||
func (source *TestSource) FromDB(bs []byte) error {
|
||||
return json.Unmarshal(bs, &source)
|
||||
auth_model.UnmarshalAuthSourceConfig(bs, source)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToDB exports a LDAPConfig to a serialized format.
|
||||
@@ -53,3 +59,37 @@ func TestDumpAuthSource(t *testing.T) {
|
||||
// INSERT INTO `login_source` (`id`, `type`, `name`, `is_active`, `is_sync_enabled`, `two_factor_policy`, `cfg`, `created_unix`, `updated_unix`) VALUES (1,6,'TestSource',0,0,'','{"TestField":"TestValue"}',1774179784,1774179784);
|
||||
assert.Contains(t, sb.String(), `'{"TestField":"TestValue"}'`)
|
||||
}
|
||||
|
||||
func TestUnmarshalAuthSourceConfig(t *testing.T) {
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
defer test.MockVariableValue(&setting.IsProd, true)()
|
||||
defer test.MockVariableValue(&setting.IsInTesting, false)()
|
||||
|
||||
cfg := &TestSource{}
|
||||
source := &auth_model.Source{Type: auth_model.OAuth2, Name: t.Name(), IsActive: true, Cfg: cfg}
|
||||
err := db.Insert(t.Context(), source)
|
||||
require.NoError(t, err)
|
||||
cfg.SetAuthSource(source)
|
||||
|
||||
t.Run("ValidConfig", func(t *testing.T) {
|
||||
for _, data := range []string{`{"TestField":"TestValue"}`, "", "null"} {
|
||||
auth_model.UnmarshalAuthSourceConfig([]byte(data), cfg)
|
||||
assert.Equal(t, "TestValue", cfg.TestField)
|
||||
assert.Same(t, source, cfg.GetAuthSource())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("InvalidConfigDeactivatesSource", func(t *testing.T) {
|
||||
_, err = db.GetEngine(t.Context()).ID(source.ID).NoAutoTime().Cols("updated_unix").Update(&auth_model.Source{UpdatedUnix: timeutil.TimeStampNow() - 60})
|
||||
require.NoError(t, err)
|
||||
has, _ := db.GetEngine(t.Context()).ID(source.ID).And("is_active=?", true).Get(&auth_model.Source{})
|
||||
assert.True(t, has)
|
||||
|
||||
auth_model.UnmarshalAuthSourceConfig([]byte(`{"TestField":false}`), cfg)
|
||||
assert.Same(t, source, cfg.GetAuthSource())
|
||||
require.Eventually(t, func() bool {
|
||||
has, _ := db.GetEngine(t.Context()).ID(source.ID).And("is_active=?", false).Get(&auth_model.Source{})
|
||||
return has
|
||||
}, time.Second, time.Millisecond)
|
||||
})
|
||||
}
|
||||
@@ -6,6 +6,7 @@ package db
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gitea.dev/modules/json"
|
||||
"gitea.dev/modules/setting"
|
||||
|
||||
"xorm.io/xorm"
|
||||
@@ -84,3 +85,21 @@ func CellToInt[T ~int | int64](cell xorm.Cell, def T) (ret T, has bool, err erro
|
||||
}
|
||||
return T(val), true, err
|
||||
}
|
||||
|
||||
func UnmarshalFromDB(data []byte, v any) bool {
|
||||
if len(data) == 0 {
|
||||
return true
|
||||
}
|
||||
err := json.Unmarshal(data, v)
|
||||
if err != nil {
|
||||
// Even if database value is corrupted, we should not let it cause unnecessary 500 error or block user's operation
|
||||
// Corrupted database value is a really rare case, it should never happen unless site admin manually modified the database.
|
||||
// Although some database values will be printed into logs, it isn't a real problem
|
||||
// because such case should never really happen. If it happens, logging the value
|
||||
// is the only way to tell site admin which record is wrong since there is no other context information,
|
||||
// and then they need to re-create the config (with new secrets)
|
||||
setting.PanicInDevOrTesting("UnmarshalFromDB %q failed: %v", string(data), err)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
+13
-35
@@ -56,12 +56,11 @@ func init() {
|
||||
// UnitConfig describes common unit config
|
||||
type UnitConfig struct{}
|
||||
|
||||
// FromDB fills up a UnitConfig from serialized format.
|
||||
func (cfg *UnitConfig) FromDB(bs []byte) error {
|
||||
return json.UnmarshalHandleDoubleEncode(bs, &cfg)
|
||||
db.UnmarshalFromDB(bs, cfg)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToDB exports a UnitConfig to a serialized format.
|
||||
func (cfg *UnitConfig) ToDB() ([]byte, error) {
|
||||
return json.Marshal(cfg)
|
||||
}
|
||||
@@ -71,12 +70,11 @@ type ExternalWikiConfig struct {
|
||||
ExternalWikiURL string
|
||||
}
|
||||
|
||||
// FromDB fills up a ExternalWikiConfig from serialized format.
|
||||
func (cfg *ExternalWikiConfig) FromDB(bs []byte) error {
|
||||
return json.UnmarshalHandleDoubleEncode(bs, &cfg)
|
||||
db.UnmarshalFromDB(bs, cfg)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToDB exports a ExternalWikiConfig to a serialized format.
|
||||
func (cfg *ExternalWikiConfig) ToDB() ([]byte, error) {
|
||||
return json.Marshal(cfg)
|
||||
}
|
||||
@@ -89,12 +87,11 @@ type ExternalTrackerConfig struct {
|
||||
ExternalTrackerRegexpPattern string
|
||||
}
|
||||
|
||||
// FromDB fills up a ExternalTrackerConfig from serialized format.
|
||||
func (cfg *ExternalTrackerConfig) FromDB(bs []byte) error {
|
||||
return json.UnmarshalHandleDoubleEncode(bs, &cfg)
|
||||
db.UnmarshalFromDB(bs, cfg)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToDB exports a ExternalTrackerConfig to a serialized format.
|
||||
func (cfg *ExternalTrackerConfig) ToDB() ([]byte, error) {
|
||||
return json.Marshal(cfg)
|
||||
}
|
||||
@@ -106,12 +103,11 @@ type IssuesConfig struct {
|
||||
EnableDependencies bool
|
||||
}
|
||||
|
||||
// FromDB fills up a IssuesConfig from serialized format.
|
||||
func (cfg *IssuesConfig) FromDB(bs []byte) error {
|
||||
return json.UnmarshalHandleDoubleEncode(bs, &cfg)
|
||||
db.UnmarshalFromDB(bs, cfg)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToDB exports a IssuesConfig to a serialized format.
|
||||
func (cfg *IssuesConfig) ToDB() ([]byte, error) {
|
||||
return json.Marshal(cfg)
|
||||
}
|
||||
@@ -155,16 +151,14 @@ func DefaultPullRequestsConfig() *PullRequestsConfig {
|
||||
return cfg
|
||||
}
|
||||
|
||||
// FromDB fills up a PullRequestsConfig from serialized format.
|
||||
func (cfg *PullRequestsConfig) FromDB(bs []byte) error {
|
||||
// set default values for existing PullRequestConfig in DB
|
||||
*cfg = *DefaultPullRequestsConfig()
|
||||
_ = json.UnmarshalHandleDoubleEncode(bs, &cfg) // don't let corrupted database value cause unnecessary 500 error
|
||||
db.UnmarshalFromDB(bs, cfg)
|
||||
cfg.DefaultUpdateStyle = util.IfZero(cfg.DefaultUpdateStyle, UpdateStyleMerge)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToDB exports a PullRequestsConfig to a serialized format.
|
||||
func (cfg *PullRequestsConfig) ToDB() ([]byte, error) {
|
||||
return json.Marshal(cfg)
|
||||
}
|
||||
@@ -228,34 +222,18 @@ type ProjectsConfig struct {
|
||||
ProjectsMode ProjectsMode
|
||||
}
|
||||
|
||||
// FromDB fills up a ProjectsConfig from serialized format.
|
||||
func (cfg *ProjectsConfig) FromDB(bs []byte) error {
|
||||
// TODO: remove GetProjectsMode, only use ProjectsMode
|
||||
cfg.ProjectsMode = ProjectsModeAll
|
||||
return json.UnmarshalHandleDoubleEncode(bs, &cfg)
|
||||
db.UnmarshalFromDB(bs, cfg)
|
||||
cfg.ProjectsMode = util.IfZero(cfg.ProjectsMode, ProjectsModeAll)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToDB exports a ProjectsConfig to a serialized format.
|
||||
func (cfg *ProjectsConfig) ToDB() ([]byte, error) {
|
||||
return json.Marshal(cfg)
|
||||
}
|
||||
|
||||
func (cfg *ProjectsConfig) GetProjectsMode() ProjectsMode {
|
||||
if cfg.ProjectsMode != "" {
|
||||
return cfg.ProjectsMode
|
||||
}
|
||||
|
||||
return ProjectsModeAll
|
||||
}
|
||||
|
||||
func (cfg *ProjectsConfig) IsProjectsAllowed(m ProjectsMode) bool {
|
||||
projectsMode := cfg.GetProjectsMode()
|
||||
|
||||
if m == ProjectsModeNone {
|
||||
return true
|
||||
}
|
||||
|
||||
return projectsMode == m || projectsMode == ProjectsModeAll
|
||||
return m == ProjectsModeNone || cfg.ProjectsMode == m || cfg.ProjectsMode == ProjectsModeAll
|
||||
}
|
||||
|
||||
// BeforeSet is invoked from XORM before setting the value of a field of this object.
|
||||
|
||||
@@ -6,6 +6,7 @@ package repo
|
||||
import (
|
||||
"slices"
|
||||
|
||||
"gitea.dev/models/db"
|
||||
"gitea.dev/models/perm"
|
||||
"gitea.dev/models/unit"
|
||||
"gitea.dev/modules/json"
|
||||
@@ -173,14 +174,12 @@ func (cfg *ActionsConfig) ClampPermissions(perms ActionsTokenPermissions) Action
|
||||
return ClampActionsTokenPermissions(perms, maxPerms)
|
||||
}
|
||||
|
||||
// FromDB fills up a ActionsConfig from serialized format.
|
||||
func (cfg *ActionsConfig) FromDB(bs []byte) error {
|
||||
_ = json.UnmarshalHandleDoubleEncode(bs, &cfg)
|
||||
db.UnmarshalFromDB(bs, cfg)
|
||||
cfg.TokenPermissionMode, _ = util.EnumValue(cfg.TokenPermissionMode)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToDB exports a ActionsConfig to a serialized format.
|
||||
func (cfg *ActionsConfig) ToDB() ([]byte, error) {
|
||||
return json.Marshal(cfg)
|
||||
}
|
||||
@@ -5,7 +5,6 @@ package json
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"encoding/json" //nolint:depguard // this package wraps it
|
||||
"io"
|
||||
)
|
||||
@@ -81,38 +80,3 @@ func MarshalIndent(v any, prefix, indent string) ([]byte, error) {
|
||||
func Valid(data []byte) bool {
|
||||
return json.Valid(data)
|
||||
}
|
||||
|
||||
// UnmarshalHandleDoubleEncode - due to a bug in xorm (see https://gitea.com/xorm/xorm/pulls/1957) - it's
|
||||
// possible that a Blob may be double encoded or gain an unwanted prefix of 0xff 0xfe.
|
||||
func UnmarshalHandleDoubleEncode(bs []byte, v any) error {
|
||||
if len(bs) == 0 {
|
||||
// json.Unmarshal will report errors if input is empty (nil or zero-length)
|
||||
// It seems that XORM ignores the nil but still passes zero-length string into this function
|
||||
// To be consistent, we should treat all empty inputs as success
|
||||
return nil
|
||||
}
|
||||
err := json.Unmarshal(bs, v)
|
||||
if err != nil {
|
||||
ok := true
|
||||
rs := []byte{}
|
||||
temp := make([]byte, 2)
|
||||
for _, rn := range string(bs) {
|
||||
if rn > 0xffff {
|
||||
ok = false
|
||||
break
|
||||
}
|
||||
binary.LittleEndian.PutUint16(temp, uint16(rn))
|
||||
rs = append(rs, temp...)
|
||||
}
|
||||
if ok {
|
||||
if len(rs) > 1 && rs[0] == 0xff && rs[1] == 0xfe {
|
||||
rs = rs[2:]
|
||||
}
|
||||
err = json.Unmarshal(rs, v)
|
||||
}
|
||||
}
|
||||
if err != nil && len(bs) > 2 && bs[0] == 0xff && bs[1] == 0xfe {
|
||||
err = json.Unmarshal(bs[2:], v)
|
||||
}
|
||||
return err
|
||||
}
|
||||
@@ -10,14 +10,6 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGiteaDBJSONUnmarshal(t *testing.T) {
|
||||
var m map[any]any
|
||||
err := UnmarshalHandleDoubleEncode(nil, &m)
|
||||
assert.NoError(t, err)
|
||||
err = UnmarshalHandleDoubleEncode([]byte(""), &m)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestIndent(t *testing.T) {
|
||||
buf := &bytes.Buffer{}
|
||||
err := Indent(buf, []byte(`{"a":1}`), ">", " ")
|
||||
|
||||
@@ -13,14 +13,7 @@ import (
|
||||
"gitea.dev/modules/setting"
|
||||
)
|
||||
|
||||
// .____ ________ _____ __________
|
||||
// | | \______ \ / _ \\______ \
|
||||
// | | | | \ / /_\ \| ___/
|
||||
// | |___ | ` \/ | \ |
|
||||
// |_______ \/_______ /\____|__ /____|
|
||||
// \/ \/ \/
|
||||
|
||||
// Package ldap provide functions & structure to query a LDAP ldap directory
|
||||
// Package ldap provide functions & structure to query an LDAP directory
|
||||
// For now, it's mainly tested again an MS Active Directory service, see README.md for more information
|
||||
|
||||
// Source Basic LDAP authentication service
|
||||
@@ -60,12 +53,8 @@ type Source struct {
|
||||
UserUID string // User Attribute listed in Group
|
||||
}
|
||||
|
||||
// FromDB fills up a LDAPConfig from serialized format.
|
||||
func (source *Source) FromDB(bs []byte) error {
|
||||
err := json.UnmarshalHandleDoubleEncode(bs, &source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
func (source *Source) FromDB(bs []byte) (err error) {
|
||||
auth.UnmarshalAuthSourceConfig(bs, source)
|
||||
if source.BindPasswordEncrypt != "" {
|
||||
source.BindPassword, err = secret.DecryptSecret(setting.SecretKey, source.BindPasswordEncrypt)
|
||||
if err != nil {
|
||||
@@ -76,7 +65,6 @@ func (source *Source) FromDB(bs []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToDB exports a LDAPConfig to a serialized format.
|
||||
func (source *Source) ToDB() ([]byte, error) {
|
||||
var err error
|
||||
source.BindPasswordEncrypt, err = secret.EncryptSecret(setting.SecretKey, source.BindPassword)
|
||||
|
||||
@@ -33,12 +33,11 @@ type Source struct {
|
||||
ExternalIDClaim string
|
||||
}
|
||||
|
||||
// FromDB fills up an OAuth2Config from serialized format.
|
||||
func (source *Source) FromDB(bs []byte) error {
|
||||
return json.UnmarshalHandleDoubleEncode(bs, &source)
|
||||
auth.UnmarshalAuthSourceConfig(bs, source)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToDB exports an OAuth2Config to a serialized format.
|
||||
func (source *Source) ToDB() ([]byte, error) {
|
||||
return json.Marshal(source)
|
||||
}
|
||||
|
||||
@@ -8,13 +8,6 @@ import (
|
||||
"gitea.dev/modules/json"
|
||||
)
|
||||
|
||||
// __________ _____ _____
|
||||
// \______ \/ _ \ / \
|
||||
// | ___/ /_\ \ / \ / \
|
||||
// | | / | \/ Y \
|
||||
// |____| \____|__ /\____|__ /
|
||||
// \/ \/
|
||||
|
||||
// Source holds configuration for the PAM login source.
|
||||
type Source struct {
|
||||
auth.ConfigBase `json:"-"`
|
||||
@@ -23,12 +16,11 @@ type Source struct {
|
||||
EmailDomain string
|
||||
}
|
||||
|
||||
// FromDB fills up a PAMConfig from serialized format.
|
||||
func (source *Source) FromDB(bs []byte) error {
|
||||
return json.UnmarshalHandleDoubleEncode(bs, &source)
|
||||
auth.UnmarshalAuthSourceConfig(bs, source)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToDB exports a PAMConfig to a serialized format.
|
||||
func (source *Source) ToDB() ([]byte, error) {
|
||||
return json.Marshal(source)
|
||||
}
|
||||
|
||||
@@ -8,13 +8,6 @@ import (
|
||||
"gitea.dev/modules/json"
|
||||
)
|
||||
|
||||
// _________ __________________________
|
||||
// / _____/ / \__ ___/\______ \
|
||||
// \_____ \ / \ / \| | | ___/
|
||||
// / \/ Y \ | | |
|
||||
// /_______ /\____|__ /____| |____|
|
||||
// \/ \/
|
||||
|
||||
// Source holds configuration for the SMTP login source.
|
||||
type Source struct {
|
||||
auth.ConfigBase `json:"-"`
|
||||
@@ -29,12 +22,11 @@ type Source struct {
|
||||
DisableHelo bool
|
||||
}
|
||||
|
||||
// FromDB fills up an SMTPConfig from serialized format.
|
||||
func (source *Source) FromDB(bs []byte) error {
|
||||
return json.UnmarshalHandleDoubleEncode(bs, &source)
|
||||
auth.UnmarshalAuthSourceConfig(bs, source)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToDB exports an SMTPConfig to a serialized format.
|
||||
func (source *Source) ToDB() ([]byte, error) {
|
||||
return json.Marshal(source)
|
||||
}
|
||||
|
||||
@@ -8,13 +8,6 @@ import (
|
||||
"gitea.dev/modules/json"
|
||||
)
|
||||
|
||||
// _________ ___________________.___
|
||||
// / _____// _____/\______ \ |
|
||||
// \_____ \ \_____ \ | ___/ |
|
||||
// / \/ \ | | | |
|
||||
// /_______ /_______ / |____| |___|
|
||||
// \/ \/
|
||||
|
||||
// Source holds configuration for SSPI single sign-on.
|
||||
type Source struct {
|
||||
auth.ConfigBase `json:"-"`
|
||||
@@ -26,14 +19,13 @@ type Source struct {
|
||||
DefaultLanguage string
|
||||
}
|
||||
|
||||
// FromDB fills up an SSPIConfig from serialized format.
|
||||
func (cfg *Source) FromDB(bs []byte) error {
|
||||
return json.UnmarshalHandleDoubleEncode(bs, &cfg)
|
||||
func (source *Source) FromDB(bs []byte) error {
|
||||
auth.UnmarshalAuthSourceConfig(bs, source)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToDB exports an SSPIConfig to a serialized format.
|
||||
func (cfg *Source) ToDB() ([]byte, error) {
|
||||
return json.Marshal(cfg)
|
||||
func (source *Source) ToDB() ([]byte, error) {
|
||||
return json.Marshal(source)
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
||||
@@ -466,19 +466,19 @@
|
||||
</p>
|
||||
<div class="ui dropdown selection">
|
||||
<select name="projects_mode">
|
||||
<option value="repo" {{if or (not $isProjectsEnabled) (eq $projectsUnit.ProjectsConfig.GetProjectsMode "repo")}}selected{{end}}>{{ctx.Locale.Tr "repo.settings.projects_mode_repo"}}</option>
|
||||
<option value="owner" {{if or (not $isProjectsEnabled) (eq $projectsUnit.ProjectsConfig.GetProjectsMode "owner")}}selected{{end}}>{{ctx.Locale.Tr "repo.settings.projects_mode_owner"}}</option>
|
||||
<option value="all" {{if or (not $isProjectsEnabled) (eq $projectsUnit.ProjectsConfig.GetProjectsMode "all")}}selected{{end}}>{{ctx.Locale.Tr "repo.settings.projects_mode_all"}}</option>
|
||||
<option value="repo" {{if or (not $isProjectsEnabled) (eq $projectsUnit.ProjectsConfig.ProjectsMode "repo")}}selected{{end}}>{{ctx.Locale.Tr "repo.settings.projects_mode_repo"}}</option>
|
||||
<option value="owner" {{if or (not $isProjectsEnabled) (eq $projectsUnit.ProjectsConfig.ProjectsMode "owner")}}selected{{end}}>{{ctx.Locale.Tr "repo.settings.projects_mode_owner"}}</option>
|
||||
<option value="all" {{if or (not $isProjectsEnabled) (eq $projectsUnit.ProjectsConfig.ProjectsMode "all")}}selected{{end}}>{{ctx.Locale.Tr "repo.settings.projects_mode_all"}}</option>
|
||||
</select>
|
||||
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
||||
<div class="default text">
|
||||
{{if (eq $projectsUnit.ProjectsConfig.GetProjectsMode "repo")}}
|
||||
{{if (eq $projectsUnit.ProjectsConfig.ProjectsMode "repo")}}
|
||||
{{ctx.Locale.Tr "repo.settings.projects_mode_repo"}}
|
||||
{{end}}
|
||||
{{if (eq $projectsUnit.ProjectsConfig.GetProjectsMode "owner")}}
|
||||
{{if (eq $projectsUnit.ProjectsConfig.ProjectsMode "owner")}}
|
||||
{{ctx.Locale.Tr "repo.settings.projects_mode_owner"}}
|
||||
{{end}}
|
||||
{{if (eq $projectsUnit.ProjectsConfig.GetProjectsMode "all")}}
|
||||
{{if (eq $projectsUnit.ProjectsConfig.ProjectsMode "all")}}
|
||||
{{ctx.Locale.Tr "repo.settings.projects_mode_all"}}
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user