mirror of
https://github.com/go-gitea/gitea.git
synced 2026-09-08 14:03:24 +09:00
refactor: prepare to decouple the "model migration" package and "models" package (#38533)
Migrations should never use model structs directly, because the model structs can be different in different releases. e.g. if one migration uses "User" model, it works in the early releases, then one day, when the User model changes, the migration breaks because it will use the new (incorrect) User model, it should only use the old User model. The same to "modules/structs". --------- Signed-off-by: wxiaoguang <wxiaoguang@gmail.com> Co-authored-by: delvh <dev.lh@web.de>
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_16
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"gitea.dev/modelmigration/migrationtest"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
migrationtest.MainTest(m)
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_16
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
|
||||
"gitea.dev/modelmigration/base"
|
||||
"gitea.dev/modules/json"
|
||||
)
|
||||
|
||||
func UnwrapLDAPSourceCfg(x base.EngineMigration) error {
|
||||
jsonUnmarshalHandleDoubleEncode := func(bs []byte, v any) 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]any
|
||||
}
|
||||
|
||||
// change lower_email as unique
|
||||
if err := x.Sync(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]any{},
|
||||
}
|
||||
err := jsonUnmarshalHandleDoubleEncode([]byte(source.Cfg), &wrapped)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to unmarshal %s: %w", source.Cfg, err)
|
||||
}
|
||||
if 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()
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_16
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"gitea.dev/modelmigration/migrationtest"
|
||||
"gitea.dev/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 := migrationtest.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]any{}
|
||||
expected := map[string]any{}
|
||||
|
||||
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.Equal(t, expected, converted, "UnwrapLDAPSourceCfg failed for %d", source.ID)
|
||||
assert.Equal(t, source.ID%2 == 0, source.IsActive, "UnwrapLDAPSourceCfg failed for %d", source.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_16
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gitea.dev/modelmigration/base"
|
||||
)
|
||||
|
||||
func AddAgitFlowPullRequest(x base.EngineMigration) error {
|
||||
type PullRequestFlow int
|
||||
|
||||
type PullRequest struct {
|
||||
Flow PullRequestFlow `xorm:"NOT NULL DEFAULT 0"`
|
||||
}
|
||||
|
||||
if err := x.Sync(new(PullRequest)); err != nil {
|
||||
return fmt.Errorf("sync2: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_16
|
||||
|
||||
import (
|
||||
"gitea.dev/modelmigration/base"
|
||||
"gitea.dev/modules/setting"
|
||||
)
|
||||
|
||||
func AlterIssueAndCommentTextFieldsToLongText(x base.EngineMigration) error {
|
||||
sess := x.NewSession()
|
||||
defer sess.Close()
|
||||
if err := sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if setting.Database.Type.IsMySQL() {
|
||||
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()
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_16
|
||||
|
||||
import (
|
||||
"gitea.dev/modelmigration/base"
|
||||
)
|
||||
|
||||
func RecreateIssueResourceIndexTable(x base.EngineMigration) error {
|
||||
type IssueIndex struct {
|
||||
GroupID int64 `xorm:"pk"`
|
||||
MaxIndex int64 `xorm:"index"`
|
||||
}
|
||||
|
||||
return base.RecreateTables(new(IssueIndex))(x)
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_16
|
||||
|
||||
import "gitea.dev/modelmigration/base"
|
||||
|
||||
func AddRepoIDForAttachment(x base.EngineMigration) 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.Sync(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
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_16
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"gitea.dev/modelmigration/migrationtest"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_AddRepoIDForAttachment(t *testing.T) {
|
||||
type Attachment struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
UUID string `xorm:"uuid UNIQUE"`
|
||||
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 := migrationtest.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
|
||||
}
|
||||
|
||||
type NewAttachment 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"`
|
||||
}
|
||||
|
||||
var issueAttachments []*NewAttachment
|
||||
err := x.Table("attachment").Where("issue_id > 0").Find(&issueAttachments)
|
||||
assert.NoError(t, err)
|
||||
for _, attach := range issueAttachments {
|
||||
assert.Positive(t, attach.RepoID)
|
||||
assert.Positive(t, attach.IssueID)
|
||||
var issue Issue
|
||||
has, err := x.ID(attach.IssueID).Get(&issue)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, has)
|
||||
assert.Equal(t, attach.RepoID, issue.RepoID)
|
||||
}
|
||||
|
||||
var releaseAttachments []*NewAttachment
|
||||
err = x.Table("attachment").Where("release_id > 0").Find(&releaseAttachments)
|
||||
assert.NoError(t, err)
|
||||
for _, attach := range releaseAttachments {
|
||||
assert.Positive(t, attach.RepoID)
|
||||
assert.Positive(t, attach.ReleaseID)
|
||||
var release Release
|
||||
has, err := x.ID(attach.ReleaseID).Get(&release)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, has)
|
||||
assert.Equal(t, attach.RepoID, release.RepoID)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_16
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gitea.dev/modelmigration/base"
|
||||
)
|
||||
|
||||
func AddBranchProtectionUnprotectedFilesColumn(x base.EngineMigration) error {
|
||||
type ProtectedBranch struct {
|
||||
UnprotectedFilePatterns string `xorm:"TEXT"`
|
||||
}
|
||||
|
||||
if err := x.Sync(new(ProtectedBranch)); err != nil {
|
||||
return fmt.Errorf("Sync: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_16
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gitea.dev/modelmigration/base"
|
||||
)
|
||||
|
||||
func AddTableCommitStatusIndex(x base.EngineMigration) 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.Sync(new(CommitStatusIndex)); err != nil {
|
||||
return fmt.Errorf("Sync: %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()
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_16
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"gitea.dev/modelmigration/migrationtest"
|
||||
|
||||
"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 := migrationtest.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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_16
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gitea.dev/modelmigration/base"
|
||||
)
|
||||
|
||||
func AddColorColToProjectBoard(x base.EngineMigration) error {
|
||||
type ProjectBoard struct {
|
||||
Color string `xorm:"VARCHAR(7)"`
|
||||
}
|
||||
|
||||
if err := x.Sync(new(ProjectBoard)); err != nil {
|
||||
return fmt.Errorf("Sync: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_16
|
||||
|
||||
import "gitea.dev/modelmigration/base"
|
||||
|
||||
func AddRenamedBranchTable(x base.EngineMigration) 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.Sync(new(RenamedBranch))
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_16
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gitea.dev/modelmigration/base"
|
||||
"gitea.dev/modules/timeutil"
|
||||
)
|
||||
|
||||
func AddTableIssueContentHistory(x base.EngineMigration) 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.Sync(new(IssueContentHistory)); err != nil {
|
||||
return fmt.Errorf("Sync: %w", err)
|
||||
}
|
||||
return sess.Commit()
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_16
|
||||
|
||||
// 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.
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_16
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gitea.dev/modelmigration/base"
|
||||
)
|
||||
|
||||
func AddTableAppState(x base.EngineMigration) error {
|
||||
type AppState struct {
|
||||
ID string `xorm:"pk varchar(200)"`
|
||||
Revision int64
|
||||
Content string `xorm:"LONGTEXT"`
|
||||
}
|
||||
if err := x.Sync(new(AppState)); err != nil {
|
||||
return fmt.Errorf("Sync: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_16
|
||||
|
||||
import "gitea.dev/modelmigration/base"
|
||||
|
||||
func DropTableRemoteVersion(x base.EngineMigration) 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
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_16
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gitea.dev/modelmigration/base"
|
||||
)
|
||||
|
||||
func CreateUserSettingsTable(x base.EngineMigration) 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.Sync(new(UserSetting)); err != nil {
|
||||
return fmt.Errorf("sync2: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_16
|
||||
|
||||
import "gitea.dev/modelmigration/base"
|
||||
|
||||
func AddProjectIssueSorting(x base.EngineMigration) error {
|
||||
// ProjectIssue saves relation from issue to a project
|
||||
type ProjectIssue struct {
|
||||
Sorting int64 `xorm:"NOT NULL DEFAULT 0"`
|
||||
}
|
||||
|
||||
return x.Sync(new(ProjectIssue))
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_16
|
||||
|
||||
import "gitea.dev/modelmigration/base"
|
||||
|
||||
func AddSSHKeyIsVerified(x base.EngineMigration) error {
|
||||
type PublicKey struct {
|
||||
Verified bool `xorm:"NOT NULL DEFAULT false"`
|
||||
}
|
||||
|
||||
return x.Sync(new(PublicKey))
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_16
|
||||
|
||||
import (
|
||||
"gitea.dev/modelmigration/base"
|
||||
|
||||
"xorm.io/xorm/schemas"
|
||||
)
|
||||
|
||||
func MigrateUserPasswordSalt(x base.EngineMigration) 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,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_16
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gitea.dev/modelmigration/base"
|
||||
)
|
||||
|
||||
func AddAuthorizeColForTeamUnit(x base.EngineMigration) 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.Sync(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
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_16
|
||||
|
||||
import "gitea.dev/modelmigration/base"
|
||||
|
||||
func AddWebAuthnCred(x base.EngineMigration) error {
|
||||
// NO-OP Don't migrate here - let v210 do this.
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_16
|
||||
|
||||
import "gitea.dev/modelmigration/base"
|
||||
|
||||
func UseBase32HexForCredIDInWebAuthnCredential(x base.EngineMigration) error {
|
||||
// noop
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_16
|
||||
|
||||
import "gitea.dev/modelmigration/base"
|
||||
|
||||
func IncreaseCredentialIDTo410(x base.EngineMigration) error {
|
||||
// no-op
|
||||
// v208 was completely wrong
|
||||
// So now we have to no-op again.
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_16
|
||||
|
||||
import (
|
||||
"encoding/base32"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"gitea.dev/modelmigration/base"
|
||||
"gitea.dev/modules/timeutil"
|
||||
|
||||
"github.com/tstranex/u2f"
|
||||
"xorm.io/xorm/schemas"
|
||||
)
|
||||
|
||||
// v208 migration was completely broken
|
||||
func RemigrateU2FCredentials(x base.EngineMigration) 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.Sync(&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.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
|
||||
}
|
||||
pubKey, err := parsed.PubKey.ECDH()
|
||||
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: pubKey.Bytes(),
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_16
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"gitea.dev/modelmigration/migrationtest"
|
||||
"gitea.dev/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 := migrationtest.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.Equal(t, expected, got)
|
||||
}
|
||||
Reference in New Issue
Block a user