<@U04UQ68083H> Question about Ory Permissions Lang...
# ory-copilot
c
@chilly-king-10285 Question about Ory Permissions Language
b
A message from kapa.ai
c
Using Ory Permissions Language (OPL) write permissions and rules that accommodate the following namespaces: - User - Organization - relations: owner, admin - Publication - relations: admin, editor - Article - editor, collaborator - Organization owners have full CRUD permits for all resources that belong to that Organization - Organization admins have full CRUD permits for all resources that belong to that Organization, but they cannot create new admins - Therefore, organization owners only have the permit
create_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
@chilly-king-10285 Using Ory Permissions Language (OPL) write permissions and rules that accommodate the following namespaces: - User - Organization - relations: owner, admin - Publication - relations: admin, editor - Article - editor, collaborator - Organization owners have full CRUD permits for all resources that belong to that Organization - Organization admins have full CRUD permits for all resources that belong to that Organization, but they cannot create new admins - Therefore, organization owners only have the permit
create_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
b
A message from kapa.ai
The Ory Permission Language (OPL) is a developer-friendly configuration language designed for defining permission models in Ory Permissions (based on Ory Keto). Let me explain its key aspects: ## What is the Ory Permission Language? The Ory Permission Language is a subset of TypeScript that allows you to configure scalable authorization rules for Ory Network and Ory Open Source [What is the Ory Permission Language](https://www.ory.sh/blog/what-is-the-ory-permission-language). It was created to address the need for a familiar, easy-to-understand language for defining permission models. ## Key Features - TypeScript-based: OPL is defined as a non-Turing-complete subset of TypeScript, making it familiar to many developers [What is the Ory Permission Language](https://www.ory.sh/blog/what-is-the-ory-permission-language) - Strong typing: Uses TypeScript's type system to help guide users in understanding their permission model - IDE support: Offers syntax highlighting and auto-completion in many editors through TypeScript type declarations - Testable: Allows unit testing of permission rules with test runners like Jest ## How It Works OPL allows you to define: 1. Namespaces: Classes that implement the
Namespace
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:
Copy code
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)
I'll create an Ory Permission Language (OPL) model that implements the hierarchical permission structure you've described. This will use the Ory Permission Language, which is a subset of TypeScript designed for defining permission models.
Copy code
typescript 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.
c
Now show how to check if
User:123
has
read
permssions on
Article:abc
using the Ory Golang Client
@chilly-king-10285 Now show how to check if
User:123
has
read
permssions on
Article:abc
using the Ory Golang Client
b
A message from kapa.ai
I'll show you how to check if
User:123
has
read
permissions on
Article:abc
using the Ory Golang Client. Here's a complete example:
Copy code
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.
c
@chilly-king-10285 Update this to show that they have the
read
permit because they’re an
editor
of
Article:abc
, not because they have a direct
read
relationship.
b
A message from kapa.ai
I'll update the example to show how to check if
User: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.
Copy code
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:
Copy code
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.
c
@#kapa
@chilly-king-10285 UPdate the example to show a batch check of permissions for the
read
and
read_status
permits.
b
A message from kapa.ai
I'll update the example to show how to perform a batch check of permissions for both
read
and
read_status
permits using the Ory Golang Client:
Copy code
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:
Copy code
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).
c
@chilly-king-10285 cannot use &namespace (value of type *string) as string value in struct literal
b
A message from kapa.ai
It looks like you're encountering a Go type error when trying to use the Ory client for batch permission checks. The error suggests you're trying to use a pointer to a string (
*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:
Copy code
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.
c
@chilly-king-10285 cannot use tuple.Subject (variable of type string) as *string value in struct literal
b
A message from kapa.ai
I see you're encountering another type error when working with the Ory client for permission checks. This error indicates that you're trying to use a string value where a pointer to a string is expected. Based on the error message, it seems the
Relationship
struct in the Ory client expects pointer types for some fields. Let me provide a corrected example for batch permission checks:
Copy code
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).
c
@chilly-king-10285 Why is this code both dereferencing and extablishing a pointer to the vars: Namespace(*&namespace). Object(*&object). Relation(*&relation). SubjectId(*&subjectId).Execute()
b
A message from kapa.ai
The code pattern
Namespace(*&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:
Copy code
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:
Copy code
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.
c
@chilly-king-10285 I have the following code, but I’m getting an error that the SubjectId is expecting a pointer: // Create batch check request var checkTuples []ory.Relationship for _, tuple := range tuples { namespace := “Article” checkTuple := ory.Relationship{ Namespace: namespace, Object: tuple.Object, Relation: tuple.Relation, SubjectId: &tuple.Subject, } 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()) }
b
A message from kapa.ai
I can see the issue in your code. The problem is that you're mixing string values and string pointers in the
Relationship
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:
Copy 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.
c
@chilly-king-10285 I posted the wrong code, this is where i”m getting the SubjectId pointer error: // Create batch check request var checkTuples []ory.Relationship for _, tuple := range tuples { namespace := “Article” checkTuple := ory.Relationship{ Namespace: namespace, Object: tuple.Object, Relation: tuple.Relation, SubjectId: tuple.Subject, } 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()) }
b
A message from kapa.ai
I see the issue in your code. The
Relationship
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:
Copy 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.
c
@chilly-king-10285 When I make them all pointers, I get the following errors: cannot use &namespace (value of type *string) as string value in struct literal
b
A message from kapa.ai
I see the issue you're encountering. There seems to be a type mismatch in the Ory client's API. Let me help you resolve this. The error message "cannot use &namespace (value of type *string) as string value in struct literal" indicates that the
Relationship
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:
Copy code
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:
Copy code
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!
c
@chilly-king-10285 This relationship exists in Ory Permissions: Object“Articleabcdefg12345”, Relation:“editors”, Subject“Userfdff2bff-7604-4a58-baf7-550ce7ae4419” And I’m checking for these permit: Object“Articleabcdefg12345", Relation:“read”, Subject“Userfdff2bff-7604-4a58-baf7-550ce7ae4419" Object“Articleabcdefg12345”, Relation:“read_author_id”, Subject“Userfdff2bff-7604-4a58-baf7-550ce7ae4419” But I’m getting this result: &client.BatchCheckPermissionResult{ Results: { { Allowed: false, Error: (*string)(nil), AdditionalProperties: { }, }, { Allowed: false, Error: (*string)(nil), AdditionalProperties: { }, }, }, AdditionalProperties: { }, } Here is my OPL code: import { Namespace, Context } from “@ory/keto-namespace-types” class User implements Namespace { } class Organization implements Namespace { related: { owners: User[] admins: User[] } permits = { create: (ctx: Context): boolean => this.related.owners.includes(ctx.subject) || this.related.admins.includes(ctx.subject), 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), 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), } } class Publication implements Namespace { related: { organizations: Organization[] admins: User[] editors: User[] } permits = { 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)), 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)), 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)), } } class Article implements Namespace { related: { publications: Publication[] editors: User[] collaborators: User[] } permits = { 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)), 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)), 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)), 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)), } }
b
A message from kapa.ai
I can see why you're getting
false
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:
Copy code
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`:
Copy code
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.
c
@chilly-king-10285 But my OPL code does check if the Subject has the
editors
relationship, so this should work: read: (ctx: Context): boolean => this.related.editors.includes(ctx.subject) ||
b
A message from kapa.ai
You're absolutely right. Your OPL code does include a direct check for the
editors
relationship in both the
read
and
read_author_id
permits:
Copy code
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.