Skip to content

Commit 844edb6

Browse files
committed
Add migration for password algorithm change
go-gitea#12688 changed the default for the user table leading to sync2 warnings Unfortunately changing defaults requires a complete table rewrite in general. However, just dropping columns could be bad - so this PR leverages the techniques used in recreate table to recreate from the inferred schema and recreates the user table. This is not necessarily the correct thing to do - but code sometimes speaks louder than words. Signed-off-by: Andrew Thornton <[email protected]>
1 parent 0cd49aa commit 844edb6

File tree

2 files changed

+224
-0
lines changed

2 files changed

+224
-0
lines changed

models/migrations/migrations.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,8 @@ var migrations = []Migration{
233233
NewMigration("remove issue dependency comments who refer to non existing issues", purgeInvalidDependenciesComments),
234234
// v149 -> v150
235235
NewMigration("Add Created and Updated to Milestone table", addCreatedAndUpdatedToMilestones),
236+
// v150 -> v151
237+
NewMigration("Set default password algorithm to Argon2", setDefaultPasswordToArgon2)
236238
}
237239

238240
// GetCurrentDBVersion returns the current db version

models/migrations/v150.go

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
// Copyright 2020 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package migrations
6+
7+
import (
8+
"fmt"
9+
"strings"
10+
11+
"code.gitea.io/gitea/modules/log"
12+
"code.gitea.io/gitea/modules/setting"
13+
"xorm.io/xorm"
14+
"xorm.io/xorm/schemas"
15+
)
16+
17+
func setDefaultPasswordToArgon2(x *xorm.Engine) error {
18+
tables, err := x.DBMetas()
19+
if err != nil {
20+
return err
21+
}
22+
23+
var table *schemas.Table
24+
tableName := "user"
25+
tempTableName := "tmp_recreate__user"
26+
27+
for _, table = range tables {
28+
if table.Name == tableName {
29+
break
30+
}
31+
}
32+
if table == nil || table.Name != tableName {
33+
type User struct {
34+
PasswdHashAlgo string `xorm:"NOT NULL DEFAULT 'argon2'"`
35+
}
36+
return x.Sync2(new(User))
37+
}
38+
column := table.GetColumn("passwd_hash_algo")
39+
if column == nil {
40+
type User struct {
41+
PasswdHashAlgo string `xorm:"NOT NULL DEFAULT 'argon2'"`
42+
}
43+
return x.Sync2(new(User))
44+
}
45+
column.Default = "argon2"
46+
47+
sess := x.NewSession()
48+
defer sess.Close()
49+
if err := sess.Begin(); err != nil {
50+
return err
51+
}
52+
53+
table.StoreEngine = "InnoDB"
54+
if _, err := sess.Exec(x.Dialect().CreateTableSQL(table, tempTableName)); err != nil {
55+
log.Error("Unable to create table %s. Error: %v", tempTableName, err)
56+
return err
57+
}
58+
for _, index := range table.Indexes {
59+
if _, err := sess.Exec(x.Dialect().CreateIndexSQL(tempTableName, index)); err != nil {
60+
log.Error("Unable to create indexes on temporary table %s. Error: %v", tempTableName, err)
61+
return err
62+
}
63+
}
64+
65+
newTableColumns := table.Columns()
66+
if len(newTableColumns) == 0 {
67+
return fmt.Errorf("no columns in new table")
68+
}
69+
hasID := false
70+
for _, column := range newTableColumns {
71+
hasID = hasID || (column.IsPrimaryKey && column.IsAutoIncrement)
72+
}
73+
74+
if hasID && setting.Database.UseMSSQL {
75+
if _, err := sess.Exec(fmt.Sprintf("SET IDENTITY_INSERT `%s` ON", tempTableName)); err != nil {
76+
log.Error("Unable to set identity insert for table %s. Error: %v", tempTableName, err)
77+
return err
78+
}
79+
}
80+
81+
sqlStringBuilder := &strings.Builder{}
82+
_, _ = sqlStringBuilder.WriteString("INSERT INTO `")
83+
_, _ = sqlStringBuilder.WriteString(tempTableName)
84+
_, _ = sqlStringBuilder.WriteString("` (`")
85+
_, _ = sqlStringBuilder.WriteString(newTableColumns[0].Name)
86+
_, _ = sqlStringBuilder.WriteString("`")
87+
for _, column := range newTableColumns[1:] {
88+
_, _ = sqlStringBuilder.WriteString(", `")
89+
_, _ = sqlStringBuilder.WriteString(column.Name)
90+
_, _ = sqlStringBuilder.WriteString("`")
91+
}
92+
_, _ = sqlStringBuilder.WriteString(")")
93+
_, _ = sqlStringBuilder.WriteString(" SELECT ")
94+
if newTableColumns[0].Default != "" {
95+
_, _ = sqlStringBuilder.WriteString("COALESCE(`")
96+
_, _ = sqlStringBuilder.WriteString(newTableColumns[0].Name)
97+
_, _ = sqlStringBuilder.WriteString("`, ")
98+
_, _ = sqlStringBuilder.WriteString(newTableColumns[0].Default)
99+
_, _ = sqlStringBuilder.WriteString(")")
100+
} else {
101+
_, _ = sqlStringBuilder.WriteString("`")
102+
_, _ = sqlStringBuilder.WriteString(newTableColumns[0].Name)
103+
_, _ = sqlStringBuilder.WriteString("`")
104+
}
105+
106+
for _, column := range newTableColumns[1:] {
107+
if column.Default != "" {
108+
_, _ = sqlStringBuilder.WriteString(", COALESCE(`")
109+
_, _ = sqlStringBuilder.WriteString(column.Name)
110+
_, _ = sqlStringBuilder.WriteString("`, ")
111+
_, _ = sqlStringBuilder.WriteString(column.Default)
112+
_, _ = sqlStringBuilder.WriteString(")")
113+
} else {
114+
_, _ = sqlStringBuilder.WriteString(", `")
115+
_, _ = sqlStringBuilder.WriteString(column.Name)
116+
_, _ = sqlStringBuilder.WriteString("`")
117+
}
118+
}
119+
_, _ = sqlStringBuilder.WriteString(" FROM `")
120+
_, _ = sqlStringBuilder.WriteString(tableName)
121+
_, _ = sqlStringBuilder.WriteString("`")
122+
123+
if _, err := sess.Exec(sqlStringBuilder.String()); err != nil {
124+
log.Error("Unable to set copy data in to temp table %s. Error: %v", tempTableName, err)
125+
return err
126+
}
127+
128+
if hasID && setting.Database.UseMSSQL {
129+
if _, err := sess.Exec(fmt.Sprintf("SET IDENTITY_INSERT `%s` OFF", tempTableName)); err != nil {
130+
log.Error("Unable to switch off identity insert for table %s. Error: %v", tempTableName, err)
131+
return err
132+
}
133+
}
134+
135+
switch {
136+
case setting.Database.UseSQLite3:
137+
// SQLite will drop all the constraints on the old table
138+
if _, err := sess.Exec(fmt.Sprintf("DROP TABLE `%s`", tableName)); err != nil {
139+
log.Error("Unable to drop old table %s. Error: %v", tableName, err)
140+
return err
141+
}
142+
143+
for _, index := range table.Indexes {
144+
if _, err := sess.Exec(x.Dialect().DropIndexSQL(tempTableName, index)); err != nil {
145+
log.Error("Unable to drop indexes on temporary table %s. Error: %v", tempTableName, err)
146+
return err
147+
}
148+
}
149+
150+
if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `%s` RENAME TO `%s`", tempTableName, tableName)); err != nil {
151+
log.Error("Unable to rename %s to %s. Error: %v", tempTableName, tableName, err)
152+
return err
153+
}
154+
155+
for _, index := range table.Indexes {
156+
if _, err := sess.Exec(x.Dialect().CreateIndexSQL(tableName, index)); err != nil {
157+
log.Error("Unable to recreate indexes on table %s. Error: %v", tableName, err)
158+
return err
159+
}
160+
}
161+
162+
case setting.Database.UseMySQL:
163+
// MySQL will drop all the constraints on the old table
164+
if _, err := sess.Exec(fmt.Sprintf("DROP TABLE `%s`", tableName)); err != nil {
165+
log.Error("Unable to drop old table %s. Error: %v", tableName, err)
166+
return err
167+
}
168+
169+
// SQLite and MySQL will move all the constraints from the temporary table to the new table
170+
if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `%s` RENAME TO `%s`", tempTableName, tableName)); err != nil {
171+
log.Error("Unable to rename %s to %s. Error: %v", tempTableName, tableName, err)
172+
return err
173+
}
174+
case setting.Database.UsePostgreSQL:
175+
// CASCADE causes postgres to drop all the constraints on the old table
176+
if _, err := sess.Exec(fmt.Sprintf("DROP TABLE `%s` CASCADE", tableName)); err != nil {
177+
log.Error("Unable to drop old table %s. Error: %v", tableName, err)
178+
return err
179+
}
180+
181+
// CASCADE causes postgres to move all the constraints from the temporary table to the new table
182+
if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `%s` RENAME TO `%s`", tempTableName, tableName)); err != nil {
183+
log.Error("Unable to rename %s to %s. Error: %v", tempTableName, tableName, err)
184+
return err
185+
}
186+
187+
var indices []string
188+
schema := sess.Engine().Dialect().URI().Schema
189+
sess.Engine().SetSchema("")
190+
if err := sess.Table("pg_indexes").Cols("indexname").Where("tablename = ? ", tableName).Find(&indices); err != nil {
191+
log.Error("Unable to rename %s to %s. Error: %v", tempTableName, tableName, err)
192+
return err
193+
}
194+
sess.Engine().SetSchema(schema)
195+
196+
for _, index := range indices {
197+
newIndexName := strings.Replace(index, "tmp_recreate__", "", 1)
198+
if _, err := sess.Exec(fmt.Sprintf("ALTER INDEX `%s` RENAME TO `%s`", index, newIndexName)); err != nil {
199+
log.Error("Unable to rename %s to %s. Error: %v", index, newIndexName, err)
200+
return err
201+
}
202+
}
203+
204+
case setting.Database.UseMSSQL:
205+
// MSSQL will drop all the constraints on the old table
206+
if _, err := sess.Exec(fmt.Sprintf("DROP TABLE `%s`", tableName)); err != nil {
207+
log.Error("Unable to drop old table %s. Error: %v", tableName, err)
208+
return err
209+
}
210+
211+
// MSSQL sp_rename will move all the constraints from the temporary table to the new table
212+
if _, err := sess.Exec(fmt.Sprintf("sp_rename `%s`,`%s`", tempTableName, tableName)); err != nil {
213+
log.Error("Unable to rename %s to %s. Error: %v", tempTableName, tableName, err)
214+
return err
215+
}
216+
217+
default:
218+
log.Fatal("Unrecognized DB")
219+
}
220+
221+
return sess.Commit()
222+
}

0 commit comments

Comments
 (0)