code.gitea.io/gitea@v1.21.7/models/migrations/v1_16/v210.go (about) 1 // Copyright 2022 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package v1_16 //nolint 5 6 import ( 7 "encoding/base32" 8 "fmt" 9 "strings" 10 11 "code.gitea.io/gitea/models/migrations/base" 12 "code.gitea.io/gitea/modules/timeutil" 13 14 "github.com/tstranex/u2f" 15 "xorm.io/xorm" 16 "xorm.io/xorm/schemas" 17 ) 18 19 // v208 migration was completely broken 20 func RemigrateU2FCredentials(x *xorm.Engine) error { 21 // Create webauthnCredential table 22 type webauthnCredential struct { 23 ID int64 `xorm:"pk autoincr"` 24 Name string 25 LowerName string `xorm:"unique(s)"` 26 UserID int64 `xorm:"INDEX unique(s)"` 27 CredentialID string `xorm:"INDEX VARCHAR(410)"` // CredentalID in U2F is at most 255bytes / 5 * 8 = 408 - add a few extra characters for safety 28 PublicKey []byte 29 AttestationType string 30 AAGUID []byte 31 SignCount uint32 `xorm:"BIGINT"` 32 CloneWarning bool 33 CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` 34 UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` 35 } 36 if err := x.Sync(&webauthnCredential{}); err != nil { 37 return err 38 } 39 40 switch x.Dialect().URI().DBType { 41 case schemas.MYSQL: 42 _, err := x.Exec("ALTER TABLE webauthn_credential MODIFY COLUMN credential_id VARCHAR(410)") 43 if err != nil { 44 return err 45 } 46 case schemas.ORACLE: 47 _, err := x.Exec("ALTER TABLE webauthn_credential MODIFY credential_id VARCHAR(410)") 48 if err != nil { 49 return err 50 } 51 case schemas.MSSQL: 52 // This column has an index on it. I could write all of the code to attempt to change the index OR 53 // I could just use recreate table. 54 sess := x.NewSession() 55 if err := sess.Begin(); err != nil { 56 _ = sess.Close() 57 return err 58 } 59 60 if err := base.RecreateTable(sess, new(webauthnCredential)); err != nil { 61 _ = sess.Close() 62 return err 63 } 64 if err := sess.Commit(); err != nil { 65 _ = sess.Close() 66 return err 67 } 68 if err := sess.Close(); err != nil { 69 return err 70 } 71 case schemas.POSTGRES: 72 _, err := x.Exec("ALTER TABLE webauthn_credential ALTER COLUMN credential_id TYPE VARCHAR(410)") 73 if err != nil { 74 return err 75 } 76 default: 77 // SQLite doesn't support ALTER COLUMN, and it already makes String _TEXT_ by default so no migration needed 78 // nor is there any need to re-migrate 79 } 80 81 exist, err := x.IsTableExist("u2f_registration") 82 if err != nil { 83 return err 84 } 85 if !exist { 86 return nil 87 } 88 89 // Now migrate the old u2f registrations to the new format 90 type u2fRegistration struct { 91 ID int64 `xorm:"pk autoincr"` 92 Name string 93 UserID int64 `xorm:"INDEX"` 94 Raw []byte 95 Counter uint32 `xorm:"BIGINT"` 96 CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` 97 UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` 98 } 99 100 var start int 101 regs := make([]*u2fRegistration, 0, 50) 102 for { 103 err := x.OrderBy("id").Limit(50, start).Find(®s) 104 if err != nil { 105 return err 106 } 107 108 err = func() error { 109 sess := x.NewSession() 110 defer sess.Close() 111 if err := sess.Begin(); err != nil { 112 return fmt.Errorf("unable to allow start session. Error: %w", err) 113 } 114 if x.Dialect().URI().DBType == schemas.MSSQL { 115 if _, err := sess.Exec("SET IDENTITY_INSERT `webauthn_credential` ON"); err != nil { 116 return fmt.Errorf("unable to allow identity insert on webauthn_credential. Error: %w", err) 117 } 118 } 119 for _, reg := range regs { 120 parsed := new(u2f.Registration) 121 err = parsed.UnmarshalBinary(reg.Raw) 122 if err != nil { 123 continue 124 } 125 pubKey, err := parsed.PubKey.ECDH() 126 if err != nil { 127 continue 128 } 129 remigrated := &webauthnCredential{ 130 ID: reg.ID, 131 Name: reg.Name, 132 LowerName: strings.ToLower(reg.Name), 133 UserID: reg.UserID, 134 CredentialID: base32.HexEncoding.EncodeToString(parsed.KeyHandle), 135 PublicKey: pubKey.Bytes(), 136 AttestationType: "fido-u2f", 137 AAGUID: []byte{}, 138 SignCount: reg.Counter, 139 UpdatedUnix: reg.UpdatedUnix, 140 CreatedUnix: reg.CreatedUnix, 141 } 142 143 has, err := sess.ID(reg.ID).Get(new(webauthnCredential)) 144 if err != nil { 145 return fmt.Errorf("unable to get webauthn_credential[%d]. Error: %w", reg.ID, err) 146 } 147 if !has { 148 has, err := sess.Where("`lower_name`=?", remigrated.LowerName).And("`user_id`=?", remigrated.UserID).Exist(new(webauthnCredential)) 149 if err != nil { 150 return fmt.Errorf("unable to check webauthn_credential[lower_name: %s, user_id: %d]. Error: %w", remigrated.LowerName, remigrated.UserID, err) 151 } 152 if !has { 153 _, err = sess.Insert(remigrated) 154 if err != nil { 155 return fmt.Errorf("unable to (re)insert webauthn_credential[%d]. Error: %w", reg.ID, err) 156 } 157 158 continue 159 } 160 } 161 162 _, err = sess.ID(remigrated.ID).AllCols().Update(remigrated) 163 if err != nil { 164 return fmt.Errorf("unable to update webauthn_credential[%d]. Error: %w", reg.ID, err) 165 } 166 } 167 return sess.Commit() 168 }() 169 if err != nil { 170 return err 171 } 172 173 if len(regs) < 50 { 174 break 175 } 176 start += 50 177 regs = regs[:0] 178 } 179 180 if x.Dialect().URI().DBType == schemas.POSTGRES { 181 if _, err := x.Exec("SELECT setval('webauthn_credential_id_seq', COALESCE((SELECT MAX(id)+1 FROM `webauthn_credential`), 1), false)"); err != nil { 182 return err 183 } 184 } 185 186 return nil 187 }