How to express "no matching predicates"?
Quite possibly there's a way to do what I want, but haven't been able to figure it out. I'm gating access to some object, based on data in that object. Normally the policy just checks for simple ownership:
Authority:
user(1234);
Authorizer:
check if user({content_owner});
// or equivalently, where the content_owner() fact is injected based on the object contents
content_owner({content_owner});
check if user($u), content_owner($u);
However, sometimes there's an extra field on the content (say, a group) and the user needs to also be a member of that if present. So you could write the authorizer like:
check if
user({content_owner}),
user_group({content_group});
I could dynamically add the check depending on the object properties, but if I'm going to have some random code doing that I might as well just check it directly; my hope is that the authorizer is static and the source of truth for the rules. There are a lot of different cases here and I was hoping to have a single Authorizer file for each.
So, I'm struggling to write policies that can selectively depend on certain facts. Conceptually what I want is something like:
check if no_fact_exists(object_group($oid, $_)) or object_group($oid, $gid), user_group($gid);
Or, some way to construct a set out of all the terms in a fact, where "no match" results in an empty set.
The best solution I've found so far is to make the injected fact carry a set, ab empty set indicates a null/missing value, and this fact is always added to the Authorizer e.g:
object_group(..., []); // object has no required groups
object_group(..., [1234]); // object does require groups
check if
object_group($oid, $groups), user_groups($user_groups),
$object_groups == $user_groups || $object_groups.intersect($user_groups).length() > 0;
Any thoughts?
about negative matching
currently, there is one way to assert the absence of a fact, the deny policy. It might work with your use-case, exploiting the fact that policies are lazily matched (contrary to checks).
An upcoming feature is something equivalent is reject if which extends this behaviour to checks and can thus be used in blocks as well as authorizers and don’t depend on ordering. The motivating use-case was to allow AWS-style policies, with the (explicit) Deny, (explicit) Allow, (implicit) Deny evaluation order.
for your case
I would generate different facts based on whether the object has a restriction on groups
// generated based on the object being accessed
object("owner"); // no group restriction
object("owner", ["groups"]); // group restrictions
// generated based on the user info
user("user id",["user groups"]);
and in the authorizer:
check if user($user, $user_groups), object($user)
or user($user, $user_groups), object($user, $object_groups), $object_groups.intersection($user_groups).length() > 0;
Thanks! That will work for me.
With the deny policy, my challenge is the reverse. e.g say I have several different kinds of actors in the system, where "users" can be in a group, but for another kind that's not possible. But maybe that other entity can pass a different allow check. So if I wrote:
deny if
object_group({oid}, $group_id),
user_groups($user_groups),
!$user_groups.contains($group_id);
Then if we didn't define an user_groups fact this would fail open. Of course we control the authority so could just require user_groups always exist (and/or assert check if user_groups($_); but it leaves me feeling a little wary of non-obvious failure cases.
yeah, relying on deny policies is a bit more risky indeed.
From what I understood of your case, putting group info in the same fact as the user id seems to be the simplest way to go
would reject if be a good solution now that it is available?