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
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