<@U04UQ68083H> I want to translate a Keto permissi...
# ory-copilot
q
@chilly-king-10285 I want to translate a Keto permission like
Namespace:<uuid>#relation@(Namespace:<uuid>#relation)
into OPL. To be more specific, what I want to create is this permission in OPL:
User:<user_uuid>#view@(Group:<group_uuid>#members)
, i.e. members of
<group_uuid>
should be able to
view
the user
<user_uuid>
. Can you help me with this?
b
A message from kapa.ai
To translate the Keto permission
User:<user_uuid>#view@(Group:<group_uuid>#members)
into Ory Permission Language (OPL), we need to define the appropriate namespaces and relationships. Here's how you can represent this permission in OPL:
Copy code
typescript import { Namespace, SubjectSet, Context } from "@ory/keto-namespace-types"  class User implements Namespace {   related: {     viewers: (User | SubjectSet<Group, "members">)[]   }    permits = {     view: (ctx: Context): boolean =>       this.related.viewers.includes(ctx.subject)   } }  class Group implements Namespace {   related: {     members: User[]   } }
Let's break down this OPL code: 1. We define two namespaces:
User
and
Group
. 2. In the
User
namespace, we define a
viewers
relation that can include either individual `User`s or members of a
Group
(using
SubjectSet<Group, "members">
). 3. We define a
view
permission in the
User
namespace that checks if the subject (the one trying to view) is included in the
viewers
relation. 4. The
Group
namespace has a
members
relation that includes `User`s. To set up the specific permission you mentioned (
User:<user_uuid>#view@(Group:<group_uuid>#members)
), you would need to create the following relationship using the Ory CLI or API:
Copy code
User:<user_uuid>#viewers@Group:<group_uuid>#members
This relationship says that the members of
Group:<group_uuid>
are viewers of
User:<user_uuid>
. When a permission check is performed, Ory Permissions will use this OPL configuration to determine if a user has the
view
permission on the specified user object. For more information on creating permission models and using the Ory Permission Language, you can refer to the [Create a permission model](https://www.ory.sh/docs/keto/modeling/create-permission-model) guide in the Ory documentation.
q
@chilly-king-10285 How would I handle an authorization scenario like this: • Users have several attributes, with different permissions. • Users themselves should be authorized to see and edit some attributes, see and not edit some and not see some at all • Admin users should be able to see and edit all attributes • Users with a specific relation to a user should be able to see some and edit some of the attributes
b
A message from kapa.ai
To handle this complex authorization scenario using Ory Permissions, we can create a permission model using the Ory Permission Language (OPL). This model will define the relationships and permissions for users and their attributes. Here's an approach to implement this scenario:
Copy code
typescript
import { Namespace, Context } from "@ory/keto-namespace-types"

class User implements Namespace {
  related: {
    admins: User[]
    managers: User[]
  }

  permits = {
    viewAllAttributes: (ctx: Context): boolean =>
      this.related.admins.includes(ctx.subject),

    editAllAttributes: (ctx: Context): boolean =>
      this.related.admins.includes(ctx.subject),

    viewBasicAttributes: (ctx: Context): boolean =>
      ctx.subject.equals(this) || 
      this.related.managers.includes(ctx.subject) ||
      this.permits.viewAllAttributes(ctx),

    editBasicAttributes: (ctx: Context): boolean =>
      ctx.subject.equals(this) || 
      this.permits.editAllAttributes(ctx),

    viewSensitiveAttributes: (ctx: Context): boolean =>
      this.related.managers.includes(ctx.subject) ||
      this.permits.viewAllAttributes(ctx),

    editSensitiveAttributes: (ctx: Context): boolean =>
      this.permits.editAllAttributes(ctx)
  }
}

class Attribute implements Namespace {
  related: {
    owner: User[]
    type: AttributeType[]
  }

  permits = {
    view: (ctx: Context): boolean =>
      this.related.type.traverse((t) => t.permits.view(ctx)) &&
      this.related.owner.traverse((o) => 
        o.permits.viewBasicAttributes(ctx) || 
        o.permits.viewSensitiveAttributes(ctx) ||
        o.permits.viewAllAttributes(ctx)
      ),

    edit: (ctx: Context): boolean =>
      this.related.type.traverse((t) => t.permits.edit(ctx)) &&
      this.related.owner.traverse((o) => 
        o.permits.editBasicAttributes(ctx) || 
        o.permits.editSensitiveAttributes(ctx) ||
        o.permits.editAllAttributes(ctx)
      )
  }
}

class AttributeType implements Namespace {
  related: {
    viewableBy: (User | SubjectSet<User, "managers">)[]
    editableBy: (User | SubjectSet<User, "managers">)[]
  }

  permits = {
    view: (ctx: Context): boolean =>
      this.related.viewableBy.includes(ctx.subject),

    edit: (ctx: Context): boolean =>
      this.related.editableBy.includes(ctx.subject)
  }
}
This OPL model addresses the requirements as follows: 1. Users have several attributes with different permissions: - We define an
Attribute
namespace that represents user attributes. - Each
Attribute
is linked to its owner (User) and has a type (AttributeType). 2. Users can see and edit some attributes, see but not edit some, and not see some at all: - The
User
namespace has different permission functions (
viewBasicAttributes
,
editBasicAttributes
,
viewSensitiveAttributes
, etc.) to represent these varying levels of access. - The
Attribute
namespace checks these permissions when determining if a user can view or edit an attribute.
To handle this complex authorization scenario using Ory Permissions, we can create a permission model using the Ory Permission Language (OPL). This model will define the relationships and permissions for users and their attributes. Here's an approach to implement this scenario:
Copy code
typescript import { Namespace, Context } from "@ory/keto-namespace-types"  class User implements Namespace {   related: {     admins: User[]     managers: User[]   }    permits = {     viewAllAttributes: (ctx: Context): boolean =>       this.related.admins.includes(ctx.subject),      editAllAttributes: (ctx: Context): boolean =>       this.related.admins.includes(ctx.subject),      viewBasicAttributes: (ctx: Context): boolean =>       ctx.subject.equals(this) ||        this.related.managers.includes(ctx.subject) ||       this.permits.viewAllAttributes(ctx),      editBasicAttributes: (ctx: Context): boolean =>       ctx.subject.equals(this) ||        this.permits.editAllAttributes(ctx),      viewSensitiveAttributes: (ctx: Context): boolean =>       this.related.managers.includes(ctx.subject) ||       this.permits.viewAllAttributes(ctx),      editSensitiveAttributes: (ctx: Context): boolean =>       this.permits.editAllAttributes(ctx)   } }  class Attribute implements Namespace {   related: {     owner: User[]     type: AttributeType[]   }    permits = {     view: (ctx: Context): boolean =>       this.related.type.traverse((t) => t.permits.view(ctx)) &&       this.related.owner.traverse((o) =>          o.permits.viewBasicAttributes(ctx) ||          o.permits.viewSensitiveAttributes(ctx) ||         o.permits.viewAllAttributes(ctx)       ),      edit: (ctx: Context): boolean =>       this.related.type.traverse((t) => t.permits.edit(ctx)) &&       this.related.owner.traverse((o) =>          o.permits.editBasicAttributes(ctx) ||          o.permits.editSensitiveAttributes(ctx) ||         o.permits.editAllAttributes(ctx)       )   } }  class AttributeType implements Namespace {   related: {     viewableBy: (User | SubjectSet<User, "managers">)[]     editableBy: (User | SubjectSet<User, "managers">)[]   }    permits = {     view: (ctx: Context): boolean =>       this.related.viewableBy.includes(ctx.subject),      edit: (ctx: Context): boolean =>       this.related.editableBy.includes(ctx.subject)   } }
This OPL model addresses the requirements as follows: 1. Users have several attributes with different permissions: - We define an
Attribute
namespace that represents user attributes. - Each
Attribute
is linked to its owner (User) and has a type (AttributeType). 2. Users can see and edit some attributes, see but not edit some, and not see some at all: - The
User
namespace has different permission functions (
viewBasicAttributes
,
editBasicAttributes
,
viewSensitiveAttributes
, etc.) to represent these varying levels of access. - The
Attribute
namespace checks these permissions when determining if a user can view or edit an attribute. 3. Admin users can see and edit all attributes: - The
User
namespace has
viewAllAttributes
and
editAllAttributes
permissions that are granted to admins. 4. Users with a specific relation (managers) can see and edit some attributes: - We define a
managers
relation in the
User
namespace. - The
AttributeType
namespace allows for fine-grained control over which users or managers can view or edit specific types of attributes. To use this model, you would need to: 1. Create relationships to define admins and managers for each user. 2. Create relationships to define the owner and type for each attribute. 3. Create relationships to define which users or managers can view or edit each attribute type. When checking permissions, you would use the
view
and
edit
permissions on the
Attribute
namespace. This model provides a flexible framework for handling complex attribute-level permissions. You can further customize it based on your specific needs, such as adding more granular permission levels or additional relationships. For more information on creating and using permission models with Ory Permissions, refer to the [Create a permission model](https://www.ory.sh/docs/keto/modeling/create-permission-model) guide in the Ory documentation.
q
@chilly-king-10285 Could you expand on this?
To use this model, you would need to:
1. Create relationships to define admins and managers for each user.
2. Create relationships to define the owner and type for each attribute.
3. Create relationships to define which users or managers can view or edit each attribute type.
When checking permissions, you would use the
view
and
edit
permissions on the
Attribute
namespace.
b
A message from kapa.ai
Certainly! I'll expand on how to create these relationships and check permissions using Ory Permissions. This process involves creating relationship tuples and using the Ory CLI or API to manage and check permissions. 1. Creating relationships to define admins and managers for each user: To set up admin and manager relationships, you would create relationship tuples like this:
Copy code
User:<user_id>#admins@User:<admin_id> User:<user_id>#managers@User:<manager_id>
For example, to make User:123 an admin of User456
Copy code
User:456#admins@User:123
2. Creating relationships to define the owner and type for each attribute: For each attribute, you need to define its owner and type:
Copy code
Attribute:<attribute_id>#owner@User:<user_id> Attribute:<attribute_id>#type@AttributeType:<type_id>
For example, to set User:456 as the owner of Attribute:789 and define it as a "basic" attribute type:
Copy code
Attribute:789#owner@User:456 Attribute:789#type@AttributeType:basic
3. Creating relationships to define which users or managers can view or edit each attribute type: To define permissions for attribute types:
Copy code
AttributeType:<type_id>#viewableBy@User:<user_id> AttributeType:<type_id>#editableBy@User:<user_id> AttributeType:<type_id>#viewableBy@User:<user_id>#managers AttributeType:<type_id>#editableBy@User:<user_id>#managers
For example, to allow User:123 and managers of User:456 to view the "basic" attribute type:
Copy code
AttributeType:basic#viewableBy@User:123 AttributeType:basic#viewableBy@User:456#managers
You can create these relationships using the Ory CLI. Here's an example of how to create a relationship:
Copy code
ory create relationship 'User:456#admins@User:123'
When checking permissions, you would use the
view
and
edit
permissions on the
Attribute
namespace. This can be done using the Ory CLI or API. Here's an example using the CLI: To check if User:123 can view Attribute789
Copy code
ory check permission User:123 view Attribute:789
To check if User:123 can edit Attribute789
Copy code
ory check permission User:123 edit Attribute:789
These commands will return whether the permission is allowed or denied based on the relationships you've set up and the permission model you've defined. For more detailed information on creating relationships and checking permissions, you can refer to the [How to integrate permission checks to your application](https://www.ory.sh/docs/keto/guides/simple-access-check-guide) guide in the Ory documentation. Remember that this is a complex permission model, and you may need to adjust it based on your specific requirements. Always test your permission checks thoroughly to ensure they behave as expected.
q
@chilly-king-10285 1. Wouldn't this approach scale badly, for example considering maybe 1000 users with 10-20 attributes each? 2. What if I don't want to assign admins to every user, but rather have a group namespace where some users are members of the group admins, and members of that group get read-write permissions for all attributes?
b
A message from kapa.ai
You raise excellent points about scalability and simplifying admin permissions. Let's address each of your concerns: 1. Scalability with many users and attributes: You're right to be concerned about scalability. The approach described earlier could indeed face challenges with a large number of users and attributes. For better scalability, Ory Permissions (based on Ory Keto) implements Google's Zanzibar system, which is designed to handle large-scale permission management efficiently. However, to optimize for your scenario with 1000 users and 10-20 attributes each, we can refine our approach: a) Use group-based permissions instead of individual user assignments. b) Implement attribute categories instead of individual attribute permissions. c) Utilize Ory Permissions' built-in optimization and caching mechanisms. 2. Group-based admin permissions: Your suggestion to use a group for admins is an excellent way to simplify the permission model and improve scalability. Let's modify our OPL to incorporate this:
Copy code
typescript
import { Namespace, SubjectSet, Context } from "@ory/keto-namespace-types"

class User implements Namespace {
  related: {
    attributes: Attribute[]
  }
}

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

class Attribute implements Namespace {
  related: {
    owner: User[]
    category: AttributeCategory[]
  }

  permits = {
    view: (ctx: Context): boolean =>
      this.related.category.traverse((c) => c.permits.view(ctx)) ||
      this.related.owner.traverse((o) => o.equals(ctx.subject)) ||
      SubjectSet.from(Group, "members", "admins").includes(ctx.subject),

    edit: (ctx: Context): boolean =>
      this.related.category.traverse((c) => c.permits.edit(ctx)) ||
      this.related.owner.traverse((o) => o.equals(ctx.subject)) ||
      SubjectSet.from(Group, "members", "admins").includes(ctx.subject)
  }
}

class AttributeCategory implements Namespace {
  related: {
    viewers: (User | SubjectSet<Group, "members">)[]
    editors: (User | SubjectSet<Group, "members">)[]
  }

  permits = {
    view: (ctx: Context): boolean =>
      this.related.viewers.includes(ctx.subject),

    edit: (ctx: Context): boolean =>
      this.related.editors.includes(ctx.subject)
  }
}
In this revised model: 1. We've introduced a
Group
namespace to handle admin permissions. 2. The
Attribute
namespace now checks if the subject is a member of the "admins" group for both view and edit permissions. 3. We've replaced
AttributeType
with
AttributeCategory
to group similar attributes, reducing the number of permission checks. To set up this system: 1. Create an admin group:
Copy code
Group:admins#members@User:<admin_user_id>
2. Assign attributes to users:
Copy code
User:<user_id>#attributes@Attribute:<attribute_id>
3. Categorize attributes:
Copy code
Attribute:<attribute_id>#category@AttributeCategory:<category_id>
You raise excellent points about scalability and simplifying admin permissions. Let's address each of your concerns: 1. Scalability with many users and attributes: You're right to be concerned about scalability. The approach described earlier could indeed face challenges with a large number of users and attributes. For better scalability, Ory Permissions (based on Ory Keto) implements Google's Zanzibar system, which is designed to handle large-scale permission management efficiently. However, to optimize for your scenario with 1000 users and 10-20 attributes each, we can refine our approach: a) Use group-based permissions instead of individual user assignments. b) Implement attribute categories instead of individual attribute permissions. c) Utilize Ory Permissions' built-in optimization and caching mechanisms. 2. Group-based admin permissions: Your suggestion to use a group for admins is an excellent way to simplify the permission model and improve scalability. Let's modify our OPL to incorporate this:
Copy code
typescript import { Namespace, SubjectSet, Context } from "@ory/keto-namespace-types"  class User implements Namespace {   related: {     attributes: Attribute[]   } }  class Group implements Namespace {   related: {     members: User[]   } }  class Attribute implements Namespace {   related: {     owner: User[]     category: AttributeCategory[]   }    permits = {     view: (ctx: Context): boolean =>       this.related.category.traverse((c) => c.permits.view(ctx)) ||       this.related.owner.traverse((o) => o.equals(ctx.subject)) ||       SubjectSet.from(Group, "members", "admins").includes(ctx.subject),      edit: (ctx: Context): boolean =>       this.related.category.traverse((c) => c.permits.edit(ctx)) ||       this.related.owner.traverse((o) => o.equals(ctx.subject)) ||       SubjectSet.from(Group, "members", "admins").includes(ctx.subject)   } }  class AttributeCategory implements Namespace {   related: {     viewers: (User | SubjectSet<Group, "members">)[]     editors: (User | SubjectSet<Group, "members">)[]   }    permits = {     view: (ctx: Context): boolean =>       this.related.viewers.includes(ctx.subject),      edit: (ctx: Context): boolean =>       this.related.editors.includes(ctx.subject)   } }
In this revised model: 1. We've introduced a
Group
namespace to handle admin permissions. 2. The
Attribute
namespace now checks if the subject is a member of the "admins" group for both view and edit permissions. 3. We've replaced
AttributeType
with
AttributeCategory
to group similar attributes, reducing the number of permission checks. To set up this system: 1. Create an admin group:
Copy code
Group:admins#members@User:<admin_user_id>
2. Assign attributes to users:
Copy code
User:<user_id>#attributes@Attribute:<attribute_id>
3. Categorize attributes:
Copy code
Attribute:<attribute_id>#category@AttributeCategory:<category_id>
4. Set up category permissions:
Copy code
AttributeCategory:<category_id>#viewers@Group:<group_id>#members    AttributeCategory:<category_id>#editors@Group:<group_id>#members
This approach scales better because: - It reduces the number of individual relationships by using groups and categories. - It simplifies admin management by using a single admin group. - It leverages Ory Permissions' built-in optimizations for handling large-scale permission checks. For more information on creating scalable permission models with Ory Permissions, you can refer to the [Create a permission model](https://www.ory.sh/docs/keto/modeling/create-permission-model) guide in the Ory documentation. Remember, while this model is more scalable, you should still monitor performance as your system grows and adjust as needed. Ory Permissions is designed to handle large-scale scenarios, but proper modeling and optimization are key to achieving the best performance.
q
@chilly-king-10285 How would I implement a simple role-based access system in Ory Keto? Imagine for example a system with admins, teachers, students and parents.
b
A message from kapa.ai
To implement a simple role-based access system in Ory Keto for a scenario with admins, teachers, students, and parents, we can use the Ory Permission Language (OPL) to define our permission model. Here's how you can set it up:
Copy code
typescript import { Namespace, Context } from "@ory/keto-namespace-types"  class User implements Namespace {   related: {     roles: Role[]   } }  class Role implements Namespace {   related: {     members: User[]   }    permits = {     access: (ctx: Context): boolean =>       this.related.members.includes(ctx.subject)   } }  class Resource implements Namespace {   related: {     viewers: Role[]     editors: Role[]   }    permits = {     view: (ctx: Context): boolean =>       this.related.viewers.traverse((role) => role.permits.access(ctx)) ||       this.permits.edit(ctx),      edit: (ctx: Context): boolean =>       this.related.editors.traverse((role) => role.permits.access(ctx))   } }
This OPL model defines three namespaces:
User
,
Role
, and
Resource
. Here's how to use this model to implement your role-based access system: 1. Create roles for admins, teachers, students, and parents:
Copy code
Role:admin Role:teacher Role:student Role:parent
2. Assign users to roles:
Copy code
Role:admin#members@User:<admin_id> Role:teacher#members@User:<teacher_id> Role:student#members@User:<student_id> Role:parent#members@User:<parent_id>
3. Define resources and their permissions:
Copy code
Resource:<resource_id>#viewers@Role:student Resource:<resource_id>#viewers@Role:parent Resource:<resource_id>#editors@Role:teacher Resource:<resource_id>#editors@Role:admin
You can create these relationships using the Ory CLI. For example:
Copy code
ory create relationship 'Role:admin#members@User:123' ory create relationship 'Resource:456#editors@Role:admin'
To check permissions, you would use the
view
and
edit
permissions on the
Resource
namespace. For example:
Copy code
ory check permission User:123 view Resource:456 ory check permission User:123 edit Resource:456
This model allows for flexible role-based access control: - Admins have full access (view and edit) to all resources. - Teachers can view and edit resources. - Students and parents can view resources but not edit them. - You can easily add or remove users from roles, or change resource permissions for different roles. For more detailed information on implementing role-based access control with Ory Keto, you can refer to the [Role Based Access Control (RBAC)](https://www.ory.sh/docs/keto/guides/rbac) guide in the Ory documentation. Remember that this is a basic implementation. Depending on your specific requirements, you might need to add more granular permissions or additional relationships between users and resources. [Role Based Access Control (RBAC)](https://www.ory.sh/docs/keto/guides/rbac)
q
@chilly-king-10285 1. What is meant by this?
Copy code
Resource:<resource_id>#viewers@​Role:student
Resource:<resource_id>#viewers@​Role:parent
Resource:<resource_id>#editors@​Role:teacher
Resource:<resource_id>#editors@​Role:admin
2. Now imagine that one of the resources to be accessed is information about users - for example a certain student might have UUID 9b0ec9fa-f661-4371-b36e-26736f2604fc and given_name Greger. This information should be accessible to the student's parents, but not any parent.
b
A message from kapa.ai
To answer your questions: 1. The lines you've provided are examples of relationship tuples in Ory Keto. Let's break down what they mean:
Copy code
Resource:<resource_id>#viewers@Role:student
This means that users with the "student" role have viewing permission for the resource identified by
<resource_id>
.
Copy code
Resource:<resource_id>#viewers@Role:parent
This grants viewing permission to users with the "parent" role for the same resource.
Copy code
Resource:<resource_id>#editors@Role:teacher
This gives editing permission to users with the "teacher" role.
Copy code
Resource:<resource_id>#editors@Role:admin
This grants editing permission to users with the "admin" role. These relationships define which roles have what type of access (viewing or editing) to a specific resource. 2. For the scenario where you want to restrict access to a student's information to only their parents, we need to modify our permission model. The previous model was too broad, allowing all parents to view all student resources. Here's how we can refine it:
Copy code
typescript import { Namespace, Context } from "@ory/keto-namespace-types"  class User implements Namespace {   related: {     roles: Role[]     parents: User[]   }    permits = {     viewInfo: (ctx: Context): boolean =>       ctx.subject.equals(this) ||        this.related.parents.includes(ctx.subject) ||       Role.admin.permits.access(ctx)   } }  class Role implements Namespace {   related: {     members: User[]   }    permits = {     access: (ctx: Context): boolean =>       this.related.members.includes(ctx.subject)   } }
In this refined model: - We've added a
parents
relation to the
User
namespace. - We've defined a
viewInfo
permission directly on the
User
namespace. To set up the relationships for a student and their parents:
Copy code
User:9b0ec9fa-f661-4371-b36e-26736f2604fc#parents@User:<parent1_id> User:9b0ec9fa-f661-4371-b36e-26736f2604fc#parents@User:<parent2_id>
To check if a user can view Greger's information:
Copy code
ory check permission User:<user_id> viewInfo User:9b0ec9fa-f661-4371-b36e-26736f2604fc
This setup ensures that: - The student can view their own information. - Only the specific parents of the student can view the student's information. - Admins can view all student information. - Other parents cannot view this student's information. This approach provides more granular control over access to student information, ensuring that only the appropriate parents can view a specific student's details. For more information on creating and managing relationships in Ory Keto, you can refer to the [Relationships](https://www.ory.sh/docs/keto/concepts/relation-tuples) concept in the Ory documentation. [Relationships](https://www.ory.sh/docs/keto/concepts/relation-tuples)
q
@chilly-king-10285 1. Could you provide an example of how these relationships would be created using the HTTP API? 2. These two lines in your OPL file doesn't seem syntactically valid?
Copy code
ctx.subject.equals(this) ||
      Role.admin.permits.access(ctx)
b
A message from kapa.ai
You're right to ask about these points. Let me address them: 1. To create relationships using the HTTP API, you would use the
/admin/relation-tuples
endpoint. Here's an example of how to create the relationships for a student and their parents using HTTP requests:
Copy code
http PUT /admin/relation-tuples Content-Type: application/json  {   "namespace": "User",   "object": "9b0ec9fa-f661-4371-b36e-26736f2604fc",   "relation": "parents",   "subject_id": "<parent1_id>" }
And for the second parent:
Copy code
http PUT /admin/relation-tuples Content-Type: application/json  {   "namespace": "User",   "object": "9b0ec9fa-f661-4371-b36e-26736f2604fc",   "relation": "parents",   "subject_id": "<parent2_id>" }
These HTTP requests would create the relationships we discussed earlier. For more details on using the REST API, you can refer to the [Ory Keto API Reference](https://www.ory.sh/docs/keto/reference/rest-api). 2. You're absolutely correct, and I apologize for the confusion. The OPL syntax I provided earlier is indeed not valid. Thank you for pointing this out. Let me provide a corrected version of the
User
namespace:
Copy code
typescript class User implements Namespace {   related: {     roles: Role[]     parents: User[]   }    permits = {     viewInfo: (ctx: Context): boolean =>       this.equals(ctx.subject) ||        this.related.parents.includes(ctx.subject) ||       this.related.roles.traverse((role) => role.name === "admin")   } }
In this corrected version: - We use
this.equals(ctx.subject)
to check if the subject is the user themselves. - We check if the subject is included in the user's parents. - We traverse the user's roles to check if they have an admin role. This syntax is more in line with the Ory Permission Language specification. For more details on the correct OPL syntax, you can refer to the [Ory Permission Language specification](https://www.ory.sh/docs/keto/reference/ory-permission-language) in the Ory documentation. Thank you for your attention to detail. It's important to have syntactically correct OPL to ensure proper functioning of the permission system. [Ory Keto API Reference](https://www.ory.sh/docs/keto/reference/rest-api) [Ory Permission Language specification](https://www.ory.sh/docs/keto/reference/ory-permission-language)
q
@chilly-king-10285 Is it possible to have subject sets as the objects of a relation tuple? For example, say that I want to define the relation that
members
of the
Group
with uuid
13872e72-06bb-48b7-988d-d842adb18522
can
view
members
of the
Group
with uuid
ebbd52bc-fc62-4336-a051-0d76a1467ac0
.
b
A message from kapa.ai
Yes, it is possible to have subject sets as the objects of a relation tuple in Ory Keto. This concept is known as "subject set rewriting" and is a powerful feature of the system. However, the specific syntax you're asking about isn't directly supported. Let me explain how you can achieve what you want: In Ory Keto, you can't directly use a subject set as the object of a relation tuple. Instead, you would model this relationship using multiple tuples and leverage the permission model in your Ory Permission Language (OPL) configuration. Here's how you could model this relationship: 1. First, define your groups:
Copy code
Group:13872e72-06bb-48b7-988d-d842adb18522 Group:ebbd52bc-fc62-4336-a051-0d76a1467ac0
2. Then, create a relationship that says "Group A can view Group B":
Copy code
Group:ebbd52bc-fc62-4336-a051-0d76a1467ac0#viewers@Group:13872e72-06bb-48b7-988d-d842adb18522
3. In your OPL, you would define the permission model like this:
Copy code
typescript class Group implements Namespace {   related: {     members: User[]     viewers: Group[]   }    permits = {     view: (ctx: Context): boolean =>       this.related.viewers.traverse((group) => group.related.members.includes(ctx.subject))   } }  class User implements Namespace {}
This OPL configuration says that a user can view a group if they are a member of any group that is in the
viewers
relation of the target group. 4. To create these relationships using the HTTP API, you would make the following calls: For the group viewing permission:
Copy code
http PUT /admin/relation-tuples Content-Type: application/json  {   "namespace": "Group",   "object": "ebbd52bc-fc62-4336-a051-0d76a1467ac0",   "relation": "viewers",   "subject_id": "Group:13872e72-06bb-48b7-988d-d842adb18522" }
For adding members to groups:
Copy code
http PUT /admin/relation-tuples Content-Type: application/json  {   "namespace": "Group",   "object": "13872e72-06bb-48b7-988d-d842adb18522",   "relation": "members",   "subject_id": "<user_id>" }
With this setup, any member of the group
13872e72-06bb-48b7-988d-d842adb18522
will be able to view the members of the group
ebbd52bc-fc62-4336-a051-0d76a1467ac0
. This approach allows you to effectively model the relationship you described, where members of one group can view members of another group, using Ory Keto's relation tuple system and OPL. For more information on subject sets and complex permission modeling in Ory Keto, you can refer to the [Subjects](https://www.ory.sh/docs/keto/concepts/subjects) concept documentation. [Subjects](https://www.ory.sh/docs/keto/concepts/subjects)
q
@chilly-king-10285 How can I extend this permission model to more resources? Let's say I have a
Resource
with uuid
23a43c32-3f5b-4df7-a8d7-50147f4f1811
that the
User
with uuid
890bd6d1-fde0-40c2-aece-a0521e4199fe
should be permitted to
view
and
edit
and that members of the group
13872e72-06bb-48b7-988d-d842adb18522
should be able to
view
.
b
A message from kapa.ai
To extend the permission model to include more resources with specific permissions for users and groups, we can modify our Ory Permission Language (OPL) configuration and add the necessary relationships. Here's how you can achieve this: 1. First, let's update our OPL to include a
Resource
namespace:
Copy code
typescript
import { Namespace, SubjectSet, Context } from "@ory/keto-namespace-types"

class User implements Namespace {}

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

class Resource implements Namespace {
  related: {
    viewers: (User | SubjectSet<Group, "members">)[]
    editors: User[]
  }

  permits = {
    view: (ctx: Context): boolean =>
      this.related.viewers.includes(ctx.subject) ||
      this.permits.edit(ctx),

    edit: (ctx: Context): boolean =>
      this.related.editors.includes(ctx.subject)
  }
}
2. Now, let's create the necessary relationships using the HTTP API. We'll need to make several API calls: a. Give the user permission to view and edit the resource:
Copy code
http
PUT /admin/relation-tuples
Content-Type: application/json

{
  "namespace": "Resource",
  "object": "23a43c32-3f5b-4df7-a8d7-50147f4f1811",
  "relation": "viewers",
  "subject_id": "User:890bd6d1-fde0-40c2-aece-a0521e4199fe"
}
Copy code
http
PUT /admin/relation-tuples
Content-Type: application/json

{
  "namespace": "Resource",
  "object": "23a43c32-3f5b-4df7-a8d7-50147f4f1811",
  "relation": "editors",
  "subject_id": "User:890bd6d1-fde0-40c2-aece-a0521e4199fe"
}
b. Give the group members permission to view the resource:
Copy code
http
PUT /admin/relation-tuples
Content-Type: application/json

{
  "namespace": "Resource",
  "object": "23a43c32-3f5b-4df7-a8d7-50147f4f1811",
  "relation": "viewers",
  "subject_set": {
    "namespace": "Group",
    "object": "13872e72-06bb-48b7-988d-d842adb18522",
    "relation": "members"
  }
}
3. To check permissions, you would use the Ory Keto API. For example, to check if a user can view the resource:
Copy code
http
POST /relation-tuples/check
Content-Type: application/json

{
  "namespace": "Resource",
  "object": "23a43c32-3f5b-4df7-a8d7-50147f4f1811",
  "relation": "view",
  "subject_id": "User:890bd6d1-fde0-40c2-aece-a0521e4199fe"
}
This model allows you to: - Give individual users specific permissions (view and/or edit) on resources. - Give groups of users permissions on resources. - Easily extend to more resources by creating new relationship tuples. You can add more resources by creating similar relationship tuples for each resource, specifying the appropriate permissions for users and groups. For more detailed information on creating and checking permissions, you can refer to the [How to integrate permission checks to your application](https://www.ory.sh/docs/keto/guides/simple-access-check-guide) guide in the Ory documentation. [How to integrate permission checks to your application](https://www.ory.sh/docs/keto/guides/simple-access-check-guide)
To extend the permission model to include more resources with specific permissions for users and groups, we can modify our Ory Permission Language (OPL) configuration and add the necessary relationships. Here's how you can achieve this: 1. First, let's update our OPL to include a
Resource
namespace:
Copy code
typescript import { Namespace, SubjectSet, Context } from "@ory/keto-namespace-types"  class User implements Namespace {}  class Group implements Namespace {   related: {     members: User[]   } }  class Resource implements Namespace {   related: {     viewers: (User | SubjectSet<Group, "members">)[]     editors: User[]   }    permits = {     view: (ctx: Context): boolean =>       this.related.viewers.includes(ctx.subject) ||       this.permits.edit(ctx),      edit: (ctx: Context): boolean =>       this.related.editors.includes(ctx.subject)   } }
2. Now, let's create the necessary relationships using the HTTP API. We'll need to make several API calls: a. Give the user permission to view and edit the resource:
Copy code
http PUT /admin/relation-tuples Content-Type: application/json  {   "namespace": "Resource",   "object": "23a43c32-3f5b-4df7-a8d7-50147f4f1811",   "relation": "viewers",   "subject_id": "User:890bd6d1-fde0-40c2-aece-a0521e4199fe" }
Copy code
http PUT /admin/relation-tuples Content-Type: application/json  {   "namespace": "Resource",   "object": "23a43c32-3f5b-4df7-a8d7-50147f4f1811",   "relation": "editors",   "subject_id": "User:890bd6d1-fde0-40c2-aece-a0521e4199fe" }
b. Give the group members permission to view the resource:
Copy code
http PUT /admin/relation-tuples Content-Type: application/json  {   "namespace": "Resource",   "object": "23a43c32-3f5b-4df7-a8d7-50147f4f1811",   "relation": "viewers",   "subject_set": {     "namespace": "Group",     "object": "13872e72-06bb-48b7-988d-d842adb18522",     "relation": "members"   } }
3. To check permissions, you would use the Ory Keto API. For example, to check if a user can view the resource:
Copy code
http POST /relation-tuples/check Content-Type: application/json  {   "namespace": "Resource",   "object": "23a43c32-3f5b-4df7-a8d7-50147f4f1811",   "relation": "view",   "subject_id": "User:890bd6d1-fde0-40c2-aece-a0521e4199fe" }
This model allows you to: - Give individual users specific permissions (view and/or edit) on resources. - Give groups of users permissions on resources. - Easily extend to more resources by creating new relationship tuples. You can add more resources by creating similar relationship tuples for each resource, specifying the appropriate permissions for users and groups. For more detailed information on creating and checking permissions, you can refer to the [How to integrate permission checks to your application](https://www.ory.sh/docs/keto/guides/simple-access-check-guide) guide in the Ory documentation. [How to integrate permission checks to your application](https://www.ory.sh/docs/keto/guides/simple-access-check-guide) Remember to always test your permission checks thoroughly to ensure they behave as expected in your specific use case.
q
@chilly-king-10285 Okay, so this is my current OPL file:
Copy code
import { Namespace, SubjectSet, Context } from "@ory/permission-namespace-types"


class KetoUser implements Namespace { }

class KetoGroup implements Namespace {
  related: {
    members: KetoUser[]
    viewers: KetoGroup[]
  }

  permits = {
    view: (ctx: Context): boolean =>
      this.related.viewers.traverse((group) => group.related.members.includes(ctx.subject))
  }
}

class Resource implements Namespace {
  related: {
    viewers: (KetoUser | SubjectSet<KetoGroup, "members">)[]
    editors: KetoUser[]
  }

  permits = {
    view: (ctx: Context): boolean =>
      this.related.viewers.includes(ctx.subject) ||
      this.permits.edit(ctx),

    edit: (ctx: Context): boolean =>
      this.related.editors.includes(ctx.subject)
  }
}
Now, say that a
Resource
object might have multiple attributes with differing conditions for `view`/`edit`-permissions. For example, it might have an attribute
uuid
with laxer
view
permissions and stricter
edit
permissions than its other attribute
name
.
b
A message from kapa.ai
To handle a scenario where a
Resource
object has multiple attributes with different
view
and
edit
permissions, we need to modify our OPL to allow for more granular control. Here's how we can adjust the model to accommodate this requirement:
Copy code
typescript
import { Namespace, SubjectSet, Context } from "@ory/permission-namespace-types"

class KetoUser implements Namespace { }

class KetoGroup implements Namespace {
  related: {
    members: KetoUser[]
    viewers: KetoGroup[]
  }

  permits = {
    view: (ctx: Context): boolean =>
      this.related.viewers.traverse((group) => group.related.members.includes(ctx.subject))
  }
}

class ResourceAttribute implements Namespace {
  related: {
    resource: Resource[]
    viewers: (KetoUser | SubjectSet<KetoGroup, "members">)[]
    editors: KetoUser[]
  }

  permits = {
    view: (ctx: Context): boolean =>
      this.related.viewers.includes(ctx.subject) ||
      this.permits.edit(ctx) ||
      this.related.resource.traverse((r) => r.permits.view(ctx)),

    edit: (ctx: Context): boolean =>
      this.related.editors.includes(ctx.subject) ||
      this.related.resource.traverse((r) => r.permits.edit(ctx))
  }
}

class Resource implements Namespace {
  related: {
    viewers: (KetoUser | SubjectSet<KetoGroup, "members">)[]
    editors: KetoUser[]
    attributes: ResourceAttribute[]
  }

  permits = {
    view: (ctx: Context): boolean =>
      this.related.viewers.includes(ctx.subject) ||
      this.permits.edit(ctx),

    edit: (ctx: Context): boolean =>
      this.related.editors.includes(ctx.subject)
  }
}
In this updated OPL: 1. We've introduced a new
ResourceAttribute
namespace to represent individual attributes of a
Resource
. 2. The
ResourceAttribute
has its own
viewers
and
editors
relations, allowing for attribute-specific permissions. 3. The
Resource
namespace now has an
attributes
relation to link to its `ResourceAttribute`s. 4. The permission checks in
ResourceAttribute
cascade up to the
Resource
level, allowing for both attribute-specific and resource-wide permissions. To use this model: 1. Create a
Resource
object:
Copy code
http
PUT /admin/relation-tuples
Content-Type: application/json

{
  "namespace": "Resource",
  "object": "<resource_id>",
  "relation": "viewers",
  "subject_id": "KetoUser:<user_id>"
}
2. Create
ResourceAttribute
objects for
uuid
and `name`:
Copy code
http
PUT /admin/relation-tuples
Content-Type: application/json

{
  "namespace": "ResourceAttribute",
  "object": "<uuid_attribute_id>",
  "relation": "resource",
  "subject_id": "Resource:<resource_id>"
}
Copy code
http
PUT /admin/relation-tuples
Content-Type: application/json

{
  "namespace": "ResourceAttribute",
  "object": "<name_attribute_id>",
  "relation": "resource",
  "subject_id": "Resource:<resource_id>"
}
3. Set specific permissions for each attribute: For
uuid
with laxer view permissions: ```http PUT /admin/relation-tuples Content-Type: application/json
To handle a scenario where a
Resource
object has multiple attributes with different
view
and
edit
permissions, we need to modify our OPL to allow for more granular control. Here's how we can adjust the model to accommodate this requirement:
Copy code
typescript import { Namespace, SubjectSet, Context } from "@ory/permission-namespace-types"  class KetoUser implements Namespace { }  class KetoGroup implements Namespace {   related: {     members: KetoUser[]     viewers: KetoGroup[]   }    permits = {     view: (ctx: Context): boolean =>       this.related.viewers.traverse((group) => group.related.members.includes(ctx.subject))   } }  class ResourceAttribute implements Namespace {   related: {     resource: Resource[]     viewers: (KetoUser | SubjectSet<KetoGroup, "members">)[]     editors: KetoUser[]   }    permits = {     view: (ctx: Context): boolean =>       this.related.viewers.includes(ctx.subject) ||       this.permits.edit(ctx) ||       this.related.resource.traverse((r) => r.permits.view(ctx)),      edit: (ctx: Context): boolean =>       this.related.editors.includes(ctx.subject) ||       this.related.resource.traverse((r) => r.permits.edit(ctx))   } }  class Resource implements Namespace {   related: {     viewers: (KetoUser | SubjectSet<KetoGroup, "members">)[]     editors: KetoUser[]     attributes: ResourceAttribute[]   }    permits = {     view: (ctx: Context): boolean =>       this.related.viewers.includes(ctx.subject) ||       this.permits.edit(ctx),      edit: (ctx: Context): boolean =>       this.related.editors.includes(ctx.subject)   } }
In this updated OPL: 1. We've introduced a new
ResourceAttribute
namespace to represent individual attributes of a
Resource
. 2. The
ResourceAttribute
has its own
viewers
and
editors
relations, allowing for attribute-specific permissions. 3. The
Resource
namespace now has an
attributes
relation to link to its `ResourceAttribute`s. 4. The permission checks in
ResourceAttribute
cascade up to the
Resource
level, allowing for both attribute-specific and resource-wide permissions. To use this model: 1. Create a
Resource
object:
Copy code
http PUT /admin/relation-tuples Content-Type: application/json  {   "namespace": "Resource",   "object": "<resource_id>",   "relation": "viewers",   "subject_id": "KetoUser:<user_id>" }
2. Create
ResourceAttribute
objects for
uuid
and `name`:
Copy code
http PUT /admin/relation-tuples Content-Type: application/json  {   "namespace": "ResourceAttribute",   "object": "<uuid_attribute_id>",   "relation": "resource",   "subject_id": "Resource:<resource_id>" }
Copy code
http PUT /admin/relation-tuples Content-Type: application/json  {   "namespace": "ResourceAttribute",   "object": "<name_attribute_id>",   "relation": "resource",   "subject_id": "Resource:<resource_id>" }
3. Set specific permissions for each attribute: For
uuid
with laxer view permissions:
Copy code
http PUT /admin/relation-tuples Content-Type: application/json  {   "namespace": "ResourceAttribute",   "object": "<uuid_attribute_id>",   "relation": "viewers",   "subject_set": {     "namespace": "KetoGroup",     "object": "<group_id>",     "relation": "members"   } }
For
name
with stricter edit permissions:
Copy code
http PUT /admin/relation-tuples Content-Type: application/json  {   "namespace": "ResourceAttribute",   "object": "<name_attribute_id>",   "relation": "editors",   "subject_id": "KetoUser:<admin_user_id>" }
This model allows you to set different permissions for different attributes of a resource, while still maintaining the ability to set resource-wide permissions. The permission checks will consider both the attribute-specific permissions and the overall resource permissions. For more information on creating complex permission models, you can refer to the [Create a permission model](https://www.ory.sh/docs/keto/modeling/create-permission-model) guide in the Ory documentation. [Create a permission model](https://www.ory.sh/docs/keto/modeling/create-permission-model) Remember to thoroughly test your permission checks to ensure they behave as expected in your specific use case.
q
@chilly-king-10285 In this model, what wins out, the permissions set on the Resource directly or the permissions set on the ResourceAttribute?
b
A message from kapa.ai
In the model we've discussed, both the permissions set on the Resource directly and the permissions set on the ResourceAttribute are considered, but the ResourceAttribute permissions take precedence for that specific attribute. Let's break this down: 1. For viewing a ResourceAttribute: The
view
permission in the ResourceAttribute namespace checks: - If the subject is in the attribute's viewers list - If the subject has edit permission on the attribute - If the subject has view permission on the parent Resource This means that if a user has view permission on the Resource but not on the specific ResourceAttribute, they would still be able to view the attribute. However, if stricter permissions are set on the ResourceAttribute, those would be enforced. 2. For editing a ResourceAttribute: The
edit
permission in the ResourceAttribute namespace checks: - If the subject is in the attribute's editors list - If the subject has edit permission on the parent Resource This means that having edit permission on the Resource grants edit access to all attributes, unless specifically restricted at the attribute level. In essence, the model implements a "most permissive" approach where: - ResourceAttribute permissions can grant additional access beyond what's set on the Resource. - ResourceAttribute permissions can restrict access that would otherwise be granted by the Resource permissions. This allows for fine-grained control at the attribute level while still maintaining the ability to set broader permissions at the Resource level. For example, if a user has edit permission on the Resource but is explicitly excluded from editing a specific ResourceAttribute, they would not be able to edit that attribute despite having general edit permissions on the Resource. It's important to note that this behavior is defined by the OPL we've written, specifically in the
permits
functions of the ResourceAttribute namespace. You can adjust these functions if you want to change how permissions are prioritized or combined. For more detailed information on how to fine-tune permission models in Ory Keto, you can refer to the [Create a permission model](https://www.ory.sh/docs/keto/modeling/create-permission-model) guide in the Ory documentation.
q
@chilly-king-10285 How would this be done?
ResourceAttribute permissions can restrict access that would otherwise be granted by the Resource permissions.
b
A message from kapa.ai
To implement a system where ResourceAttribute permissions can restrict access that would otherwise be granted by the Resource permissions, we need to modify our Ory Permission Language (OPL) model. Here's how we can achieve this:
Copy code
typescript
import { Namespace, SubjectSet, Context } from "@ory/permission-namespace-types"

class KetoUser implements Namespace { }

class KetoGroup implements Namespace {
  related: {
    members: KetoUser[]
  }
}

class ResourceAttribute implements Namespace {
  related: {
    resource: Resource[]
    viewers: (KetoUser | SubjectSet<KetoGroup, "members">)[]
    editors: KetoUser[]
    restricted_viewers: KetoUser[]
    restricted_editors: KetoUser[]
  }

  permits = {
    view: (ctx: Context): boolean =>
      !this.related.restricted_viewers.includes(ctx.subject) &&
      (this.related.viewers.includes(ctx.subject) ||
       this.permits.edit(ctx) ||
       this.related.resource.traverse((r) => r.permits.view(ctx))),

    edit: (ctx: Context): boolean =>
      !this.related.restricted_editors.includes(ctx.subject) &&
      (this.related.editors.includes(ctx.subject) ||
       this.related.resource.traverse((r) => r.permits.edit(ctx)))
  }
}

class Resource implements Namespace {
  related: {
    viewers: (KetoUser | SubjectSet<KetoGroup, "members">)[]
    editors: KetoUser[]
    attributes: ResourceAttribute[]
  }

  permits = {
    view: (ctx: Context): boolean =>
      this.related.viewers.includes(ctx.subject) ||
      this.permits.edit(ctx),

    edit: (ctx: Context): boolean =>
      this.related.editors.includes(ctx.subject)
  }
}
In this updated model: 1. We've added
restricted_viewers
and
restricted_editors
relations to the
ResourceAttribute
namespace. 2. The
permits
functions in
ResourceAttribute
now first check if the subject is in the restricted list before checking for permissions. To implement this restriction, you would create relationship tuples using the Ory Keto API. Here's an example of how to restrict a user from viewing a specific attribute:
Copy code
http
PUT /admin/relation-tuples
Content-Type: application/json

{
  "namespace": "ResourceAttribute",
  "object": "<attribute_id>",
  "relation": "restricted_viewers",
  "subject_id": "KetoUser:<user_id>"
}
This setup ensures that even if a user has view permissions on the Resource level, they won't be able to view a specific attribute if they're in its
restricted_viewers
list. The same principle applies to edit permissions with the
restricted_editors
relation. This approach allows you to: 1. Set broad permissions at the Resource level. 2. Refine permissions for specific attributes. 3. Explicitly deny access to certain attributes for specific users, overriding broader permissions.
To implement a system where ResourceAttribute permissions can restrict access that would otherwise be granted by the Resource permissions, we need to modify our Ory Permission Language (OPL) model. Here's how we can achieve this:
Copy code
typescript import { Namespace, SubjectSet, Context } from "@ory/permission-namespace-types"  class KetoUser implements Namespace { }  class KetoGroup implements Namespace {   related: {     members: KetoUser[]   } }  class ResourceAttribute implements Namespace {   related: {     resource: Resource[]     viewers: (KetoUser | SubjectSet<KetoGroup, "members">)[]     editors: KetoUser[]     restricted_viewers: KetoUser[]     restricted_editors: KetoUser[]   }    permits = {     view: (ctx: Context): boolean =>       !this.related.restricted_viewers.includes(ctx.subject) &&       (this.related.viewers.includes(ctx.subject) ||        this.permits.edit(ctx) ||        this.related.resource.traverse((r) => r.permits.view(ctx))),      edit: (ctx: Context): boolean =>       !this.related.restricted_editors.includes(ctx.subject) &&       (this.related.editors.includes(ctx.subject) ||        this.related.resource.traverse((r) => r.permits.edit(ctx)))   } }  class Resource implements Namespace {   related: {     viewers: (KetoUser | SubjectSet<KetoGroup, "members">)[]     editors: KetoUser[]     attributes: ResourceAttribute[]   }    permits = {     view: (ctx: Context): boolean =>       this.related.viewers.includes(ctx.subject) ||       this.permits.edit(ctx),      edit: (ctx: Context): boolean =>       this.related.editors.includes(ctx.subject)   } }
In this updated model: 1. We've added
restricted_viewers
and
restricted_editors
relations to the
ResourceAttribute
namespace. 2. The
permits
functions in
ResourceAttribute
now first check if the subject is in the restricted list before checking for permissions. To implement this restriction, you would create relationship tuples using the Ory Keto API. Here's an example of how to restrict a user from viewing a specific attribute:
Copy code
http PUT /admin/relation-tuples Content-Type: application/json  {   "namespace": "ResourceAttribute",   "object": "<attribute_id>",   "relation": "restricted_viewers",   "subject_id": "KetoUser:<user_id>" }
This setup ensures that even if a user has view permissions on the Resource level, they won't be able to view a specific attribute if they're in its
restricted_viewers
list. The same principle applies to edit permissions with the
restricted_editors
relation. This approach allows you to: 1. Set broad permissions at the Resource level. 2. Refine permissions for specific attributes. 3. Explicitly deny access to certain attributes for specific users, overriding broader permissions. Remember to thoroughly test your permission checks to ensure they behave as expected in your specific use case. For more information on creating complex permission models in Ory Keto, you can refer to the [Create a permission model](https://www.ory.sh/docs/keto/modeling/create-permission-model) guide in the Ory documentation. [Create a permission model](https://www.ory.sh/docs/keto/modeling/create-permission-model)
q
@chilly-king-10285 Given this OPL model:
Copy code
typescript
import { Namespace, SubjectSet, Context } from "@ory/permission-namespace-types"

class KetoUser implements Namespace { }

class KetoGroup implements Namespace {
  related: {
    members: KetoUser[]
    viewers: KetoGroup[]
  }

  permits = {
    view: (ctx: Context): boolean =>
      this.related.viewers.traverse((group) => group.related.members.includes(ctx.subject))
  }
}

class ResourceAttribute implements Namespace {
  related: {
    resource: Resource[]
    viewers: (KetoUser | SubjectSet<KetoGroup, "members">)[]
    editors: KetoUser[]
  }

  permits = {
    view: (ctx: Context): boolean =>
      this.related.viewers.includes(ctx.subject) ||
      this.permits.edit(ctx) ||
      this.related.resource.traverse((r) => r.permits.view(ctx)),

    edit: (ctx: Context): boolean =>
      this.related.editors.includes(ctx.subject) ||
      this.related.resource.traverse((r) => r.permits.edit(ctx))
  }
}

class Resource implements Namespace {
  related: {
    viewers: (KetoUser | SubjectSet<KetoGroup, "members">)[]
    editors: KetoUser[]
    attributes: ResourceAttribute[]
  }

  permits = {
    view: (ctx: Context): boolean =>
      this.related.viewers.includes(ctx.subject) ||
      this.permits.edit(ctx),

    edit: (ctx: Context): boolean =>
      this.related.editors.includes(ctx.subject)
  }
}
When creating a
ResourceAttribute
I would create a relation
resource
in its namespace - would I also need to create the inverse relation
attributes
in the
Resource
namespace?
b
A message from kapa.ai
Based on the Ory Permission Language (OPL) model you've provided, you don't need to explicitly create the inverse relation
attributes
in the
Resource
namespace when creating a
ResourceAttribute
. Here's why: 1. In Ory Keto, relationships are unidirectional by default. The system doesn't automatically create or manage bidirectional relationships. 2. The
Resource
namespace has an
attributes
relation defined in its
related
section, but this is used in the OPL to define the structure and permissions, not to create actual relationship tuples. 3. When you create a
ResourceAttribute
and set its
resource
relation, that's sufficient for the permission checks to work as defined in your OPL. To create a
ResourceAttribute
and associate it with a
Resource
, you would only need to create a relationship tuple like this:
Copy code
http PUT /admin/relation-tuples Content-Type: application/json  {   "namespace": "ResourceAttribute",   "object": "<attribute_id>",   "relation": "resource",   "subject_id": "Resource:<resource_id>" }
This relationship is enough for the permission checks to traverse from the
ResourceAttribute
to its associated
Resource
when needed, as defined in your
permits
functions. However, it's important to note that if you need to query from the
Resource
to find its
ResourceAttributes
, you might face some limitations with this unidirectional approach. If such queries are important for your application logic (outside of permission checks), you might want to consider creating the inverse relationship as well, even though it's not strictly necessary for the permission model to function. For more information on relationship management in Ory Keto, you can refer to the [Relationships](https://www.ory.sh/docs/keto/concepts/relation-tuples) concept in the Ory documentation. [Relationships](https://www.ory.sh/docs/keto/concepts/relation-tuples) Remember, the most efficient approach depends on your specific use case and query patterns. Always test your implementation thoroughly to ensure it meets your application's requirements.
q
@chilly-king-10285 We're close to something useful here. The client would need to check permissions for a specific attribute
uuid
belonging to a specific resource
3db0efed-32bf-4e3e-a8ba-81da1257c997
for a user
266a92b0-15fd-4776-823f-4e1449591fcc
. Can you translate that permission check into HTTP API calls to a Keto service with the provided OPL model?
b
A message from kapa.ai
To check permissions for a specific attribute
uuid
belonging to a specific resource for a given user using the Ory Keto HTTP API, we need to make a series of API calls based on the OPL model you've provided. Here's how we can translate this permission check into HTTP API calls: 1. First, we need to check the permission for viewing the specific ResourceAttribute:
Copy code
http
POST /relation-tuples/check
Content-Type: application/json

{
  "namespace": "ResourceAttribute",
  "object": "<uuid_attribute_id>",
  "relation": "view",
  "subject_id": "KetoUser:266a92b0-15fd-4776-823f-4e1449591fcc"
}
Replace
<uuid_attribute_id>
with the actual ID of the
uuid
attribute. 2. If the first check returns
false
, we need to check if the user has permission to view the Resource itself:
Copy code
http
POST /relation-tuples/check
Content-Type: application/json

{
  "namespace": "Resource",
  "object": "3db0efed-32bf-4e3e-a8ba-81da1257c997",
  "relation": "view",
  "subject_id": "KetoUser:266a92b0-15fd-4776-823f-4e1449591fcc"
}
3. If both checks return
false
, we need to check if the user has edit permissions on either the ResourceAttribute or the Resource: For the ResourceAttribute:
Copy code
http
POST /relation-tuples/check
Content-Type: application/json

{
  "namespace": "ResourceAttribute",
  "object": "<uuid_attribute_id>",
  "relation": "edit",
  "subject_id": "KetoUser:266a92b0-15fd-4776-823f-4e1449591fcc"
}
For the Resource:
Copy code
http
POST /relation-tuples/check
Content-Type: application/json

{
  "namespace": "Resource",
  "object": "3db0efed-32bf-4e3e-a8ba-81da1257c997",
  "relation": "edit",
  "subject_id": "KetoUser:266a92b0-15fd-4776-823f-4e1449591fcc"
}
The user has permission to view the attribute if any of these checks return
true
. Alternatively, you can use the batch check endpoint to perform all these checks in a single API call:
Copy code
http
POST /relation-tuples/check/bulk
Content-Type: application/json

{
  "tuples": [
    {
      "namespace": "ResourceAttribute",
      "object": "<uuid_attribute_id>",
      "relation": "view",
      "subject_id": "KetoUser:266a92b0-15fd-4776-823f-4e1449591fcc"
    },
    {
      "namespace": "Resource",
      "object": "3db0efed-32bf-4e3e-a8ba-81da1257c997",
      "relation": "view",
      "subject_id": "KetoUser:266a92b0-15fd-4776-823f-4e1449591fcc"
    },
    {
      "namespace": "ResourceAttribute",
      "object": "<uuid_attribute_id>",
      "relation": "edit",
      "subject_id": "KetoUser:266a92b0-15fd-4776-823f-4e1449591fcc"
    },
    {
      "namespace": "Resource",
      "object": "3db0efed-32bf-4e3e-a8ba-81da1257c997",
      "relation": "edit",
      "subject_id": "KetoUser:266a92b0-15fd-4776-823f-4e1449591fcc"
    }
  ]
}
This batch check will return the results for all the permission checks in a single response. The user has permission to view the attribute if any of these checks return
true
.
To check permissions for a specific attribute
uuid
belonging to a specific resource for a given user using the Ory Keto HTTP API, we need to make a series of API calls based on the OPL model you've provided. Here's how we can translate this permission check into HTTP API calls: 1. First, we need to check the permission for viewing the specific ResourceAttribute:
Copy code
http POST /relation-tuples/check Content-Type: application/json  {   "namespace": "ResourceAttribute",   "object": "<uuid_attribute_id>",   "relation": "view",   "subject_id": "KetoUser:266a92b0-15fd-4776-823f-4e1449591fcc" }
Replace
<uuid_attribute_id>
with the actual ID of the
uuid
attribute. 2. If the first check returns
false
, we need to check if the user has permission to view the Resource itself:
Copy code
http POST /relation-tuples/check Content-Type: application/json  {   "namespace": "Resource",   "object": "3db0efed-32bf-4e3e-a8ba-81da1257c997",   "relation": "view",   "subject_id": "KetoUser:266a92b0-15fd-4776-823f-4e1449591fcc" }
3. If both checks return
false
, we need to check if the user has edit permissions on either the ResourceAttribute or the Resource: For the ResourceAttribute:
Copy code
http POST /relation-tuples/check Content-Type: application/json  {   "namespace": "ResourceAttribute",   "object": "<uuid_attribute_id>",   "relation": "edit",   "subject_id": "KetoUser:266a92b0-15fd-4776-823f-4e1449591fcc" }
For the Resource:
Copy code
http POST /relation-tuples/check Content-Type: application/json  {   "namespace": "Resource",   "object": "3db0efed-32bf-4e3e-a8ba-81da1257c997",   "relation": "edit",   "subject_id": "KetoUser:266a92b0-15fd-4776-823f-4e1449591fcc" }
The user has permission to view the attribute if any of these checks return
true
. Alternatively, you can use the batch check endpoint to perform all these checks in a single API call:
Copy code
http POST /relation-tuples/check/bulk Content-Type: application/json  {   "tuples": [     {       "namespace": "ResourceAttribute",       "object": "<uuid_attribute_id>",       "relation": "view",       "subject_id": "KetoUser:266a92b0-15fd-4776-823f-4e1449591fcc"     },     {       "namespace": "Resource",       "object": "3db0efed-32bf-4e3e-a8ba-81da1257c997",       "relation": "view",       "subject_id": "KetoUser:266a92b0-15fd-4776-823f-4e1449591fcc"     },     {       "namespace": "ResourceAttribute",       "object": "<uuid_attribute_id>",       "relation": "edit",       "subject_id": "KetoUser:266a92b0-15fd-4776-823f-4e1449591fcc"     },     {       "namespace": "Resource",       "object": "3db0efed-32bf-4e3e-a8ba-81da1257c997",       "relation": "edit",       "subject_id": "KetoUser:266a92b0-15fd-4776-823f-4e1449591fcc"     }   ] }
This batch check will return the results for all the permission checks in a single response. The user has permission to view the attribute if any of these checks return
true
. For more details on using the Ory Keto API for permission checks, you can refer to the [Ory Keto API Reference](https://www.ory.sh/docs/keto/reference/rest-api). Remember to replace
<uuid_attribute_id>
with the actual ID of the
uuid
attribute in your system. Also, ensure that your Keto service is properly configured and accessible at the correct endpoint. [Ory Keto API Reference](https://www.ory.sh/docs/keto/reference/rest-api)
q
@chilly-king-10285 This is our OPL model:
Copy code
import { Namespace, SubjectSet, Context } from "@ory/permission-namespace-types"

class KetoUser implements Namespace { }

class KetoGroup implements Namespace {
  related: {
    members: KetoUser[]
  }
}

class ResourceAttribute implements Namespace {
  related: {
    resource: Resource[]
    viewers: (KetoUser | SubjectSet<KetoGroup, "members">)[]
    editors: (KetoUser | SubjectSet<KetoGroup, "members">)[]
  }

  permits = {
    view: (ctx: Context): boolean =>
      this.related.viewers.includes(ctx.subject) ||
      this.permits.edit(ctx) ||
      this.related.resource.traverse((r) => r.permits.view(ctx)),

    edit: (ctx: Context): boolean =>
      this.related.editors.includes(ctx.subject) ||
      this.related.resource.traverse((r) => r.permits.edit(ctx))
  }
}

class Resource implements Namespace {
  related: {
    viewers: (KetoUser | SubjectSet<KetoGroup, "members">)[]
    editors: (KetoUser | SubjectSet<KetoGroup, "members">)[]
    attributes: ResourceAttribute[]
  }

  permits = {
    view: (ctx: Context): boolean =>
      this.related.viewers.includes(ctx.subject) ||
      this.permits.edit(ctx),

    edit: (ctx: Context): boolean =>
      this.related.editors.includes(ctx.subject)
  }
}
Now imagine that we create the following in Keto:
Copy code
[
  {
    "namespace": "KetoUser",
    "subject_id": "df1eaebb-8dfb-4495-ab8d-c96feaa9f99b"
  },
  {
    "namespace": "KetoUser",
    "subject_id": "00822a61-5ab1-491f-bbaf-da37913adb6a"
  },
  {
    "namespace": "KetoGroup",
    "subject_id": "parents_of_df1eaebb-8dfb-4495-ab8d-c96feaa9f99b"
  },
  {
    "namespace": "KetoGroup",
    "subject_id": "00822a61-5ab1-491f-bbaf-da37913adb6a",
    "relation": "members",
    "object": "parents_of_df1eaebb-8dfb-4495-ab8d-c96feaa9f99b"
  },
  {
    "namespace": "Resource",
    "subject_id": "3db0efed-32bf-4e3e-a8ba-81da1257c997"
  },
  {
    "namespace": "Resource",
    "subject_set": {
      "namespace": "KetoGroup",
      "relation": "members",
      "object": "parents_of_df1eaebb-8dfb-4495-ab8d-c96feaa9f99b"
    },
    "relation": "viewers",
    "object": "3db0efed-32bf-4e3e-a8ba-81da1257c997"
  },
  {
    "namespace": "ResourceAttribute",
    "subject_id": "uuid"
  },
  {
    "namespace": "ResourceAttribute",
    "subject_id": "uuid",
    "relation": "resource",
    "object": "3db0efed-32bf-4e3e-a8ba-81da1257c997"
  }
]
Would this make the permission check with the body below return True?
Copy code
{
  "namespace": "ResourceAttribute",
  "subject_id": "00822a61-5ab1-491f-bbaf-da37913adb6a",
  "relation": "view",
  "object": "uuid"
}
b
A message from kapa.ai
Based on the OPL model and the relationships you've created in Keto, let's analyze whether the permission check for the user "00822a61-5ab1-491f-bbaf-da37913adb6a" to view the "uuid" ResourceAttribute would return True. 1. The permission check is for viewing the "uuid" ResourceAttribute. 2. In the ResourceAttribute's
view
permit, it checks three conditions: a. If the subject is in the attribute's viewers list (not set in this case) b. If the subject has edit permission on the attribute (not set in this case) c. If the subject has view permission on the parent Resource 3. Since the first two conditions are not met, we need to check if the user has view permission on the parent Resource "3db0efed-32bf-4e3e-a8ba-81da1257c997". 4. In the Resource's
view
permit, it checks: a. If the subject is in the resource's viewers list b. If the subject has edit permission on the resource 5. Looking at the relationships you've created: - The user "00822a61-5ab1-491f-bbaf-da37913adb6a" is a member of the group "parents_of_df1eaebb-8dfb-4495-ab8d-c96feaa9f99b" - The group "parents_of_df1eaebb-8dfb-4495-ab8d-c96feaa9f99b" has viewing permission on the Resource "3db0efed-32bf-4e3e-a8ba-81da1257c997" Given this analysis, the permission check would indeed return True. Here's why: 1. The user is a member of a group that has viewing permission on the parent Resource. 2. This satisfies the condition in the Resource's
view
permit. 3. Which in turn satisfies the third condition in the ResourceAttribute's
view
permit. Therefore, the permission check with the body you provided would return True, allowing the user "00822a61-5ab1-491f-bbaf-da37913adb6a" to view the "uuid" ResourceAttribute. It's worth noting that this permission is granted indirectly through group membership and resource-level permissions, rather than direct permissions on the ResourceAttribute itself. This demonstrates the power and flexibility of the permission model you've set up, allowing for hierarchical and group-based access control.
q
@chilly-king-10285 Great. Now imagine that we have two
Resource
objects
1b291885-9aa2-41a4-899f-67ee5e322ee9
and
4ab8f34f-608c-4bd6-84de-d230a30ede38
. Both of them should have a
uuid
attribute that permissions can be checked on. How could I accomplish this simply?
b
A message from kapa.ai
To accomplish this scenario where you have two
Resource
objects, each with a
uuid
attribute that permissions can be checked on, you can use the existing OPL model and create the necessary relationships in Ory Keto. Here's how you can do this simply: 1. First, create the two Resource objects:
Copy code
http PUT /admin/relation-tuples Content-Type: application/json  {   "namespace": "Resource",   "object": "1b291885-9aa2-41a4-899f-67ee5e322ee9" }
Copy code
http PUT /admin/relation-tuples Content-Type: application/json  {   "namespace": "Resource",   "object": "4ab8f34f-608c-4bd6-84de-d230a30ede38" }
2. Then, create a
uuid
ResourceAttribute for each Resource:
Copy code
http PUT /admin/relation-tuples Content-Type: application/json  {   "namespace": "ResourceAttribute",   "object": "uuid_1b291885-9aa2-41a4-899f-67ee5e322ee9",   "relation": "resource",   "subject_id": "Resource:1b291885-9aa2-41a4-899f-67ee5e322ee9" }
Copy code
http PUT /admin/relation-tuples Content-Type: application/json  {   "namespace": "ResourceAttribute",   "object": "uuid_4ab8f34f-608c-4bd6-84de-d230a30ede38",   "relation": "resource",   "subject_id": "Resource:4ab8f34f-608c-4bd6-84de-d230a30ede38" }
In these examples, we're using
uuid_<resource_id>
as the object identifier for the ResourceAttribute to ensure uniqueness. You could use any unique identifier that makes sense for your application. Now, to check permissions on these
uuid
attributes, you would use: For the first Resource:
Copy code
http POST /relation-tuples/check Content-Type: application/json  {   "namespace": "ResourceAttribute",   "object": "uuid_1b291885-9aa2-41a4-899f-67ee5e322ee9",   "relation": "view",   "subject_id": "KetoUser:<user_id>" }
For the second Resource:
Copy code
http POST /relation-tuples/check Content-Type: application/json  {   "namespace": "ResourceAttribute",   "object": "uuid_4ab8f34f-608c-4bd6-84de-d230a30ede38",   "relation": "view",   "subject_id": "KetoUser:<user_id>" }
This approach allows you to: 1. Create multiple Resource objects 2. Associate a
uuid
attribute with each Resource 3. Check permissions on the
uuid
attribute for each Resource independently The permissions will be evaluated based on the OPL model you provided earlier, considering both the ResourceAttribute permissions and the permissions of the parent Resource. For more information on managing relationships and checking permissions in Ory Keto, you can refer to the [Ory Keto API Reference](https://www.ory.sh/docs/keto/reference/rest-api). [Ory Keto API Reference](https://www.ory.sh/docs/keto/reference/rest-api) Remember to replace
<user_id>
with the actual ID of the user you're checking permissions for.
q
@chilly-king-10285 Thanks. I noticed that you put namespace identifiers inside the
subject_id
fields. Is this standard practice? For example you wrote
"subject_id": "KetoUser:<user_id>"
.
b
A message from kapa.ai
You're very observant, and I appreciate you pointing this out. I made a mistake in my previous responses by including the namespace identifiers in the
subject_id
fields. This is not standard practice in Ory Keto, and I apologize for the confusion. Let me clarify the correct way to specify subjects in Ory Keto: 1. When referring to a subject in a relation tuple, you should use only the identifier of the subject, without the namespace prefix. 2. The namespace is already specified in the
namespace
field of the relation tuple, so it doesn't need to be repeated in the
subject_id
. Here's the correct way to specify a subject:
Copy code
json {   "namespace": "KetoUser",   "object": "<object_id>",   "relation": "some_relation",   "subject_id": "<user_id>" }
Not:
Copy code
json {   "namespace": "KetoUser",   "object": "<object_id>",   "relation": "some_relation",   "subject_id": "KetoUser:<user_id>"  // This is incorrect }
Thank you for bringing this to my attention. It's important to use the correct syntax when working with Ory Keto to ensure proper functioning of your permission system. For more detailed information on the correct syntax for relation tuples in Ory Keto, you can refer to the [Relationships](https://www.ory.sh/docs/keto/concepts/relation-tuples) concept in the Ory documentation. [Relationships](https://www.ory.sh/docs/keto/concepts/relation-tuples)
q
@chilly-king-10285 Does the
namespace
apply to both
object
and
subject_id
?
b
A message from kapa.ai
The
namespace
in an Ory Keto relation tuple applies to the
object
, but not to the
subject_id
. Let me explain this in more detail: 1. For the `object`: The
namespace
field specifies the namespace of the object in the relation tuple. This means that the
object
identifier is understood to be within the context of the specified namespace. 2. For the `subject_id`: The
subject_id
is treated differently. It does not inherit the namespace specified in the relation tuple. Instead, when you use a
subject_id
, Ory Keto assumes it belongs to its own namespace, which should be specified elsewhere when that subject is defined. This distinction is important because it allows for cross-namespace relationships. For example, you can have a user (from a User namespace) having permissions on a resource (from a Resource namespace). Here's an example to illustrate this:
Copy code
json {   "namespace": "Resource",   "object": "document123",   "relation": "viewer",   "subject_id": "alice" }
In this relation tuple: - The
object
"document123" is in the "Resource" namespace. - The
subject_id
"alice" is not in the "Resource" namespace. It's assumed to be in its own namespace (e.g., User), which would be defined separately. This design allows for flexible and powerful permission modeling across different types of entities in your system. For more detailed information on how namespaces work in Ory Keto, you can refer to the [Namespaces](https://www.ory.sh/docs/keto/concepts/namespaces) concept in the Ory documentation. [Namespaces](https://www.ory.sh/docs/keto/concepts/namespaces)