clean watches when make a repository private and check permission when send release emails (#36319) (#36370)
Backport #36319 by @lunny Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
This commit is contained in:
@@ -93,15 +93,21 @@ func init() {
|
|||||||
db.RegisterModel(new(Release))
|
db.RegisterModel(new(Release))
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadAttributes load repo and publisher attributes for a release
|
func (r *Release) LoadRepo(ctx context.Context) (err error) {
|
||||||
func (r *Release) LoadAttributes(ctx context.Context) error {
|
if r.Repo != nil {
|
||||||
var err error
|
return nil
|
||||||
if r.Repo == nil {
|
|
||||||
r.Repo, err = GetRepositoryByID(ctx, r.RepoID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
r.Repo, err = GetRepositoryByID(ctx, r.RepoID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadAttributes load repo and publisher attributes for a release
|
||||||
|
func (r *Release) LoadAttributes(ctx context.Context) (err error) {
|
||||||
|
if err := r.LoadRepo(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if r.Publisher == nil {
|
if r.Publisher == nil {
|
||||||
r.Publisher, err = user_model.GetUserByID(ctx, r.PublisherID)
|
r.Publisher, err = user_model.GetUserByID(ctx, r.PublisherID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -176,3 +176,13 @@ func WatchIfAuto(ctx context.Context, userID, repoID int64, isWrite bool) error
|
|||||||
}
|
}
|
||||||
return watchRepoMode(ctx, watch, WatchModeAuto)
|
return watchRepoMode(ctx, watch, WatchModeAuto)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ClearRepoWatches clears all watches for a repository and from the user that watched it.
|
||||||
|
// Used when a repository is set to private.
|
||||||
|
func ClearRepoWatches(ctx context.Context, repoID int64) error {
|
||||||
|
if _, err := db.Exec(ctx, "UPDATE `repository` SET num_watches = 0 WHERE id = ?", repoID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return db.DeleteBeans(ctx, Watch{RepoID: repoID})
|
||||||
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestIsWatching(t *testing.T) {
|
func TestIsWatching(t *testing.T) {
|
||||||
@@ -119,3 +120,21 @@ func TestWatchIfAuto(t *testing.T) {
|
|||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Len(t, watchers, prevCount)
|
assert.Len(t, watchers, prevCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestClearRepoWatches(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
const repoID int64 = 1
|
||||||
|
watchers, err := repo_model.GetRepoWatchersIDs(t.Context(), repoID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotEmpty(t, watchers)
|
||||||
|
|
||||||
|
assert.NoError(t, repo_model.ClearRepoWatches(t.Context(), repoID))
|
||||||
|
|
||||||
|
watchers, err = repo_model.GetRepoWatchersIDs(t.Context(), repoID)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Empty(t, watchers)
|
||||||
|
|
||||||
|
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID})
|
||||||
|
assert.Zero(t, repo.NumWatches)
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,9 +7,12 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
|
||||||
|
access_model "code.gitea.io/gitea/models/perm/access"
|
||||||
"code.gitea.io/gitea/models/renderhelper"
|
"code.gitea.io/gitea/models/renderhelper"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
|
"code.gitea.io/gitea/models/unit"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/markup/markdown"
|
"code.gitea.io/gitea/modules/markup/markdown"
|
||||||
@@ -44,6 +47,16 @@ func MailNewRelease(ctx context.Context, rel *repo_model.Release) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := rel.LoadRepo(ctx); err != nil {
|
||||||
|
log.Error("rel.LoadRepo: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete publisher or any users with no permission
|
||||||
|
recipients = slices.DeleteFunc(recipients, func(u *user_model.User) bool {
|
||||||
|
return u.ID == rel.PublisherID || !access_model.CheckRepoUnitUser(ctx, rel.Repo, u, unit.TypeReleases)
|
||||||
|
})
|
||||||
|
|
||||||
langMap := make(map[string][]*user_model.User)
|
langMap := make(map[string][]*user_model.User)
|
||||||
for _, user := range recipients {
|
for _, user := range recipients {
|
||||||
if user.ID != rel.PublisherID {
|
if user.ID != rel.PublisherID {
|
||||||
|
|||||||
71
services/mailer/mail_release_test.go
Normal file
71
services/mailer/mail_release_test.go
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package mailer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
|
"code.gitea.io/gitea/models/unittest"
|
||||||
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
sender_service "code.gitea.io/gitea/services/mailer/sender"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMailNewReleaseFiltersUnauthorizedWatchers(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
origMailService := setting.MailService
|
||||||
|
origDomain := setting.Domain
|
||||||
|
origAppName := setting.AppName
|
||||||
|
origAppURL := setting.AppURL
|
||||||
|
origTemplates := LoadedTemplates()
|
||||||
|
defer func() {
|
||||||
|
setting.MailService = origMailService
|
||||||
|
setting.Domain = origDomain
|
||||||
|
setting.AppName = origAppName
|
||||||
|
setting.AppURL = origAppURL
|
||||||
|
loadedTemplates.Store(origTemplates)
|
||||||
|
}()
|
||||||
|
|
||||||
|
setting.MailService = &setting.Mailer{
|
||||||
|
From: "Gitea",
|
||||||
|
FromEmail: "noreply@example.com",
|
||||||
|
}
|
||||||
|
setting.Domain = "example.com"
|
||||||
|
setting.AppName = "Gitea"
|
||||||
|
setting.AppURL = "https://example.com/"
|
||||||
|
prepareMailTemplates(string(tplNewReleaseMail), "{{.Subject}}", "<p>{{.Release.TagName}}</p>")
|
||||||
|
|
||||||
|
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
|
||||||
|
require.True(t, repo.IsPrivate)
|
||||||
|
|
||||||
|
admin := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||||
|
unauthorized := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5})
|
||||||
|
|
||||||
|
assert.NoError(t, repo_model.WatchRepo(t.Context(), admin, repo, true))
|
||||||
|
assert.NoError(t, repo_model.WatchRepo(t.Context(), unauthorized, repo, true))
|
||||||
|
|
||||||
|
rel := unittest.AssertExistsAndLoadBean(t, &repo_model.Release{ID: 11})
|
||||||
|
rel.Repo = nil
|
||||||
|
rel.Publisher = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: rel.PublisherID})
|
||||||
|
|
||||||
|
var sent []*sender_service.Message
|
||||||
|
origSend := SendAsync
|
||||||
|
SendAsync = func(msgs ...*sender_service.Message) {
|
||||||
|
sent = append(sent, msgs...)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
SendAsync = origSend
|
||||||
|
}()
|
||||||
|
|
||||||
|
MailNewRelease(t.Context(), rel)
|
||||||
|
|
||||||
|
require.Len(t, sent, 1)
|
||||||
|
assert.Equal(t, admin.EmailTo(), sent[0].To)
|
||||||
|
assert.NotEqual(t, unauthorized.EmailTo(), sent[0].To)
|
||||||
|
}
|
||||||
@@ -197,6 +197,10 @@ func MakeRepoPrivate(ctx context.Context, repo *repo_model.Repository) (err erro
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err = repo_model.ClearRepoWatches(ctx, repo.ID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// Create/Remove git-daemon-export-ok for git-daemon...
|
// Create/Remove git-daemon-export-ok for git-daemon...
|
||||||
if err := CheckDaemonExportOK(ctx, repo); err != nil {
|
if err := CheckDaemonExportOK(ctx, repo); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
"code.gitea.io/gitea/models/unittest"
|
"code.gitea.io/gitea/models/unittest"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestLinkedRepository(t *testing.T) {
|
func TestLinkedRepository(t *testing.T) {
|
||||||
@@ -70,3 +71,24 @@ func TestRepository_HasWiki(t *testing.T) {
|
|||||||
repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
|
repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
|
||||||
assert.False(t, HasWiki(t.Context(), repo2))
|
assert.False(t, HasWiki(t.Context(), repo2))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMakeRepoPrivateClearsWatches(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||||
|
repo.IsPrivate = false
|
||||||
|
|
||||||
|
watchers, err := repo_model.GetRepoWatchersIDs(t.Context(), repo.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotEmpty(t, watchers)
|
||||||
|
|
||||||
|
assert.NoError(t, MakeRepoPrivate(t.Context(), repo))
|
||||||
|
|
||||||
|
watchers, err = repo_model.GetRepoWatchersIDs(t.Context(), repo.ID)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Empty(t, watchers)
|
||||||
|
|
||||||
|
updatedRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repo.ID})
|
||||||
|
assert.True(t, updatedRepo.IsPrivate)
|
||||||
|
assert.Zero(t, updatedRepo.NumWatches)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user