Hello community :wave: How well does the Ory Permi...
# talk-keto
s
Hello community 👋 How well does the Ory Permission Language work for bigger models? E.g. when you have quite a lot of relations and/or permissions for one namespace? We figured it can get quite hard to maintain and review when you have 50 lines of permissions, all looking fairly similar except of minor changes. We are currently tinkering how we can improve OPL there, so any input would be helpful!
Lets say you want to define permissions for a project. A project contains identities, permissions, and other objects. We want to now define that someone has access to all parts of the project, or only some of them. First draft:
Copy code
class Project implements Namespace {
  related: {
    reader: User[]
    writer: User[]
    identityReader: User[]
    identityWriter: User[]
    permissionReader: User[]
    permissionWriter: User[]
  }
  
  permits = {
    "identities.read": (ctx: Context) =>
      this.related.reader.includes(ctx.subject) ||
      this.related.identityReader.includes(ctx.subject),
    "identities.write": (ctx: Context) =>
      this.related.writer.includes(ctx.subject) ||
      this.related.identityWriter.includes(ctx.subject),
    "permissions.read": (ctx: Context) =>
      this.related.reader.includes(ctx.subject) ||
      this.related.permissionReader.includes(ctx.subject),
    "permissions.write": (ctx: Context) =>
      this.related.writer.includes(ctx.subject) ||
      this.related.permissionWriter.includes(ctx.subject),
  }
}
This works, but you can easily see how that gets out of hand and hard to review.
What do you think of allowing subclasses to scope relations and permissions?
Copy code
class Identity extends SubNamespace<Project> {
  related: {
    reader: User[]
    writer: User[]
  }

  permits = {
    read: (ctx: Context) =>
      this.related.reader.includes(ctx.subject) ||
      this.parent.permits.read(ctx),
    write: (ctx: Context) =>
      this.related.writer.includes(ctx.subject) ||
      this.parent.permits.write(ctx),
  }
}

class Project implements Namespace {
  embed: {
    identities: Identity[],
  }

  related: {
    reader: User[]
    writer: User[]
  }

  permits = {
    read: (ctx: Context) => this.related.reader.includes(ctx.subject),
    write: (ctx: Context) => this.related.writer.includes(ctx.subject),
  }
}
The difference is now that relations of subnamespaces cannot be created, but instead one has to create the relation
identities.reader
. Same with permissions, on the API level they get flattened. Do you think that would help you with bigger models? Also consider that we might create an import mechanism down the road.
Technically we don't need the
embed
field, as the project would probably not want to use identity relations or permissions, but maybe there is a use-case where it would? This way we have kind of a circular dependency between identites and projects, as both can reference the other.