120 lines
3.7 KiB
Go
120 lines
3.7 KiB
Go
package useCases
|
|
|
|
import (
|
|
"RenewCMS/internal/domain/audit"
|
|
"RenewCMS/internal/domain/contenttype"
|
|
"RenewCMS/internal/domain/role"
|
|
"RenewCMS/internal/domain/user"
|
|
"context"
|
|
"errors"
|
|
)
|
|
|
|
type ChangeUserRoleUseCase struct {
|
|
users user.Repository
|
|
roles role.Repository
|
|
contentTypes contenttype.Repository
|
|
recorder audit.Recorder
|
|
}
|
|
|
|
type ChangeUserRoleCommand struct {
|
|
Caller ContentCaller
|
|
TargetID uint32
|
|
RoleID uint32
|
|
}
|
|
|
|
func NewChangeUserRoleUseCase(users user.Repository, roles role.Repository, contentTypes contenttype.Repository, recorder audit.Recorder) *ChangeUserRoleUseCase {
|
|
return &ChangeUserRoleUseCase{users: users, roles: roles, contentTypes: contentTypes, recorder: recorder}
|
|
}
|
|
|
|
var roleChangeRefusals = []error{user.ErrRoleChanged, user.ErrUserNotFound, role.ErrRoleNotFound, role.ErrUnknownRole}
|
|
|
|
func (u *ChangeUserRoleUseCase) ChangeRole(ctx context.Context, command ChangeUserRoleCommand) (DirectoryEntry, error) {
|
|
target, err := u.users.Get(command.TargetID)
|
|
if err != nil {
|
|
return DirectoryEntry{}, err
|
|
}
|
|
current, held, err := u.heldRole(target)
|
|
if err != nil {
|
|
return DirectoryEntry{}, err
|
|
}
|
|
if held && current.Locked {
|
|
return DirectoryEntry{}, role.ErrRoleLocked
|
|
}
|
|
next, err := assignableRole(u.roles, command.RoleID)
|
|
if err != nil {
|
|
return DirectoryEntry{}, err
|
|
}
|
|
if err := u.checkCoverage(command, target, current, held, next); err != nil {
|
|
return DirectoryEntry{}, err
|
|
}
|
|
if held && current.ID == next.ID {
|
|
return u.entryOf(target, next)
|
|
}
|
|
return u.write(ctx, command, target, current, held, next)
|
|
}
|
|
|
|
func (u *ChangeUserRoleUseCase) heldRole(target user.User) (role.Role, bool, error) {
|
|
current, err := resolveRole(u.roles, target)
|
|
if errors.Is(err, role.ErrRoleNotFound) {
|
|
return role.Role{}, false, nil
|
|
}
|
|
if err != nil {
|
|
return role.Role{}, false, err
|
|
}
|
|
return current, true, nil
|
|
}
|
|
|
|
func (u *ChangeUserRoleUseCase) checkCoverage(command ChangeUserRoleCommand, target user.User, current role.Role, held bool, next role.Role) error {
|
|
if target.ID == command.Caller.UserID {
|
|
return role.ErrOwnRole
|
|
}
|
|
typeKeys, err := allowedTypeKeys(u.contentTypes)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if held {
|
|
if err := ensureCoveredBy(command.Caller, typeKeys, current); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return ensureCoveredBy(command.Caller, typeKeys, next)
|
|
}
|
|
|
|
func (u *ChangeUserRoleUseCase) write(ctx context.Context, command ChangeUserRoleCommand, target user.User, current role.Role, held bool, next role.Role) (DirectoryEntry, error) {
|
|
event := audit.NewEvent(audit.ActionUserChangeRole, audit.CategoryUsers)
|
|
event.Target = audit.NewTarget(audit.TargetUser, target.ID, target.Username)
|
|
updated, err := u.users.UpdateRole(target.ID, target.RoleID, next.ID, command.Caller.UserID)
|
|
if errors.Is(err, role.ErrRoleNotFound) {
|
|
return DirectoryEntry{}, role.ErrUnknownRole
|
|
}
|
|
if err != nil {
|
|
u.recordWriteFailure(ctx, event, err)
|
|
return DirectoryEntry{}, err
|
|
}
|
|
if held {
|
|
event.Params[audit.ParamFrom] = current.ID
|
|
event.Changes = audit.AppendTextChange(event.Changes, audit.FieldRole, current.Name, next.Name)
|
|
}
|
|
event.Params[audit.ParamTo] = next.ID
|
|
u.recorder.Record(ctx, event)
|
|
return u.entryOf(updated, next)
|
|
}
|
|
|
|
func (u *ChangeUserRoleUseCase) entryOf(account user.User, assigned role.Role) (DirectoryEntry, error) {
|
|
assigner, err := resolveRoleAssigner(u.users, account.RoleAssignedByID)
|
|
if err != nil {
|
|
return DirectoryEntry{}, err
|
|
}
|
|
ref := NewRoleRef(assigned)
|
|
return userEntry(account, &ref, assigner), nil
|
|
}
|
|
|
|
func (u *ChangeUserRoleUseCase) recordWriteFailure(ctx context.Context, event audit.Event, err error) {
|
|
for _, refusal := range roleChangeRefusals {
|
|
if errors.Is(err, refusal) {
|
|
return
|
|
}
|
|
}
|
|
recordFailure(ctx, u.recorder, event, err)
|
|
}
|