fix(moderation): администратор инстанса тоже не может модерировать себя
Проверка иерархии пропускала все запреты для администратора инстанса, включая запрет на модерацию себя: он мог забанить или исключить самого себя и потерять доступ к серверу (поймано при проверке интерфейса на стенде — самобан снял участие в «Главном сервере»). Теперь проверка `targetID == actor.ID` выполняется до обхода иерархии, для администратора инстанса остаются доступны любые другие цели. Регрессионный тест: самобан и самоисключение администратора инстанса дают 403.
This commit is contained in:
@@ -974,6 +974,11 @@ func (s *Server) changeMemberRole(ctx context.Context, guildIDRaw, userIDRaw, ro
|
|||||||
// checkMemberHierarchy запрещает действовать против владельца и участников с
|
// checkMemberHierarchy запрещает действовать против владельца и участников с
|
||||||
// более высокими ролями (AGENT.md 6.3, 7.17).
|
// более высокими ролями (AGENT.md 6.3, 7.17).
|
||||||
func (s *Server) checkMemberHierarchy(ctx context.Context, guildID uint64, actor *store.User, resolved permissions.Resolved, targetID uint64) error {
|
func (s *Server) checkMemberHierarchy(ctx context.Context, guildID uint64, actor *store.User, resolved permissions.Resolved, targetID uint64) error {
|
||||||
|
// Модерировать себя нельзя никому, включая администратора инстанса: иначе
|
||||||
|
// он может случайно исключить или забанить сам себя (проверено на стенде).
|
||||||
|
if targetID == actor.ID {
|
||||||
|
return humaErrorStatus(http.StatusForbidden, "perm.denied", "you cannot moderate yourself")
|
||||||
|
}
|
||||||
if actor.IsInstanceAdmin {
|
if actor.IsInstanceAdmin {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -989,9 +994,6 @@ func (s *Server) checkMemberHierarchy(ctx context.Context, guildID uint64, actor
|
|||||||
if targetID == guild.OwnerID && actor.ID != guild.OwnerID {
|
if targetID == guild.OwnerID && actor.ID != guild.OwnerID {
|
||||||
return humaErrorStatus(http.StatusForbidden, "perm.denied", "guild owner cannot be moderated")
|
return humaErrorStatus(http.StatusForbidden, "perm.denied", "guild owner cannot be moderated")
|
||||||
}
|
}
|
||||||
if targetID == actor.ID {
|
|
||||||
return humaErrorStatus(http.StatusForbidden, "perm.denied", "you cannot moderate yourself")
|
|
||||||
}
|
|
||||||
actorRoles, err := s.roleData(ctx, guildID, actor.ID)
|
actorRoles, err := s.roleData(ctx, guildID, actor.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -209,6 +209,30 @@ func TestMemberTimeoutBlocksSending(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestInstanceAdminCannotModerateSelf: администратор инстанса обходит иерархию
|
||||||
|
// ролей, но не запрет на модерацию себя — иначе он исключает себя из сервера.
|
||||||
|
func TestInstanceAdminCannotModerateSelf(t *testing.T) {
|
||||||
|
srv, owner, _, guildID, _ := banFixture(t)
|
||||||
|
promoteAdmin(t, srv, "ban-owner@example.com")
|
||||||
|
|
||||||
|
self := doJSON(t, srv, http.MethodGet, "/api/v1/users/@me", "", owner)
|
||||||
|
ownerID := decodeResponse[struct {
|
||||||
|
User struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
} `json:"user"`
|
||||||
|
}](t, self).User.ID
|
||||||
|
|
||||||
|
ban := doJSON(t, srv, http.MethodPut, "/api/v1/guilds/"+guildID+"/bans/"+ownerID,
|
||||||
|
`{"reason":"сам себя"}`, owner)
|
||||||
|
if ban.Code != http.StatusForbidden {
|
||||||
|
t.Fatalf("самобан администратора инстанса = %d, ожидался 403", ban.Code)
|
||||||
|
}
|
||||||
|
kick := doJSON(t, srv, http.MethodDelete, "/api/v1/guilds/"+guildID+"/members/"+ownerID, "", owner)
|
||||||
|
if kick.Code != http.StatusForbidden {
|
||||||
|
t.Fatalf("самоисключение администратора инстанса = %d, ожидался 403", kick.Code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestTimeoutCannotBeSetOnSelf(t *testing.T) {
|
func TestTimeoutCannotBeSetOnSelf(t *testing.T) {
|
||||||
srv, owner, _, guildID, _ := banFixture(t) // участник не нужен: проверяем себя
|
srv, owner, _, guildID, _ := banFixture(t) // участник не нужен: проверяем себя
|
||||||
self := doJSON(t, srv, http.MethodGet, "/api/v1/users/@me", "", owner)
|
self := doJSON(t, srv, http.MethodGet, "/api/v1/users/@me", "", owner)
|
||||||
|
|||||||
Reference in New Issue
Block a user