diff options
author | Lunny Xiao <xiaolunwen@gmail.com> | 2022-11-02 16:54:36 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-11-02 16:54:36 +0800 |
commit | e72acd5e5b2d043fcf0a0182a1eedaed8120c155 (patch) | |
tree | 77e4c341bef6450e5dfa7a1f61c9693527a133d0 /models/migrations/v1_16 | |
parent | 4827f42f56bcc70d40e073a8502930d9cce39798 (diff) | |
download | gitea-e72acd5e5b2d043fcf0a0182a1eedaed8120c155.tar.gz gitea-e72acd5e5b2d043fcf0a0182a1eedaed8120c155.zip |
Split migrations folder (#21549)
There are too many files in `models/migrations` folder so that I split
them into sub folders.
Diffstat (limited to 'models/migrations/v1_16')
27 files changed, 1078 insertions, 0 deletions
diff --git a/models/migrations/v1_16/main_test.go b/models/migrations/v1_16/main_test.go new file mode 100644 index 0000000000..8109a8a263 --- /dev/null +++ b/models/migrations/v1_16/main_test.go @@ -0,0 +1,15 @@ +// 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 v1_16 //nolint + +import ( + "testing" + + "code.gitea.io/gitea/models/migrations/base" +) + +func TestMain(m *testing.M) { + base.MainTest(m) +} diff --git a/models/migrations/v1_16/v189.go b/models/migrations/v1_16/v189.go new file mode 100644 index 0000000000..b04115845e --- /dev/null +++ b/models/migrations/v1_16/v189.go @@ -0,0 +1,112 @@ +// 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 v1_16 //nolint + +import ( + "encoding/binary" + "fmt" + + "code.gitea.io/gitea/models/migrations/base" + "code.gitea.io/gitea/modules/json" + + "xorm.io/xorm" +) + +func UnwrapLDAPSourceCfg(x *xorm.Engine) error { + jsonUnmarshalHandleDoubleEncode := func(bs []byte, v interface{}) error { + 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 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 + } + + // LoginSource represents an external way for authorizing users. + type LoginSource struct { + ID int64 `xorm:"pk autoincr"` + Type int + IsActived bool `xorm:"INDEX NOT NULL DEFAULT false"` + IsActive bool `xorm:"INDEX NOT NULL DEFAULT false"` + Cfg string `xorm:"TEXT"` + } + + const ldapType = 2 + const dldapType = 5 + + type WrappedSource struct { + Source map[string]interface{} + } + + // change lower_email as unique + if err := x.Sync2(new(LoginSource)); err != nil { + return err + } + + sess := x.NewSession() + defer sess.Close() + + const batchSize = 100 + for start := 0; ; start += batchSize { + sources := make([]*LoginSource, 0, batchSize) + if err := sess.Limit(batchSize, start).Where("`type` = ? OR `type` = ?", ldapType, dldapType).Find(&sources); err != nil { + return err + } + if len(sources) == 0 { + break + } + + for _, source := range sources { + wrapped := &WrappedSource{ + Source: map[string]interface{}{}, + } + err := jsonUnmarshalHandleDoubleEncode([]byte(source.Cfg), &wrapped) + if err != nil { + return fmt.Errorf("failed to unmarshal %s: %w", source.Cfg, err) + } + if wrapped.Source != nil && len(wrapped.Source) > 0 { + bs, err := json.Marshal(wrapped.Source) + if err != nil { + return err + } + source.Cfg = string(bs) + if _, err := sess.ID(source.ID).Cols("cfg").Update(source); err != nil { + return err + } + } + } + } + + if _, err := x.SetExpr("is_active", "is_actived").Update(&LoginSource{}); err != nil { + return fmt.Errorf("SetExpr Update failed: %w", err) + } + + if err := sess.Begin(); err != nil { + return err + } + if err := base.DropTableColumns(sess, "login_source", "is_actived"); err != nil { + return err + } + + return sess.Commit() +} diff --git a/models/migrations/v1_16/v189_test.go b/models/migrations/v1_16/v189_test.go new file mode 100644 index 0000000000..9c0f0967cd --- /dev/null +++ b/models/migrations/v1_16/v189_test.go @@ -0,0 +1,83 @@ +// 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 v1_16 //nolint + +import ( + "testing" + + "code.gitea.io/gitea/models/migrations/base" + "code.gitea.io/gitea/modules/json" + + "github.com/stretchr/testify/assert" +) + +// LoginSource represents an external way for authorizing users. +type LoginSourceOriginalV189 struct { + ID int64 `xorm:"pk autoincr"` + Type int + IsActived bool `xorm:"INDEX NOT NULL DEFAULT false"` + Cfg string `xorm:"TEXT"` + Expected string `xorm:"TEXT"` +} + +func (ls *LoginSourceOriginalV189) TableName() string { + return "login_source" +} + +func Test_UnwrapLDAPSourceCfg(t *testing.T) { + // Prepare and load the testing database + x, deferable := base.PrepareTestEnv(t, 0, new(LoginSourceOriginalV189)) + if x == nil || t.Failed() { + defer deferable() + return + } + defer deferable() + + // LoginSource represents an external way for authorizing users. + type LoginSource struct { + ID int64 `xorm:"pk autoincr"` + Type int + IsActive bool `xorm:"INDEX NOT NULL DEFAULT false"` + Cfg string `xorm:"TEXT"` + Expected string `xorm:"TEXT"` + } + + // Run the migration + if err := UnwrapLDAPSourceCfg(x); err != nil { + assert.NoError(t, err) + return + } + + const batchSize = 100 + for start := 0; ; start += batchSize { + sources := make([]*LoginSource, 0, batchSize) + if err := x.Table("login_source").Limit(batchSize, start).Find(&sources); err != nil { + assert.NoError(t, err) + return + } + + if len(sources) == 0 { + break + } + + for _, source := range sources { + converted := map[string]interface{}{} + expected := map[string]interface{}{} + + if err := json.Unmarshal([]byte(source.Cfg), &converted); err != nil { + assert.NoError(t, err) + return + } + + if err := json.Unmarshal([]byte(source.Expected), &expected); err != nil { + assert.NoError(t, err) + return + } + + assert.EqualValues(t, expected, converted, "UnwrapLDAPSourceCfg failed for %d", source.ID) + assert.EqualValues(t, source.ID%2 == 0, source.IsActive, "UnwrapLDAPSourceCfg failed for %d", source.ID) + } + } +} diff --git a/models/migrations/v1_16/v190.go b/models/migrations/v1_16/v190.go new file mode 100644 index 0000000000..a669fc31fa --- /dev/null +++ b/models/migrations/v1_16/v190.go @@ -0,0 +1,24 @@ +// 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 v1_16 //nolint + +import ( + "fmt" + + "xorm.io/xorm" +) + +func AddAgitFlowPullRequest(x *xorm.Engine) error { + type PullRequestFlow int + + type PullRequest struct { + Flow PullRequestFlow `xorm:"NOT NULL DEFAULT 0"` + } + + if err := x.Sync2(new(PullRequest)); err != nil { + return fmt.Errorf("sync2: %w", err) + } + return nil +} diff --git a/models/migrations/v1_16/v191.go b/models/migrations/v1_16/v191.go new file mode 100644 index 0000000000..461ac653d4 --- /dev/null +++ b/models/migrations/v1_16/v191.go @@ -0,0 +1,29 @@ +// 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 v1_16 //nolint + +import ( + "code.gitea.io/gitea/modules/setting" + + "xorm.io/xorm" +) + +func AlterIssueAndCommentTextFieldsToLongText(x *xorm.Engine) error { + sess := x.NewSession() + defer sess.Close() + if err := sess.Begin(); err != nil { + return err + } + + if setting.Database.UseMySQL { + if _, err := sess.Exec("ALTER TABLE `issue` CHANGE `content` `content` LONGTEXT"); err != nil { + return err + } + if _, err := sess.Exec("ALTER TABLE `comment` CHANGE `content` `content` LONGTEXT, CHANGE `patch` `patch` LONGTEXT"); err != nil { + return err + } + } + return sess.Commit() +} diff --git a/models/migrations/v1_16/v192.go b/models/migrations/v1_16/v192.go new file mode 100644 index 0000000000..e3ac2654fd --- /dev/null +++ b/models/migrations/v1_16/v192.go @@ -0,0 +1,20 @@ +// 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 v1_16 //nolint + +import ( + "code.gitea.io/gitea/models/migrations/base" + + "xorm.io/xorm" +) + +func RecreateIssueResourceIndexTable(x *xorm.Engine) error { + type IssueIndex struct { + GroupID int64 `xorm:"pk"` + MaxIndex int64 `xorm:"index"` + } + + return base.RecreateTables(new(IssueIndex))(x) +} diff --git a/models/migrations/v1_16/v193.go b/models/migrations/v1_16/v193.go new file mode 100644 index 0000000000..8bf960feb3 --- /dev/null +++ b/models/migrations/v1_16/v193.go @@ -0,0 +1,33 @@ +// 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 v1_16 //nolint + +import ( + "xorm.io/xorm" +) + +func AddRepoIDForAttachment(x *xorm.Engine) error { + type Attachment struct { + ID int64 `xorm:"pk autoincr"` + UUID string `xorm:"uuid UNIQUE"` + RepoID int64 `xorm:"INDEX"` // this should not be zero + IssueID int64 `xorm:"INDEX"` // maybe zero when creating + ReleaseID int64 `xorm:"INDEX"` // maybe zero when creating + UploaderID int64 `xorm:"INDEX DEFAULT 0"` + } + if err := x.Sync2(new(Attachment)); err != nil { + return err + } + + if _, err := x.Exec("UPDATE `attachment` set repo_id = (SELECT repo_id FROM `issue` WHERE `issue`.id = `attachment`.issue_id) WHERE `attachment`.issue_id > 0"); err != nil { + return err + } + + if _, err := x.Exec("UPDATE `attachment` set repo_id = (SELECT repo_id FROM `release` WHERE `release`.id = `attachment`.release_id) WHERE `attachment`.release_id > 0"); err != nil { + return err + } + + return nil +} diff --git a/models/migrations/v1_16/v193_test.go b/models/migrations/v1_16/v193_test.go new file mode 100644 index 0000000000..b573a54bb0 --- /dev/null +++ b/models/migrations/v1_16/v193_test.go @@ -0,0 +1,73 @@ +// 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 v1_16 //nolint + +import ( + "testing" + + "code.gitea.io/gitea/models/migrations/base" + + "github.com/stretchr/testify/assert" +) + +func Test_AddRepoIDForAttachment(t *testing.T) { + type Attachment struct { + ID int64 `xorm:"pk autoincr"` + UUID string `xorm:"uuid UNIQUE"` + RepoID int64 `xorm:"INDEX"` // this should not be zero + IssueID int64 `xorm:"INDEX"` // maybe zero when creating + ReleaseID int64 `xorm:"INDEX"` // maybe zero when creating + UploaderID int64 `xorm:"INDEX DEFAULT 0"` + } + + type Issue struct { + ID int64 + RepoID int64 + } + + type Release struct { + ID int64 + RepoID int64 + } + + // Prepare and load the testing database + x, deferrable := base.PrepareTestEnv(t, 0, new(Attachment), new(Issue), new(Release)) + defer deferrable() + if x == nil || t.Failed() { + return + } + + // Run the migration + if err := AddRepoIDForAttachment(x); err != nil { + assert.NoError(t, err) + return + } + + var issueAttachments []*Attachment + err := x.Where("issue_id > 0").Find(&issueAttachments) + assert.NoError(t, err) + for _, attach := range issueAttachments { + assert.Greater(t, attach.RepoID, 0) + assert.Greater(t, attach.IssueID, 0) + var issue Issue + has, err := x.ID(attach.IssueID).Get(&issue) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, attach.RepoID, issue.RepoID) + } + + var releaseAttachments []*Attachment + err = x.Where("release_id > 0").Find(&releaseAttachments) + assert.NoError(t, err) + for _, attach := range releaseAttachments { + assert.Greater(t, attach.RepoID, 0) + assert.Greater(t, attach.IssueID, 0) + var release Release + has, err := x.ID(attach.ReleaseID).Get(&release) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, attach.RepoID, release.RepoID) + } +} diff --git a/models/migrations/v1_16/v194.go b/models/migrations/v1_16/v194.go new file mode 100644 index 0000000000..8486b1131a --- /dev/null +++ b/models/migrations/v1_16/v194.go @@ -0,0 +1,22 @@ +// 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 v1_16 //nolint + +import ( + "fmt" + + "xorm.io/xorm" +) + +func AddBranchProtectionUnprotectedFilesColumn(x *xorm.Engine) error { + type ProtectedBranch struct { + UnprotectedFilePatterns string `xorm:"TEXT"` + } + + if err := x.Sync2(new(ProtectedBranch)); err != nil { + return fmt.Errorf("Sync2: %w", err) + } + return nil +} diff --git a/models/migrations/v1_16/v195.go b/models/migrations/v1_16/v195.go new file mode 100644 index 0000000000..a7165cbf75 --- /dev/null +++ b/models/migrations/v1_16/v195.go @@ -0,0 +1,47 @@ +// 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 v1_16 //nolint + +import ( + "fmt" + + "xorm.io/xorm" +) + +func AddTableCommitStatusIndex(x *xorm.Engine) error { + // CommitStatusIndex represents a table for commit status index + type CommitStatusIndex struct { + ID int64 + RepoID int64 `xorm:"unique(repo_sha)"` + SHA string `xorm:"unique(repo_sha)"` + MaxIndex int64 `xorm:"index"` + } + + if err := x.Sync2(new(CommitStatusIndex)); err != nil { + return fmt.Errorf("Sync2: %w", err) + } + + sess := x.NewSession() + defer sess.Close() + + if err := sess.Begin(); err != nil { + return err + } + + // Remove data we're goint to rebuild + if _, err := sess.Table("commit_status_index").Where("1=1").Delete(&CommitStatusIndex{}); err != nil { + return err + } + + // Create current data for all repositories with issues and PRs + if _, err := sess.Exec("INSERT INTO commit_status_index (repo_id, sha, max_index) " + + "SELECT max_data.repo_id, max_data.sha, max_data.max_index " + + "FROM ( SELECT commit_status.repo_id AS repo_id, commit_status.sha AS sha, max(commit_status.`index`) AS max_index " + + "FROM commit_status GROUP BY commit_status.repo_id, commit_status.sha) AS max_data"); err != nil { + return err + } + + return sess.Commit() +} diff --git a/models/migrations/v1_16/v195_test.go b/models/migrations/v1_16/v195_test.go new file mode 100644 index 0000000000..06ff13b52b --- /dev/null +++ b/models/migrations/v1_16/v195_test.go @@ -0,0 +1,64 @@ +// 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 v1_16 //nolint + +import ( + "testing" + + "code.gitea.io/gitea/models/migrations/base" + + "github.com/stretchr/testify/assert" +) + +func Test_AddTableCommitStatusIndex(t *testing.T) { + // Create the models used in the migration + type CommitStatus struct { + ID int64 `xorm:"pk autoincr"` + Index int64 `xorm:"INDEX UNIQUE(repo_sha_index)"` + RepoID int64 `xorm:"INDEX UNIQUE(repo_sha_index)"` + SHA string `xorm:"VARCHAR(64) NOT NULL INDEX UNIQUE(repo_sha_index)"` + } + + // Prepare and load the testing database + x, deferable := base.PrepareTestEnv(t, 0, new(CommitStatus)) + if x == nil || t.Failed() { + defer deferable() + return + } + defer deferable() + + // Run the migration + if err := AddTableCommitStatusIndex(x); err != nil { + assert.NoError(t, err) + return + } + + type CommitStatusIndex struct { + ID int64 + RepoID int64 `xorm:"unique(repo_sha)"` + SHA string `xorm:"unique(repo_sha)"` + MaxIndex int64 `xorm:"index"` + } + + start := 0 + const batchSize = 1000 + for { + indexes := make([]CommitStatusIndex, 0, batchSize) + err := x.Table("commit_status_index").Limit(batchSize, start).Find(&indexes) + assert.NoError(t, err) + + for _, idx := range indexes { + var maxIndex int + has, err := x.SQL("SELECT max(`index`) FROM commit_status WHERE repo_id = ? AND sha = ?", idx.RepoID, idx.SHA).Get(&maxIndex) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, maxIndex, idx.MaxIndex) + } + if len(indexes) < batchSize { + break + } + start += len(indexes) + } +} diff --git a/models/migrations/v1_16/v196.go b/models/migrations/v1_16/v196.go new file mode 100644 index 0000000000..b73a56607e --- /dev/null +++ b/models/migrations/v1_16/v196.go @@ -0,0 +1,22 @@ +// 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 v1_16 //nolint + +import ( + "fmt" + + "xorm.io/xorm" +) + +func AddColorColToProjectBoard(x *xorm.Engine) error { + type ProjectBoard struct { + Color string `xorm:"VARCHAR(7)"` + } + + if err := x.Sync2(new(ProjectBoard)); err != nil { + return fmt.Errorf("Sync2: %w", err) + } + return nil +} diff --git a/models/migrations/v1_16/v197.go b/models/migrations/v1_16/v197.go new file mode 100644 index 0000000000..da2e9ae76f --- /dev/null +++ b/models/migrations/v1_16/v197.go @@ -0,0 +1,20 @@ +// 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 v1_16 //nolint + +import ( + "xorm.io/xorm" +) + +func AddRenamedBranchTable(x *xorm.Engine) error { + type RenamedBranch struct { + ID int64 `xorm:"pk autoincr"` + RepoID int64 `xorm:"INDEX NOT NULL"` + From string + To string + CreatedUnix int64 `xorm:"created"` + } + return x.Sync2(new(RenamedBranch)) +} diff --git a/models/migrations/v1_16/v198.go b/models/migrations/v1_16/v198.go new file mode 100644 index 0000000000..0e09ee5306 --- /dev/null +++ b/models/migrations/v1_16/v198.go @@ -0,0 +1,33 @@ +// 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 v1_16 //nolint + +import ( + "fmt" + + "code.gitea.io/gitea/modules/timeutil" + + "xorm.io/xorm" +) + +func AddTableIssueContentHistory(x *xorm.Engine) error { + type IssueContentHistory struct { + ID int64 `xorm:"pk autoincr"` + PosterID int64 + IssueID int64 `xorm:"INDEX"` + CommentID int64 `xorm:"INDEX"` + EditedUnix timeutil.TimeStamp `xorm:"INDEX"` + ContentText string `xorm:"LONGTEXT"` + IsFirstCreated bool + IsDeleted bool + } + + sess := x.NewSession() + defer sess.Close() + if err := sess.Sync2(new(IssueContentHistory)); err != nil { + return fmt.Errorf("Sync2: %w", err) + } + return sess.Commit() +} diff --git a/models/migrations/v1_16/v199.go b/models/migrations/v1_16/v199.go new file mode 100644 index 0000000000..53ed7e4e8a --- /dev/null +++ b/models/migrations/v1_16/v199.go @@ -0,0 +1,7 @@ +// 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 v1_16 //nolint + +// We used to use a table `remote_version` to store information for updater, now we use `AppState`, so this migration task is a no-op now. diff --git a/models/migrations/v1_16/v200.go b/models/migrations/v1_16/v200.go new file mode 100644 index 0000000000..70ee36354c --- /dev/null +++ b/models/migrations/v1_16/v200.go @@ -0,0 +1,23 @@ +// 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 v1_16 //nolint + +import ( + "fmt" + + "xorm.io/xorm" +) + +func AddTableAppState(x *xorm.Engine) error { + type AppState struct { + ID string `xorm:"pk varchar(200)"` + Revision int64 + Content string `xorm:"LONGTEXT"` + } + if err := x.Sync2(new(AppState)); err != nil { + return fmt.Errorf("Sync2: %w", err) + } + return nil +} diff --git a/models/migrations/v1_16/v201.go b/models/migrations/v1_16/v201.go new file mode 100644 index 0000000000..6a2eda77b1 --- /dev/null +++ b/models/migrations/v1_16/v201.go @@ -0,0 +1,15 @@ +// 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 v1_16 //nolint + +import ( + "xorm.io/xorm" +) + +func DropTableRemoteVersion(x *xorm.Engine) error { + // drop the orphaned table introduced in `v199`, now the update checker also uses AppState, do not need this table + _ = x.DropTables("remote_version") + return nil +} diff --git a/models/migrations/v1_16/v202.go b/models/migrations/v1_16/v202.go new file mode 100644 index 0000000000..de0576b8d6 --- /dev/null +++ b/models/migrations/v1_16/v202.go @@ -0,0 +1,24 @@ +// 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 v1_16 //nolint + +import ( + "fmt" + + "xorm.io/xorm" +) + +func CreateUserSettingsTable(x *xorm.Engine) error { + type UserSetting struct { + ID int64 `xorm:"pk autoincr"` + UserID int64 `xorm:"index unique(key_userid)"` // to load all of someone's settings + SettingKey string `xorm:"varchar(255) index unique(key_userid)"` // ensure key is always lowercase + SettingValue string `xorm:"text"` + } + if err := x.Sync2(new(UserSetting)); err != nil { + return fmt.Errorf("sync2: %w", err) + } + return nil +} diff --git a/models/migrations/v1_16/v203.go b/models/migrations/v1_16/v203.go new file mode 100644 index 0000000000..78903cb595 --- /dev/null +++ b/models/migrations/v1_16/v203.go @@ -0,0 +1,18 @@ +// 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 v1_16 //nolint + +import ( + "xorm.io/xorm" +) + +func AddProjectIssueSorting(x *xorm.Engine) error { + // ProjectIssue saves relation from issue to a project + type ProjectIssue struct { + Sorting int64 `xorm:"NOT NULL DEFAULT 0"` + } + + return x.Sync2(new(ProjectIssue)) +} diff --git a/models/migrations/v1_16/v204.go b/models/migrations/v1_16/v204.go new file mode 100644 index 0000000000..8151866fa2 --- /dev/null +++ b/models/migrations/v1_16/v204.go @@ -0,0 +1,15 @@ +// 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 v1_16 //nolint + +import "xorm.io/xorm" + +func AddSSHKeyIsVerified(x *xorm.Engine) error { + type PublicKey struct { + Verified bool `xorm:"NOT NULL DEFAULT false"` + } + + return x.Sync2(new(PublicKey)) +} diff --git a/models/migrations/v1_16/v205.go b/models/migrations/v1_16/v205.go new file mode 100644 index 0000000000..9cca445d35 --- /dev/null +++ b/models/migrations/v1_16/v205.go @@ -0,0 +1,43 @@ +// Copyright 2022 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 v1_16 //nolint + +import ( + "code.gitea.io/gitea/models/migrations/base" + + "xorm.io/xorm" + "xorm.io/xorm/schemas" +) + +func MigrateUserPasswordSalt(x *xorm.Engine) error { + dbType := x.Dialect().URI().DBType + // For SQLITE, the max length doesn't matter. + if dbType == schemas.SQLITE { + return nil + } + + if err := base.ModifyColumn(x, "user", &schemas.Column{ + Name: "rands", + SQLType: schemas.SQLType{ + Name: "VARCHAR", + }, + Length: 32, + // MySQL will like us again. + Nullable: true, + DefaultIsEmpty: true, + }); err != nil { + return err + } + + return base.ModifyColumn(x, "user", &schemas.Column{ + Name: "salt", + SQLType: schemas.SQLType{ + Name: "VARCHAR", + }, + Length: 32, + Nullable: true, + DefaultIsEmpty: true, + }) +} diff --git a/models/migrations/v1_16/v206.go b/models/migrations/v1_16/v206.go new file mode 100644 index 0000000000..b2530d1005 --- /dev/null +++ b/models/migrations/v1_16/v206.go @@ -0,0 +1,29 @@ +// Copyright 2022 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 v1_16 //nolint + +import ( + "fmt" + + "xorm.io/xorm" +) + +func AddAuthorizeColForTeamUnit(x *xorm.Engine) error { + type TeamUnit struct { + ID int64 `xorm:"pk autoincr"` + OrgID int64 `xorm:"INDEX"` + TeamID int64 `xorm:"UNIQUE(s)"` + Type int `xorm:"UNIQUE(s)"` + AccessMode int + } + + if err := x.Sync2(new(TeamUnit)); err != nil { + return fmt.Errorf("sync2: %w", err) + } + + // migrate old permission + _, err := x.Exec("UPDATE team_unit SET access_mode = (SELECT authorize FROM team WHERE team.id = team_unit.team_id)") + return err +} diff --git a/models/migrations/v1_16/v207.go b/models/migrations/v1_16/v207.go new file mode 100644 index 0000000000..f93ae4c339 --- /dev/null +++ b/models/migrations/v1_16/v207.go @@ -0,0 +1,15 @@ +// 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 v1_16 //nolint + +import ( + "xorm.io/xorm" +) + +func AddWebAuthnCred(x *xorm.Engine) error { + // NO-OP Don't migrate here - let v210 do this. + + return nil +} diff --git a/models/migrations/v1_16/v208.go b/models/migrations/v1_16/v208.go new file mode 100644 index 0000000000..40f8b05b80 --- /dev/null +++ b/models/migrations/v1_16/v208.go @@ -0,0 +1,14 @@ +// 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 v1_16 //nolint + +import ( + "xorm.io/xorm" +) + +func UseBase32HexForCredIDInWebAuthnCredential(x *xorm.Engine) error { + // noop + return nil +} diff --git a/models/migrations/v1_16/v209.go b/models/migrations/v1_16/v209.go new file mode 100644 index 0000000000..e2f06bbfb0 --- /dev/null +++ b/models/migrations/v1_16/v209.go @@ -0,0 +1,17 @@ +// Copyright 2022 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 v1_16 //nolint + +import ( + "xorm.io/xorm" +) + +func IncreaseCredentialIDTo410(x *xorm.Engine) error { + // no-op + // v208 was completely wrong + // So now we have to no-op again. + + return nil +} diff --git a/models/migrations/v1_16/v210.go b/models/migrations/v1_16/v210.go new file mode 100644 index 0000000000..b59b356607 --- /dev/null +++ b/models/migrations/v1_16/v210.go @@ -0,0 +1,185 @@ +// Copyright 2022 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 v1_16 //nolint + +import ( + "crypto/elliptic" + "encoding/base32" + "fmt" + "strings" + + "code.gitea.io/gitea/models/migrations/base" + "code.gitea.io/gitea/modules/timeutil" + + "github.com/tstranex/u2f" + "xorm.io/xorm" + "xorm.io/xorm/schemas" +) + +// v208 migration was completely broken +func RemigrateU2FCredentials(x *xorm.Engine) error { + // Create webauthnCredential table + type webauthnCredential struct { + ID int64 `xorm:"pk autoincr"` + Name string + LowerName string `xorm:"unique(s)"` + UserID int64 `xorm:"INDEX unique(s)"` + CredentialID string `xorm:"INDEX VARCHAR(410)"` // CredentalID in U2F is at most 255bytes / 5 * 8 = 408 - add a few extra characters for safety + PublicKey []byte + AttestationType string + AAGUID []byte + SignCount uint32 `xorm:"BIGINT"` + CloneWarning bool + CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` + UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` + } + if err := x.Sync2(&webauthnCredential{}); err != nil { + return err + } + + switch x.Dialect().URI().DBType { + case schemas.MYSQL: + _, err := x.Exec("ALTER TABLE webauthn_credential MODIFY COLUMN credential_id VARCHAR(410)") + if err != nil { + return err + } + case schemas.ORACLE: + _, err := x.Exec("ALTER TABLE webauthn_credential MODIFY credential_id VARCHAR(410)") + if err != nil { + return err + } + case schemas.MSSQL: + // This column has an index on it. I could write all of the code to attempt to change the index OR + // I could just use recreate table. + sess := x.NewSession() + if err := sess.Begin(); err != nil { + _ = sess.Close() + return err + } + + if err := base.RecreateTable(sess, new(webauthnCredential)); err != nil { + _ = sess.Close() + return err + } + if err := sess.Commit(); err != nil { + _ = sess.Close() + return err + } + if err := sess.Close(); err != nil { + return err + } + case schemas.POSTGRES: + _, err := x.Exec("ALTER TABLE webauthn_credential ALTER COLUMN credential_id TYPE VARCHAR(410)") + if err != nil { + return err + } + default: + // SQLite doesn't support ALTER COLUMN, and it already makes String _TEXT_ by default so no migration needed + // nor is there any need to re-migrate + } + + exist, err := x.IsTableExist("u2f_registration") + if err != nil { + return err + } + if !exist { + return nil + } + + // Now migrate the old u2f registrations to the new format + type u2fRegistration struct { + ID int64 `xorm:"pk autoincr"` + Name string + UserID int64 `xorm:"INDEX"` + Raw []byte + Counter uint32 `xorm:"BIGINT"` + CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` + UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` + } + + var start int + regs := make([]*u2fRegistration, 0, 50) + for { + err := x.OrderBy("id").Limit(50, start).Find(®s) + if err != nil { + return err + } + + err = func() error { + sess := x.NewSession() + defer sess.Close() + if err := sess.Begin(); err != nil { + return fmt.Errorf("unable to allow start session. Error: %w", err) + } + if x.Dialect().URI().DBType == schemas.MSSQL { + if _, err := sess.Exec("SET IDENTITY_INSERT `webauthn_credential` ON"); err != nil { + return fmt.Errorf("unable to allow identity insert on webauthn_credential. Error: %w", err) + } + } + for _, reg := range regs { + parsed := new(u2f.Registration) + err = parsed.UnmarshalBinary(reg.Raw) + if err != nil { + continue + } + remigrated := &webauthnCredential{ + ID: reg.ID, + Name: reg.Name, + LowerName: strings.ToLower(reg.Name), + UserID: reg.UserID, + CredentialID: base32.HexEncoding.EncodeToString(parsed.KeyHandle), + PublicKey: elliptic.Marshal(elliptic.P256(), parsed.PubKey.X, parsed.PubKey.Y), + AttestationType: "fido-u2f", + AAGUID: []byte{}, + SignCount: reg.Counter, + UpdatedUnix: reg.UpdatedUnix, + CreatedUnix: reg.CreatedUnix, + } + + has, err := sess.ID(reg.ID).Get(new(webauthnCredential)) + if err != nil { + return fmt.Errorf("unable to get webauthn_credential[%d]. Error: %w", reg.ID, err) + } + if !has { + has, err := sess.Where("`lower_name`=?", remigrated.LowerName).And("`user_id`=?", remigrated.UserID).Exist(new(webauthnCredential)) + if err != nil { + return fmt.Errorf("unable to check webauthn_credential[lower_name: %s, user_id: %d]. Error: %w", remigrated.LowerName, remigrated.UserID, err) + } + if !has { + _, err = sess.Insert(remigrated) + if err != nil { + return fmt.Errorf("unable to (re)insert webauthn_credential[%d]. Error: %w", reg.ID, err) + } + + continue + } + } + + _, err = sess.ID(remigrated.ID).AllCols().Update(remigrated) + if err != nil { + return fmt.Errorf("unable to update webauthn_credential[%d]. Error: %w", reg.ID, err) + } + } + return sess.Commit() + }() + if err != nil { + return err + } + + if len(regs) < 50 { + break + } + start += 50 + regs = regs[:0] + } + + if x.Dialect().URI().DBType == schemas.POSTGRES { + if _, err := x.Exec("SELECT setval('webauthn_credential_id_seq', COALESCE((SELECT MAX(id)+1 FROM `webauthn_credential`), 1), false)"); err != nil { + return err + } + } + + return nil +} diff --git a/models/migrations/v1_16/v210_test.go b/models/migrations/v1_16/v210_test.go new file mode 100644 index 0000000000..20c430594e --- /dev/null +++ b/models/migrations/v1_16/v210_test.go @@ -0,0 +1,76 @@ +// 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 v1_16 //nolint + +import ( + "testing" + + "code.gitea.io/gitea/models/migrations/base" + "code.gitea.io/gitea/modules/timeutil" + + "github.com/stretchr/testify/assert" + "xorm.io/xorm/schemas" +) + +func Test_RemigrateU2FCredentials(t *testing.T) { + // Create webauthnCredential table + type WebauthnCredential struct { + ID int64 `xorm:"pk autoincr"` + Name string + LowerName string `xorm:"unique(s)"` + UserID int64 `xorm:"INDEX unique(s)"` + CredentialID string `xorm:"INDEX VARCHAR(410)"` // CredentalID in U2F is at most 255bytes / 5 * 8 = 408 - add a few extra characters for safety + PublicKey []byte + AttestationType string + SignCount uint32 `xorm:"BIGINT"` + CloneWarning bool + } + + // Now migrate the old u2f registrations to the new format + type U2fRegistration struct { + ID int64 `xorm:"pk autoincr"` + Name string + UserID int64 `xorm:"INDEX"` + Raw []byte + Counter uint32 `xorm:"BIGINT"` + CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` + UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` + } + + type ExpectedWebauthnCredential struct { + ID int64 `xorm:"pk autoincr"` + CredentialID string `xorm:"INDEX VARCHAR(410)"` // CredentalID in U2F is at most 255bytes / 5 * 8 = 408 - add a few extra characters for safety + } + + // Prepare and load the testing database + x, deferable := base.PrepareTestEnv(t, 0, new(WebauthnCredential), new(U2fRegistration), new(ExpectedWebauthnCredential)) + if x == nil || t.Failed() { + defer deferable() + return + } + defer deferable() + + if x.Dialect().URI().DBType == schemas.SQLITE { + return + } + + // Run the migration + if err := RemigrateU2FCredentials(x); err != nil { + assert.NoError(t, err) + return + } + + expected := []ExpectedWebauthnCredential{} + if err := x.Table("expected_webauthn_credential").Asc("id").Find(&expected); !assert.NoError(t, err) { + return + } + + got := []ExpectedWebauthnCredential{} + if err := x.Table("webauthn_credential").Select("id, credential_id").Asc("id").Find(&got); !assert.NoError(t, err) { + return + } + + assert.EqualValues(t, expected, got) +} |