From 42229dc0b88ec715c470104be5d91868cc611422 Mon Sep 17 00:00:00 2001
From: 6543 <6543@obermui.de>
Date: Thu, 24 Mar 2022 00:36:38 +0100
Subject: [PATCH] Fix showing issues in your repositories (#18916) (#19191)

- Make a restriction on which issues can be shown based on if you the user or team has write permission to the repository.
- Fixes a issue whereby you wouldn't see any associated issues with a specific team on a organization if you wasn't a member(fixed by zeroing the User{ID} in the options).
- Resolves #18913

Co-authored-by: Gusted <williamzijl7@hotmail.com>
---
 models/issue.go          |  7 ++++-
 routers/web/user/home.go | 69 ++++++++++++++++++++++++++++++++++++++----------
 2 files changed, 61 insertions(+), 15 deletions(-)

diff --git a/models/issue.go b/models/issue.go
index cb5791be9e..81b04548e1 100644
--- a/models/issue.go
+++ b/models/issue.go
@@ -1551,6 +1551,7 @@ const (
 	FilterModeCreate
 	FilterModeMention
 	FilterModeReviewRequested
+	FilterModeYourRepositories
 )
 
 func parseCountResult(results []map[string][]byte) int64 {
@@ -1695,6 +1696,7 @@ type UserIssueStatsOptions struct {
 	IssueIDs   []int64
 	IsArchived util.OptionalBool
 	LabelIDs   []int64
+	RepoCond   builder.Cond
 	Org        *Organization
 	Team       *Team
 }
@@ -1712,6 +1714,9 @@ func GetUserIssueStats(opts UserIssueStatsOptions) (*IssueStats, error) {
 	if len(opts.IssueIDs) > 0 {
 		cond = cond.And(builder.In("issue.id", opts.IssueIDs))
 	}
+	if opts.RepoCond != nil {
+		cond = cond.And(opts.RepoCond)
+	}
 
 	if opts.UserID > 0 {
 		cond = cond.And(issuePullAccessibleRepoCond("issue.repo_id", opts.UserID, opts.Org, opts.Team, opts.IsPull))
@@ -1733,7 +1738,7 @@ func GetUserIssueStats(opts UserIssueStatsOptions) (*IssueStats, error) {
 	}
 
 	switch opts.FilterMode {
-	case FilterModeAll:
+	case FilterModeAll, FilterModeYourRepositories:
 		stats.OpenCount, err = sess(cond).
 			And("issue.is_closed = ?", false).
 			Count(new(Issue))
diff --git a/routers/web/user/home.go b/routers/web/user/home.go
index 367cf5ca08..6f1234d096 100644
--- a/routers/web/user/home.go
+++ b/routers/web/user/home.go
@@ -197,7 +197,7 @@ func Milestones(ctx *context.Context) {
 		if issueReposQueryPattern.MatchString(reposQuery) {
 			// remove "[" and "]" from string
 			reposQuery = reposQuery[1 : len(reposQuery)-1]
-			//for each ID (delimiter ",") add to int to repoIDs
+			// for each ID (delimiter ",") add to int to repoIDs
 
 			for _, rID := range strings.Split(reposQuery, ",") {
 				// Ensure nonempty string entries
@@ -350,7 +350,6 @@ func Issues(ctx *context.Context) {
 var issueReposQueryPattern = regexp.MustCompile(`^\[\d+(,\d+)*,?\]$`)
 
 func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
-
 	// ----------------------------------------------------
 	// Determine user; can be either user or organization.
 	// Return with NotFound or ServerError if unsuccessful.
@@ -364,7 +363,7 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
 	var (
 		viewType   string
 		sortType   = ctx.FormString("sort")
-		filterMode = models.FilterModeAll
+		filterMode int
 	)
 
 	// --------------------------------------------------------------------------------
@@ -390,8 +389,10 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
 		filterMode = models.FilterModeMention
 	case "review_requested":
 		filterMode = models.FilterModeReviewRequested
-	case "your_repositories": // filterMode already set to All
+	case "your_repositories":
+		fallthrough
 	default:
+		filterMode = models.FilterModeYourRepositories
 		viewType = "your_repositories"
 	}
 
@@ -421,6 +422,30 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
 		User:       ctx.User,
 	}
 
+	// Search all repositories which
+	//
+	// As user:
+	// - Owns the repository.
+	// - Have collaborator permissions in repository.
+	//
+	// As org:
+	// - Owns the repository.
+	//
+	// As team:
+	// - Team org's owns the repository.
+	// - Team has read permission to repository.
+	repoOpts := &models.SearchRepoOptions{
+		Actor:      ctx.User,
+		OwnerID:    ctx.User.ID,
+		Private:    true,
+		AllPublic:  false,
+		AllLimited: false,
+	}
+
+	if ctxUser.IsOrganization() && ctx.Org.Team != nil {
+		repoOpts.TeamID = ctx.Org.Team.ID
+	}
+
 	switch filterMode {
 	case models.FilterModeAll:
 	case models.FilterModeAssign:
@@ -431,6 +456,19 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
 		opts.MentionedID = ctx.User.ID
 	case models.FilterModeReviewRequested:
 		opts.ReviewRequestedID = ctx.User.ID
+	case models.FilterModeYourRepositories:
+		if ctxUser.IsOrganization() && ctx.Org.Team != nil {
+			// Fixes a issue whereby the user's ID would be used
+			// to check if it's in the team(which possible isn't the case).
+			opts.User = nil
+		}
+		userRepoIDs, _, err := models.SearchRepositoryIDs(repoOpts)
+		if err != nil {
+			ctx.ServerError("models.SearchRepositoryIDs: %v", err)
+			return
+		}
+
+		opts.RepoIDs = userRepoIDs
 	}
 
 	// keyword holds the search term entered into the search field.
@@ -562,8 +600,12 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
 			Org:        org,
 			Team:       team,
 		}
-		if len(repoIDs) > 0 {
-			statsOpts.RepoIDs = repoIDs
+		if filterMode == models.FilterModeYourRepositories {
+			statsOpts.RepoCond = models.SearchRepositoryCondition(repoOpts)
+		}
+		// Detect when we only should search by team.
+		if opts.User == nil {
+			statsOpts.UserID = 0
 		}
 		issueStats, err = models.GetUserIssueStats(statsOpts)
 		if err != nil {
@@ -586,8 +628,7 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
 
 	ctx.Data["IsShowClosed"] = isShowClosed
 
-	ctx.Data["IssueRefEndNames"], ctx.Data["IssueRefURLs"] =
-		issue_service.GetRefEndNamesAndURLs(issues, ctx.FormString("RepoLink"))
+	ctx.Data["IssueRefEndNames"], ctx.Data["IssueRefURLs"] = issue_service.GetRefEndNamesAndURLs(issues, ctx.FormString("RepoLink"))
 
 	ctx.Data["Issues"] = issues
 
@@ -661,7 +702,7 @@ func getRepoIDs(reposQuery string) []int64 {
 	var repoIDs []int64
 	// remove "[" and "]" from string
 	reposQuery = reposQuery[1 : len(reposQuery)-1]
-	//for each ID (delimiter ",") add to int to repoIDs
+	// for each ID (delimiter ",") add to int to repoIDs
 	for _, rID := range strings.Split(reposQuery, ",") {
 		// Ensure nonempty string entries
 		if rID != "" && rID != "0" {
@@ -693,8 +734,8 @@ func issueIDsFromSearch(ctxUser *user_model.User, keyword string, opts *models.I
 }
 
 func loadRepoByIDs(ctxUser *user_model.User, issueCountByRepo map[int64]int64, unitType unit.Type) (map[int64]*repo_model.Repository, error) {
-	var totalRes = make(map[int64]*repo_model.Repository, len(issueCountByRepo))
-	var repoIDs = make([]int64, 0, 500)
+	totalRes := make(map[int64]*repo_model.Repository, len(issueCountByRepo))
+	repoIDs := make([]int64, 0, 500)
 	for id := range issueCountByRepo {
 		if id <= 0 {
 			continue
@@ -745,7 +786,7 @@ func ShowGPGKeys(ctx *context.Context, uid int64) {
 		if err != nil {
 			if asymkey_model.IsErrGPGKeyImportNotExist(err) {
 				failedEntitiesID = append(failedEntitiesID, k.KeyID)
-				continue //Skip previous import without backup of imported armored key
+				continue // Skip previous import without backup of imported armored key
 			}
 			ctx.ServerError("ShowGPGKeys", err)
 			return
@@ -755,12 +796,12 @@ func ShowGPGKeys(ctx *context.Context, uid int64) {
 	var buf bytes.Buffer
 
 	headers := make(map[string]string)
-	if len(failedEntitiesID) > 0 { //If some key need re-import to be exported
+	if len(failedEntitiesID) > 0 { // If some key need re-import to be exported
 		headers["Note"] = fmt.Sprintf("The keys with the following IDs couldn't be exported and need to be reuploaded %s", strings.Join(failedEntitiesID, ", "))
 	}
 	writer, _ := armor.Encode(&buf, "PGP PUBLIC KEY BLOCK", headers)
 	for _, e := range entities {
-		err = e.Serialize(writer) //TODO find why key are exported with a different cipherTypeByte as original (should not be blocking but strange)
+		err = e.Serialize(writer) // TODO find why key are exported with a different cipherTypeByte as original (should not be blocking but strange)
 		if err != nil {
 			ctx.ServerError("ShowGPGKeys", err)
 			return