DDD如何正确分离权限和角色

问题描述 投票:1回答:1

假设我有一个允许用户创建,分配和编辑任务的应用程序。该应用程序旨在支持多个客户帐户。此应用程序具有各种角色,但我们将重点介绍三个角色以及与特定操作和资源(更新任务文本)相关的权限-管理员(具有更新所有帐户中所有任务的文本的权限),帐户管理员(具有更新属于其帐户的任何任务上的文本的权限,但它们可以属于多个帐户)和帐户用户(只有将任务分配给他们,才具有更新任务上的文本的权限)。

该示例有些人为,角色名称有些笼统,但请耐心等待。

这里的目标是尝试找到一种巧妙的方法来分隔角色和权限,但是似乎角色不可避免地与权限相关联(请参见下面的代码)。>>

也许权限应该只是task:updateText,但是如何检查角色?我是否将域模型中的switch (actor.type)块移至域服务中,并检查该用户是否与该特定帐户的管理员,帐户管理员或帐户用户相关联?可以缓存数据,但是帐户管理员(以及潜在的其他用户)可以与多个帐户关联,这意味着预加载此数据可能在上下文中需要太多数据,并且可能会在服务之间传递此数据时出现问题。

所有权/分配检查是域的一部分,因为它们取决于模型的当前状态。这里没有介绍,但是使用了一种简单的版本控制机制来确保模型在检索时间和应用更新之间不发生变化。看来策略至少可以使此逻辑更整洁,但是如果我要将逻辑移到策略中,则不确定如何继续保证,除非策略和服务方法有办法保证它们共享相同的版本资源。

我在这里有什么选择?任何指导将不胜感激。

class TaskApplicationService {
  constructor(private taskRepository: TaskRepository) { }
  async updateText({ taskId, text, accountId, context }: { taskId: string, text: string, accountId?: string, context: Context }) {
    let actor: Actor;
    const userId = context.user.id;
    // permissions follow pattern resource:action:qualifier
    if (await hasPermission('task:updateText:all')) {
      actor = await anAdmin({ userId });
    } else if (await hasPermission('task:updateText:account')) {
      actor = await anAccountAdmin({ accountId, userId });
    } else if (await hasPermission('task.updateText:assigned')) {
      actor = await anAccountUser({ accountId, userId });
    } else {
      throw new Error('not authorized');
    }

    const task = await this.taskRepository.findOne({ taskId });
    task.updateText({ text, actor });
    await this.taskRepository.save(task);
    // return TaskMapper.toDto(task);
  }
}

class TaskDomainModel {
  private props: {
    text: string,
    accountId: string,
    assignedAccountUserId: string;
  };

  get text(): string {
    return this.props.text;
  }

  updateText({ text, actor }: { text: string, actor: Actor }) {
    switch (actor.type) {
      case ActorType.ADMIN:
        break;
      case ActorType.ACCOUNT_ADMIN:
        assert(this.props.accountId === actor.tenantId);
        break;
      case ActorType.ACCOUNT_USER:
        assert(this.props.accountId === actor.tenantId);
        assert(this.props.assignedAccountUserId === actor.tenantUserId);
        break;
      default:
        // note assertions and throwing errors are here for brevity,
        // but normally would use something similar to this:
        // https://khalilstemmler.com/articles/enterprise-typescript-nodejs/handling-errors-result-class/
        throw new Error('unknown actor type');
    }

    this.props.text = text;
  }
}

// supporting cast

interface User {
  id: string;
}

interface Context {
  user: User;
}

enum ActorType {
  ADMIN,
  ACCOUNT_ADMIN,
  ACCOUNT_USER
}

interface Admin {
  type: ActorType.ADMIN,
  userId: string
}

interface AccountAdmin {
  type: ActorType.ACCOUNT_ADMIN,
  tenantId: string,
  userId: string
}

interface AccountUser {
  type: ActorType.ACCOUNT_USER,
  tenantUserId: string,
  tenantId: string,
  userId: string
}

async function anAdmin({ userId }: { userId: string }): Promise<Admin> {
  // gets an admin
}

async function anAccountAdmin({ accountId, userId }: { accountId: string, userId: string }): Promise<AccountAdmin> {
  // gets an account admin
}

async function anAccountUser({ accountId, userId }: { accountId: string, userId: string }): Promise<AccountUser> {
  // gets an account user
}

async function hasPermission(permission: string) {
  // checks permissions in cache or calls to external service
}

type Actor = Admin | AccountAdmin | AccountUser;

interface TaskRepository {
  findOne({ taskId }: { taskId: string }): Promise<TaskModel>;
  save(task: TaskModel): Promise<TaskModel>;
}

假设我有一个允许用户创建,分配和编辑任务的应用程序。该应用程序旨在支持多个客户帐户。该应用程序具有各种作用,但我们将重点关注...

typescript security permissions authorization domain-driven-design
1个回答
0
投票

我认为没有必要为此而将角色耦合到权限。相反,您可能具有包含如下通用权限列表的角色类。

© www.soinside.com 2019 - 2024. All rights reserved.