在 NestJS 防护中使用 CASL

Fer*_*c T 9 authorization nestjs casl

在这部分文档中,并未清楚地解释所有防护用法的用例:
NestJS 文档 - 基于声明的授权

CaslAbilityFactory 为这些用例实现:

  • 管理员可以管理(创建/读取/更新/删除)所有实体
  • 用户对所有内容拥有只读访问权限
  • 用户可以更新他们的文章(article.authorId === userId)
  • 无法删除已发布的文章 (article.isPublished === true)

并仅解释了最琐碎的用例:

用户对所有内容拥有只读访问权限

它通过以下控制器方法进行了演示:

@Get()
@UseGuards(PoliciesGuard)
@checkPolicies((ability: AppAbility) => ability.can(Action.Read, Article))
findAll() {
    return this.articlesService.findAll();
}
Run Code Online (Sandbox Code Playgroud)

但我应该如何注释一个方法来检查第三个或第四个用例:

已发布的文章无法删除:
(article.isPublished === true)

@Delete()
@UseGuards(PoliciesGuard)
@checkPolicies(?????????????????????????????)
delete(@Body() article: Article) {
    return this.articlesService.delete(article.id);
}
Run Code Online (Sandbox Code Playgroud)

有可能吗?对于此要求,@checkPolicies 中声明的 PoliciesGuard 或处理程序应该能够访问方法参数。

如何从守卫访问控制器方法参数?

当然,如果您直接从控制器方法调用ability.can(...),则有一个解决方案:

@Delete()
@UseGuards(SomeGuards but NOT PoliciesGuard)
delete(@Body() article: Article) {
    const ability = this.caslAbilityFactory.createForUser(<<user from request>>);
    if (!ability.can(Action.Delete, article)) {
       throw new UnauthorizedException();
    }
    return this.articlesService.delete(article.id);
}
Run Code Online (Sandbox Code Playgroud)

但这个解决方案不符合原来的声明模式。

his*_*ola 1

您可以在 PolicyGuard 中实现这一点。NestJS 文档中提到了这一点

你的政策守卫会是这样的

@Injectable()
export class PoliciesGuard extends RequestGuard implements CanActivate {
  public constructor(private reflector: Reflector, private caslAbilityFactory: CaslAbilityFactory) {
    super();
  }

  public async canActivate(context: ExecutionContext): Promise<boolean> {
    const policyHandlers = this.reflector.get<PolicyHandler[]>(CHECK_POLICIES_KEY, context.getHandler()) || [];
    const request = this.getRequest(context);
    const { user } = request;
    const ability = await this.caslAbilityFactory.createForUser(user?.userId);

    return policyHandlers.every(handler => this.execPolicyHandler(handler, ability, request));
  }

  private execPolicyHandler(handler: PolicyHandler, ability: AppAbility, request: Request) {
    if (typeof handler === 'function') {
      return handler(ability, request);
    }
    return handler.handle(ability, request);
  }
}
Run Code Online (Sandbox Code Playgroud)

那么 checkPolicy 将接受这个函数

export class ReadArticlePolicyHandler implements IPolicyHandler {
  handle(ability: AppAbility, request) {
    const { query } = request;
    const article = new Article();
    article.scope = query.scope;
    return ability.can(Action.Read, article) || ability.can(Action.Delete, article);
  }
}
Run Code Online (Sandbox Code Playgroud)