In our app, a tenant represents a farm. Each farm ...
# permit-saas
m
In our app, a tenant represents a farm. Each farm has a subscription level (e.g., Basic / Advanced) that determines available features, and permissions also depend on the user’s role. Within each farm, there can be multiple herds. Each herd has: 1. Its own subscription level (which may differ from the farm’s subscription) 2. A species type (e.g., dairy, beef, sheep, etc.), which can unlock features specific to that species The features available to a user should depend on four factors: • The user’s role • The farm (tenant) context • The currently selected herd’s subscription level • The herd’s species type How can we best model this scenario in Permit.io so that permissions and feature access are evaluated dynamically based on role, farm, selected herd, herd subscription, and herd species?
a
Hi @Maxime Leduc, I'm an AI assistant for Permit.io. I'll analyze available sources and get back to you shortly.
p
Hey, thanks for reaching out ! It seems our team is a little busy right now, a human will be with you within the next hour or so. In the meantime, can I help you ?
a
For your farm management scenario, you can model this hierarchical multi-tenant structure in Permit.io using a combination of tenants, resource instances, attributes, and relationships. Modeling Approach 1. Farms as Tenants Your farms should be modeled as tenants in Permit.io (1) . Each tenant represents an isolated environment with its own users and resources . You can add the farm's subscription level as a tenant attribute (2) :
Copy code
await permit.api.tenants.create({
  key: "stripeinc",
  name: "Stripe Inc",
  attributes: {
    allowed_locations: ["US", "CA"],
  },
}) ;
2. Herds as Resource Instances Model herds as resource instances that belong to specific farm tenants . Each herd can have attributes for subscription level and species type:
Copy code
await permit.api.resource_instances.create({
  key: "react",
  resource: "repository",
  tenant: "default",
  attributes: {
    private: "false",
    owner: "Company C",
  },
}) ;
3. User Role Assignments Assign users to roles within specific tenants (farms) (1) . When checking permissions, include the tenant context:
Copy code
const permitted = await permit.check(userKey, "create", {
  type: "document", // The resource name
  tenant: tenantKey, // The tenant key
}) ;
4. Hierarchical Permissions with ReBAC Use relationship-based access control to model the farm-to-herd hierarchy (3) . Create parent relationships between farms and herds, then define role derivations:
Copy code
await permit.api.relationshipTuples.create({
  subject: "folder:finance",
  relation: "parent",
  object: "file:2023_report",
}) ;
Role derivations can propagate permissions from farm level to herd level :
Copy code
await permit.api.resourceRoles.update("file", "editor", {
  granted_to: {
    users_with_role: [
      {
        linked_by_relation: "parent",
        on_resource: "folder",
        role: "editor",
      },
    ],
  },
}) ;
Dynamic Permission Evaluation When evaluating permissions, pass the relevant attributes in your check calls. The PDP will evaluate policies based on: • User's role in the farm (tenant) (1) • Herd's attributes (subscription level, species type) (2) • Farm's subscription level (tenant attributes) • Relationships between farms and herds (3) This approach allows you to create policies that dynamically evaluate access based on all four factors you mentioned, ensuring that features are properly gated based on the combination of user role, farm context, herd subscription, and species type.
To learn more, see the sources I considered: [[1] Multi-Tenant Authorization](https://docs.permit.io/concepts/multi-tenant-authorization) [[2] Sync Data into your App](https://docs.permit.io/overview/sync-applications-data) [[3] Modeling Google Drive Permissions with ReBAC](https://docs.permit.io/modeling/google-drive)
t
This actually sounds like a combination of ReBAC and ABAC.
a
Thanks for the feedback, glad I was helpful.
m
Thank you
t
If you break out of either the ReBAC and ABAC, chances are you'll need to add some custom rego
Currently in permit our UI will handle RBAC + ABAC, but ReBAC + ABAC still needs some rego for you to get the combinations to work.
m
Then the solution above is it the only solution possible for my problem
t
I believe so, because you're using attributes in ReBAC resources
m
Ok
What you mean by rego?
o
Rego is the langue for OpenPolicyAgent - the underlying policy engine we use in Permit by default. You can load more Rego via Git onto your Permit environments
Please see this demo app that combines ReBAC and ABAC via custom Rego https://docs.permit.io/modeling/rebac-GHC
m
@Jules Robichaud-Gagnon
👍 1
j
@Or Weis did anyone tried the demo? It asks to use
<https://ghc.up.railway.app/>
. but we cannot tweak the demo dashboard or login as one of the email without having the password. I may have missed some important part.
o
The hosted URL is just meant as an example, You're not supposed to use it directly, if you want to play with it you should fork its repository and spin it up on your own. That said, you don't have to run the demo to learn from its components e.g. the custom Rego code
👍 1
j
Since we do not need to manage who have access to each herd. ReBAC seems to be overkill. Correct me if I am wrong but we could achieve what we want to do at passing the herd plan’s list of features in the context of the
check
function. Ex:
Copy code
await permit.check(user, "create", "animal", {
  context: {
    // additional data to be passed to the policy evaluation
    herd_plan_features: herd.current_plan.features
  },
});
Then use ABAC normally using
herd_plan_features
in our rules. https://docs.permit.io/how-to/enforce-permissions/check/#using-the-context-object
The doc seems weird:
Copy code
await permit.check(user, "create", "document", {
  context: {
    // additional data to be passed to the policy evaluation
    totalDocuments: 100,
  },
});
I believe it should be:
Copy code
await permit.check(user, "create", "document", {
  // additional data to be passed to the policy evaluation
  totalDocuments: 100,
});
Unless is is intended there’s a
"context"
in a context https://pdp-api.permit.io/redoc#tag/Authorization-API/operation/is_allowed_bulk_allowed_bulk_post but it does not appear to require to have a context in context.
t
Hey @Jules Robichaud-Gagnon. I assume there is still hierarchy with ReBAC, no? Farm is parent of the herds?
j
in our database yes, but I believe it is not necessary in permit in our situation.
in our business case. a user have access to all the herds of a given farm if he has the role for the farm.
so it is why I believe ReBac is not necessary.
t
gotcha. yeah, makes sense.
so is it not possible to do the ABAC permission with the ootb
list
operators?
j
I am not sure of your question. I believe I can use ABAC rules in our business case, as long I pass in the
context
the features of the current herd’s subscription level. Is that right?
t
no. i am asking, are you sure you need custom rego? custom rego would work but we have those operators that can potentially do what you're looking to do without having to write custom rego
you have all these attribute types for the resource, in combination with the operators to build an abac policy.
j
I was sent in the direction of using ReBAC by your colleague with a custom rego. I am saying I could maybe avoid using ReBAC at all and just do it with ABAC and a context instead. so, no custom rego at all.
and it is the confirmation I am looking for.
t
if you aren't using custom rego, then the context won't matter. The context is any additional context required for custom rego you may use.
it does sound like you can use pure ABAC here without custom rego, which also means no
context
j
context requires to use custom rego? its not mentionned here: https://docs.permit.io/how-to/enforce-permissions/check
t
any context you may provide there is unknown to the generated rego
j
ok, maybe this could be clarified in the documentation.
💯 1
t
for your use case, you'll want to use attributes
resource.attributes or user.attributes
and then your ABAC rule can use any of the supported operators.
looks like it is mentioned here, but would be easy to also note in the blob above https://docs.permit.io/how-to/enforce-permissions/check/#using-the-context-object
j
ok, so I toss in the attributes in that dict
Copy code
const permitted = await permit.check(
  // the user object
  {
    // the user key
    key: "<mailto:john@permit.io|john@permit.io>",
    // just-in-time attributes on the user
    attributes: {
      location: "England",
      department: "Engineering",
    },
  },
  // the action the user is trying to do
  "create",
  // Resource
  {
    // the type of the resource (the resource key)
    type: "document",
    // just-in-time attributes on the resource
    attributes: {
      hasApproval: "true",
    },
    // the tenant the resource belong to
    tenant: "companyB",
  }
);
https://docs.permit.io/how-to/enforce-permissions/check#attributes
t
yessir
👌 1
and then you can use it in any of the ABAC rules on the policy
j
ok, thanks.
my pleasure 1
I am trying to figure out how to use the “is subset” is supposed to work. There is not example in the documentation and I get unexpected results. With 1 attribute and 1 value in the subset it works: ABAC rule:
Copy code
{
  "allOf": [
    {
      "allOf": [
        {
          "resource.herd_features": {
            "array_subset": [
              "feature_1"
            ]
          }
        }
      ]
    }
  ]
}
Copy code
"attributes": {
    "herd_features": [
        "feature_1"
    ]
}
equivalent python code
Copy code
>>> {"feature_1"}.issubset({"feature_1"})
True
With more than 2 attributes it does not and two values in the subset it does not work: ABAC rule:
Copy code
{
  "allOf": [
    {
      "allOf": [
        {
          "resource.herd_features": {
            "array_subset": [
              "feature_1",
              "feature_2"
            ]
          }
        }
      ]
    }
  ]
}
Copy code
"attributes": {
    "herd_features": [
        "feature_1",
        "feature_2"
    ]
}
equivalent python code:
Copy code
>>> {"feature_1", "feature_2"}.issubset({"feature_1", "feature_2"})
True
It gets even weirder if I just pass 1 of the value as attributes (with the two values in the subset): This returns `allow: true`:
Copy code
"attributes": {
    "herd_features": [
        "feature_1"
    ]
}
This returns `allow: false`:
Copy code
"attributes": {
    "herd_features": [
        "feature_2"
    ]
}
Which does not make any sense.
o
@Ask Inkeep
a
Thanks for tagging me @Or Weis. I'll look through relevant sources and get back to you shortly.
Looking at the
array_subset
operator behavior you're describing, let me clarify how it works based on the documentation. According to the operators documentation (1) ,
array_subset
checks if an array has a subset of values. The operator validates:
["a", "b"] has ["a", "b", "c"]
- meaning the attribute array should contain all the values specified in the condition. The semantics appear to be inverted from what you might expect. When you define:
Copy code
{
  "resource.herd_features": {
    "array_subset": [
      "feature_1",
      "feature_2"
    ]
  }
}
This checks if
["feature_1", "feature_2"]
is a subset of the
herd_features
attribute - meaning
herd_features
must contain at least those two features for the condition to pass. For your use case with herd features, you might want to consider using
array_contains
instead , which checks if an array contains a specific value:
["a", "b", "c"] has "a"
. If you need to check that the herd has certain features available, you could structure your ABAC rule to check each required feature individually using
array_contains
, or ensure your
herd_features
attribute includes all features the herd subscription provides, then use
array_subset
appropriately. The documentation shows that these operators are designed to work with the attribute types (Bool, Number, String, Array) , so make sure your attributes are properly typed when passing them in the check call.
To learn more, see the sources I considered: [[1] Operators](https://docs.permit.io/api/working-with-abac/operators)
Appreciate the feedback! Feel free to ask me again with additional guidance or context.
t
@Jules Robichaud-Gagnon does AI answer make sense? Is that in line with what you are looking for?
j
not at all
array_contains
is not what I need. I need to know if it has all the required features. This why the use of the set operations. Note: I may have to use “superset” if the comparison is inverted but it is not what it is about. I have unexpected results with basic set operations.
o
Yes, superset is a good option as well. I think the instructions here are pretty clear; the tool might not behave the way that you’d like, but it still works within its limitations. If you need more complex conditions you can always implant them with PBAC using Rego code.
j
@Or Weis with the ABAC rule
Copy code
{
  "allOf": [
    {
      "allOf": [
        {
          "resource.herd_features": {
            "array_subset": [
              "feature_1",
              "feature_2"
            ]
          }
        }
      ]
    }
  ]
}
This returns `allow: true`:
Copy code
"attributes": {
    "herd_features": [
        "feature_1"
    ]
}
This returns `allow: false`:
Copy code
"attributes": {
    "herd_features": [
        "feature_2"
    ]
}
This is not normal
o
I’ll ask our engineering to review your feedback here. At this point I’d point you to exploring the PBAC option so you can implement your logic in the way that you’d like
j
Is it possible there is a very long propagation/cache delay between a change in the dashboard and to effectively have the change taken in account when calling
/allowed
though PDP? (that can be up to an hour or even more)
o
Delays in our system shouldn't ever be this long. You can use the consistent updates feature, or the PDP callback feature to track the propagation of your updates. If you're talking about delays in your own system, you can potentially experss those as additional attributes
j
I am testing with postman directly to PDP, in a local instance on docker.
The weird behaviour I was having this morning between allow true/false disappeared when I tried again much later. But now I changed from subset to superset and it is like they have the exact same behaviour.
o
You should be able to see in the PDP logs of it's connected and if it's receiving updates. If you local networking issues it might explain these sideffects
j
in the log of PDP I get the changes instantly almost instantly. I would assume with one of “superset” or “subset” one would return
"allow": true
With these attributes:
Copy code
"attributes": {
            "herd_features": [
                "feature_1",
                "feature_2",
                "feature_3"
            ]
        }
and this superset
Copy code
{
  "allOf": [
    {
      "allOf": [
        {
          "resource.herd_features": {
            "array_superset": [
              "feature_1",
              "feature_2"
            ]
          }
        }
      ]
    }
  ]
}
or this subset
Copy code
{
  "allOf": [
    {
      "allOf": [
        {
          "resource.herd_features": {
            "array_subset": [
              "feature_1",
              "feature_2"
            ]
          }
        }
      ]
    }
  ]
}
In both case, it returns
"allow": false
and its not supposed to. I say “one of” because it is never clear to me which one in A or B for
A ⊇ B
(superset) or
A ⊆ B
(subset). Is
A
supposed to be the attributes or the values the policy?
o
I've queued this issue to review by engineering, in the meantime I suggest you try different avenues
👍 1
j
I also found an issue with the UI. When selecting any array attribute, the widget does not adapt automatically. It remains a textbox. We need to use the “code editor” to change it to an array. After that the UI will display properly as an array picker.