Casl: Rule with class and conditions doesn't work

Created on 2 Jan 2021  ·  11Comments  ·  Source: stalniy/casl

First at all, sorry if I forgot something.

Describe the bug
When I define rules using a class name in typescript, in that rules where there are some condition always return false.

To Reproduce

I have these classes User and Post

enum Role {
  ADMIN = 'admin',
  USER = 'user'
}

class User {
  id: number;
  login: string;
  role: Role;
}

class Post {
  title: string;
  text: string;
  authrorId: number;
}

I define the rules in a static class method called createForUser as follow:

enum Action {
  MANAGE = 'manage',
  CREATE = 'create',
  READ = 'read',
  UPDATE = 'update',
  DELETE = 'delete'
}

type Subjects = typeof Post | typeof User | Post | User | 'all';
type AppAbility = Ability<[Action, Subjects}>;

class CaslAbilityFactory {
  static createForUser(user: User) {
    cosnt { can, cannot, build } = new AbilityBuilder<AppAbility>(Ability as AbilityClass<AppAbility>);
    if (user.role === Role.ADMIN) {
      can(Action.MANAGE, 'all');
    } else {
      can(Action.READ, Post);
      cannot(Action.DELETE, Post);
    }
    can(Action.UPDATE, Post, { authorId: user.id });
    return build();
  }
}

When I check the ability of READ a post, with a user with Role USER, it works as expected. The problem is when I check to update a post as follow:

const user: User = new User();
user.id = 1;
user.login = 'user01';
user.role = Role.USER;

const post: Post = new Post();
post.title = "Dummy post";
post.text = "Something interesting";
post.authorId = user.id;

const ability = CaslAbilityFactory.createForUser(user);
console.log(ability.can(Action.READ, Post));  // ---> Prints true and expected true.
console.log(ability.can(Action.UPDATE, post));  // ---> Prints false, but expected true

But if we add 'Post' to the type Subjects and change Post for 'Post' as following:

type Subjects = typeof Post | typeof User | Post | User | 'Post' | 'all';

class CaslAbilityFactory {
  ...

  can(Action.UPDATE, 'Post', { authorId: user.id });
  ...
  return build();
}

console.log(ability.can(Action.READ, Post));  // ---> Prints true and expected true.
console.log(ability.can(Action.UPDATE, post));  // ---> Now prints true and expected true

I tried too of downgrade @casl/ability to version 4.1.6 and the first code (using Post in the rule specification) works as expected.

Interactive example
Here is a link to the interactive example in codesandobx. https://codesandbox.io/s/casl-bug-3txfo?file=/src/index.ts

CASL Version
@casl/ability - 5.1.1

Environment:
node - 12.18.4
typescript - 4.1.3

bug

All 11 comments

Hi,

thanks for the issue! It’s actually strange because there is a test for this behavior - https://github.com/stalniy/casl/blob/master/packages/casl-ability/spec/ability.spec.js#L99

but I’ll check your example a bit later

Ah I see. The test doesn’t take into account class instance check...

That is! When there is no "instance conditions" (don't know how name it exactly 😅) it works fine.

Ok. The documentation is a bit wrong on this subject. What you need to do is to provide a custom detectSubjectType:

class Factory {
static createForUser(user: User) {
  // all code except return
  return build({
    detectSubjecType: type => type!.constructor
  })
}
}

updated: changed example. No need to check “all”, it should be processed as subject type already

To be able to do it (without typescript errors) I had to add // @ts-ignore because detectSubjectType expects a string, but type.constructor returns a Function

static createForUser(user: User) {
  // all code except return
  return build({
    // @ts-ignore
    detectSubjecType: type => type!.constructor
  })
}

Good catch! Will fix it tomorrow! Thanks!

I also plan to add instrumentation to casl a bit later so it’s easier to understand what’s wrong

fixed in 5.1.2

Just only a comment. In the example that I said, I still have to add //@ts-ignore in the build({ ... }). Don't know if that is as expected or not. In any case, thanks for fixing it so quickly.

Ts issue should be fixed properly. The issue with constructor is that it’s not strongly typed in TS. That’s why it complains.

object.constructor is Function in ts. For details https://github.com/Microsoft/TypeScript/issues/3841 . So, either use type casting with as or @ts-ignore

Or strongly type your constructors

Was this page helpful?
0 / 5 - 0 ratings