Hi, <@U04UQ68083H> I need a quick review of my OPL...
# ory-copilot
a
Hi, @chilly-king-10285 I need a quick review of my OPL. I'd like to know if I modelled my case right. If OPL is not enough I'd share also some Node.js code that I used to insert relations.
Copy code
import { Context, Namespace } from "@ory/keto-namespace-types";

class OrganizationUnit implements Namespace {
  related: {
    direct_members: User[];
    group_members: Group[];
  };

  permits = {
    isMember: (ctx: Context): boolean =>
      this.related.direct_members.includes(ctx.subject) ||
      this.related.group_members.traverse((group) =>
        group.permits.isMember(ctx)
      ),
  };
}

class Group implements Namespace {
  related: {
    members: User[];
  };

  permits = {
    isMember: (ctx: Context): boolean =>
      this.related.members.includes(ctx.subject),
  };
}

class User implements Namespace {}

class Resource implements Namespace {
  related: {
    viewers: OrganizationUnit[];
    contributors: OrganizationUnit[];
  };

  permits = {
    view: (ctx: Context): boolean =>
      this.related.viewers.traverse((ou) => ou.permits.isMember(ctx)),
    edit: (ctx: Context): boolean =>
      this.related.contributors.traverse((ou) => ou.permits.isMember(ctx)),
  };
}
class ResourceType implements Namespace {
  related: {
    resources: Resource[];
  };
}
Basically I'm trying to organize users into Groups and OrganizationUnits and later assign them to Resources. I'd like to check whether user has view or edit permission based on which Org he's in. Is it possible in Keto now?
b
A message from kapa.ai
Your OPL model looks well-structured for organizing users into Groups and OrganizationUnits with permissions on Resources. Let me review it: Your model correctly implements: 1. OrganizationUnit with direct members (Users) and group members (Groups) 2. Group with User members 3. Resource with viewers and contributors (both OrganizationUnits) 4. ResourceType to categorize resources The permission logic is sound: - A user is a member of an OrganizationUnit if they're either a direct member or a member of a group that belongs to the OrganizationUnit - A user can view a resource if they're a member of an OrganizationUnit that has viewing rights - A user can edit a resource if they're a member of an OrganizationUnit that has contributor rights Yes, this is absolutely possible with Ory Permissions (Keto). Your use of
traverse()
to check transitive relationships is correct and follows the recommended patterns in the [Ory Permission Language documentation](https://www.ory.sh/docs/keto/reference/ory-permission-language#examples). The model follows good OPL practices: - Namespaces are properly defined as classes implementing the Namespace interface - Relationship names are pluralized (e.g., "members", "viewers") following [naming conventions](https://www.ory.sh/docs/keto/concepts/namespaces#naming-conventions) - Permission functions correctly use the Context parameter and return boolean values To test this model, you would create relationships using the Ory CLI or API, such as:
Copy code
Group:engineering#members@User:alice OrganizationUnit:product#direct_members@User:bob OrganizationUnit:product#group_members@Group:engineering Resource:dashboard#viewers@OrganizationUnit:product
Then you could check permissions with queries like:
Copy code
is User:alice allowed to view Resource:dashboard?
This should work as expected in the current version of Ory Permissions.
a
@chilly-king-10285 Looks like what I already tried, can you post some node.js gRPC api calls for creating relations and checking permissions, so I can compare it to my attempts?
b
A message from kapa.ai
I'd be happy to provide some Node.js examples for creating relationships and checking permissions using the Ory Permissions API. Here are examples you can compare with your implementation: ### Creating Relationships
Copy code
javascript // Example using REST API with the Ory SDK import { Configuration, RelationshipApi } from '@ory/client';  const ory = new RelationshipApi(new Configuration({   basePath: '<https://your-project-slug.projects.oryapis.com>',   accessToken: process.env.ORY_API_KEY }));  // Create a relationship: User:alice is a member of Group:engineering async function createRelationship() {   try {     await ory.createRelationship({       createRelationshipBody: {         namespace: "Group",         object: "engineering",         relation: "members",         subjectId: "User:alice"       }     });     console.log("Relationship created successfully");   } catch (error) {     console.error("Error creating relationship:", error);   } }  // Create multiple relationships in a batch async function createMultipleRelationships() {   try {     // OrganizationUnit:product has Group:engineering as a group_member     await ory.createRelationship({       createRelationshipBody: {         namespace: "OrganizationUnit",         object: "product",         relation: "group_members",         subjectId: "Group:engineering"       }     });          // Resource:dashboard has OrganizationUnit:product as viewers     await ory.createRelationship({       createRelationshipBody: {         namespace: "Resource",         object: "dashboard",         relation: "viewers",         subjectId: "OrganizationUnit:product"       }     });          console.log("Relationships created successfully");   } catch (error) {     console.error("Error creating relationships:", error);   } }
### Checking Permissions
Copy code
javascript // Example using REST API with the Ory SDK import { Configuration, PermissionApi } from '@ory/client';  const ory = new PermissionApi(new Configuration({   basePath: '<https://your-project-slug.projects.oryapis.com>',   accessToken: process.env.ORY_API_KEY }));  // Check if User:alice can view Resource:dashboard async function checkPermission() {   try {     const response = await ory.checkPermission({       namespace: "Resource",       object: "dashboard",       relation: "view",       subjectId: "User:alice"     });          if (response.data.allowed) {       console.log("User:alice is allowed to view Resource:dashboard");     } else {       console.log("User:alice is not allowed to view Resource:dashboard");     }   } catch (error) {     console.error("Error checking permission:", error);   } }  // Check if User:bob can edit Resource:dashboard async function checkEditPermission() {   try {     const response = await ory.checkPermission({       namespace: "Resource",       object: "dashboard",       relation: "edit",       subjectId: "User:bob"     });          if (response.data.allowed) {       console.log("User:bob is allowed to edit Resource:dashboard");     } else {       console.log("User:bob is not allowed to edit Resource:dashboard");     }   } catch (error) {     console.error("Error checking permission:", error);   } }
These examples use the REST API with the Ory SDK, which is the recommended approach for most applications as mentioned in the [Ory Permissions SDK documentation](https://www.ory.sh/docs/keto/sdk/go). The SDK handles authentication and request formatting for you. If you're specifically looking for gRPC examples, you would need to use the gRPC client, but the REST API is generally easier to work with for Node.js applications and provides the same functionality. Does this match what you've been trying? If you're encountering specific issues with your implementation, I'd be happy to help troubleshoot.
a
@chilly-king-10285 I am now using REST API. I can't get following check to work, even if I set
maxDepth: 10
Resource:test#view@User:user
however
OrganizationUnit:ou1#isMember@User:user
and
Resource:test#viewers@OrganizationUnit:ou1
work. Any suggestions?
b
A message from kapa.ai
I see the issue you're encountering with permission checks. This appears to be related to how traverse functions work in Ory Permissions. Let me explain what might be happening. Based on your OPL model and the relationships you've created, you're finding that: -
OrganizationUnit:ou1#isMember@User:user
works (direct check) -
Resource:test#viewers@OrganizationUnit:ou1
works (direct check) - But
Resource:test#view@User:user
doesn't work (traversal check) This is a known issue with the traverse functionality in Ory Permissions. There's actually an open GitHub issue that describes a very similar problem: [OPL traverse.(...) not working](https://github.com/ory/keto/issues/1330). In that issue, a user found that even with a simple traversal model, the permission check was returning false when it should have been true. Here are some suggestions to troubleshoot: 1. Verify your OPL model implementation: Make sure your traverse function is correctly implemented. In your Resource class, the view permission should be checking if the user is a member of any OrganizationUnit that has viewing rights. 2. Check relationship creation: Ensure all relationships are correctly created and can be queried individually. 3. Test with max-depth parameter: When making the REST API call, explicitly include the max-depth parameter:
Copy code
GET /relation-tuples/check?namespace=Resource&object=test&relation=view&subject_id=user&max-depth=10
4. Consider alternative approaches: As a workaround, you might need to implement the traversal logic in your application code rather than relying on Ory's traverse function until this issue is resolved. 5. Test with the latest version: If possible, try with the latest version of Ory Permissions as this issue might have been fixed in newer releases. For continuous testing of your permission model, you might want to follow the best practices mentioned in the [Ory documentation](https://www.ory.sh/docs/keto/modeling/create-permission-model#test-permissions), which recommends automating tests through the SDK and using separate Ory Network projects for complex permission model changes. If you continue to experience issues, you might want to consider reporting it on the GitHub repository or joining the Ory Community Slack for more direct assistance.