mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 16:40:24 +08:00 
			
		
		
		
	Fix not removed watches on unallowed repositories (#4201)
This commit is contained in:
		
				
					committed by
					
						
						techknowlogick
					
				
			
			
				
	
			
			
			
						parent
						
							467ff4d343
						
					
				
				
					commit
					a93f13849c
				
			@@ -71,3 +71,15 @@ func getIssueWatchers(e Engine, issueID int64) (watches []*IssueWatch, err error
 | 
				
			|||||||
		Find(&watches)
 | 
							Find(&watches)
 | 
				
			||||||
	return
 | 
						return
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func removeIssueWatchersByRepoID(e Engine, userID int64, repoID int64) error {
 | 
				
			||||||
 | 
						iw := &IssueWatch{
 | 
				
			||||||
 | 
							IsWatching: false,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						_, err := e.
 | 
				
			||||||
 | 
							Join("INNER", "issue", "`issue`.id = `issue_watch`.issue_id AND `issue`.repo_id = ?", repoID).
 | 
				
			||||||
 | 
							Cols("is_watching", "updated_unix").
 | 
				
			||||||
 | 
							Where("`issue_watch`.user_id = ?", userID).
 | 
				
			||||||
 | 
							Update(iw)
 | 
				
			||||||
 | 
						return err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -186,6 +186,8 @@ var migrations = []Migration{
 | 
				
			|||||||
	NewMigration("add u2f", addU2FReg),
 | 
						NewMigration("add u2f", addU2FReg),
 | 
				
			||||||
	// v66 -> v67
 | 
						// v66 -> v67
 | 
				
			||||||
	NewMigration("add login source id column for public_key table", addLoginSourceIDToPublicKeyTable),
 | 
						NewMigration("add login source id column for public_key table", addLoginSourceIDToPublicKeyTable),
 | 
				
			||||||
 | 
						// v67 -> v68
 | 
				
			||||||
 | 
						NewMigration("remove stale watches", removeStaleWatches),
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Migrate database to current version
 | 
					// Migrate database to current version
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										158
									
								
								models/migrations/v67.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										158
									
								
								models/migrations/v67.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,158 @@
 | 
				
			|||||||
 | 
					// Copyright 2018 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 migrations
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/setting"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/go-xorm/xorm"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func removeStaleWatches(x *xorm.Engine) error {
 | 
				
			||||||
 | 
						type Watch struct {
 | 
				
			||||||
 | 
							ID     int64
 | 
				
			||||||
 | 
							UserID int64
 | 
				
			||||||
 | 
							RepoID int64
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						type IssueWatch struct {
 | 
				
			||||||
 | 
							ID         int64
 | 
				
			||||||
 | 
							UserID     int64
 | 
				
			||||||
 | 
							RepoID     int64
 | 
				
			||||||
 | 
							IsWatching bool
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						type Repository struct {
 | 
				
			||||||
 | 
							ID        int64
 | 
				
			||||||
 | 
							IsPrivate bool
 | 
				
			||||||
 | 
							OwnerID   int64
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						type Access struct {
 | 
				
			||||||
 | 
							UserID int64
 | 
				
			||||||
 | 
							RepoID int64
 | 
				
			||||||
 | 
							Mode   int
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const (
 | 
				
			||||||
 | 
							// AccessModeNone no access
 | 
				
			||||||
 | 
							AccessModeNone int = iota // 0
 | 
				
			||||||
 | 
							// AccessModeRead read access
 | 
				
			||||||
 | 
							AccessModeRead // 1
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						accessLevel := func(userID int64, repo *Repository) (int, error) {
 | 
				
			||||||
 | 
							mode := AccessModeNone
 | 
				
			||||||
 | 
							if !repo.IsPrivate {
 | 
				
			||||||
 | 
								mode = AccessModeRead
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if userID == 0 {
 | 
				
			||||||
 | 
								return mode, nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if userID == repo.OwnerID {
 | 
				
			||||||
 | 
								return 4, nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							a := &Access{UserID: userID, RepoID: repo.ID}
 | 
				
			||||||
 | 
							if has, err := x.Get(a); !has || err != nil {
 | 
				
			||||||
 | 
								return mode, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return a.Mode, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						sess := x.NewSession()
 | 
				
			||||||
 | 
						defer sess.Close()
 | 
				
			||||||
 | 
						if err := sess.Begin(); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						repoCache := make(map[int64]*Repository)
 | 
				
			||||||
 | 
						err := x.BufferSize(setting.IterateBufferSize).Iterate(new(Watch),
 | 
				
			||||||
 | 
							func(idx int, bean interface{}) error {
 | 
				
			||||||
 | 
								watch := bean.(*Watch)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								repo := repoCache[watch.RepoID]
 | 
				
			||||||
 | 
								if repo == nil {
 | 
				
			||||||
 | 
									repo = &Repository{
 | 
				
			||||||
 | 
										ID: watch.RepoID,
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									if _, err := x.Get(repo); err != nil {
 | 
				
			||||||
 | 
										return err
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									repoCache[watch.RepoID] = repo
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Remove watches from now unaccessible repositories
 | 
				
			||||||
 | 
								mode, err := accessLevel(watch.UserID, repo)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								has := AccessModeRead <= mode
 | 
				
			||||||
 | 
								if has {
 | 
				
			||||||
 | 
									return nil
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if _, err = sess.Delete(&Watch{0, watch.UserID, repo.ID}); err != nil {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								_, err = sess.Exec("UPDATE `repository` SET num_watches = num_watches - 1 WHERE id = ?", repo.ID)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						repoCache = make(map[int64]*Repository)
 | 
				
			||||||
 | 
						err = x.BufferSize(setting.IterateBufferSize).
 | 
				
			||||||
 | 
							Distinct("issue_watch.user_id", "issue.repo_id").
 | 
				
			||||||
 | 
							Join("INNER", "issue", "issue_watch.issue_id = issue.id").
 | 
				
			||||||
 | 
							Where("issue_watch.is_watching = ?", true).
 | 
				
			||||||
 | 
							Iterate(new(IssueWatch),
 | 
				
			||||||
 | 
								func(idx int, bean interface{}) error {
 | 
				
			||||||
 | 
									watch := bean.(*IssueWatch)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									repo := repoCache[watch.RepoID]
 | 
				
			||||||
 | 
									if repo == nil {
 | 
				
			||||||
 | 
										repo = &Repository{
 | 
				
			||||||
 | 
											ID: watch.RepoID,
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										if _, err := x.Get(repo); err != nil {
 | 
				
			||||||
 | 
											return err
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										repoCache[watch.RepoID] = repo
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									// Remove issue watches from now unaccssible repositories
 | 
				
			||||||
 | 
									mode, err := accessLevel(watch.UserID, repo)
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										return err
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									has := AccessModeRead <= mode
 | 
				
			||||||
 | 
									if has {
 | 
				
			||||||
 | 
										return nil
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									iw := &IssueWatch{
 | 
				
			||||||
 | 
										IsWatching: false,
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									_, err = sess.
 | 
				
			||||||
 | 
										Join("INNER", "issue", "`issue`.id = `issue_watch`.issue_id AND `issue`.repo_id = ?", watch.RepoID).
 | 
				
			||||||
 | 
										Cols("is_watching", "updated_unix").
 | 
				
			||||||
 | 
										Where("`issue_watch`.user_id = ?", watch.UserID).
 | 
				
			||||||
 | 
										Update(iw)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return sess.Commit()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -178,6 +178,11 @@ func (t *Team) removeRepository(e Engine, repo *Repository, recalculate bool) (e
 | 
				
			|||||||
		if err = watchRepo(e, teamUser.UID, repo.ID, false); err != nil {
 | 
							if err = watchRepo(e, teamUser.UID, repo.ID, false); err != nil {
 | 
				
			||||||
			return err
 | 
								return err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Remove all IssueWatches a user has subscribed to in the repositories
 | 
				
			||||||
 | 
							if err := removeIssueWatchersByRepoID(e, teamUser.UID, repo.ID); err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
@@ -374,11 +379,34 @@ func DeleteTeam(t *Team) error {
 | 
				
			|||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := t.getMembers(sess); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Delete all accesses.
 | 
						// Delete all accesses.
 | 
				
			||||||
	for _, repo := range t.Repos {
 | 
						for _, repo := range t.Repos {
 | 
				
			||||||
		if err := repo.recalculateTeamAccesses(sess, t.ID); err != nil {
 | 
							if err := repo.recalculateTeamAccesses(sess, t.ID); err != nil {
 | 
				
			||||||
			return err
 | 
								return err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Remove watches from all users and now unaccessible repos
 | 
				
			||||||
 | 
							for _, user := range t.Members {
 | 
				
			||||||
 | 
								has, err := hasAccess(sess, user.ID, repo, AccessModeRead)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								} else if has {
 | 
				
			||||||
 | 
									continue
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if err = watchRepo(sess, user.ID, repo.ID, false); err != nil {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Remove all IssueWatches a user has subscribed to in the repositories
 | 
				
			||||||
 | 
								if err = removeIssueWatchersByRepoID(sess, user.ID, repo.ID); err != nil {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Delete team-repo
 | 
						// Delete team-repo
 | 
				
			||||||
@@ -518,6 +546,10 @@ func AddTeamMember(team *Team, userID int64) error {
 | 
				
			|||||||
		if err := repo.recalculateTeamAccesses(sess, 0); err != nil {
 | 
							if err := repo.recalculateTeamAccesses(sess, 0); err != nil {
 | 
				
			||||||
			return err
 | 
								return err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err = watchRepo(sess, userID, repo.ID, true); err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return sess.Commit()
 | 
						return sess.Commit()
 | 
				
			||||||
@@ -558,6 +590,23 @@ func removeTeamMember(e *xorm.Session, team *Team, userID int64) error {
 | 
				
			|||||||
		if err := repo.recalculateTeamAccesses(e, 0); err != nil {
 | 
							if err := repo.recalculateTeamAccesses(e, 0); err != nil {
 | 
				
			||||||
			return err
 | 
								return err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Remove watches from now unaccessible
 | 
				
			||||||
 | 
							has, err := hasAccess(e, userID, repo, AccessModeRead)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							} else if has {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err = watchRepo(e, userID, repo.ID, false); err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Remove all IssueWatches a user has subscribed to in the repositories
 | 
				
			||||||
 | 
							if err := removeIssueWatchersByRepoID(e, userID, repo.ID); err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Check if the user is a member of any team in the organization.
 | 
						// Check if the user is a member of any team in the organization.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1851,6 +1851,9 @@ func DeleteRepository(doer *User, uid, repoID int64) error {
 | 
				
			|||||||
		if _, err = sess.In("issue_id", issueIDs).Delete(&Reaction{}); err != nil {
 | 
							if _, err = sess.In("issue_id", issueIDs).Delete(&Reaction{}); err != nil {
 | 
				
			||||||
			return err
 | 
								return err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
							if _, err = sess.In("issue_id", issueIDs).Delete(&IssueWatch{}); err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		attachments := make([]*Attachment, 0, 5)
 | 
							attachments := make([]*Attachment, 0, 5)
 | 
				
			||||||
		if err = sess.
 | 
							if err = sess.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -172,5 +172,14 @@ func (repo *Repository) DeleteCollaboration(uid int64) (err error) {
 | 
				
			|||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err = watchRepo(sess, uid, repo.ID, false); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Remove all IssueWatches a user has subscribed to in the repository
 | 
				
			||||||
 | 
						if err := removeIssueWatchersByRepoID(sess, uid, repo.ID); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return sess.Commit()
 | 
						return sess.Commit()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user