fix(roles): роли на себя выдавать можно, самомодерация запрещена

Владелец сервера или участник с MANAGE_ROLES не мог выдать роль себе: общий
запрет самомодерации (кик, бан, тайм-аут) распространялся и на оформление.
Проверка иерархии для ролей вынесена отдельно: себя менять можно, владельца и
администратора инстанса — по-прежнему нет.
This commit is contained in:
2026-09-21 01:40:17 +03:00
parent 0646faae2a
commit 75b71da7b7
2 changed files with 83 additions and 1 deletions
+37 -1
View File
@@ -957,7 +957,9 @@ func (s *Server) changeMemberRole(ctx context.Context, guildIDRaw, userIDRaw, ro
if err != nil {
return nil, err
}
if err := s.checkMemberHierarchy(ctx, guildID, user, resolved, targetID); err != nil {
// Роли на себя выдавать можно (MANAGE_ROLES уже проверено): запрет
// самомодерации касается кика, бана и тайм-аута, а не оформления.
if err := s.checkRoleHierarchy(ctx, guildID, user, resolved, targetID); err != nil {
return nil, err
}
role, err := s.store.GetRole(ctx, roleID)
@@ -1006,6 +1008,40 @@ func (s *Server) changeMemberRole(ctx context.Context, guildIDRaw, userIDRaw, ro
return newOKOutput(), nil
}
// checkRoleHierarchy — проверка иерархии для выдачи и снятия ролей: в отличие
// от модерации, свои роли менять можно (владелец сервера не теряет владение),
// но владельца и администратора инстанса по-прежнему не трогаем.
func (s *Server) checkRoleHierarchy(ctx context.Context, guildID uint64, actor *store.User, resolved permissions.Resolved, targetID uint64) error {
if actor.IsInstanceAdmin {
return nil
}
guild, err := s.store.GetGuild(ctx, guildID)
if err != nil {
return humaError(err)
}
if targetID == guild.OwnerID && actor.ID != guild.OwnerID {
return humaErrorStatus(http.StatusForbidden, "perm.denied", "guild owner cannot be moderated")
}
if target, err := s.store.GetUser(ctx, targetID); err == nil && target.IsInstanceAdmin && actor.ID != targetID {
return humaErrorStatus(http.StatusForbidden, "instance.admin_protected", "instance administrator cannot be moderated")
}
if targetID == actor.ID {
return nil
}
actorRoles, err := s.roleData(ctx, guildID, actor.ID)
if err != nil {
return err
}
targetRoles, err := s.roleData(ctx, guildID, targetID)
if err != nil {
return err
}
if !permissions.OutranksMember(actorRoles, targetRoles, resolved.IsOwner, targetID == guild.OwnerID) {
return humaErrorStatus(http.StatusForbidden, "perm.denied", "member has a higher role")
}
return nil
}
// checkMemberHierarchy запрещает действовать против владельца и участников с
// более высокими ролями (AGENT.md 6.3, 7.17).
func (s *Server) checkMemberHierarchy(ctx context.Context, guildID uint64, actor *store.User, resolved permissions.Resolved, targetID uint64) error {