We created a small extensions for rbac policies

GitHub
GitHub - traveltechdeluxe/ash-rbac
Contribute to traveltechdeluxe/ash-rbac development by creating an account on GitHub.
104 Replies
natterstefan
natterstefanβ€’3y ago
Awesome! πŸš€
Blibs
Blibsβ€’3y ago
Does this generate, under the hood, a policies code block, meaning it still uses the policies mechanism from Ash? Will it always check for the roles field in a resource for its role or is there any way to configure that (for example, for my users, I normally have a roles and organization_roles fields that mean different things).
barnabasj
barnabasjOPβ€’3y ago
It is just generating normal policy and field_policy blocks. I was thinking of making the role getter function configurable, but as of now it expects an actor with a roles attribute We also combine this with normal policy blocks to add filter policies on resources that need them, as the extension is not handling that case at the moment. Tests are the best docs atm As relationships work a bit differently, e.g. not supported by field_policies. But you can specify that a user can only use an action if it is accessed from a relationship
Blibs
Blibsβ€’3y ago
Super cool man, this seems to have the potential to make my projects policies way simpler, amazing work πŸ‘
barnabasj
barnabasjOPβ€’3y ago
Thanks, field policies really brought everything together.
Blibs
Blibsβ€’3y ago
Would you be interested in a PR adding support for custom checkers? I was able to change the code to allow it like this:
rbac do
role :admin do
fields [:*]
actions [:create, :read]
end

role :user, checker: HasRole2 do
fields [:id, :child, :children, :number]
actions [:read]
end
end
rbac do
role :admin do
fields [:*]
actions [:create, :read]
end

role :user, checker: HasRole2 do
fields [:id, :child, :children, :number]
actions [:read]
end
end
I can't figure out how to make the bypass part take 2 arguments instead of one to allow something like this:
bypass :admin, checker: HasRole2
bypass :admin, checker: HasRole2
barnabasj
barnabasjOPβ€’3y ago
Sure, if you open the PR we can check what is needed for the bypass and stuff also not sure if changing the whole module is the right approach, as the semantics of the check shouldn't change. Would it be enough for you use case if you were able to pass a function that takes the actor and returns the roles?
Blibs
Blibsβ€’3y ago
Sure, I thought about passing a module just to try to make less verbose, but I guess something like this:
role :user, field: &get_role_field/1 do
role :user, field: &get_role_field/1 do
Is fine too. What would be a good key name for it? Or should it not even be a keyword and just the function directly like this:
role :user, &get_role_field/1 do
role :user, &get_role_field/1 do
barnabasj
barnabasjOPβ€’3y ago
could be just a function if it is an argument i think. Could you pleaseopen an issue on the repo. I think that is the better place for this discussion we probably also need a way to configure this globaly if the actor looks different in general, having to specify it on every resource would become tedious very quickly
Blibs
Blibsβ€’3y ago
So, another unrelated question about the lib (not sure if I should ask that here or in the repo since that is not a PR). Does AshRbac works alongside a normal policies block? For example, let's say I have this policies:
policies do
policy action(:read) do
forbid_if actor_attribute_equals(:confirmed?, false)
authorize_if always()
end

policy action(:read) do
authorize_if expr(:user in ^actor(:roles))
end
end
policies do
policy action(:read) do
forbid_if actor_attribute_equals(:confirmed?, false)
authorize_if always()
end

policy action(:read) do
authorize_if expr(:user in ^actor(:roles))
end
end
I was under the impression that this would be equivalent to this:
rbac do
role :user do
actions [:read]
end
end

policies do
policy action(:read) do
forbid_if actor_attribute_equals(:confirmed?, false)
authorize_if always()
end
end
rbac do
role :user do
actions [:read]
end
end

policies do
policy action(:read) do
forbid_if actor_attribute_equals(:confirmed?, false)
authorize_if always()
end
end
But from what I can tell from my testings the rbac rule is simply ignored. Is that not supported right now?
barnabasj
barnabasjOPβ€’3y ago
As of now it is creating policies like this
# this

rbac do
role :user do
actions [:read]
end
end

# ends up being this

policies do
policy action(:read), has_role(:user) do
authorize_if always()
end
end
# this

rbac do
role :user do
actions [:read]
end
end

# ends up being this

policies do
policy action(:read), has_role(:user) do
authorize_if always()
end
end
As of now I'm not completely sure what the best way is to make this composable. I think allowing custom conditions shouldn't be to hard and might solve this problem.
rbac do
role :user do
actions [:read]
end
end

policies do
policy action(:read) do
forbid_if actor_attribute_equals(:confirmed?, false)
authorize_if always()
end
end
rbac do
role :user do
actions [:read]
end
end

policies do
policy action(:read) do
forbid_if actor_attribute_equals(:confirmed?, false)
authorize_if always()
end
end
In your example your read check would forbid it if the user was not confirmed but otherwise would authrize it. the block created from rbac would not lead to a definitive answer, therefore the authorization from your policy would allow the query to go through The default for the rbac extension is to deny acccess and explicilty set conditions where an action is allowed. so to make it play well together you need to either add policies that deny certain stuff or use the same condition as the rbac role creates.
Blibs
Blibsβ€’3y ago
By custom conditions you mean add the conditions directly to rbac code block? Would that mean that we would be able to add custom conditions inside the role or actions blocks? I guess that would alleviate the problem, but seems like something that would create a lot of code duplication if I have to set the same rules for all roles (like my confirmed example since that applies to all roles). @Zach Daniel I'm not familiar with the policies code, but is there any technical issue that makes it hard to be composable (meaning that I can split my policies into multiple policies code blocks inside a resource)?
barnabasj
barnabasjOPβ€’3y ago
you can already add conditions to the actions like this:
rbac do
role :user do
actions [
{:read, accessing_from(OtherResource, :relationship)}
]
end
end
rbac do
role :user do
actions [
{:read, accessing_from(OtherResource, :relationship)}
]
end
end
It would be possible to also allow a third element in the tuple with custom checks. e.g.
rbac do
role :user do
actions [
{:read, accessing_from(OtherResource, :relationship), [forbid_if: [actor_attribute_equals(:confirmed?, false)]}
]
end
end
rbac do
role :user do
actions [
{:read, accessing_from(OtherResource, :relationship), [forbid_if: [actor_attribute_equals(:confirmed?, false)]}
]
end
end
which would result in
policies do
policy has_role(:user), action(:read), accesing_from(OtherResource, :relationship) do
forbid_if actor_attribute_equals(:confirmed?, false)
authorize_if always()
end
end
policies do
policy has_role(:user), action(:read), accesing_from(OtherResource, :relationship) do
forbid_if actor_attribute_equals(:confirmed?, false)
authorize_if always()
end
end
Maybe composable was not the right word. Policies follow certain rules, if you follow them you can put your policies into different blocks. If I'm not mistaken: all policy block with a matching conditions are executed. a block executes all checks until an answer is found (:authorized | :forbidden) or all checks are done (:unknown) afterwards the result of policy blocks is checked if any returned :forbidden the request is stopped if all are :unknown the request is also stopped if one of them :authorized and the rest is :unknown the request is processed the extension relies on the default being forbidden and explicitly authorizes if a condition matches. if you add another policies like your read policy, that policy essentially bypasses the extensions policies because their policy conditions are not matching. Now that I think about, you could possible already pass your actor_attribute_equals(:confirmed?, false} as condition.
rbac do
roles :user do
actions [
{:read, actor_attribute_equals(:confirmed?, :true)}
]
end
end
rbac do
roles :user do
actions [
{:read, actor_attribute_equals(:confirmed?, :true)}
]
end
end
That way the :user would only be allowed to execute the read actor is confirmed
policies do
policy has_role(:user), action(:read), actor_attribute_equals(:confirmed?, :true) do
authorize_if always()
end
end
policies do
policy has_role(:user), action(:read), actor_attribute_equals(:confirmed?, :true) do
authorize_if always()
end
end
I have to ask chatgpt to summarize this thread and create docs from it afterwards 🀣 In this case I think I probably only have to allow an array of conditions. I think this is more in line with allow list philosophy. That way you can define explicit conditions for when a user should be allowed to do something.
ZachDaniel
ZachDanielβ€’3y ago
Policies compose just fine, but it’s important to know the main things: policies apply in order (important to account for bypasses), and all policies that apply to a request must pass. (Except for bypasses) So the essentially question is where does rbac put the policies it adds to your resource. As long as it puts them at the end, then there should be no issue πŸ™‚ And all dsl entities from fragments also get put at the end, so if you have a fragment for policies you should put rbac and your policies in the same fragment.
barnabasj
barnabasjOPβ€’3y ago
It prepends bypasses and appends policies. But mixing allow-list policies together with deny-list style policies leads to unwanted results
ZachDaniel
ZachDanielβ€’3y ago
Hm…it shouldn’t Every policy has to pass always no matter what So why does that lead to unwanted results?
barnabasj
barnabasjOPβ€’3y ago
Ok, in the example above the policy with the forbid_if also has an authorize_if always in there. That one overrides all the other allow-list style policies. But it's only the authorize if in there that is the problem
ZachDaniel
ZachDanielβ€’3y ago
that should not override any other policies no policy will ever override another policy the only thing capable of doing that is a bypass higher up in the policy list
barnabasj
barnabasjOPβ€’3y ago
policies do
policy action(:read) do
forbid_if some_check
authorize_if always
end

policy has_role(:user), action(:read) do
authorize_if always()
end
end
policies do
policy action(:read) do
forbid_if some_check
authorize_if always
end

policy has_role(:user), action(:read) do
authorize_if always()
end
end
in this example the second one is never looked at if the user has not the role :user and the first policy doesn't care about the role and only forbids in case of some_check therefore the request is authorized no matter the role of the user
ZachDaniel
ZachDanielβ€’3y ago
if that's actually happening then it is a massive bug πŸ™‚ All policies that apply have to result in an authorized status
barnabasj
barnabasjOPβ€’3y ago
no the logic is sound the second policy just isn't applied because of the condition
ZachDaniel
ZachDanielβ€’3y ago
you said "therefore the request is authorized no matter the role of the user" that is not what those policies say
barnabasj
barnabasjOPβ€’3y ago
yes because the first policy returns :authorized and the second one does not apply
ZachDaniel
ZachDanielβ€’3y ago
if has_role(:user) is true, the second policy must pass oh well if its just authorize_if always() then yes, you're right sorry a policy authorize_if always() has no way of failing so is effectively a noop if it applies or not The reason its probably working when you only use the rbac extension is because we require that at least one policy applies
barnabasj
barnabasjOPβ€’3y ago
if no policy applies it is denied right
ZachDaniel
ZachDanielβ€’3y ago
Gotcha, so I think what your rbac extension needs to do is
policy action(:read) do
authorize_if has_role(:user)
end
policy action(:read) do
authorize_if has_role(:user)
end
barnabasj
barnabasjOPβ€’3y ago
I can not recall at the moment what it was, but I did this at first and had similar problems too.
ZachDaniel
ZachDanielβ€’3y ago
gotcha. Well in the short term the library should probably express that it can't actually be mixed w/ your own policies on any action w/ rbac applied currently
barnabasj
barnabasjOPβ€’3y ago
you can, but can only add other allow-list style policies but yeah, that should be in the docs
ZachDaniel
ZachDanielβ€’3y ago
it doesn't matter what kind of policies they are if any other policy applies, then they've gotten past the "at least one policy must apply" rule a policy that contains only authorize_if always() can never forbid a request So it actually only works if you have no other policies that apply
barnabasj
barnabasjOPβ€’3y ago
yes, but if your policy is explicitly allowing something than that is what you want and it is ok if it applies
ZachDaniel
ZachDanielβ€’3y ago
sorry, but I don't think so. Let me show you what I mean
policy action(:create) do
authorize_if actor_attribute_equals(:can_create, true)
forbid_if always()
end

rbac_policy_about_create
policy action(:create) do
authorize_if actor_attribute_equals(:can_create, true)
forbid_if always()
end

rbac_policy_about_create
if that first policy applies, it will override the rbac policy which means you can really easily accidentally poke holes in your authorization
barnabasj
barnabasjOPβ€’3y ago
ok, i got what you mean. What i was thinking about was something with a condition more specific than the one from the extension
ZachDaniel
ZachDanielβ€’3y ago
Yeah, if you are like narrowing it down then it works, but that's only kind of by chance
policy has_role(:foo) do
authorize_if actor_attribute_equals(:foo_is_active, true)
forbid_if always()
end
policy has_role(:foo) do
authorize_if actor_attribute_equals(:foo_is_active, true)
forbid_if always()
end
i.e if you only want users w/ a given role to be able to work against foo_is_active, you're actually making any rbac policies about that role ignored based on the rbac stuff, I think what you actually want is for the role to be the condition, and the allowed actions to be the checks i.e
policy has_role(:foo) do
authorize_if action(:read)
end
policy has_role(:foo) do
authorize_if action(:read)
end
barnabasj
barnabasjOPβ€’3y ago
how would I add an filter to this?
ZachDaniel
ZachDanielβ€’3y ago
what kind of filter?
barnabasj
barnabasjOPβ€’3y ago
expr(id == ^actor(:id)) for example
ZachDaniel
ZachDanielβ€’3y ago
is that something the rbac does?
barnabasj
barnabasjOPβ€’3y ago
not at the moment I just think filters were where I had my first problem and i started moving it over to conditions
ZachDaniel
ZachDanielβ€’3y ago
if you do
policy has_role(:foo) do
authorize_if action(:read)
end

policy [has_role(:foo), action(:read)] do
authorize_if expr(id == ^actor)
end
policy has_role(:foo) do
authorize_if action(:read)
end

policy [has_role(:foo), action(:read)] do
authorize_if expr(id == ^actor)
end
Those two policies would result in :foo role being able to call the read action, and automatically applying the expr(id == ^actor) filter. note: If building them in a transformer you may need to reference the built in Expr check module
barnabasj
barnabasjOPβ€’3y ago
but if you leave of the has_role(:foo) from the second policy you would ave the same problem as before, right?
ZachDaniel
ZachDanielβ€’3y ago
πŸ€” sorry, what exactly should the behavior be? should all users have the filter applied? or only has_role(:foo) users?
barnabasj
barnabasjOPβ€’3y ago
only has_role this also leads to narrowing
ZachDaniel
ZachDanielβ€’3y ago
I don't see how The first policy says "any user w/ has_role(:foo) can only call the read action"
barnabasj
barnabasjOPβ€’3y ago
policy has_role(:foo) do
authorize_if action(:read)
end

# narrower condition
policy [has_role(:foo), action(:read)] do
authorize_if expr(id == ^actor)
end
policy has_role(:foo) do
authorize_if action(:read)
end

# narrower condition
policy [has_role(:foo), action(:read)] do
authorize_if expr(id == ^actor)
end
ZachDaniel
ZachDanielβ€’3y ago
The second policy says "any user w/ `has_role(:foo), calling the read action, can only read themselves"
barnabasj
barnabasjOPβ€’3y ago
I'm just saying that there is no way around narrowing the condition
ZachDaniel
ZachDanielβ€’3y ago
Well, that's just because its the example you gave are you talking about the rbac extension adding this filter? or you as a user of the rbac extension adding the filter?
barnabasj
barnabasjOPβ€’3y ago
if the rbac extensio would do this
policy has_role(:foo) do
authorize_if action(:read)
end
policy has_role(:foo) do
authorize_if action(:read)
end
you as a user would still need to narrow the condition to add the filter
ZachDaniel
ZachDanielβ€’3y ago
Not necessarily if you as a user just did this:
policy action(:read) do
authorize_if expr(id == ^actor(:id))
end
policy action(:read) do
authorize_if expr(id == ^actor(:id))
end
then both policies would apply
barnabasj
barnabasjOPβ€’3y ago
only if the user has_role(:foo) right?
ZachDaniel
ZachDanielβ€’3y ago
Nope well the first one applies if the user has_role(:foo), yes the second one applies if action(:read) oh, lol I see what you're saying
barnabasj
barnabasjOPβ€’3y ago
the circle closes xD
ZachDaniel
ZachDanielβ€’3y ago
you're right it has to be
policy action(:read) do
authorize_if has_role(:foo)
end
policy action(:read) do
authorize_if has_role(:foo)
end
So what you'd need to do to do this right is group all roles for each action
barnabasj
barnabasjOPβ€’3y ago
but the filter would still authorize? or is filter == :unkown
ZachDaniel
ZachDanielβ€’3y ago
the filter authorizes, but applies the filter
barnabasj
barnabasjOPβ€’3y ago
yes but it would apply if no role matches also
ZachDaniel
ZachDanielβ€’3y ago
So what you'd do is, for each action, list all roles that can do it
barnabasj
barnabasjOPβ€’3y ago
and add forbid_if always at the end
ZachDaniel
ZachDanielβ€’3y ago
policy action(:read) do
authorize_if has_role(:foo)
authorize_if has_role(:bar)
end
policy action(:read) do
authorize_if has_role(:foo)
authorize_if has_role(:bar)
end
you don't technically need forbid_if always() at the end its implied
barnabasj
barnabasjOPβ€’3y ago
but the filter would still authorize if none of the roles match
ZachDaniel
ZachDanielβ€’3y ago
Nah, this time for real lol
# applied by rbac extension
policy action(:read) do
authorize_if has_role(:foo)
authorize_if has_role(:bar)
end

# added by user
policy action(:read) do
authorize_if expr(id == ^actor(:id))
end
# applied by rbac extension
policy action(:read) do
authorize_if has_role(:foo)
authorize_if has_role(:bar)
end

# added by user
policy action(:read) do
authorize_if expr(id == ^actor(:id))
end
if action(:read) then both apply this time so if you don't have an allowed role, the policy is forbidden and the filter will be applied
barnabasj
barnabasjOPβ€’3y ago
yes, but if the actor acutally has_role(:baz)
ZachDaniel
ZachDanielβ€’3y ago
then its forbidden
barnabasj
barnabasjOPβ€’3y ago
why?
ZachDaniel
ZachDanielβ€’3y ago
because the first policy applies (because action(:read)) and you don't have one of those roles
barnabasj
barnabasjOPβ€’3y ago
ok, now I think i understand where my problem in understanding was. I thought if there was no matching check it would result in :unknown and the other policies would either :authorize or :forbid and if all policies returned :unkown it would default to :forbidden
ZachDaniel
ZachDanielβ€’3y ago
nope, every policy that applies must always result in :authorized πŸ‘
barnabasj
barnabasjOPβ€’3y ago
Thanks for clearing things up for me. gotta fix this tomorrow
Myrmyr
Myrmyrβ€’3y ago
Did you do any performance tests? I've started using this and sadly found out that somehow it made app really unresponsive and pinned my CPU to 100% and while in normal conditions LiveView took max of 1 second to send a response, now it took 35 seconds. I've started :observer to check what's happening and this is what I've seen(screenshot attached) I've added
rbac do
role "public" do
fields([:*])
actions([:read])
end
end
rbac do
role "public" do
fields([:*])
actions([:read])
end
end
to like 6 resources, which are in a relationship. Sadly I don't have time to debug or test it further but will try if I will have some free-ish time.
No description
ZachDaniel
ZachDanielβ€’3y ago
πŸ€” Something seems strange there well, not "seems", something is very strange there. It probably isn't @barnabasj's fault, but something in core doing something strange. Can you show me the result of Ash.Policy.Info.policies(Resource) of the resource w/ those roles? @Myrmyr
barnabasj
barnabasjOPβ€’3y ago
I also felt, that since I started using field policies my tests were executing slower. But I haven't investigated yet.
ZachDaniel
ZachDanielβ€’3y ago
Interesting It might actually be the way they are modeled with conditions, now that I think about it but with a single one that shouldn't be enough to cause problems...
barnabasj
barnabasjOPβ€’3y ago
I just published 0.2.0 with the refactor
ZachDaniel
ZachDanielβ€’3y ago
oh, nice πŸ™‚ Maybe you can try that out @Myrmyr
barnabasj
barnabasjOPβ€’3y ago
there are only conditions in certain cases. But I didn't feel like it was faster afterwards. only if you use the condition for a relationship for example
ZachDaniel
ZachDanielβ€’3y ago
okay, so probably won't help then. How strange I mean, I don't see what would cause the issue from a policies perspective, since its basically just generating the policies you would have hand written before
barnabasj
barnabasjOPβ€’3y ago
have you benchmarked field_policies in general?
ZachDaniel
ZachDanielβ€’3y ago
um...no πŸ™‚ So maybe its actually just field policies
Myrmyr
Myrmyrβ€’3y ago
FYI I'm working on providing Ash.Policy.Info.policies but have to bleep some details so might take a while
ZachDaniel
ZachDanielβ€’3y ago
I'd bet it is here is a way to test this out @barnabasj can you make it so that [:*] doesn't add any field policies? or what does it do for :* currently?
Myrmyr
Myrmyrβ€’3y ago
I feel like this extension adding a lot of field policies, probably one for every field, is triggering this behavior
barnabasj
barnabasjOPβ€’3y ago
what i"m doing is going over every field and generate one policy each, I think I saw that you were doing that for :* somewhere anyway.. roles with the :* field are added to every policiy as authorize_if check.
ZachDaniel
ZachDanielβ€’3y ago
Well, what we do for :* is generate one field policy for all of those fields since you can do field_policy [:list, :of, :fields] even still, the idea that it could take 35 seconds is pretty wild
barnabasj
barnabasjOPβ€’3y ago
ok, let me try and combine those then.
ZachDaniel
ZachDanielβ€’3y ago
but it would be good to confirm in some way that the cause is field policies, and then I can benchmark them in core and figure out whats going on
Myrmyr
Myrmyrβ€’3y ago
Pastebin
[ %Ash.Policy.Policy{ condition: [{Checks.IsSiteAdminActor, [ac...
Pastebin.com is the number one paste tool since 2002. Pastebin is a website where you can store text online for a set period of time.
Pastebin
[ %Ash.Policy.FieldPolicy{ fields: [:organization_id], condi...
Pastebin.com is the number one paste tool since 2002. Pastebin is a website where you can store text online for a set period of time.
ZachDaniel
ZachDanielβ€’3y ago
you can also have multiple field policies for a given field, and those can have a condition. So what I might suggest is doing this:
for {role, fields} <- roles do
# you'd have to use transforms for this of course
field_policy fields, [has_role(role)] do
authorize_if always()
end
end
for {role, fields} <- roles do
# you'd have to use transforms for this of course
field_policy fields, [has_role(role)] do
authorize_if always()
end
end
barnabasj
barnabasjOPβ€’3y ago
12 field policies if I counted correctly
ZachDaniel
ZachDanielβ€’3y ago
no, wait
for {role, fields} <- roles do
# you'd have to use transforms for this of course
field_policy fields do
authorize_if has_role(role)
end
end
for {role, fields} <- roles do
# you'd have to use transforms for this of course
field_policy fields do
authorize_if has_role(role)
end
end
Myrmyr
Myrmyrβ€’3y ago
35 seconds happens only if I use this extension in like 7 resources that are in a relationship. If I only add this to the one I've printed policies for above it takes a bit longer(like 2-3 seconds).
ZachDaniel
ZachDanielβ€’3y ago
ah, okay. Even 2-3 seconds is pretty wildly long but that makes sense
barnabasj
barnabasjOPβ€’3y ago
I'll try and combine the field policies into less policies and see if that helps
ZachDaniel
ZachDanielβ€’3y ago
both of my examples above were wrong πŸ˜†
barnabasj
barnabasjOPβ€’3y ago
in what way? I'm currenly looking at my role check and see if it can be done in a more performant way. Right now the check is probably not the fastest and because it is executed a lot it might also be the problem. I mean I didn't see numbers as high as yours but the implementation is definetively more on the it's nice to read, then on the performance side of things
Operating System: Linux
CPU Information: AMD Ryzen 9 5950X 16-Core Processor
Number of Available Cores: 32
Available memory: 62.70 GB
Elixir 1.14.3
Erlang 25.2.1

Benchmark suite executing with the following configuration:
warmup: 2 s
time: 5 s
memory time: 0 ns
reduction time: 0 ns
parallel: 1
inputs: none specified
Estimated total run time: 28 s

Benchmarking role_check_atom ...
Benchmarking role_check_string ...
Benchmarking roles_check_atom ...
Benchmarking roles_check_string ...

Name ips average deviation median 99th %
roles_check_atom 2.80 M 356.52 ns Β±6899.27% 300 ns 610 ns
role_check_string 2.70 M 369.74 ns Β±9181.27% 310 ns 610 ns
role_check_atom 2.70 M 370.80 ns Β±8415.90% 310 ns 630 ns
roles_check_string 2.58 M 388.06 ns Β±7806.80% 330 ns 640 ns

Comparison:
roles_check_atom 2.80 M
role_check_string 2.70 M - 1.04x slower +13.22 ns
role_check_atom 2.70 M - 1.04x slower +14.27 ns
roles_check_string 2.58 M - 1.09x slower +31.54 ns
Operating System: Linux
CPU Information: AMD Ryzen 9 5950X 16-Core Processor
Number of Available Cores: 32
Available memory: 62.70 GB
Elixir 1.14.3
Erlang 25.2.1

Benchmark suite executing with the following configuration:
warmup: 2 s
time: 5 s
memory time: 0 ns
reduction time: 0 ns
parallel: 1
inputs: none specified
Estimated total run time: 28 s

Benchmarking role_check_atom ...
Benchmarking role_check_string ...
Benchmarking roles_check_atom ...
Benchmarking roles_check_string ...

Name ips average deviation median 99th %
roles_check_atom 2.80 M 356.52 ns Β±6899.27% 300 ns 610 ns
role_check_string 2.70 M 369.74 ns Β±9181.27% 310 ns 610 ns
role_check_atom 2.70 M 370.80 ns Β±8415.90% 310 ns 630 ns
roles_check_string 2.58 M 388.06 ns Β±7806.80% 330 ns 640 ns

Comparison:
roles_check_atom 2.80 M
role_check_string 2.70 M - 1.04x slower +13.22 ns
role_check_atom 2.70 M - 1.04x slower +14.27 ns
roles_check_string 2.58 M - 1.09x slower +31.54 ns
without changes:
alias AshRbacTest.{Api, ChildResource, RootResource, SharedResource}


root_resource = Api.create!(Ash.Changeset.for_create(RootResource, :create))
shared_resource = Api.create!(Ash.Changeset.for_create(SharedResource, :create))

child_resource =
Api.create!(Ash.Changeset.for_create(ChildResource, :create, %{root_id: root_resource.id}))

Benchee.run(%{
"read" => fn ->
RootResource
|> Ash.Query.select([:id])
|> Ash.Query.load([:child, :number, :children])
|> Api.read(actor: %{roles: [:user]})
end
})
alias AshRbacTest.{Api, ChildResource, RootResource, SharedResource}


root_resource = Api.create!(Ash.Changeset.for_create(RootResource, :create))
shared_resource = Api.create!(Ash.Changeset.for_create(SharedResource, :create))

child_resource =
Api.create!(Ash.Changeset.for_create(ChildResource, :create, %{root_id: root_resource.id}))

Benchee.run(%{
"read" => fn ->
RootResource
|> Ash.Query.select([:id])
|> Ash.Query.load([:child, :number, :children])
|> Api.read(actor: %{roles: [:user]})
end
})
this is the result:
Operating System: Linux
CPU Information: AMD Ryzen 9 5950X 16-Core Processor
Number of Available Cores: 32
Available memory: 62.70 GB
Elixir 1.14.3
Erlang 25.2.1

Benchmark suite executing with the following configuration:
warmup: 2 s
time: 5 s
memory time: 0 ns
reduction time: 0 ns
parallel: 1
inputs: none specified
Estimated total run time: 7 s

Benchmarking read ...

Name ips average deviation median 99th %
read 210.20 4.76 ms Β±6.22% 4.65 ms 5.63 ms
Operating System: Linux
CPU Information: AMD Ryzen 9 5950X 16-Core Processor
Number of Available Cores: 32
Available memory: 62.70 GB
Elixir 1.14.3
Erlang 25.2.1

Benchmark suite executing with the following configuration:
warmup: 2 s
time: 5 s
memory time: 0 ns
reduction time: 0 ns
parallel: 1
inputs: none specified
Estimated total run time: 7 s

Benchmarking read ...

Name ips average deviation median 99th %
read 210.20 4.76 ms Β±6.22% 4.65 ms 5.63 ms
Myrmyr
Myrmyrβ€’3y ago
If I find a bit of time, but sadly that probably will take a while, I will try to recreate this issue in a separate app.
barnabasj
barnabasjOPβ€’3y ago
Thanks, I'll try and optimize as best I can in the meantime.
ZachDaniel
ZachDanielβ€’3y ago
interesting So your tests show that its quite fast We may need to wait for more instances of this to show up, or a usable reproduction in that case 😦
barnabasj
barnabasjOPβ€’3y ago
I created a pr that should result in a lot less policies, maybe you can try it out and it helps already https://github.com/traveltechdeluxe/ash-rbac/pull/11
Blibs
Blibsβ€’3y ago
It took me some time, but I finally was able to reserve some time to finish my PR for adding support for other roles fields. there is the PR @barnabasj https://github.com/traveltechdeluxe/ash-rbac/pull/15 The only thing that I believe is missing there is adding support for bypass.
barnabasj
barnabasjOPβ€’3y ago
I just released a new version with your changes
Blibs
Blibsβ€’3y ago
Hey @barnabasj I noticed that when I use ash_rbac, id fields from relationships return ForbiddenField. For example, if resource A has a relationship with resource B, b_id attribute will be returned with ForbiddenField even if I use fields [:*]. Removing the rbac rules and using this one instead works fine:
field_policies do
field_policy :* do
authorize_if always()
end
end
field_policies do
field_policy :* do
authorize_if always()
end
end
Is this a bug that should be reported or an I just doing something wrong? Looking at the policy generated by rbac, seems like it filters all the :*_id attributes when generating the policy where field_policy :* doesn't
barnabasj
barnabasjOPβ€’3y ago
Sounds like a bug to me, I'll take a look today I publish a new version that I think should fix it, if not I have to dig a little deeper.
Blibs
Blibsβ€’3y ago
Yep, that fixed it, thanks a lot!

Did you find this page helpful?