Files
gitea/models/user/block.go
T
EvenandGitHub 06e334121f fix(user): allow unblocking users promoted to admin (#39192)
Fixes #39189.

`IsUserBlockedBy` intentionally treats admin users as not blocked, but
`CanUnblockUser` was also using it to determine whether a blocking
relationship exists. If a previously blocked user is later promoted to
admin, the existing `user_blocking` record remains but can no longer be
removed.

This change separates those two concerns by adding `HasBlocking` for
checking the persisted blocking relationship. `CanUnblockUser` uses that
relationship check while `IsUserBlockedBy` keeps its existing admin-user
behavior.

A regression test verifies that an admin is still not considered blocked
while an existing blocking relationship can still be unblocked.
2026-09-23 16:54:12 +00:00

133 lines
3.2 KiB
Go

// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package user
import (
"context"
"gitea.dev/models/db"
"gitea.dev/modules/container"
"gitea.dev/modules/timeutil"
"gitea.dev/modules/util"
"xorm.io/builder"
)
var (
ErrBlockOrganization = util.NewInvalidArgumentErrorf("cannot block an organization")
ErrCanNotBlock = util.NewInvalidArgumentErrorf("cannot block the user")
ErrCanNotUnblock = util.NewInvalidArgumentErrorf("cannot unblock the user")
ErrBlockedUser = util.NewPermissionDeniedErrorf("user is blocked")
)
type Blocking struct {
ID int64 `xorm:"pk autoincr"`
BlockerID int64 `xorm:"UNIQUE(block)"`
Blocker *User `xorm:"-"`
BlockeeID int64 `xorm:"UNIQUE(block)"`
Blockee *User `xorm:"-"`
Note string
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
}
func (*Blocking) TableName() string {
return "user_blocking"
}
func init() {
db.RegisterModel(new(Blocking))
}
func UpdateBlockingNote(ctx context.Context, id int64, note string) error {
_, err := db.GetEngine(ctx).ID(id).Cols("note").Update(&Blocking{Note: note})
return err
}
func IsUserBlockedBy(ctx context.Context, blockee *User, blockerIDs ...int64) bool {
if blockee.IsAdmin {
return false
}
return HasBlocking(ctx, blockee.ID, blockerIDs...)
}
// HasBlocking reports whether a blocking relationship exists regardless of the blockee's admin status.
func HasBlocking(ctx context.Context, blockeeID int64, blockerIDs ...int64) bool {
if len(blockerIDs) == 0 {
return false
}
cond := builder.Eq{"user_blocking.blockee_id": blockeeID}.
And(builder.In("user_blocking.blocker_id", blockerIDs))
has, _ := db.GetEngine(ctx).Where(cond).Exist(&Blocking{})
return has
}
type FindBlockingOptions struct {
db.ListOptions
BlockerID int64
BlockeeID int64
}
func (opts *FindBlockingOptions) ToOrders() string {
return "id"
}
func (opts *FindBlockingOptions) ToConds() builder.Cond {
cond := builder.NewCond()
if opts.BlockerID != 0 {
cond = cond.And(builder.Eq{"user_blocking.blocker_id": opts.BlockerID})
}
if opts.BlockeeID != 0 {
cond = cond.And(builder.Eq{"user_blocking.blockee_id": opts.BlockeeID})
}
return cond
}
func FindBlockings(ctx context.Context, opts *FindBlockingOptions) ([]*Blocking, int64, error) {
return db.FindAndCount[Blocking](ctx, opts)
}
func GetBlocking(ctx context.Context, blockerID, blockeeID int64) (*Blocking, error) {
blocks, _, err := FindBlockings(ctx, &FindBlockingOptions{
BlockerID: blockerID,
BlockeeID: blockeeID,
})
if err != nil {
return nil, err
}
if len(blocks) == 0 {
return nil, util.NewNotExistErrorf("blocking record doesn't exist")
}
return blocks[0], nil
}
type BlockingList []*Blocking
func (blocks BlockingList) LoadAttributes(ctx context.Context) error {
ids := make(container.Set[int64], len(blocks)*2)
for _, b := range blocks {
ids.Add(b.BlockerID)
ids.Add(b.BlockeeID)
}
userList, err := GetUsersByIDs(ctx, ids.Values())
if err != nil {
return err
}
userMap := make(map[int64]*User, len(userList))
for _, u := range userList {
userMap[u.ID] = u
}
for _, b := range blocks {
b.Blocker = userMap[b.BlockerID]
b.Blockee = userMap[b.BlockeeID]
}
return nil
}