osohq / oso

Deprecated: See README
Apache License 2.0
3.48k stars 177 forks source link

Oso does not support Kotlin data classes #1655

Open brizzbuzz opened 1 year ago

brizzbuzz commented 1 year ago

Hey 👋 Finally getting around to trialing Oso as an auth solution for a Kotlin application that I'm building.

However, it seems that Oso does not support Kotlin data classes :( Or, as is always possible... I'm just doing something dumb

I am trying to emulate the Java quickstart example, with a User trying to read from a repository.

I have the following models

data class Repo(
  val id: UUID,
  val name: String,
  val isPublic: Boolean
)

data class User (
  val id: UUID,
  val email: String,
  val repoRoles: List<RepoRole>
)

I have set up OSO with the following

private val oso: Oso = Oso()

init {
  // On a tangent... it doesn't seem to even load 
  // unless I explicitly repeat the class name as the second param
  oso.registerClass(Repo::class.java, "Repo")
  oso.registerClass(User::class.java, "User")
  oso.loadStr(
    """
allow(actor, action, resource) if
has_permission(actor, action, resource);

actor User {}

resource Repo {
permissions = ["read", "push", "delete"];
roles = ["contributor", "maintainer", "admin"];

"read" if "contributor";
"push" if "maintainer";
"delete" if "admin";

"maintainer" if "admin";
"contributor" if "maintainer";
}

# This rule tells Oso how to fetch roles for a Repo
has_role(actor: User, role_name: String, Repo: Repo) if
role in actor.repoRoles and
role_name = role.name and
Repo = role.Repo;

has_permission(_actor: User, "read", Repo: Repo) if
Repo.isPublic;

allow(actor, action, resource) if
has_permission(actor, action, resource);
""".trimIndent()
  )
}

Just as a test, I have created a repo with isPublic=true with name test. However, when I run the following

fun readByName(name: String): RepoModels.Response {
    val result = Repo(
      id = UUID.randomUUID(),
      name = name,
      isPublic = true
    )
    val user = User(
      id = UUID.randomUUID(),
      email = "admin@bkbn.io",
      repoRoles = listOf(RepoRole(role = "admin", repo = result))
    )
    oso.authorize(user, "read", result)
    return RepoModels.Response.fromRepo(result)
  }

I get an authorization error from oso

com.osohq.oso.Exceptions$NotFoundException: Oso NotFoundException -- The current user does not have permission to read the given resource. You should handle this error by returning a 404 error to the client.
    at com.osohq.oso.Oso.authorize(Oso.java:110)
    at com.osohq.oso.Oso.authorize(Oso.java:118)
    at io.bkbn.sourdough.api.service.RepoService.readByName(RepoService.kt:81)
        // ...

If it helps, I have pushed all of this code to a repo https://github.com/bkbnio/oso-poc Instructions in the README for how to run the app. If you have any issues with getting it set up just let me know :)

You can emulate this error by running GET localhost:8080/repo?name=test

wcurrie commented 1 year ago

Out of curiosity I tried making this work in https://github.com/osohq/oso/pull/1662.

Seems to work with some to the repo

Index: api/src/main/kotlin/io/bkbn/sourdough/api/service/RepoService.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/api/src/main/kotlin/io/bkbn/sourdough/api/service/RepoService.kt b/api/src/main/kotlin/io/bkbn/sourdough/api/service/RepoService.kt
--- a/api/src/main/kotlin/io/bkbn/sourdough/api/service/RepoService.kt  (revision e4e856f304b1c09de9f2cdcdb658dce59deffab5)
+++ b/api/src/main/kotlin/io/bkbn/sourdough/api/service/RepoService.kt  (date 1673734442646)
@@ -35,10 +35,10 @@
 }

 # This rule tells Oso how to fetch roles for a Repo
-has_role(actor: User, role_name: String, Repo: Repo) if
+has_role(actor: User, role_name: String, repo: Repo) if
   role in actor.repoRoles and
-  role_name = role.name and
-  Repo = role.Repo;
+  role_name = role.role and
+  repo = role.repo;

 has_permission(_actor: User, "read", Repo: Repo) if
   Repo.isPublic;
Index: api/build.gradle.kts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/api/build.gradle.kts b/api/build.gradle.kts
--- a/api/build.gradle.kts  (revision e4e856f304b1c09de9f2cdcdb658dce59deffab5)
+++ b/api/build.gradle.kts  (date 1673734519566)
@@ -41,7 +41,7 @@
   implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0")

   // Auth
-  implementation("com.osohq:oso:0.26.4")
+  implementation("com.osohq:oso:0.26.5-SNAPSHOT")
 }

 testing {
image

I used this test for faster feedback:

package io.bkbn.sourdough.api.service

import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.Test

internal class RepoServiceTest {
  @Test
  internal fun `can read by name`() {
    val response = RepoService.readByName("foo")
    assertEquals("foo", response.name)
  }
}