keto icon indicating copy to clipboard operation
keto copied to clipboard

OPL permissions have inconsistencies when using this.permits(ctx) vs calling a transitive permits directly

Open AustinCase opened this issue 1 year ago • 1 comments

Preflight checklist

Ory Network Project

No response

Describe the bug

When using the OPL file, the this.permits(ctx) not translating into the proper permissions structure when calling self that calls a transitive relation.

Reproducing the bug

The following example returns different results when calling the check permission api for read relation then the update, delete, create_media_stack, and list_media_stacks. The read relation returns true while all others return false. We have other Namespaces that do NOT exhibit this behavior. They work as expected.

class Bin implements Namespace {
  related: {
    projects: Project[];
  };
  permits = {
    read: (ctx: Context): boolean => this.related.projects.transitive((project) => project.permits.list_bins(ctx)),
    update: (ctx: Context): boolean => this.permits.read(ctx),
    delete: (ctx: Context): boolean => this.permits.read(ctx),
    create_media_stack: (ctx: Context): boolean => this.permits.read(ctx),
    list_media_stacks: (ctx: Context): boolean => this.permits.read(ctx),
  };
}

When changing the above to the following, everything works as expected:

class Bin implements Namespace {
  related: {
    projects: Project[];
  };
  permits = {
    read: (ctx: Context): boolean => this.related.projects.transitive((project) => project.permits.list_bins(ctx)),
    update: (ctx: Context): boolean => this.related.projects.transitive((project) => project.permits.list_bins(ctx)),
    delete: (ctx: Context): boolean => this.related.projects.transitive((project) => project.permits.list_bins(ctx)),
    create_media_stack: (ctx: Context): boolean => this.related.projects.transitive((project) => project.permits.list_bins(ctx))),
    list_media_stacks: (ctx: Context): boolean => this.related.projects.transitive((project) => project.permits.list_bins(ctx)),
  };
}

Relevant log output

No response

Relevant configuration

The entirety of the OPL file ( that is relevant to this issue ) is copied below

import { Namespace, Context } from "@ory/keto-namespace-types";

class User implements Namespace {}

class Workspace implements Namespace {
  related: {
    owners: User[];
    admins: User[];
    members: User[];
  };
  permits = {
    read: (ctx: Context): boolean =>
      this.related.owners.includes(ctx.subject) ||
      this.related.admins.includes(ctx.subject) ||
      this.related.members.includes(ctx.subject),
    update: (ctx: Context): boolean =>
      this.related.owners.includes(ctx.subject) || this.related.admins.includes(ctx.subject),
    deactivate: (ctx: Context): boolean => this.related.owners.includes(ctx.subject),
    transfer_ownership: (ctx: Context): boolean => this.related.owners.includes(ctx.subject),
    manage_members: (ctx: Context): boolean => this.permits.update(ctx),
    create_project: (ctx: Context): boolean => this.permits.read(ctx),
    list_projects: (ctx: Context): boolean => this.permits.read(ctx),
  };
}

class Project implements Namespace {
  related: {
    workspaces: Workspace[];
    admins: User[];
    members: User[];
  };
  permits = {
    read: (ctx: Context): boolean =>
      this.related.workspaces.traverse((workspace) => workspace.permits.list_projects(ctx)) ||
      this.related.admins.includes(ctx.subject) ||
      this.related.members.includes(ctx.subject),
    update: (ctx: Context): boolean =>
      this.related.workspaces.traverse((workspace) => workspace.permits.list_projects(ctx)) ||
      this.related.admins.includes(ctx.subject),
    delete: (ctx: Context): boolean =>
      this.related.workspaces.traverse((workspace) => workspace.permits.list_projects(ctx)) ||
      this.related.admins.includes(ctx.subject),
    invite: (ctx: Context): boolean =>
      this.related.workspaces.traverse((workspace) => workspace.permits.manage_members(ctx)) ||
      this.related.admins.includes(ctx.subject),
    create_bin: (ctx: Context): boolean => this.permits.read(ctx),
    list_bins: (ctx: Context): boolean => this.permits.read(ctx),
    analyze_assets: (ctx: Context): boolean => this.permits.read(ctx),
    transcribe_assets: (ctx: Context): boolean => this.permits.read(ctx),
  };
}

class Bin implements Namespace {
  related: {
    projects: Project[];
  };
  permits = {
    read: (ctx: Context): boolean => this.related.projects.transitive((project) => project.permits.list_bins(ctx)),
    update: (ctx: Context): boolean => this.permits.read(ctx),
    delete: (ctx: Context): boolean => this.permits.read(ctx),
    create_media_stack: (ctx: Context): boolean => this.permits.read(ctx),
    list_media_stacks: (ctx: Context): boolean => this.permits.read(ctx),
  };
}

Version

0.13.0-alpha.0

On which operating system are you observing this issue?

macOS

In which environment are you deploying?

Other

Additional Context

This is in our devenv nix environment, same behavior with docker. Postgres backend.

AustinCase avatar Nov 13 '24 16:11 AustinCase

I just noticed this issue as well. I couldn't wait for a new release anymore to test batch permission checks 🤗 Is there any way to help here? I am not a seasoned Go developer but i'm willing to learn, and that could help if someone is hinting me with a good place to start looking at the code.

getlarge avatar Feb 05 '25 16:02 getlarge