careful-honey-27437
05/03/2025, 1:34 AMbrief-egg-61061
05/03/2025, 1:34 AMcareful-honey-27437
05/03/2025, 1:34 AMcreate_organization_admin
- Organization admins can create_publication_admin
- Publication admins have full CRUD permits for all the resources that belong to that Publication, and they can create_publication_editor
- Publication editors have full CRUD permits for all the resources that belong to that Publication, but cannot create_publication_admin
or create_publication_editor
- Article editors have full CRUD permits for all the resources that belong to that Article, and they can create_article_collaborator
but cannot create_article_editor
- Article collaborators have full CRUD permits for all the resources that belong to that Article, but cannot create_article_editor
or create_article_collaborator
careful-honey-27437
05/03/2025, 1:34 AMcreate_organization_admin
- Organization admins can create_publication_admin
- Publication admins have full CRUD permits for all the resources that belong to that Publication, and they can create_publication_editor
- Publication editors have full CRUD permits for all the resources that belong to that Publication, but cannot create_publication_admin
or create_publication_editor
- Article editors have full CRUD permits for all the resources that belong to that Article, and they can create_article_collaborator
but cannot create_article_editor
- Article collaborators have full CRUD permits for all the resources that belong to that Article, but cannot create_article_editor
or create_article_collaborator
brief-egg-61061
05/03/2025, 1:34 AMbrief-egg-61061
05/03/2025, 1:34 AMNamespace
interface 2. Relationships: Defined within namespaces to establish connections between entities 3. Permissions: Functions that determine access based on relationships and context Here's a simple example: typescript import { Namespace, Context } from "@ory/keto-namespace-types" class User implements Namespace {} class Document implements Namespace { // All relationships for a single document related: { editors: User[] viewers: User[] } // The permissions derived from the relationships and context permits = { write: (ctx: Context): boolean => this.related.editors.includes(ctx.subject), read: (ctx: Context): boolean => this.permits.write(ctx) || this.related.viewers.includes(ctx.subject), } }
This example defines a permission model where: - Users can be editors or viewers of documents - Editors can write to documents - Both editors and viewers can read documents ## Why OPL Was Created The Ory team conducted extensive user interviews and found that existing permission languages were often unfamiliar and difficult to understand. They created OPL with these goals [What is the Ory Permission Language](https://www.ory.sh/blog/what-is-the-ory-permission-language): - Be self-explanatory and familiar to developers - Provide good editor support - Support linters and unit test frameworks - Be not Turing-complete to minimize errors and prevent resource exhaustion ## Getting Started You can use OPL in the Ory Console. Go to the Ory Console, navigate to Permissions → Relationships, and switch to the Permission Rules tab [Get started with Ory Permissions](https://www.ory.sh/docs/guides/permissions/overview#example). To learn more, you can: - Read the [OPL specification](https://www.ory.sh/docs/keto/reference/ory-permission-language) - Learn about [creating permission models](https://www.ory.sh/docs/keto/modeling/create-permission-model) - Try the [Ory Permissions quickstart guide](https://www.ory.sh/docs/guides/permissions/overview)brief-egg-61061
05/03/2025, 1:34 AMtypescript import { Namespace, Context } from "@ory/keto-namespace-types" class User implements Namespace {} class Organization implements Namespace { related: { owners: User[] admins: User[] } permits = { // Basic CRUD operations create: (ctx: Context): boolean => this.related.owners.includes(ctx.subject) || this.related.admins.includes(ctx.subject), read: (ctx: Context): boolean => this.related.owners.includes(ctx.subject) || this.related.admins.includes(ctx.subject), update: (ctx: Context): boolean => this.related.owners.includes(ctx.subject) || this.related.admins.includes(ctx.subject), delete: (ctx: Context): boolean => this.related.owners.includes(ctx.subject) || this.related.admins.includes(ctx.subject), // Special permissions create_organization_admin: (ctx: Context): boolean => this.related.owners.includes(ctx.subject), create_publication_admin: (ctx: Context): boolean => this.related.owners.includes(ctx.subject) || this.related.admins.includes(ctx.subject), } } class Publication implements Namespace { related: { organizations: Organization[] admins: User[] editors: User[] } permits = { // Basic CRUD operations create: (ctx: Context): boolean => this.related.admins.includes(ctx.subject) || this.related.editors.includes(ctx.subject) || this.related.organizations.traverse((org) => org.permits.create(ctx)), read: (ctx: Context): boolean => this.related.admins.includes(ctx.subject) || this.related.editors.includes(ctx.subject) || this.related.organizations.traverse((org) => org.permits.read(ctx)), update: (ctx: Context): boolean => this.related.admins.includes(ctx.subject) || this.related.editors.includes(ctx.subject) || this.related.organizations.traverse((org) => org.permits.update(ctx)), delete: (ctx: Context): boolean => this.related.admins.includes(ctx.subject) || this.related.editors.includes(ctx.subject) || this.related.organizations.traverse((org) => org.permits.delete(ctx)), // Special permissions create_publication_admin: (ctx: Context): boolean => this.related.organizations.traverse((org) => org.permits.create_publication_admin(ctx)), create_publication_editor: (ctx: Context): boolean => this.related.admins.includes(ctx.subject) || this.related.organizations.traverse((org) => org.permits.create(ctx)), } } class Article implements Namespace { related: { publications: Publication[] editors: User[] collaborators: User[] } permits = { // Basic CRUD operations create: (ctx: Context): boolean => this.related.editors.includes(ctx.subject) || this.related.collaborators.includes(ctx.subject) || this.related.publications.traverse((pub) => pub.permits.create(ctx)), read: (ctx: Context): boolean => this.related.editors.includes(ctx.subject) || this.related.collaborators.includes(ctx.subject) || this.related.publications.traverse((pub) => pub.permits.read(ctx)), update: (ctx: Context): boolean => this.related.editors.includes(ctx.subject) || this.related.collaborators.includes(ctx.subject) || this.related.publications.traverse((pub) => pub.permits.update(ctx)), delete: (ctx: Context): boolean => this.related.editors.includes(ctx.subject) || this.related.collaborators.includes(ctx.subject) || this.related.publications.traverse((pub) => pub.permits.delete(ctx)), // Special permissions create_article_editor: (ctx: Context): boolean => this.related.publications.traverse((pub) => pub.permits.create(ctx)), create_article_collaborator: (ctx: Context): boolean => this.related.editors.includes(ctx.subject) || this.related.publications.traverse((pub) => pub.permits.create(ctx)), } }
This OPL model implements the hierarchical permission structure you described, with permissions flowing from Organization owners down to Article collaborators. The model uses the traverse
method to check permissions up the hierarchy, which is a key feature of the Ory Permission Language [as described in the OPL specification](https://www.ory.sh/docs/keto/reference/ory-permission-language#statements). The model follows the permission refactoring pattern shown in the [Ory documentation](https://www.ory.sh/docs/keto/modeling/create-permission-model#optional-refactoring), where higher-level permissions can be used to define lower-level ones, creating a clean hierarchical structure. Each namespace has its own set of relations and permissions that match your requirements, with the appropriate checks to ensure that only users with the right permissions can perform specific actions.careful-honey-27437
05/03/2025, 2:19 AMUser:123
has read
permssions on Article:abc
using the Ory Golang Clientcareful-honey-27437
05/03/2025, 2:19 AMUser:123
has read
permssions on Article:abc
using the Ory Golang Clientbrief-egg-61061
05/03/2025, 2:19 AMbrief-egg-61061
05/03/2025, 2:19 AMUser:123
has read
permissions on Article:abc
using the Ory Golang Client. Here's a complete example: go package main import ( "context" "fmt" "os" ory "<http://github.com/ory/client-go|github.com/ory/client-go>" ) func main() { // Use this context to access Ory APIs which require an Ory API Key var oryAuthedContext = context.WithValue(context.Background(), ory.ContextAccessToken, os.Getenv("ORY_API_KEY")) // Define the permission check parameters var namespace = "Article" var object = "abc" var relation = "read" var subjectId = "User:123" // Configure the client configuration := ory.NewConfiguration() configuration.Servers = []ory.ServerConfiguration{ { URL: "<https://your-project-slug.projects.oryapis.com>", // Replace with your Ory Network project URL }, } // Create the client client := ory.NewAPIClient(configuration) // Perform the permission check check, r, err := client.PermissionApi.CheckPermission(oryAuthedContext). Namespace(namespace). Object(object). Relation(relation). SubjectId(subjectId). Execute() if err != nil { fmt.Fprintf(os.Stderr, "Full HTTP response: %v\n", r) panic("Encountered error: " + err.Error()) } // Check the result if check.Allowed { fmt.Println(subjectId + " can " + relation + " the " + object) } else { fmt.Println(subjectId + " cannot " + relation + " the " + object) } }
This code: 1. Sets up authentication using an API key (set as an environment variable) 2. Configures the client with your Ory Network project URL 3. Uses the PermissionApi.CheckPermission()
method to check if User:123
has read
permission on Article:abc
4. Prints the result based on the Allowed
field in the response Make sure to: - Replace the URL with your actual Ory Network project URL - Set your Ory API key as the ORY_API_KEY
environment variable - Install the Ory client with go get <http://github.com/ory/client-go@master|github.com/ory/client-go@master>
[Ory Permissions Go SDK](https://www.ory.sh/docs/keto/sdk/go) provides more examples and details about using the SDK.careful-honey-27437
05/03/2025, 2:20 AMread
permit because they’re an editor
of Article:abc
, not because they have a direct read
relationship.brief-egg-61061
05/03/2025, 2:20 AMbrief-egg-61061
05/03/2025, 2:21 AMUser:123
has read
permissions on Article:abc
because they're an editor
of the article, not because of a direct read
relationship. This requires creating the appropriate relationship first and then checking the permission. go package main import ( "context" "fmt" "os" ory "<http://github.com/ory/client-go|github.com/ory/client-go>" ) func main() { // Use this context to access Ory APIs which require an Ory API Key var oryAuthedContext = context.WithValue(context.Background(), ory.ContextAccessToken, os.Getenv("ORY_API_KEY")) // Define the namespace, object, and subject var namespace = "Article" var object = "abc" var editorRelation = "editor" var readRelation = "read" var subjectId = "User:123" // Configure the client configuration := ory.NewConfiguration() configuration.Servers = []ory.ServerConfiguration{ { URL: "<https://your-project-slug.projects.oryapis.com>", // Replace with your Ory Network project URL }, } // Create the client client := ory.NewAPIClient(configuration) // First, create the editor relationship editorPayload := ory.CreateRelationshipBody{ Namespace: &namespace, Object: &object, Relation: &editorRelation, SubjectId: &subjectId, } _, r, err := client.RelationshipApi.CreateRelationship(oryAuthedContext).CreateRelationshipBody(editorPayload).Execute() if err != nil { fmt.Fprintf(os.Stderr, "Full HTTP response: %v\n", r) panic("Encountered error creating relationship: " + err.Error()) } fmt.Println("Successfully created editor relationship") // Now check if the user has read permission (which they should have because they're an editor) check, r, err := client.PermissionApi.CheckPermission(oryAuthedContext). Namespace(namespace). Object(object). Relation(readRelation). SubjectId(subjectId). Execute() if err != nil { fmt.Fprintf(os.Stderr, "Full HTTP response: %v\n", r) panic("Encountered error checking permission: " + err.Error()) } // Check the result if check.Allowed { fmt.Println(subjectId + " can " + readRelation + " the " + object + " because they are an editor") } else { fmt.Println(subjectId + " cannot " + readRelation + " the " + object) } }
This example assumes you have a permission model defined in Ory Permission Language (OPL) that grants read
permissions to users who have the editor
relationship. Your OPL might look something like this: typescript import { Namespace, Context } from "@ory/keto-namespace-types" class User implements Namespace {} class Article implements Namespace { related: { editors: User[] viewers: User[] } permits = { edit: (ctx: Context): boolean => this.related.editors.includes(ctx.subject), read: (ctx: Context): boolean => this.permits.edit(ctx) || this.related.viewers.includes(ctx.subject), } }
In this model, anyone who is an editor automatically has read permissions through the permits definition, without needing a direct read relationship. [Ory Permissions Go SDK](https://www.ory.sh/docs/keto/sdk/go) provides more examples for working with the SDK.careful-honey-27437
05/03/2025, 2:24 AMcareful-honey-27437
05/03/2025, 2:24 AMread
and read_status
permits.brief-egg-61061
05/03/2025, 2:24 AMbrief-egg-61061
05/03/2025, 2:25 AMread
and read_status
permits using the Ory Golang Client: go package main import ( "context" "fmt" "os" ory "<http://github.com/ory/client-go|github.com/ory/client-go>" ) func main() { // Use this context to access Ory APIs which require an Ory API Key var oryAuthedContext = context.WithValue(context.Background(), ory.ContextAccessToken, os.Getenv("ORY_API_KEY")) // Define the namespace, object, and subject var namespace = "Article" var object = "abc" var editorRelation = "editor" var subjectId = "User:123" // Configure the client configuration := ory.NewConfiguration() configuration.Servers = []ory.ServerConfiguration{ { URL: "<https://your-project-slug.projects.oryapis.com>", // Replace with your Ory Network project URL }, } // Create the client client := ory.NewAPIClient(configuration) // First, create the editor relationship editorPayload := ory.CreateRelationshipBody{ Namespace: &namespace, Object: &object, Relation: &editorRelation, SubjectId: &subjectId, } _, r, err := client.RelationshipApi.CreateRelationship(oryAuthedContext).CreateRelationshipBody(editorPayload).Execute() if err != nil { fmt.Fprintf(os.Stderr, "Full HTTP response: %v\n", r) panic("Encountered error creating relationship: " + err.Error()) } fmt.Println("Successfully created editor relationship") // Create the batch check request with multiple permissions to check readTuple := ory.Relationship{ Namespace: &namespace, Object: &object, Relation: stringPtr("read"), SubjectId: &subjectId, } readStatusTuple := ory.Relationship{ Namespace: &namespace, Object: &object, Relation: stringPtr("read_status"), SubjectId: &subjectId, } // Perform batch permission check batchBody := ory.BatchCheckPermissionBody{ Tuples: []ory.Relationship{readTuple, readStatusTuple}, } batchResult, r, err := client.PermissionApi.BatchCheckPermission(oryAuthedContext). BatchCheckPermissionBody(batchBody). Execute() if err != nil { fmt.Fprintf(os.Stderr, "Full HTTP response: %v\n", r) panic("Encountered error checking permissions: " + err.Error()) } // Process the results for i, result := range batchResult.Results { var permissionName string if i == 0 { permissionName = "read" } else { permissionName = "read_status" } if result.Allowed { fmt.Printf("%s can %s the %s\n", subjectId, permissionName, object) } else { fmt.Printf("%s cannot %s the %s\n", subjectId, permissionName, object) if result.Error != "" { fmt.Printf("Error: %s\n", result.Error) } } } } // Helper function to create string pointers func stringPtr(s string) *string { return &s }
This example: 1. Creates an editor relationship for User:123
on Article:abc
2. Creates a batch check request for both read
and read_status
permissions 3. Uses the BatchCheckPermission
method to check both permissions in a single API call 4. Processes the results, which come back in the same order as the input tuples The batch check is more efficient than making multiple individual permission checks, especially when you need to check several permissions at once. This assumes your permission model in OPL grants appropriate permissions based on the editor relationship. For example, your model might look like: typescript class Article implements Namespace { related: { editors: User[] viewers: User[] } permits = { edit: (ctx: Context): boolean => this.related.editors.includes(ctx.subject), read: (ctx: Context): boolean => this.permits.edit(ctx) || this.related.viewers.includes(ctx.subject), read_status: (ctx: Context): boolean => this.permits.edit(ctx) } }
For more information on batch permission checks, see the [Ory API Reference](https://www.ory.sh/docs/reference/api#tag/permission).careful-honey-27437
05/03/2025, 2:27 AMbrief-egg-61061
05/03/2025, 2:27 AMbrief-egg-61061
05/03/2025, 2:27 AM*string
) where a string value is expected. This is a common issue when working with the Ory Go client. Let me fix the code example from our previous conversation: go // Create the batch check request with multiple permissions to check readTuple := ory.Relationship{ Namespace: namespace, // Use the string directly, not a pointer Object: object, // Use the string directly, not a pointer Relation: "read", // Use string literal, not a pointer SubjectId: subjectId, // Use the string directly, not a pointer } readStatusTuple := ory.Relationship{ Namespace: namespace, Object: object, Relation: "read_status", SubjectId: subjectId, }
The issue is that in the previous example, I incorrectly used pointers to strings in the Relationship
struct. The Relationship
struct expects string values, not pointers to strings. If you're using a newer version of the Ory client, the field types might have changed. In that case, you might need to check the current API documentation or use the Go documentation tool to verify the expected types for the Relationship
struct. If you continue to have issues, please share the specific version of the Ory client you're using, and I can provide more targeted assistance.careful-honey-27437
05/03/2025, 2:28 AMbrief-egg-61061
05/03/2025, 2:28 AMbrief-egg-61061
05/03/2025, 2:28 AMRelationship
struct in the Ory client expects pointer types for some fields. Let me provide a corrected example for batch permission checks: go package main import ( "context" "fmt" "os" ory "<http://github.com/ory/client-go|github.com/ory/client-go>" ) func main() { // Use this context to access Ory APIs which require an Ory API Key var oryAuthedContext = context.WithValue(context.Background(), ory.ContextAccessToken, os.Getenv("ORY_API_KEY")) // Define the namespace, object, and subject namespace := "Article" object := "abc" subjectId := "User:123" // Configure the client configuration := ory.NewConfiguration() configuration.Servers = []ory.ServerConfiguration{ { URL: "<https://your-project-slug.projects.oryapis.com>", // Replace with your Ory Network project URL }, } // Create the client client := ory.NewAPIClient(configuration) // Helper function to create string pointers stringPtr := func(s string) *string { return &s } // Create the batch check request with multiple permissions to check readRelation := "read" readStatusRelation := "read_status" readTuple := ory.Relationship{ Namespace: stringPtr(namespace), Object: stringPtr(object), Relation: stringPtr(readRelation), SubjectId: stringPtr(subjectId), } readStatusTuple := ory.Relationship{ Namespace: stringPtr(namespace), Object: stringPtr(object), Relation: stringPtr(readStatusRelation), SubjectId: stringPtr(subjectId), } // Perform batch permission check batchBody := ory.BatchCheckPermissionBody{ Tuples: []ory.Relationship{readTuple, readStatusTuple}, } batchResult, r, err := client.PermissionApi.BatchCheckPermission(oryAuthedContext). BatchCheckPermissionBody(batchBody). Execute() if err != nil { fmt.Fprintf(os.Stderr, "Full HTTP response: %v\n", r) panic("Encountered error checking permissions: " + err.Error()) } // Process the results for i, result := range batchResult.Results { var permissionName string if i == 0 { permissionName = "read" } else { permissionName = "read_status" } if result.Allowed { fmt.Printf("%s can %s the %s\n", subjectId, permissionName, object) } else { fmt.Printf("%s cannot %s the %s\n", subjectId, permissionName, object) if result.Error != "" { fmt.Printf("Error: %s\n", result.Error) } } } }
The key change here is using the stringPtr
helper function to convert string values to string pointers (*string
) when creating the Relationship
structs. This matches the expected types in the Ory client's API. This approach is consistent with the API documentation for batch permission checks in the [Ory API Reference](https://www.ory.sh/docs/reference/api#tag/permission).careful-honey-27437
05/03/2025, 2:42 AMbrief-egg-61061
05/03/2025, 2:42 AMbrief-egg-61061
05/03/2025, 2:43 AMNamespace(*&namespace)
in the Ory SDK is indeed unusual and redundant. The &
operator creates a pointer to the variable, and the *
operator dereferences it, essentially canceling each other out. Looking at the example from the Ory Permissions Go SDK documentation, this pattern appears in their official examples: go check, r, err := ory.PermissionApi.CheckPermission(oryAuthedContext). Namespace(*&namespace). Object(*&object). Relation(*&relation). SubjectId(*&subjectId).Execute()
This code from the [Ory Permissions Go SDK](https://www.ory.sh/docs/keto/sdk/go) documentation is functionally equivalent to: go check, r, err := ory.PermissionApi.CheckPermission(oryAuthedContext). Namespace(namespace). Object(object). Relation(relation). SubjectId(subjectId).Execute()
The *&
combination is unnecessary and likely a stylistic quirk or oversight in the documentation examples. It doesn't provide any functional benefit and actually makes the code more complex without adding any value. This pattern might have been introduced if the API originally expected pointers and was later changed to accept values directly, with the documentation not being fully updated to reflect the simpler approach. When writing your own code with the Ory SDK, you can safely use the simpler form without the *&
combination, as long as you're passing the correct type (string or pointer) that the API expects.careful-honey-27437
05/03/2025, 2:44 AMbrief-egg-61061
05/03/2025, 2:44 AMbrief-egg-61061
05/03/2025, 2:44 AMRelationship
struct. Based on the error message, the Namespace
field is expecting a pointer to a string (*string
), but you're providing a string value directly. Here's the corrected code: go // Create batch check request var checkTuples []ory.Relationship for _, tuple := range tuples { namespace := "Article" checkTuple := ory.Relationship{ Namespace: &namespace, // Convert to pointer Object: &tuple.Object, // Convert to pointer Relation: &tuple.Relation, // Convert to pointer SubjectId: &tuple.Subject, // This is already correct } checkTuples = append(checkTuples, checkTuple) } body := ory.BatchCheckPermissionBody{ Tuples: checkTuples, } // Use this authed context to access Ory APIs which require an Ory API Key ory_api_key := os.Getenv("ORY_API_KEY") var oryAuthedContext = context.WithValue(context.Background(), ory.ContextAccessToken, ory_api_key) // Execute batch permission check result, r, err := oryClient.PermissionAPI.BatchCheckPermission(oryAuthedContext). BatchCheckPermissionBody(body). Execute() if err != nil { fmt.Println(r) fmt.Println(err.Error()) }
The key changes: 1. Added &
to create pointers for namespace
, tuple.Object
, and tuple.Relation
2. Left &tuple.Subject
as is since it's already correct The Ory client's Relationship
struct expects pointers to strings for all its fields. This is consistent with the [Ory API Reference](https://www.ory.sh/docs/reference/api) for batch permission checks. If you're still having issues, you might want to check if your tuple
struct has the correct field types. The fields Object
, Relation
, and Subject
should be of type string
for the above code to work correctly.careful-honey-27437
05/03/2025, 2:45 AMbrief-egg-61061
05/03/2025, 2:45 AMbrief-egg-61061
05/03/2025, 2:45 AMRelationship
struct in the Ory client expects pointer types for all its string fields, but you're providing string values directly. Here's the corrected code: go // Create batch check request var checkTuples []ory.Relationship for _, tuple := range tuples { namespace := "Article" checkTuple := ory.Relationship{ Namespace: &namespace, // Convert to pointer Object: &tuple.Object, // Convert to pointer Relation: &tuple.Relation, // Convert to pointer SubjectId: &tuple.Subject, // Convert to pointer } checkTuples = append(checkTuples, checkTuple) } body := ory.BatchCheckPermissionBody{ Tuples: checkTuples, } // Use this authed context to access Ory APIs which require an Ory API Key ory_api_key := os.Getenv("ORY_API_KEY") var oryAuthedContext = context.WithValue(context.Background(), ory.ContextAccessToken, ory_api_key) // Execute batch permission check result, r, err := oryClient.PermissionAPI.BatchCheckPermission(oryAuthedContext). BatchCheckPermissionBody(body). Execute() if err != nil { fmt.Println(r) fmt.Println(err.Error()) }
The key change is adding the &
operator to create pointers for all string fields in the Relationship
struct. According to the [Ory API Reference](https://www.ory.sh/docs/reference/api) and [Keto API Reference](https://www.ory.sh/docs/keto/reference/rest-api), the Relationship
struct expects pointer types for its fields. This is consistent with the batch permission check API documentation, which shows that the fields in the request body should be properly referenced as pointers.careful-honey-27437
05/03/2025, 2:46 AMbrief-egg-61061
05/03/2025, 2:46 AMbrief-egg-61061
05/03/2025, 2:47 AMRelationship
struct is expecting string values, not pointers to strings, which contradicts what we discussed earlier. This suggests that the API might have changed or there are different versions of the client with different type requirements. Let's try a different approach: go // Create batch check request var checkTuples []ory.Relationship for _, tuple := range tuples { namespace := "Article" // Create a copy of the strings to avoid any issues with variable scope ns := namespace obj := tuple.Object rel := tuple.Relation subj := tuple.Subject checkTuple := ory.Relationship{ Namespace: ns, Object: obj, Relation: rel, SubjectId: subj, } checkTuples = append(checkTuples, checkTuple) } body := ory.BatchCheckPermissionBody{ Tuples: checkTuples, }
If this still doesn't work, you might need to check the exact version of the Ory client you're using and inspect the Relationship
struct definition. You can do this by looking at the Go documentation or the source code. For example, you could add this debugging code to your program: go // Print the type of the Relationship struct fields relationshipType := reflect.TypeOf(ory.Relationship{}) for i := 0; i < relationshipType.NumField(); i++ { field := relationshipType.Field(i) fmt.Printf("Field: %s, Type: %s\n", field.Name, field.Type) }
This will help you determine the exact types expected by the Relationship
struct in your version of the client. Based on the [Keto API Reference](https://www.ory.sh/docs/keto/reference/proto-api#oryketorelation_tuplesv1alpha2relation_tuplesproto), the RelationTuple
struct (which is similar to Relationship
) expects string values, not pointers. This might explain the error you're seeing. Let me know if this helps or if you need further assistance!careful-honey-27437
05/03/2025, 3:04 AMbrief-egg-61061
05/03/2025, 3:04 AMbrief-egg-61061
05/03/2025, 3:04 AMfalse
results for your permission checks. There's an issue with how you're using the OPL (Ory Permission Language) in relation to the permission checks you're making. ## The Issue Looking at your OPL code and the relationship you've established: 1. You have a relationship: Article:abcdefg12345#editors@User:fdff2bff-7604-4a58-baf7-550ce7ae4419
2. You're checking for read
and read_author_id
permissions The problem is that in your OPL, you're using traverse
for checking permissions through publications, but you haven't established any relationship between your article and any publication. Your permission checks are failing because: 1. The user is an editor of the article (which should grant these permissions) 2. But the OPL code isn't properly using this.permits
to check for permissions based on the editor relationship ## The Solution There are two issues that need to be fixed: 1. Issue with this.permits
: There's a known inconsistency in OPL when using this.permits(ctx)
vs calling a transitive permits directly. This is documented in an [open GitHub issue](https://github.com/ory/keto/issues/1626). 2. Missing direct permission check: Your permission definitions should directly check if the user is an editor. Here's how you should modify your Article class permits: typescript permits = { // Other permits... read: (ctx: Context): boolean => this.related.editors.includes(ctx.subject) || this.related.collaborators.includes(ctx.subject) || this.related.publications.traverse((pub) => pub.permits.read(ctx)), read_author_id: (ctx: Context): boolean => this.related.editors.includes(ctx.subject) || this.related.publications.traverse((pub) => pub.permits.read(ctx)), }
This looks correct in your code, but there might be an issue with how OPL is processing the permissions. ## Workaround As a workaround, you could try explicitly duplicating the permission logic instead of relying on `this.permits`: typescript read_author_id: (ctx: Context): boolean => this.related.editors.includes(ctx.subject) || // This should work directly this.related.publications.traverse((pub) => pub.permits.read(ctx)),
## Debugging Steps 1. Check for typos: Ensure the object ID, relation names, and subject ID are exactly as expected 2. Verify relationship creation: Confirm the editor relationship was successfully created 3. Try a simpler permission check: Test with just a direct editors.includes(ctx.subject)
check 4. Check max-depth parameter: If using deep traversals, ensure your max-depth parameter is sufficient ## Known Issues There's a known issue with OPL permissions having inconsistencies when using this.permits(ctx)
vs calling a transitive permits directly, as documented in [GitHub issue #1626](https://github.com/ory/keto/issues/1626). This might be affecting your permission checks. Similarly, there's another issue with traverse(...)
not working correctly in some cases, as documented in [GitHub issue #1330](https://github.com/ory/keto/issues/1330). If you continue to have issues after trying these solutions, you might need to wait for these issues to be resolved in a future release of Ory Permissions.careful-honey-27437
05/03/2025, 3:07 AMeditors
relationship, so this should work: read: (ctx: Context): boolean =>
this.related.editors.includes(ctx.subject) ||brief-egg-61061
05/03/2025, 3:07 AMbrief-egg-61061
05/03/2025, 3:07 AMeditors
relationship in both the read
and read_author_id
permits: typescript read: (ctx: Context): boolean => this.related.editors.includes(ctx.subject) || this.related.collaborators.includes(ctx.subject) || this.related.publications.traverse((pub) => pub.permits.read(ctx)), read_author_id: (ctx: Context): boolean => this.related.editors.includes(ctx.subject) || this.related.publications.traverse((pub) => pub.permits.read(ctx)),
This should indeed work as expected. Since you have the relationship Article:abcdefg12345#editors@User:fdff2bff-7604-4a58-baf7-550ce7ae4419
established, the check this.related.editors.includes(ctx.subject)
should return true
for both permits. The fact that it's not working suggests there might be one of these issues: 1. Namespace format issue: Make sure your relationship is using the correct format. In Ory Permissions, the namespace should be just Article
(not Article:abcdefg12345
). The object ID should be abcdefg12345
. 2. Known OPL bugs: As mentioned in [GitHub issue #1626](https://github.com/ory/keto/issues/1626), there are inconsistencies in how OPL processes permissions. This could be affecting your checks. 3. Relationship not properly created: Double-check that the relationship was successfully created by listing all relationships for the article. To troubleshoot, I'd recommend: 1. Verify the relationship exists by querying all relationships for the article 2. Try a direct permission check (not batch) to see if that works 3. Check if your Ory Permissions version is affected by the known issues The direct check this.related.editors.includes(ctx.subject)
should definitely work if the relationship exists correctly in the system.