ory / keto

Open Source (Go) implementation of "Zanzibar: Google's Consistent, Global Authorization System". Ships gRPC, REST APIs, newSQL, and an easy and granular permission language. Supports ACL, RBAC, and other access models.

Home Page:https://www.ory.sh/?utm_source=github&utm_medium=banner&utm_campaign=keto

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

OPL: nested traverse are not allowed

zepatrik opened this issue · comments

Preflight checklist

Describe the bug

It is not allowed to have nested traverse expressions.

Reproducing the bug

See the permits section of the S3ResourceType, the can_create permission has two nested traverse calls.

class Role implements Namespace {
  related: {
    principal: Project[]
  }
  permits = {
    can_assume: (ctx: Context) => this.related.principal.traverse((p) => p.related.access.includes(ctx.subject))
  }
}

class Policy implements Namespace {
  related: {
    attach: Role[]
  }
}

class S3ResourceType implements Namespace {
  related: {
    create: Policy[]
    edit: Policy[]
    read: Policy[]
  }

  permits = {
    can_create: (ctx: Context) => this.related.create.traverse((p) => p.related.attach.traverse((r) => r.permits.can_assume(ctx)))
  }
}

This is however only a limitation on the OPL level. The same result can be achieved by using:

class Policy implements Namespace {
  related: {
    attach: Role[]
  }

  permits = {
    can_create_tmp: (ctx: Context) => this.related.attach.traverse((r) => r.permits.can_assume(ctx))
  }
}

class S3ResourceType implements Namespace {
  related: {
    create: Policy[]
    edit: Policy[]
    read: Policy[]
  }

  permits = {
    can_create: (ctx: Context) => this.related.create.traverse((p) => p.can_create_tmp(ctx)),
  }
}

which is a lot more complex and harder to manage.

Relevant log output

No response

Relevant configuration

No response

Version

v0.10.0

On which operating system are you observing this issue?

No response

In which environment are you deploying?

No response

Additional Context

No response

I have a bit more condensed version of this bug.

OPL:

class Project implements Namespace {
  related: {
    companies: Company[]
  }

  permits = {
    view: (ctx: Context) => this.related.companies.traverse(company => company.related.users.includes(ctx.subject))
  }
}

class User implements Namespace {}

class Company implements Namespace {
  related: {
    users: User[]
  }
}

So User has view permit on Project if he is in Company.

Relations:

{
	"namespace": "Company",
	"object": "zenmo",
	"relation": "users",
	"subject_id": "User:erik"
},
{
	"namespace": "Project",
	"object": "myproj",
	"relation": "companies",
	"subject_id": "Company:zenmo"
}

Permission check which returns false but should return true:

POST /relation-tuples/check/openapi
{
    "namespace": "Project",
    "object": "myproj",
    "relation": "view",
    "subject_id": "User:erik"
}

Could I ask where Keto OPL currently sits with nested traversals?

I just started using Keto again after a few months break, and spent a painful amount of time trying to get the following to work, only to find this limitation and issue via Slack 😅

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

class User implements Namespace {}

class Team implements Namespace {
    related: {
        owners: User[]
        members: User[]
    }

    permits = {
        delete: (ctx: Context): boolean => this.related.owners.includes(ctx.subject),
        manage: (ctx: Context): boolean => this.related.owners.includes(ctx.subject),
    }
}

class Project implements Namespace {
    related: {
        teams: Team[]
    }

    permits = {
        manage: (ctx: Context): boolean => this.related.teams.traverse((t) => t.permits.manage(ctx)),
        contribute: (ctx: Context): boolean => this.permits.manage(ctx),
    }
}

class File implements Namespace {
    related: {
        projects: Project[]
    }

    permits = {
        write: (ctx: Context): boolean => this.related.projects.traverse((p) => p.permits.manage(ctx)),
        read: (ctx: Context): boolean => this.related.projects.traverse((p) => p.permits.contribute(ctx)),
    }
}

With the following relations

[
  {
    "namespace": "Team",
    "object": "my_team",
    "relation": "owners",
    "subject_id": "User:taylor"
  },
  {
    "namespace": "Project",
    "object": "my_project",
    "relation": "teams",
    "subject_id": "Team:my_team"
  },
  {
    "namespace": "File",
    "object": "my_file",
    "relation": "projects",
    "subject_id": "Project:my_project"
  }
]

If I make the following requests, I get allowed: false

GET /relation-tuples/check
{
    "namespace": "Project",
    "object": "my_project",
    "relation": "manage",
    "subject_id": "User:taylor"
 }

GET /relation-tuples/check
{
    "namespace": "File",
    "object": "my_file",
    "relation": "write",
    "subject_id": "User:taylor"
 }

Yet, if I do the following, I get allowed: true

GET /relation-tuples/check
{
   "namespace": "Team",
   "object": "my_team",
   "relation": "manage",
   "subject_id": "User:taylor"
}

Any concrete information/status on this would be really useful, thanks!