diff --git a/.gitignore b/.gitignore index 9e553e6f..180370b3 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,7 @@ bin gradle/wrapper !gradle/wrapper/gradle-wrapper.jar !gradle/wrapper/gradle-wrapper.properties + +# Eclipse/Buildship +.project +.settings/ diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 00000000..6d2aae2f --- /dev/null +++ b/examples/README.md @@ -0,0 +1,130 @@ +# Authzed Java Client Examples + +This directory contains examples demonstrating how to use the Authzed Java client library from various JVM languages. + +## Available Examples + +### Java Examples (`v1/`) +- `CallingCheck.java` - Demonstrates basic permission checking with schema writing, relationship creation, and permission checks +- `CallingWatch.java` - Demonstrates watching for changes in SpiceDB with automatic reconnection + +### Kotlin Examples (`kotlin/`) +- `CallingCheck.kt` - Same functionality as the Java example, but using idiomatic Kotlin syntax + +### Scala Examples (`scala/`) +- `CallingCheck.scala` - Same functionality as the Java example, but using idiomatic Scala syntax + +## Installation + +The Authzed Java client library works seamlessly with all JVM languages. Choose your build tool and language: + +**Note:** You need to include a gRPC transport implementation like `grpc-netty-shaded` to run these examples. The `authzed` library doesn't include a transport by default to give you flexibility in choosing one. + +### Maven (Java) + +```xml + + + com.authzed.api + authzed + 1.5.4 + + + io.grpc + grpc-api + 1.75.0 + + + io.grpc + grpc-stub + 1.75.0 + + + io.grpc + grpc-netty-shaded + 1.75.0 + + +``` + +### Gradle (Java/Kotlin) + +For `build.gradle`: +```groovy +dependencies { + implementation "com.authzed.api:authzed:1.5.4" + implementation 'io.grpc:grpc-api:1.75.0' + implementation 'io.grpc:grpc-stub:1.75.0' + implementation 'io.grpc:grpc-netty-shaded:1.75.0' +} +``` + +For `build.gradle.kts` (Kotlin DSL): +```kotlin +dependencies { + implementation("com.authzed.api:authzed:1.5.4") + implementation("io.grpc:grpc-api:1.75.0") + implementation("io.grpc:grpc-stub:1.75.0") + implementation("io.grpc:grpc-netty-shaded:1.75.0") +} +``` + +### SBT (Scala) + +```scala +libraryDependencies ++= Seq( + "com.authzed.api" % "authzed" % "1.5.4", + "io.grpc" % "grpc-api" % "1.75.0", + "io.grpc" % "grpc-stub" % "1.75.0", + "io.grpc" % "grpc-netty-shaded" % "1.75.0" +) +``` + +## Usage + +All examples follow the same pattern: + +1. **Create a channel** - Connect to SpiceDB using gRPC +2. **Authenticate** - Use a bearer token for authentication +3. **Create service stubs** - Initialize the schema and permissions services +4. **Write a schema** - Define your permission model +5. **Write relationships** - Create relationships between objects +6. **Check permissions** - Verify if a subject has permission on a resource + +### Key Differences Between Languages + +#### Java +- Uses verbose builder pattern +- Requires explicit exception handling with try-catch +- Traditional class structure + +#### Kotlin +- More concise syntax with named parameters and string interpolation +- Better null safety with nullable types (`?`) +- Can use `try-catch` as expressions +- Cleaner lambda syntax + +#### Scala +- Functional programming style with `Try` and `Option` monads +- Pattern matching with `recover` +- For-comprehensions for sequential operations (not shown in basic example) +- Immutable by default + +## Running the Examples + +These examples are meant to be copied into your own project. They demonstrate the API usage patterns but are not standalone runnable programs without: + +1. A valid SpiceDB instance (local or cloud) +2. A valid authentication token +3. Proper build configuration for your language + +## Getting Started + +For a complete guide on integrating SpiceDB with your application, see the [Protecting Your First App](https://authzed.com/docs/guides/first-app) guide. + +## Additional Resources + +- [Authzed Java Client Documentation](https://authzed.github.io/authzed-java/index.html) +- [SpiceDB API Reference](https://authzed.com/docs/spicedb/api/http-api) +- [Buf Registry SpiceDB API](https://buf.build/authzed/api/docs/main) +- [SpiceDB Documentation](https://authzed.com/docs) diff --git a/examples/kotlin/CallingCheck.kt b/examples/kotlin/CallingCheck.kt new file mode 100644 index 00000000..480df07b --- /dev/null +++ b/examples/kotlin/CallingCheck.kt @@ -0,0 +1,188 @@ +/* + * Authzed API examples - Kotlin + * + * This example demonstrates how to use the Authzed Java client library from Kotlin. + * It shows the more idiomatic Kotlin syntax while using the same Java client library. + */ + +import com.authzed.api.v1.* +import com.authzed.grpcutil.BearerToken +import io.grpc.ManagedChannel +import io.grpc.ManagedChannelBuilder +import java.util.concurrent.TimeUnit +import java.util.logging.Level +import java.util.logging.Logger + +// Installation +// Add to your build.gradle.kts: +// dependencies { +// implementation("com.authzed.api:authzed:1.5.4") +// implementation("io.grpc:grpc-api:1.75.0") +// implementation("io.grpc:grpc-stub:1.75.0") +// implementation("io.grpc:grpc-netty-shaded:1.75.0") +// } + +class AuthzedClient(channel: ManagedChannel, token: String) { + private val logger = Logger.getLogger(AuthzedClient::class.java.name) + private val bearerToken = BearerToken(token) + + private val schemaService: SchemaServiceGrpc.SchemaServiceBlockingStub = + SchemaServiceGrpc.newBlockingStub(channel) + .withCallCredentials(bearerToken) + + private val permissionsService: PermissionsServiceGrpc.PermissionsServiceBlockingStub = + PermissionsServiceGrpc.newBlockingStub(channel) + .withCallCredentials(bearerToken) + + fun writeSchema(): String { + logger.info("Writing schema...") + + val schema = """ + definition thelargeapp/article { + relation author: thelargeapp/user + relation commenter: thelargeapp/user + + permission can_comment = commenter + author + } + + definition thelargeapp/user {} + """.trimIndent() + + val request = WriteSchemaRequest.newBuilder() + .setSchema(schema) + .build() + + return try { + val response = schemaService.writeSchema(request) + logger.info("Response: $response") + response.toString() + } catch (e: Exception) { + logger.log(Level.WARNING, "RPC failed: ${e.message}") + "" + } + } + + fun readSchema(): String { + logger.info("Reading schema...") + + val request = ReadSchemaRequest.newBuilder().build() + + return try { + val response = schemaService.readSchema(request) + logger.info(response.toString()) + response.toString() + } catch (e: Exception) { + logger.log(Level.WARNING, "RPC failed: ${e.message}") + "" + } + } + + fun writeRelationship(): String { + logger.info("Write relationship...") + + val request = WriteRelationshipsRequest.newBuilder() + .addUpdates( + RelationshipUpdate.newBuilder() + .setOperation(RelationshipUpdate.Operation.OPERATION_CREATE) + .setRelationship( + Relationship.newBuilder() + .setResource( + ObjectReference.newBuilder() + .setObjectType("thelargeapp/article") + .setObjectId("kotlin_test") + .build() + ) + .setRelation("author") + .setSubject( + SubjectReference.newBuilder() + .setObject( + ObjectReference.newBuilder() + .setObjectType("thelargeapp/user") + .setObjectId("george") + .build() + ) + .build() + ) + .build() + ) + .build() + ) + .build() + + return try { + val response = permissionsService.writeRelationships(request) + logger.info("Response: $response") + response.writtenAt.token + } catch (e: Exception) { + logger.log(Level.WARNING, "RPC failed: ${e.message}") + "" + } + } + + fun check(zedToken: ZedToken): CheckPermissionResponse.Permissionship? { + logger.info("Checking...") + + val request = CheckPermissionRequest.newBuilder() + .setConsistency( + Consistency.newBuilder() + .setAtLeastAsFresh(zedToken) + .build() + ) + .setResource( + ObjectReference.newBuilder() + .setObjectType("thelargeapp/article") + .setObjectId("kotlin_test") + .build() + ) + .setSubject( + SubjectReference.newBuilder() + .setObject( + ObjectReference.newBuilder() + .setObjectType("thelargeapp/user") + .setObjectId("george") + .build() + ) + .build() + ) + .setPermission("can_comment") + .build() + + return try { + val response = permissionsService.checkPermission(request) + logger.info("Response: $response") + response.permissionship + } catch (e: Exception) { + logger.log(Level.WARNING, "RPC failed: ${e.message}") + null + } + } +} + +fun main() { + val target = "grpc.authzed.com:443" + val token = "tc_test_def_token" + + val channel = ManagedChannelBuilder + .forTarget(target) + .useTransportSecurity() // for local development without TLS, use .usePlaintext() + .build() + + try { + val client = AuthzedClient(channel, token) + + client.writeSchema() + client.readSchema() + + val tokenVal = client.writeRelationship() + + val result = client.check( + ZedToken.newBuilder() + .setToken(tokenVal) + .build() + ) + + println("Check result: $result") + } finally { + channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS) + } +} diff --git a/examples/scala/CallingCheck.scala b/examples/scala/CallingCheck.scala new file mode 100644 index 00000000..36b67b41 --- /dev/null +++ b/examples/scala/CallingCheck.scala @@ -0,0 +1,193 @@ +/* + * Authzed API examples - Scala + * + * This example demonstrates how to use the Authzed Java client library from Scala. + * It shows the more idiomatic Scala syntax while using the same Java client library. + */ + +import com.authzed.api.v1._ +import com.authzed.grpcutil.BearerToken +import io.grpc.{ManagedChannel, ManagedChannelBuilder} +import java.util.concurrent.TimeUnit +import java.util.logging.{Level, Logger} +import scala.util.Try + +// Installation +// Add to your build.sbt: +// libraryDependencies ++= Seq( +// "com.authzed.api" % "authzed" % "1.5.4", +// "io.grpc" % "grpc-api" % "1.75.0", +// "io.grpc" % "grpc-stub" % "1.75.0", +// "io.grpc" % "grpc-netty-shaded" % "1.75.0" +// ) + +class AuthzedClient(channel: ManagedChannel, token: String) { + private val logger = Logger.getLogger(classOf[AuthzedClient].getName) + private val bearerToken = new BearerToken(token) + + private val schemaService: SchemaServiceGrpc.SchemaServiceBlockingStub = + SchemaServiceGrpc.newBlockingStub(channel) + .withCallCredentials(bearerToken) + + private val permissionsService: PermissionsServiceGrpc.PermissionsServiceBlockingStub = + PermissionsServiceGrpc.newBlockingStub(channel) + .withCallCredentials(bearerToken) + + def writeSchema(): String = { + logger.info("Writing schema...") + + val schema = + """definition thelargeapp/article { + | relation author: thelargeapp/user + | relation commenter: thelargeapp/user + | + | permission can_comment = commenter + author + |} + | + |definition thelargeapp/user {} + |""".stripMargin + + val request = WriteSchemaRequest.newBuilder() + .setSchema(schema) + .build() + + Try { + val response = schemaService.writeSchema(request) + logger.info(s"Response: $response") + response.toString + }.recover { + case e: Exception => + logger.log(Level.WARNING, s"RPC failed: ${e.getMessage}") + "" + }.get + } + + def readSchema(): String = { + logger.info("Reading schema...") + + val request = ReadSchemaRequest.newBuilder().build() + + Try { + val response = schemaService.readSchema(request) + logger.info(response.toString) + response.toString + }.recover { + case e: Exception => + logger.log(Level.WARNING, s"RPC failed: ${e.getMessage}") + "" + }.get + } + + def writeRelationship(): String = { + logger.info("Write relationship...") + + val request = WriteRelationshipsRequest.newBuilder() + .addUpdates( + RelationshipUpdate.newBuilder() + .setOperation(RelationshipUpdate.Operation.OPERATION_CREATE) + .setRelationship( + Relationship.newBuilder() + .setResource( + ObjectReference.newBuilder() + .setObjectType("thelargeapp/article") + .setObjectId("scala_test") + .build() + ) + .setRelation("author") + .setSubject( + SubjectReference.newBuilder() + .setObject( + ObjectReference.newBuilder() + .setObjectType("thelargeapp/user") + .setObjectId("george") + .build() + ) + .build() + ) + .build() + ) + .build() + ) + .build() + + Try { + val response = permissionsService.writeRelationships(request) + logger.info(s"Response: $response") + response.getWrittenAt.getToken + }.recover { + case e: Exception => + logger.log(Level.WARNING, s"RPC failed: ${e.getMessage}") + "" + }.get + } + + def check(zedToken: ZedToken): Option[CheckPermissionResponse.Permissionship] = { + logger.info("Checking...") + + val request = CheckPermissionRequest.newBuilder() + .setConsistency( + Consistency.newBuilder() + .setAtLeastAsFresh(zedToken) + .build() + ) + .setResource( + ObjectReference.newBuilder() + .setObjectType("thelargeapp/article") + .setObjectId("scala_test") + .build() + ) + .setSubject( + SubjectReference.newBuilder() + .setObject( + ObjectReference.newBuilder() + .setObjectType("thelargeapp/user") + .setObjectId("george") + .build() + ) + .build() + ) + .setPermission("can_comment") + .build() + + Try { + val response = permissionsService.checkPermission(request) + logger.info(s"Response: $response") + Some(response.getPermissionship) + }.recover { + case e: Exception => + logger.log(Level.WARNING, s"RPC failed: ${e.getMessage}") + None + }.get + } +} + +object CallingCheck { + def main(args: Array[String]): Unit = { + val target = "grpc.authzed.com:443" + val token = "tc_test_def_token" + + val channel = ManagedChannelBuilder + .forTarget(target) + .useTransportSecurity() // for local development without TLS, use .usePlaintext() + .build() + + try { + val client = new AuthzedClient(channel, token) + + client.writeSchema() + client.readSchema() + + val tokenVal = client.writeRelationship() + + val result = client.check( + ZedToken.newBuilder() + .setToken(tokenVal) + .build() + ) + + println(s"Check result: $result") + } finally { + channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS) + } + } +}