bland-finland-95044
06/09/2025, 6:38 PMrelated
section of a permission model. How can I accomplish something similar to this?
class Feature implements Namespace {
related: {
// Feature requires entitlement
entitled: boolean;
// Users or customer entitled for this Feature
subscribers: (User | SubjectSet<Customer, "members">)[];
// Feature requires flag
flagged: boolean;
// Feature flag is enabled for these users
enabled_for: (User | SubjectSet<Customer, "members">)[];
}
permits = {
subscribe: (ctx: Context): boolean => !this.related.entitled || this.related.subscribers.includes(ctx.subject),
enabled_for: (ctx: Context): boolean => !this.related.flagged || this.related.enabled_for.includes(ctx.subject),
// Feature flag is enabled AND feature is entitled
activated_for: (ctx: Context): boolean => this.permits.enabled_for(ctx) && this.permits.subscribe(ctx)
};
}
brief-egg-61061
06/09/2025, 6:38 PMbrief-egg-61061
06/09/2025, 6:38 PMrelated
section can only declare relationships to other namespaces (i.e., arrays of subjects or subject sets), not primitive types like boolean
. The OPL is designed around expressing relationships between entities, and all relations must be many-to-many and reference other namespaces or subject sets, not simple values or flags [Ory Permission Language specification](https://www.ory.sh/docs/keto/reference/ory-permission-language#statements). To achieve similar logic to a boolean flag, you should model the "entitled" or "flagged" state as a relationship. For example, instead of a boolean entitled
, you could have a relation like entitled_for: (User | SubjectSet<Customer, "members">)[]
, and then check if the subject is in that relation. If you want to represent a "global" flag, you could use a special subject (e.g., a "system" user or group) to indicate that the feature is globally enabled. Here’s how you might refactor your model: typescript class Feature implements Namespace { related: { // Users or customers entitled for this Feature entitled_for: (User | SubjectSet<Customer, "members">)[]; // Users or customers for whom the feature flag is enabled enabled_for: (User | SubjectSet<Customer, "members">)[]; } permits = { subscribe: (ctx: Context): boolean => this.related.entitled_for.includes(ctx.subject), enabled_for: (ctx: Context): boolean => this.related.enabled_for.includes(ctx.subject), // Feature flag is enabled AND feature is entitled activated_for: (ctx: Context): boolean => this.permits.enabled_for(ctx) && this.permits.subscribe(ctx), }; }
If you want to support a "default allow" (i.e., if no one is entitled, then everyone is), you could model this by adding a special subject (like a "public" group) to the relation, or by handling the "empty" case in your application logic, since OPL itself does not support conditional logic based on the presence or absence of relationships [Ory Permission Language specification](https://www.ory.sh/docs/keto/reference/ory-permission-language#statements). In summary: - You cannot use booleans in related
. - Model flags as relationships to special subjects or groups. - All permission logic must be expressed in terms of relationships and set membership. For more details, see the Ory Permission Language specification and examples [Ory Permission Language specification](https://www.ory.sh/docs/keto/reference/ory-permission-language#statements) [OPL Examples](https://www.ory.sh/docs/keto/reference/ory-permission-language#examples).bland-finland-95044
06/09/2025, 7:12 PMbrief-egg-61061
06/09/2025, 7:12 PMbrief-egg-61061
06/09/2025, 7:12 PMtraverse
function is used to iterate over an array of related objects (such as parents, folders, or other namespaces) and evaluate a permission or condition for each element. It returns true
if at least one of the elements in the array satisfies the provided condition (i.e., the callback function returns true
for that element). For example, in the following OPL snippet: typescript class Document implements Namespace { related: { parents: Folder[]; }; permits = { view: (ctx: Context): boolean => this.related.parents.traverse((parent) => parent.permits.view(ctx)), }; }
Here, this.related.parents.traverse((parent) => parent.permits.view(ctx))
checks if any parent folder grants the view
permission to the subject in the context. If at least one parent folder allows viewing, the function returns true
[OPL Examples](https://www.ory.sh/docs/keto/reference/ory-permission-language#examples). This is similar to the Array.prototype.some()
method in JavaScript/TypeScript, where the function returns true
if the callback returns true
for any element in the array. Summary: - traverse
iterates over related entities. - Returns true
if the callback returns true
for any element. - Commonly used to check permissions up a hierarchy or across related objects [OPL Examples](https://www.ory.sh/docs/keto/reference/ory-permission-language#examples).bland-finland-95044
06/09/2025, 7:16 PMrelated
section of type declarations defines relations. Unlike regular TypeScript, RelationName
must be a unique identifier used as the relation strings in the tuples.
Does that mean unique to the namespace or unique globally?brief-egg-61061
06/09/2025, 7:16 PMbrief-egg-61061
06/09/2025, 7:16 PMRelationName
must be a unique identifier refers to uniqueness within the namespace, not globally. Each namespace (represented as a class) defines its own set of relations, and the relation names must be unique within that class. This is supported by the documentation, which states: > The related
section of type declarations defines relations. Unlike regular TypeScript, RelationName
must be a unique identifier used as the relation strings in the tuples. The TypeName
must be the name of another type
that is defined above or below (in TypeScript: a class that implements Namespace
) [Ory Permission Language specification](https://www.ory.sh/docs/keto/reference/ory-permission-language#statements). There is no indication in the documentation that relation names must be unique across all namespaces—only within the context of each namespace's definition.many-dawn-19625
06/10/2025, 5:39 AMbrief-egg-61061
06/10/2025, 5:39 AMbrief-egg-61061
06/10/2025, 5:39 AM/admin/relation-tuples
endpoint, you need to provide at least the following fields in the request body: namespace
, object
, and relation
. Additionally, you must specify either subject_id
or a subject_set
. This is the minimum required to define a relationship tuple in Ory Keto. Here is an example of the minimal JSON payload for the /admin/relation-tuples
endpoint: json { "namespace": "feature", "object": "my-feature", "relation": "entitled_for", "subject_id": "user-alice" }
This tuple states that the subject with ID user-alice
is entitled for the feature my-feature
in the feature
namespace. You can also use a subject_set
instead of subject_id
if you want to refer to a group or another set of subjects: json { "namespace": "feature", "object": "my-feature", "relation": "entitled_for", "subject_set": { "namespace": "customer", "object": "customer-123", "relation": "members" } }
This tuple states that all members of customer-123
are entitled for the feature my-feature
. These examples follow the requirements described in the API reference for the /admin/relation-tuples
endpoint [Ory Keto API - createRelationship](https://www.ory.sh/docs/keto/reference/rest-api#createrelationship).