loading up the forgejo repo on tangled to test page performance
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Move user/org deletion to services (#17673)

authored by

KN4CK3R and committed by
GitHub
f34151bd 55be5fe3

+382 -301
+2 -1
cmd/admin.go
··· 26 26 auth_service "code.gitea.io/gitea/services/auth" 27 27 "code.gitea.io/gitea/services/auth/source/oauth2" 28 28 repo_service "code.gitea.io/gitea/services/repository" 29 + user_service "code.gitea.io/gitea/services/user" 29 30 30 31 "github.com/urfave/cli" 31 32 ) ··· 534 535 return fmt.Errorf("The user %s does not match the provided id %d", user.Name, c.Int64("id")) 535 536 } 536 537 537 - return models.DeleteUser(user) 538 + return user_service.DeleteUser(user) 538 539 } 539 540 540 541 func runRepoSyncReleases(_ *cli.Context) error {
+9 -24
models/admin/notice.go
··· 43 43 } 44 44 45 45 // CreateNotice creates new system notice. 46 - func CreateNotice(tp NoticeType, desc string, args ...interface{}) error { 47 - return CreateNoticeCtx(db.DefaultContext, tp, desc, args...) 48 - } 49 - 50 - // CreateNoticeCtx creates new system notice. 51 - func CreateNoticeCtx(ctx context.Context, tp NoticeType, desc string, args ...interface{}) error { 46 + func CreateNotice(ctx context.Context, tp NoticeType, desc string, args ...interface{}) error { 52 47 if len(args) > 0 { 53 48 desc = fmt.Sprintf(desc, args...) 54 49 } ··· 61 56 62 57 // CreateRepositoryNotice creates new system notice with type NoticeRepository. 63 58 func CreateRepositoryNotice(desc string, args ...interface{}) error { 64 - return CreateNoticeCtx(db.DefaultContext, NoticeRepository, desc, args...) 59 + return CreateNotice(db.DefaultContext, NoticeRepository, desc, args...) 65 60 } 66 61 67 62 // RemoveAllWithNotice removes all directories in given path and 68 63 // creates a system notice when error occurs. 69 - func RemoveAllWithNotice(title, path string) { 70 - RemoveAllWithNoticeCtx(db.DefaultContext, title, path) 71 - } 72 - 73 - // RemoveStorageWithNotice removes a file from the storage and 74 - // creates a system notice when error occurs. 75 - func RemoveStorageWithNotice(bucket storage.ObjectStorage, title, path string) { 76 - removeStorageWithNotice(db.DefaultContext, bucket, title, path) 77 - } 78 - 79 - func removeStorageWithNotice(ctx context.Context, bucket storage.ObjectStorage, title, path string) { 80 - if err := bucket.Delete(path); err != nil { 64 + func RemoveAllWithNotice(ctx context.Context, title, path string) { 65 + if err := util.RemoveAll(path); err != nil { 81 66 desc := fmt.Sprintf("%s [%s]: %v", title, path, err) 82 67 log.Warn(title+" [%s]: %v", path, err) 83 - if err = CreateNoticeCtx(ctx, NoticeRepository, desc); err != nil { 68 + if err = CreateNotice(ctx, NoticeRepository, desc); err != nil { 84 69 log.Error("CreateRepositoryNotice: %v", err) 85 70 } 86 71 } 87 72 } 88 73 89 - // RemoveAllWithNoticeCtx removes all directories in given path and 74 + // RemoveStorageWithNotice removes a file from the storage and 90 75 // creates a system notice when error occurs. 91 - func RemoveAllWithNoticeCtx(ctx context.Context, title, path string) { 92 - if err := util.RemoveAll(path); err != nil { 76 + func RemoveStorageWithNotice(ctx context.Context, bucket storage.ObjectStorage, title, path string) { 77 + if err := bucket.Delete(path); err != nil { 93 78 desc := fmt.Sprintf("%s [%s]: %v", title, path, err) 94 79 log.Warn(title+" [%s]: %v", path, err) 95 - if err = CreateNoticeCtx(ctx, NoticeRepository, desc); err != nil { 80 + if err = CreateNotice(ctx, NoticeRepository, desc); err != nil { 96 81 log.Error("CreateRepositoryNotice: %v", err) 97 82 } 98 83 }
+2 -1
models/admin/notice_test.go
··· 7 7 import ( 8 8 "testing" 9 9 10 + "code.gitea.io/gitea/models/db" 10 11 "code.gitea.io/gitea/models/unittest" 11 12 12 13 "github.com/stretchr/testify/assert" ··· 28 29 Description: "test description", 29 30 } 30 31 unittest.AssertNotExistsBean(t, noticeBean) 31 - assert.NoError(t, CreateNotice(noticeBean.Type, noticeBean.Description)) 32 + assert.NoError(t, CreateNotice(db.DefaultContext, noticeBean.Type, noticeBean.Description)) 32 33 unittest.AssertExistsAndLoadBean(t, noticeBean) 33 34 } 34 35
+1 -1
models/consistency.go
··· 128 128 129 129 // Remove issue attachment files. 130 130 for i := range attachmentPaths { 131 - admin_model.RemoveAllWithNoticeCtx(db.DefaultContext, "Delete issue attachment", attachmentPaths[i]) 131 + admin_model.RemoveAllWithNotice(db.DefaultContext, "Delete issue attachment", attachmentPaths[i]) 132 132 } 133 133 return nil 134 134 }
+9 -55
models/org.go
··· 6 6 package models 7 7 8 8 import ( 9 + "context" 9 10 "fmt" 10 11 "strings" 11 12 ··· 14 15 user_model "code.gitea.io/gitea/models/user" 15 16 "code.gitea.io/gitea/modules/log" 16 17 "code.gitea.io/gitea/modules/setting" 17 - "code.gitea.io/gitea/modules/storage" 18 18 "code.gitea.io/gitea/modules/structs" 19 - "code.gitea.io/gitea/modules/util" 20 19 21 20 "xorm.io/builder" 22 21 "xorm.io/xorm" ··· 254 253 return count 255 254 } 256 255 257 - // DeleteOrganization completely and permanently deletes everything of organization. 258 - func DeleteOrganization(org *User) (err error) { 259 - if !org.IsOrganization() { 260 - return fmt.Errorf("%s is a user not an organization", org.Name) 261 - } 262 - 263 - sess := db.NewSession(db.DefaultContext) 264 - defer sess.Close() 265 - 266 - if err = sess.Begin(); err != nil { 267 - return err 268 - } 269 - 270 - if err = deleteOrg(sess, org); err != nil { 271 - if IsErrUserOwnRepos(err) { 272 - return err 273 - } else if err != nil { 274 - return fmt.Errorf("deleteOrg: %v", err) 275 - } 276 - } 277 - 278 - return sess.Commit() 279 - } 280 - 281 - func deleteOrg(e *xorm.Session, u *User) error { 282 - // Check ownership of repository. 283 - count, err := getRepositoryCount(e, u) 284 - if err != nil { 285 - return fmt.Errorf("GetRepositoryCount: %v", err) 286 - } else if count > 0 { 287 - return ErrUserOwnRepos{UID: u.ID} 288 - } 256 + // DeleteOrganization deletes models associated to an organization. 257 + func DeleteOrganization(ctx context.Context, org *User) error { 258 + e := db.GetEngine(ctx) 289 259 290 260 if err := deleteBeans(e, 291 - &Team{OrgID: u.ID}, 292 - &OrgUser{OrgID: u.ID}, 293 - &TeamUser{OrgID: u.ID}, 294 - &TeamUnit{OrgID: u.ID}, 261 + &Team{OrgID: org.ID}, 262 + &OrgUser{OrgID: org.ID}, 263 + &TeamUser{OrgID: org.ID}, 264 + &TeamUnit{OrgID: org.ID}, 295 265 ); err != nil { 296 266 return fmt.Errorf("deleteBeans: %v", err) 297 267 } 298 268 299 - if _, err = e.ID(u.ID).Delete(new(User)); err != nil { 269 + if _, err := e.ID(org.ID).Delete(new(User)); err != nil { 300 270 return fmt.Errorf("Delete: %v", err) 301 - } 302 - 303 - // FIXME: system notice 304 - // Note: There are something just cannot be roll back, 305 - // so just keep error logs of those operations. 306 - path := UserPath(u.Name) 307 - 308 - if err := util.RemoveAll(path); err != nil { 309 - return fmt.Errorf("Failed to RemoveAll %s: %v", path, err) 310 - } 311 - 312 - if len(u.Avatar) > 0 { 313 - avatarPath := u.CustomAvatarRelativePath() 314 - if err := storage.Avatars.Delete(avatarPath); err != nil { 315 - return fmt.Errorf("Failed to remove %s: %v", avatarPath, err) 316 - } 317 271 } 318 272 319 273 return nil
-18
models/org_test.go
··· 261 261 assert.Equal(t, expected, CountOrganizations()) 262 262 } 263 263 264 - func TestDeleteOrganization(t *testing.T) { 265 - assert.NoError(t, unittest.PrepareTestDatabase()) 266 - org := unittest.AssertExistsAndLoadBean(t, &User{ID: 6}).(*User) 267 - assert.NoError(t, DeleteOrganization(org)) 268 - unittest.AssertNotExistsBean(t, &User{ID: 6}) 269 - unittest.AssertNotExistsBean(t, &OrgUser{OrgID: 6}) 270 - unittest.AssertNotExistsBean(t, &Team{OrgID: 6}) 271 - 272 - org = unittest.AssertExistsAndLoadBean(t, &User{ID: 3}).(*User) 273 - err := DeleteOrganization(org) 274 - assert.Error(t, err) 275 - assert.True(t, IsErrUserOwnRepos(err)) 276 - 277 - user := unittest.AssertExistsAndLoadBean(t, &User{ID: 5}).(*User) 278 - assert.Error(t, DeleteOrganization(user)) 279 - unittest.CheckConsistencyFor(t, &User{}, &Team{}) 280 - } 281 - 282 264 func TestIsOrganizationOwner(t *testing.T) { 283 265 assert.NoError(t, unittest.PrepareTestDatabase()) 284 266 test := func(orgID, userID int64, expected bool) {
+10 -10
models/repo.go
··· 134 134 loadRepoConfig() 135 135 unit.LoadUnitConfig() 136 136 137 - admin_model.RemoveAllWithNotice("Clean up repository temporary data", filepath.Join(setting.AppDataPath, "tmp")) 137 + admin_model.RemoveAllWithNotice(db.DefaultContext, "Clean up repository temporary data", filepath.Join(setting.AppDataPath, "tmp")) 138 138 } 139 139 140 140 // RepositoryStatus defines the status of repository ··· 1649 1649 1650 1650 // Remove repository files. 1651 1651 repoPath := repo.RepoPath() 1652 - admin_model.RemoveAllWithNotice("Delete repository files", repoPath) 1652 + admin_model.RemoveAllWithNotice(db.DefaultContext, "Delete repository files", repoPath) 1653 1653 1654 1654 // Remove wiki files 1655 1655 if repo.HasWiki() { 1656 - admin_model.RemoveAllWithNotice("Delete repository wiki", repo.WikiPath()) 1656 + admin_model.RemoveAllWithNotice(db.DefaultContext, "Delete repository wiki", repo.WikiPath()) 1657 1657 } 1658 1658 1659 1659 // Remove archives 1660 1660 for i := range archivePaths { 1661 - admin_model.RemoveStorageWithNotice(storage.RepoArchives, "Delete repo archive file", archivePaths[i]) 1661 + admin_model.RemoveStorageWithNotice(db.DefaultContext, storage.RepoArchives, "Delete repo archive file", archivePaths[i]) 1662 1662 } 1663 1663 1664 1664 // Remove lfs objects 1665 1665 for i := range lfsPaths { 1666 - admin_model.RemoveStorageWithNotice(storage.LFS, "Delete orphaned LFS file", lfsPaths[i]) 1666 + admin_model.RemoveStorageWithNotice(db.DefaultContext, storage.LFS, "Delete orphaned LFS file", lfsPaths[i]) 1667 1667 } 1668 1668 1669 1669 // Remove issue attachment files. 1670 1670 for i := range attachmentPaths { 1671 - admin_model.RemoveStorageWithNotice(storage.Attachments, "Delete issue attachment", attachmentPaths[i]) 1671 + admin_model.RemoveStorageWithNotice(db.DefaultContext, storage.Attachments, "Delete issue attachment", attachmentPaths[i]) 1672 1672 } 1673 1673 1674 1674 // Remove release attachment files. 1675 1675 for i := range releaseAttachments { 1676 - admin_model.RemoveStorageWithNotice(storage.Attachments, "Delete release attachment", releaseAttachments[i]) 1676 + admin_model.RemoveStorageWithNotice(db.DefaultContext, storage.Attachments, "Delete release attachment", releaseAttachments[i]) 1677 1677 } 1678 1678 1679 1679 // Remove attachment with no issue_id and release_id. 1680 1680 for i := range newAttachmentPaths { 1681 - admin_model.RemoveStorageWithNotice(storage.Attachments, "Delete issue attachment", attachmentPaths[i]) 1681 + admin_model.RemoveStorageWithNotice(db.DefaultContext, storage.Attachments, "Delete issue attachment", attachmentPaths[i]) 1682 1682 } 1683 1683 1684 1684 if len(repo.Avatar) > 0 { ··· 1803 1803 } 1804 1804 1805 1805 // GetRepositoryCount returns the total number of repositories of user. 1806 - func GetRepositoryCount(u *User) (int64, error) { 1807 - return getRepositoryCount(db.GetEngine(db.DefaultContext), u) 1806 + func GetRepositoryCount(ctx context.Context, u *User) (int64, error) { 1807 + return getRepositoryCount(db.GetEngine(ctx), u) 1808 1808 } 1809 1809 1810 1810 // GetPublicRepositoryCount returns the total number of public repositories of user.
+1 -1
models/repo_test.go
··· 71 71 func TestGetRepositoryCount(t *testing.T) { 72 72 assert.NoError(t, unittest.PrepareTestDatabase()) 73 73 74 - count, err1 := GetRepositoryCount(&User{ID: int64(10)}) 74 + count, err1 := GetRepositoryCount(db.DefaultContext, &User{ID: int64(10)}) 75 75 privateCount, err2 := GetPrivateRepositoryCount(&User{ID: int64(10)}) 76 76 publicCount, err3 := GetPublicRepositoryCount(&User{ID: int64(10)}) 77 77 assert.NoError(t, err1)
+14 -96
models/user.go
··· 22 22 23 23 _ "image/jpeg" // Needed for jpeg support 24 24 25 - admin_model "code.gitea.io/gitea/models/admin" 26 25 "code.gitea.io/gitea/models/db" 27 26 "code.gitea.io/gitea/models/login" 28 27 "code.gitea.io/gitea/models/unit" ··· 32 31 "code.gitea.io/gitea/modules/git" 33 32 "code.gitea.io/gitea/modules/log" 34 33 "code.gitea.io/gitea/modules/setting" 35 - "code.gitea.io/gitea/modules/storage" 36 34 "code.gitea.io/gitea/modules/structs" 37 35 "code.gitea.io/gitea/modules/timeutil" 38 36 "code.gitea.io/gitea/modules/util" ··· 542 540 return isMember 543 541 } 544 542 545 - func (u *User) getOrganizationCount(e db.Engine) (int64, error) { 546 - return e. 543 + // GetOrganizationCount returns count of membership of organization of the user. 544 + func GetOrganizationCount(ctx context.Context, u *User) (int64, error) { 545 + return db.GetEngine(ctx). 547 546 Where("uid=?", u.ID). 548 547 Count(new(OrgUser)) 549 548 } 550 549 551 550 // GetOrganizationCount returns count of membership of organization of user. 552 551 func (u *User) GetOrganizationCount() (int64, error) { 553 - return u.getOrganizationCount(db.GetEngine(db.DefaultContext)) 552 + return GetOrganizationCount(db.DefaultContext, u) 554 553 } 555 554 556 555 // GetRepositories returns repositories that user owns, including private repositories. ··· 1149 1148 return nil 1150 1149 } 1151 1150 1152 - func deleteUser(ctx context.Context, u *User) error { 1151 + // DeleteUser deletes models associated to an user. 1152 + func DeleteUser(ctx context.Context, u *User) (err error) { 1153 1153 e := db.GetEngine(ctx) 1154 - // Note: A user owns any repository or belongs to any organization 1155 - // cannot perform delete operation. 1156 - 1157 - // Check ownership of repository. 1158 - count, err := getRepositoryCount(e, u) 1159 - if err != nil { 1160 - return fmt.Errorf("GetRepositoryCount: %v", err) 1161 - } else if count > 0 { 1162 - return ErrUserOwnRepos{UID: u.ID} 1163 - } 1164 - 1165 - // Check membership of organization. 1166 - count, err = u.getOrganizationCount(e) 1167 - if err != nil { 1168 - return fmt.Errorf("GetOrganizationCount: %v", err) 1169 - } else if count > 0 { 1170 - return ErrUserHasOrgs{UID: u.ID} 1171 - } 1172 1154 1173 1155 // ***** START: Watch ***** 1174 1156 watchedRepoIDs := make([]int64, 0, 10) ··· 1301 1283 return fmt.Errorf("Delete: %v", err) 1302 1284 } 1303 1285 1304 - // Note: There are something just cannot be roll back, 1305 - // so just keep error logs of those operations. 1306 - path := UserPath(u.Name) 1307 - if err = util.RemoveAll(path); err != nil { 1308 - err = fmt.Errorf("Failed to RemoveAll %s: %v", path, err) 1309 - _ = admin_model.CreateNoticeCtx(ctx, admin_model.NoticeTask, fmt.Sprintf("delete user '%s': %v", u.Name, err)) 1310 - return err 1311 - } 1312 - 1313 - if len(u.Avatar) > 0 { 1314 - avatarPath := u.CustomAvatarRelativePath() 1315 - if err = storage.Avatars.Delete(avatarPath); err != nil { 1316 - err = fmt.Errorf("Failed to remove %s: %v", avatarPath, err) 1317 - _ = admin_model.CreateNoticeCtx(ctx, admin_model.NoticeTask, fmt.Sprintf("delete user '%s': %v", u.Name, err)) 1318 - return err 1319 - } 1320 - } 1321 - 1322 1286 return nil 1323 1287 } 1324 1288 1325 - // DeleteUser completely and permanently deletes everything of a user, 1326 - // but issues/comments/pulls will be kept and shown as someone has been deleted, 1327 - // unless the user is younger than USER_DELETE_WITH_COMMENTS_MAX_DAYS. 1328 - func DeleteUser(u *User) (err error) { 1329 - if u.IsOrganization() { 1330 - return fmt.Errorf("%s is an organization not a user", u.Name) 1331 - } 1332 - 1333 - ctx, committer, err := db.TxContext() 1334 - if err != nil { 1335 - return err 1336 - } 1337 - defer committer.Close() 1289 + // GetInactiveUsers gets all inactive users 1290 + func GetInactiveUsers(ctx context.Context, olderThan time.Duration) ([]*User, error) { 1291 + var cond builder.Cond = builder.Eq{"is_active": false} 1338 1292 1339 - if err = deleteUser(ctx, u); err != nil { 1340 - // Note: don't wrapper error here. 1341 - return err 1293 + if olderThan > 0 { 1294 + cond = cond.And(builder.Lt{"created_unix": time.Now().Add(-olderThan).Unix()}) 1342 1295 } 1343 1296 1344 - return committer.Commit() 1345 - } 1346 - 1347 - // DeleteInactiveUsers deletes all inactive users and email addresses. 1348 - func DeleteInactiveUsers(ctx context.Context, olderThan time.Duration) (err error) { 1349 1297 users := make([]*User, 0, 10) 1350 - if olderThan > 0 { 1351 - if err = db.GetEngine(db.DefaultContext). 1352 - Where("is_active = ? and created_unix < ?", false, time.Now().Add(-olderThan).Unix()). 1353 - Find(&users); err != nil { 1354 - return fmt.Errorf("get all inactive users: %v", err) 1355 - } 1356 - } else { 1357 - if err = db.GetEngine(db.DefaultContext). 1358 - Where("is_active = ?", false). 1359 - Find(&users); err != nil { 1360 - return fmt.Errorf("get all inactive users: %v", err) 1361 - } 1362 - } 1363 - // FIXME: should only update authorized_keys file once after all deletions. 1364 - for _, u := range users { 1365 - select { 1366 - case <-ctx.Done(): 1367 - return db.ErrCancelledf("Before delete inactive user %s", u.Name) 1368 - default: 1369 - } 1370 - if err = DeleteUser(u); err != nil { 1371 - // Ignore users that were set inactive by admin. 1372 - if IsErrUserOwnRepos(err) || IsErrUserHasOrgs(err) { 1373 - continue 1374 - } 1375 - return err 1376 - } 1377 - } 1378 - 1379 - _, err = db.GetEngine(db.DefaultContext). 1380 - Where("is_activated = ?", false). 1381 - Delete(new(user_model.EmailAddress)) 1382 - return err 1298 + return users, db.GetEngine(ctx). 1299 + Where(cond). 1300 + Find(&users) 1383 1301 } 1384 1302 1385 1303 // UserPath returns the path absolute path of user repositories.
+8
models/user/email_address.go
··· 267 267 268 268 return nil 269 269 } 270 + 271 + // DeleteInactiveEmailAddresses deletes inactive email addresses 272 + func DeleteInactiveEmailAddresses(ctx context.Context) error { 273 + _, err := db.GetEngine(ctx). 274 + Where("is_activated = ?", false). 275 + Delete(new(EmailAddress)) 276 + return err 277 + }
-80
models/user_test.go
··· 177 177 []int64{24}) 178 178 } 179 179 180 - func TestDeleteUser(t *testing.T) { 181 - test := func(userID int64) { 182 - assert.NoError(t, unittest.PrepareTestDatabase()) 183 - user := unittest.AssertExistsAndLoadBean(t, &User{ID: userID}).(*User) 184 - 185 - ownedRepos := make([]*Repository, 0, 10) 186 - assert.NoError(t, db.GetEngine(db.DefaultContext).Find(&ownedRepos, &Repository{OwnerID: userID})) 187 - if len(ownedRepos) > 0 { 188 - err := DeleteUser(user) 189 - assert.Error(t, err) 190 - assert.True(t, IsErrUserOwnRepos(err)) 191 - return 192 - } 193 - 194 - orgUsers := make([]*OrgUser, 0, 10) 195 - assert.NoError(t, db.GetEngine(db.DefaultContext).Find(&orgUsers, &OrgUser{UID: userID})) 196 - for _, orgUser := range orgUsers { 197 - if err := RemoveOrgUser(orgUser.OrgID, orgUser.UID); err != nil { 198 - assert.True(t, IsErrLastOrgOwner(err)) 199 - return 200 - } 201 - } 202 - assert.NoError(t, DeleteUser(user)) 203 - unittest.AssertNotExistsBean(t, &User{ID: userID}) 204 - unittest.CheckConsistencyFor(t, &User{}, &Repository{}) 205 - } 206 - test(2) 207 - test(4) 208 - test(8) 209 - test(11) 210 - 211 - org := unittest.AssertExistsAndLoadBean(t, &User{ID: 3}).(*User) 212 - assert.Error(t, DeleteUser(org)) 213 - } 214 - 215 180 func TestEmailNotificationPreferences(t *testing.T) { 216 181 assert.NoError(t, unittest.PrepareTestDatabase()) 217 182 ··· 333 298 } 334 299 } 335 300 336 - func TestCreateUser(t *testing.T) { 337 - user := &User{ 338 - Name: "GiteaBot", 339 - Email: "GiteaBot@gitea.io", 340 - Passwd: ";p['////..-++']", 341 - IsAdmin: false, 342 - Theme: setting.UI.DefaultTheme, 343 - MustChangePassword: false, 344 - } 345 - 346 - assert.NoError(t, CreateUser(user)) 347 - 348 - assert.NoError(t, DeleteUser(user)) 349 - } 350 - 351 301 func TestCreateUserInvalidEmail(t *testing.T) { 352 302 user := &User{ 353 303 Name: "GiteaBot", ··· 361 311 err := CreateUser(user) 362 312 assert.Error(t, err) 363 313 assert.True(t, user_model.IsErrEmailInvalid(err)) 364 - } 365 - 366 - func TestCreateUser_Issue5882(t *testing.T) { 367 - // Init settings 368 - _ = setting.Admin 369 - 370 - passwd := ".//.;1;;//.,-=_" 371 - 372 - tt := []struct { 373 - user *User 374 - disableOrgCreation bool 375 - }{ 376 - {&User{Name: "GiteaBot", Email: "GiteaBot@gitea.io", Passwd: passwd, MustChangePassword: false}, false}, 377 - {&User{Name: "GiteaBot2", Email: "GiteaBot2@gitea.io", Passwd: passwd, MustChangePassword: false}, true}, 378 - } 379 - 380 - setting.Service.DefaultAllowCreateOrganization = true 381 - 382 - for _, v := range tt { 383 - setting.Admin.DisableRegularOrgCreation = v.disableOrgCreation 384 - 385 - assert.NoError(t, CreateUser(v.user)) 386 - 387 - u, err := GetUserByEmail(v.user.Email) 388 - assert.NoError(t, err) 389 - 390 - assert.Equal(t, !u.AllowCreateOrganization, v.disableOrgCreation) 391 - 392 - assert.NoError(t, DeleteUser(v.user)) 393 - } 394 314 } 395 315 396 316 func TestGetUserIDsByNames(t *testing.T) {
+2 -1
modules/repository/create_test.go
··· 9 9 "testing" 10 10 11 11 "code.gitea.io/gitea/models" 12 + "code.gitea.io/gitea/models/db" 12 13 "code.gitea.io/gitea/models/unittest" 13 14 "code.gitea.io/gitea/modules/structs" 14 15 ··· 142 143 assert.NoError(t, models.DeleteRepository(user, org.ID, rid), "DeleteRepository %d", i) 143 144 } 144 145 } 145 - assert.NoError(t, models.DeleteOrganization(org), "DeleteOrganization") 146 + assert.NoError(t, models.DeleteOrganization(db.DefaultContext, org), "DeleteOrganization") 146 147 }
+2 -1
routers/api/v1/admin/user.go
··· 22 22 "code.gitea.io/gitea/routers/api/v1/user" 23 23 "code.gitea.io/gitea/routers/api/v1/utils" 24 24 "code.gitea.io/gitea/services/mailer" 25 + user_service "code.gitea.io/gitea/services/user" 25 26 ) 26 27 27 28 func parseLoginSource(ctx *context.APIContext, u *models.User, sourceID int64, loginName string) { ··· 289 290 return 290 291 } 291 292 292 - if err := models.DeleteUser(u); err != nil { 293 + if err := user_service.DeleteUser(u); err != nil { 293 294 if models.IsErrUserOwnRepos(err) || 294 295 models.IsErrUserHasOrgs(err) { 295 296 ctx.Error(http.StatusUnprocessableEntity, "", err)
+2 -1
routers/api/v1/org/org.go
··· 16 16 "code.gitea.io/gitea/modules/web" 17 17 "code.gitea.io/gitea/routers/api/v1/user" 18 18 "code.gitea.io/gitea/routers/api/v1/utils" 19 + "code.gitea.io/gitea/services/org" 19 20 ) 20 21 21 22 func listUserOrgs(ctx *context.APIContext, u *models.User) { ··· 364 365 // "204": 365 366 // "$ref": "#/responses/empty" 366 367 367 - if err := models.DeleteOrganization(ctx.Org.Organization); err != nil { 368 + if err := org.DeleteOrganization(ctx.Org.Organization); err != nil { 368 369 ctx.Error(http.StatusInternalServerError, "DeleteOrganization", err) 369 370 return 370 371 }
+2 -1
routers/web/admin/users.go
··· 26 26 router_user_setting "code.gitea.io/gitea/routers/web/user/setting" 27 27 "code.gitea.io/gitea/services/forms" 28 28 "code.gitea.io/gitea/services/mailer" 29 + "code.gitea.io/gitea/services/user" 29 30 ) 30 31 31 32 const ( ··· 377 378 return 378 379 } 379 380 380 - if err = models.DeleteUser(u); err != nil { 381 + if err = user.DeleteUser(u); err != nil { 381 382 switch { 382 383 case models.IsErrUserOwnRepos(err): 383 384 ctx.Flash.Error(ctx.Tr("admin.users.still_own_repo"))
+4 -4
routers/web/org/setting.go
··· 20 20 "code.gitea.io/gitea/modules/web" 21 21 userSetting "code.gitea.io/gitea/routers/web/user/setting" 22 22 "code.gitea.io/gitea/services/forms" 23 + "code.gitea.io/gitea/services/org" 23 24 ) 24 25 25 26 const ( ··· 156 157 ctx.Data["Title"] = ctx.Tr("org.settings") 157 158 ctx.Data["PageIsSettingsDelete"] = true 158 159 159 - org := ctx.Org.Organization 160 160 if ctx.Req.Method == "POST" { 161 - if org.Name != ctx.FormString("org_name") { 161 + if ctx.Org.Organization.Name != ctx.FormString("org_name") { 162 162 ctx.Data["Err_OrgName"] = true 163 163 ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_org_name"), tplSettingsDelete, nil) 164 164 return 165 165 } 166 166 167 - if err := models.DeleteOrganization(org); err != nil { 167 + if err := org.DeleteOrganization(ctx.Org.Organization); err != nil { 168 168 if models.IsErrUserOwnRepos(err) { 169 169 ctx.Flash.Error(ctx.Tr("form.org_still_own_repo")) 170 170 ctx.Redirect(ctx.Org.OrgLink + "/settings/delete") ··· 172 172 ctx.ServerError("DeleteOrganization", err) 173 173 } 174 174 } else { 175 - log.Trace("Organization deleted: %s", org.Name) 175 + log.Trace("Organization deleted: %s", ctx.Org.Organization.Name) 176 176 ctx.Redirect(setting.AppSubURL + "/") 177 177 } 178 178 return
+2 -1
routers/web/user/setting/account.go
··· 22 22 "code.gitea.io/gitea/services/auth" 23 23 "code.gitea.io/gitea/services/forms" 24 24 "code.gitea.io/gitea/services/mailer" 25 + "code.gitea.io/gitea/services/user" 25 26 ) 26 27 27 28 const ( ··· 241 242 return 242 243 } 243 244 244 - if err := models.DeleteUser(ctx.User); err != nil { 245 + if err := user.DeleteUser(ctx.User); err != nil { 245 246 switch { 246 247 case models.IsErrUserOwnRepos(err): 247 248 ctx.Flash.Error(ctx.Tr("form.still_own_repo"))
+3 -3
services/cron/tasks.go
··· 90 90 if err := t.fun(ctx, doer, config); err != nil { 91 91 if db.IsErrCancelled(err) { 92 92 message := err.(db.ErrCancelled).Message 93 - if err := admin_model.CreateNotice(admin_model.NoticeTask, config.FormatMessage(t.Name, "aborted", doer, message)); err != nil { 93 + if err := admin_model.CreateNotice(ctx, admin_model.NoticeTask, config.FormatMessage(t.Name, "aborted", doer, message)); err != nil { 94 94 log.Error("CreateNotice: %v", err) 95 95 } 96 96 return 97 97 } 98 - if err := admin_model.CreateNotice(admin_model.NoticeTask, config.FormatMessage(t.Name, "error", doer, err)); err != nil { 98 + if err := admin_model.CreateNotice(ctx, admin_model.NoticeTask, config.FormatMessage(t.Name, "error", doer, err)); err != nil { 99 99 log.Error("CreateNotice: %v", err) 100 100 } 101 101 return 102 102 } 103 103 if config.DoNoticeOnSuccess() { 104 - if err := admin_model.CreateNotice(admin_model.NoticeTask, config.FormatMessage(t.Name, "finished", doer)); err != nil { 104 + if err := admin_model.CreateNotice(ctx, admin_model.NoticeTask, config.FormatMessage(t.Name, "finished", doer)); err != nil { 105 105 log.Error("CreateNotice: %v", err) 106 106 } 107 107 }
+2 -1
services/cron/tasks_extended.go
··· 13 13 "code.gitea.io/gitea/modules/setting" 14 14 "code.gitea.io/gitea/modules/updatechecker" 15 15 repo_service "code.gitea.io/gitea/services/repository" 16 + user_service "code.gitea.io/gitea/services/user" 16 17 ) 17 18 18 19 func registerDeleteInactiveUsers() { ··· 25 26 OlderThan: 0 * time.Second, 26 27 }, func(ctx context.Context, _ *models.User, config Config) error { 27 28 olderThanConfig := config.(*OlderThanConfig) 28 - return models.DeleteInactiveUsers(ctx, olderThanConfig.OlderThan) 29 + return user_service.DeleteInactiveUsers(ctx, olderThanConfig.OlderThan) 29 30 }) 30 31 } 31 32
+61
services/org/org.go
··· 1 + // Copyright 2021 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 org 6 + 7 + import ( 8 + "fmt" 9 + 10 + "code.gitea.io/gitea/models" 11 + "code.gitea.io/gitea/models/db" 12 + "code.gitea.io/gitea/modules/storage" 13 + "code.gitea.io/gitea/modules/util" 14 + ) 15 + 16 + // DeleteOrganization completely and permanently deletes everything of organization. 17 + func DeleteOrganization(org *models.User) error { 18 + if !org.IsOrganization() { 19 + return fmt.Errorf("%s is a user not an organization", org.Name) 20 + } 21 + 22 + ctx, commiter, err := db.TxContext() 23 + if err != nil { 24 + return err 25 + } 26 + defer commiter.Close() 27 + 28 + // Check ownership of repository. 29 + count, err := models.GetRepositoryCount(ctx, org) 30 + if err != nil { 31 + return fmt.Errorf("GetRepositoryCount: %v", err) 32 + } else if count > 0 { 33 + return models.ErrUserOwnRepos{UID: org.ID} 34 + } 35 + 36 + if err := models.DeleteOrganization(ctx, org); err != nil { 37 + return fmt.Errorf("DeleteOrganization: %v", err) 38 + } 39 + 40 + if err := commiter.Commit(); err != nil { 41 + return err 42 + } 43 + 44 + // FIXME: system notice 45 + // Note: There are something just cannot be roll back, 46 + // so just keep error logs of those operations. 47 + path := models.UserPath(org.Name) 48 + 49 + if err := util.RemoveAll(path); err != nil { 50 + return fmt.Errorf("Failed to RemoveAll %s: %v", path, err) 51 + } 52 + 53 + if len(org.Avatar) > 0 { 54 + avatarPath := org.CustomAvatarRelativePath() 55 + if err := storage.Avatars.Delete(avatarPath); err != nil { 56 + return fmt.Errorf("Failed to remove %s: %v", avatarPath, err) 57 + } 58 + } 59 + 60 + return nil 61 + }
+37
services/org/org_test.go
··· 1 + // Copyright 2021 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 org 6 + 7 + import ( 8 + "path/filepath" 9 + "testing" 10 + 11 + "code.gitea.io/gitea/models" 12 + "code.gitea.io/gitea/models/unittest" 13 + 14 + "github.com/stretchr/testify/assert" 15 + ) 16 + 17 + func TestMain(m *testing.M) { 18 + unittest.MainTest(m, filepath.Join("..", "..")) 19 + } 20 + 21 + func TestDeleteOrganization(t *testing.T) { 22 + assert.NoError(t, unittest.PrepareTestDatabase()) 23 + org := unittest.AssertExistsAndLoadBean(t, &models.User{ID: 6}).(*models.User) 24 + assert.NoError(t, DeleteOrganization(org)) 25 + unittest.AssertNotExistsBean(t, &models.User{ID: 6}) 26 + unittest.AssertNotExistsBean(t, &models.OrgUser{OrgID: 6}) 27 + unittest.AssertNotExistsBean(t, &models.Team{OrgID: 6}) 28 + 29 + org = unittest.AssertExistsAndLoadBean(t, &models.User{ID: 3}).(*models.User) 30 + err := DeleteOrganization(org) 31 + assert.Error(t, err) 32 + assert.True(t, models.IsErrUserOwnRepos(err)) 33 + 34 + user := unittest.AssertExistsAndLoadBean(t, &models.User{ID: 5}).(*models.User) 35 + assert.Error(t, DeleteOrganization(user)) 36 + unittest.CheckConsistencyFor(t, &models.User{}, &models.Team{}) 37 + }
+106
services/user/user.go
··· 1 + // Copyright 2021 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 user 6 + 7 + import ( 8 + "context" 9 + "fmt" 10 + "time" 11 + 12 + "code.gitea.io/gitea/models" 13 + admin_model "code.gitea.io/gitea/models/admin" 14 + "code.gitea.io/gitea/models/db" 15 + user_model "code.gitea.io/gitea/models/user" 16 + "code.gitea.io/gitea/modules/storage" 17 + "code.gitea.io/gitea/modules/util" 18 + ) 19 + 20 + // DeleteUser completely and permanently deletes everything of a user, 21 + // but issues/comments/pulls will be kept and shown as someone has been deleted, 22 + // unless the user is younger than USER_DELETE_WITH_COMMENTS_MAX_DAYS. 23 + func DeleteUser(u *models.User) error { 24 + if u.IsOrganization() { 25 + return fmt.Errorf("%s is an organization not a user", u.Name) 26 + } 27 + 28 + ctx, commiter, err := db.TxContext() 29 + if err != nil { 30 + return err 31 + } 32 + defer commiter.Close() 33 + 34 + // Note: A user owns any repository or belongs to any organization 35 + // cannot perform delete operation. 36 + 37 + // Check ownership of repository. 38 + count, err := models.GetRepositoryCount(ctx, u) 39 + if err != nil { 40 + return fmt.Errorf("GetRepositoryCount: %v", err) 41 + } else if count > 0 { 42 + return models.ErrUserOwnRepos{UID: u.ID} 43 + } 44 + 45 + // Check membership of organization. 46 + count, err = models.GetOrganizationCount(ctx, u) 47 + if err != nil { 48 + return fmt.Errorf("GetOrganizationCount: %v", err) 49 + } else if count > 0 { 50 + return models.ErrUserHasOrgs{UID: u.ID} 51 + } 52 + 53 + if err := models.DeleteUser(ctx, u); err != nil { 54 + return fmt.Errorf("DeleteUser: %v", err) 55 + } 56 + 57 + if err := commiter.Commit(); err != nil { 58 + return err 59 + } 60 + 61 + // Note: There are something just cannot be roll back, 62 + // so just keep error logs of those operations. 63 + path := models.UserPath(u.Name) 64 + if err := util.RemoveAll(path); err != nil { 65 + err = fmt.Errorf("Failed to RemoveAll %s: %v", path, err) 66 + _ = admin_model.CreateNotice(db.DefaultContext, admin_model.NoticeTask, fmt.Sprintf("delete user '%s': %v", u.Name, err)) 67 + return err 68 + } 69 + 70 + if u.Avatar != "" { 71 + avatarPath := u.CustomAvatarRelativePath() 72 + if err := storage.Avatars.Delete(avatarPath); err != nil { 73 + err = fmt.Errorf("Failed to remove %s: %v", avatarPath, err) 74 + _ = admin_model.CreateNotice(db.DefaultContext, admin_model.NoticeTask, fmt.Sprintf("delete user '%s': %v", u.Name, err)) 75 + return err 76 + } 77 + } 78 + 79 + return nil 80 + } 81 + 82 + // DeleteInactiveUsers deletes all inactive users and email addresses. 83 + func DeleteInactiveUsers(ctx context.Context, olderThan time.Duration) error { 84 + users, err := models.GetInactiveUsers(ctx, olderThan) 85 + if err != nil { 86 + return err 87 + } 88 + 89 + // FIXME: should only update authorized_keys file once after all deletions. 90 + for _, u := range users { 91 + select { 92 + case <-ctx.Done(): 93 + return db.ErrCancelledf("Before delete inactive user %s", u.Name) 94 + default: 95 + } 96 + if err := DeleteUser(u); err != nil { 97 + // Ignore users that were set inactive by admin. 98 + if models.IsErrUserOwnRepos(err) || models.IsErrUserHasOrgs(err) { 99 + continue 100 + } 101 + return err 102 + } 103 + } 104 + 105 + return user_model.DeleteInactiveEmailAddresses(ctx) 106 + }
+101
services/user/user_test.go
··· 1 + // Copyright 2021 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 user 6 + 7 + import ( 8 + "path/filepath" 9 + "testing" 10 + 11 + "code.gitea.io/gitea/models" 12 + "code.gitea.io/gitea/models/db" 13 + "code.gitea.io/gitea/models/unittest" 14 + "code.gitea.io/gitea/modules/setting" 15 + 16 + "github.com/stretchr/testify/assert" 17 + ) 18 + 19 + func TestMain(m *testing.M) { 20 + unittest.MainTest(m, filepath.Join("..", "..")) 21 + } 22 + 23 + func TestDeleteUser(t *testing.T) { 24 + test := func(userID int64) { 25 + assert.NoError(t, unittest.PrepareTestDatabase()) 26 + user := unittest.AssertExistsAndLoadBean(t, &models.User{ID: userID}).(*models.User) 27 + 28 + ownedRepos := make([]*models.Repository, 0, 10) 29 + assert.NoError(t, db.GetEngine(db.DefaultContext).Find(&ownedRepos, &models.Repository{OwnerID: userID})) 30 + if len(ownedRepos) > 0 { 31 + err := DeleteUser(user) 32 + assert.Error(t, err) 33 + assert.True(t, models.IsErrUserOwnRepos(err)) 34 + return 35 + } 36 + 37 + orgUsers := make([]*models.OrgUser, 0, 10) 38 + assert.NoError(t, db.GetEngine(db.DefaultContext).Find(&orgUsers, &models.OrgUser{UID: userID})) 39 + for _, orgUser := range orgUsers { 40 + if err := models.RemoveOrgUser(orgUser.OrgID, orgUser.UID); err != nil { 41 + assert.True(t, models.IsErrLastOrgOwner(err)) 42 + return 43 + } 44 + } 45 + assert.NoError(t, DeleteUser(user)) 46 + unittest.AssertNotExistsBean(t, &models.User{ID: userID}) 47 + unittest.CheckConsistencyFor(t, &models.User{}, &models.Repository{}) 48 + } 49 + test(2) 50 + test(4) 51 + test(8) 52 + test(11) 53 + 54 + org := unittest.AssertExistsAndLoadBean(t, &models.User{ID: 3}).(*models.User) 55 + assert.Error(t, DeleteUser(org)) 56 + } 57 + 58 + func TestCreateUser(t *testing.T) { 59 + user := &models.User{ 60 + Name: "GiteaBot", 61 + Email: "GiteaBot@gitea.io", 62 + Passwd: ";p['////..-++']", 63 + IsAdmin: false, 64 + Theme: setting.UI.DefaultTheme, 65 + MustChangePassword: false, 66 + } 67 + 68 + assert.NoError(t, models.CreateUser(user)) 69 + 70 + assert.NoError(t, DeleteUser(user)) 71 + } 72 + 73 + func TestCreateUser_Issue5882(t *testing.T) { 74 + // Init settings 75 + _ = setting.Admin 76 + 77 + passwd := ".//.;1;;//.,-=_" 78 + 79 + tt := []struct { 80 + user *models.User 81 + disableOrgCreation bool 82 + }{ 83 + {&models.User{Name: "GiteaBot", Email: "GiteaBot@gitea.io", Passwd: passwd, MustChangePassword: false}, false}, 84 + {&models.User{Name: "GiteaBot2", Email: "GiteaBot2@gitea.io", Passwd: passwd, MustChangePassword: false}, true}, 85 + } 86 + 87 + setting.Service.DefaultAllowCreateOrganization = true 88 + 89 + for _, v := range tt { 90 + setting.Admin.DisableRegularOrgCreation = v.disableOrgCreation 91 + 92 + assert.NoError(t, models.CreateUser(v.user)) 93 + 94 + u, err := models.GetUserByEmail(v.user.Email) 95 + assert.NoError(t, err) 96 + 97 + assert.Equal(t, !u.AllowCreateOrganization, v.disableOrgCreation) 98 + 99 + assert.NoError(t, DeleteUser(v.user)) 100 + } 101 + }
+2 -1
services/wiki/wiki.go
··· 13 13 14 14 "code.gitea.io/gitea/models" 15 15 admin_model "code.gitea.io/gitea/models/admin" 16 + "code.gitea.io/gitea/models/db" 16 17 "code.gitea.io/gitea/models/unit" 17 18 "code.gitea.io/gitea/modules/git" 18 19 "code.gitea.io/gitea/modules/log" ··· 375 376 return err 376 377 } 377 378 378 - admin_model.RemoveAllWithNotice("Delete repository wiki", repo.WikiPath()) 379 + admin_model.RemoveAllWithNotice(db.DefaultContext, "Delete repository wiki", repo.WikiPath()) 379 380 return nil 380 381 }