From 7582b0cef36d51ca8c1570bef4faac09bec4e405 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Cig=C3=A1nek?= Date: Tue, 6 Feb 2024 16:26:37 +0100 Subject: [PATCH] bindings/kotlin: Update to the new access mode / local secrets API --- .../org/equalitie/ouisync/Repository.kt | 137 +++++++++--------- .../kotlin/org/equalitie/ouisync/Request.kt | 43 +++--- .../kotlin/org/equalitie/ouisync/Session.kt | 1 - .../org/equalitie/ouisync/RepositoryTest.kt | 11 +- 4 files changed, 97 insertions(+), 95 deletions(-) diff --git a/bindings/kotlin/lib/src/main/kotlin/org/equalitie/ouisync/Repository.kt b/bindings/kotlin/lib/src/main/kotlin/org/equalitie/ouisync/Repository.kt index 83c0af514..19bb31cc9 100644 --- a/bindings/kotlin/lib/src/main/kotlin/org/equalitie/ouisync/Repository.kt +++ b/bindings/kotlin/lib/src/main/kotlin/org/equalitie/ouisync/Repository.kt @@ -1,5 +1,7 @@ package org.equalitie.ouisync +import org.msgpack.core.MessagePacker + /** * * A Ouisync repository. @@ -93,27 +95,6 @@ class Repository private constructor(internal val handle: Long, internal val cli return Repository(handle, client) } - - /** - * Reopen a repo using a *reopen token*. - * - * This is useful when one wants to move the repo to a different location while the repo is - * currently open without requiring the user to input the local password again. - * To do that: - * - * 1. Obtain the reopen token with [createReopenToken]. - * 2. [Close][close] the repo. - * 3. Move the repo file. - * 4. Reopen the repo with [reopen], passing it the reopen token from step 1. - * - * @see createReopenToken - */ - suspend fun reopen(session: Session, path: String, token: ByteArray): Repository { - val client = session.client - val handle = client.invoke(RepositoryReopen(path, token)) as Long - - return Repository(handle, client) - } } /** @@ -145,14 +126,6 @@ class Repository private constructor(internal val handle: Long, internal val cli */ suspend fun databaseId() = client.invoke(RepositoryDatabaseId(handle)) as ByteArray - /** - * Returns the access mode (*blind*, *read* or *write*) the repo is opened in. - */ - suspend fun accessMode(): AccessMode { - val raw = client.invoke(RepositoryAccessMode(handle)) as Byte - return AccessMode.decode(raw) - } - /** * Creates a *share token* to share this repository with other devices. * @@ -177,14 +150,6 @@ class Repository private constructor(internal val handle: Long, internal val cli return ShareToken(raw, client) } - /** - * Creates a token for reopening the repo after it's been renamed/moved. - * - * @see reopen - */ - suspend fun createReopenToken() = - client.invoke(RepositoryCreateReopenToken(handle)) as ByteArray - /** * Is local password required to read this repo? */ @@ -198,50 +163,41 @@ class Repository private constructor(internal val handle: Long, internal val cli client.invoke(RepositoryRequiresLocalPasswordForWriting(handle)) as Boolean /** - * Changes the local read password. - * - * The repo must be either opened in at least read mode or the `shareToken` must be set to a - * token with at least read access, otherwise [Error] with [ErrorCode.PERMISSION_DENIED] is - * thrown. - * - * @param password new password to set. If null, local read password is removed (the repo - * becomes readable without a password). - * @param shareToken share token to use in case the repo is currently not opened in at least - * read mode. Can be used to escalate access mode from blind to read. + * Returns the access mode (*blind*, *read* or *write*) the repo is opened in. */ - suspend fun setReadAccess(password: String?, shareToken: ShareToken? = null) = - client.invoke(RepositorySetReadAccess(handle, password, shareToken?.toString())) + suspend fun accessMode(): AccessMode { + val raw = client.invoke(RepositoryAccessMode(handle)) as Byte + return AccessMode.decode(raw) + } /** - * Changes the local read and write password. - * - * The repo must be either opened in write mode or the `shareToken` must be set to a token with - * write mode, otherwise [Error] with [ErrorCode.PERMISSION_DENIED] is thrown. - * - * To set different read and write passwords, call first this function and then [setReadAccess]. + * Switches the repository to the given access mode. + */ + suspend fun setAccessMode( + accessMode: AccessMode, + password: String?, + ) = + client.invoke(RepositorySetAccessMode(handle, accessMode, password)) + + /** + * Gets the current credentials of this repository. Can be used to restore access after + * closing and reopening the repository. * - * @param oldPassword previous local write password. This is optional and only needed if one - * wants to preserve the *writer id* of this replica. If null, this - * repository effectively becomes a different replica from the one it was - * before this function was called. This currently has no user observable - * effect apart from slight performance impact. - * @param newPassword new local read and write password to set. If null, password access is - * removed. - * @param shareToken share token to use in case the repo is currently not opened in write mode. - * Can be used to escalate access mode from blind or read to write. + * @see setCredentials */ - suspend fun setReadAndWriteAccess(oldPassword: String?, newPassword: String?, shareToken: ShareToken? = null) = - client.invoke(RepositorySetReadAndWriteAccess(handle, oldPassword, newPassword, shareToken?.toString())) + suspend fun credentials(): ByteArray = client.invoke(RepositoryCredentials(handle)) as ByteArray /** - * Removes read access to the repository using the local read password. + * Sets the current credentials of the repository. */ - suspend fun removeReadKey() = client.invoke(RepositoryRemoveReadKey(handle)) + suspend fun setCredentials(credentials: ByteArray) = client.invoke(RepositorySetCredentials(handle, credentials)) /** - * Removes write access to the repository using the local write password. + * Sets, unsets or changes local passwords for accessing the repository or disables the given + * access mode. */ - suspend fun removeWriteKey() = client.invoke(RepositoryRemoveWriteKey(handle)) + suspend fun setAccess(read: AccessChange? = null, write: AccessChange? = null) = + client.invoke(RepositorySetAccess(handle, read, write)) /** * Is Bittorrent DHT enabled? @@ -254,7 +210,7 @@ class Repository private constructor(internal val handle: Long, internal val cli * Enables/disabled Bittorrent DHT (for peer discovery). * * @see isDhtEnabled - * @see [infoHash] + * @see infoHash */ suspend fun setDhtEnabled(enabled: Boolean) = client.invoke(RepositorySetDhtEnabled(handle, enabled)) @@ -310,3 +266,42 @@ class Repository private constructor(internal val handle: Long, internal val cli suspend fun moveEntry(src: String, dst: String) = client.invoke(RepositoryMoveEntry(handle, src, dst)) } + +/** + * How to change access to a repository. + * + * @see [Repository.setAccess] + */ +sealed class AccessChange { + fun pack(packer: MessagePacker) { + when (this) { + is EnableAccess -> { + packer.packMapHeader(1) + packer.packString("enable") + + if (password != null) { + packer.packString(password) + } else { + packer.packNil() + } + } + is DiableAccess -> { + packer.packString("disabled") + } + } + } +} + +/** + * Enable read or write access, optionally with local password + * + * @see [Repository.setAccess] + */ +class EnableAccess(val password: String?) : AccessChange() + +/** + * Disable access + * + * @see [Repository.setAccess] + */ +class DiableAccess() : AccessChange() diff --git a/bindings/kotlin/lib/src/main/kotlin/org/equalitie/ouisync/Request.kt b/bindings/kotlin/lib/src/main/kotlin/org/equalitie/ouisync/Request.kt index 33721e3cf..8afd63f78 100644 --- a/bindings/kotlin/lib/src/main/kotlin/org/equalitie/ouisync/Request.kt +++ b/bindings/kotlin/lib/src/main/kotlin/org/equalitie/ouisync/Request.kt @@ -78,49 +78,59 @@ internal class RepositoryClose : ValueRequest { constructor(value: Long) : super(value) } -internal class RepositoryCreateReopenToken : ValueRequest { +internal class RepositorySubscribe : ValueRequest { constructor(value: Long) : super(value) } -internal class RepositoryReopen(val path: String, val token: ByteArray) : Request() { +internal class RepositorySetAccess( + val repository: Long, + val read: AccessChange?, + val write: AccessChange?, +) : Request() { override fun packContent(packer: MessagePacker) { - packer.packMap(mapOf("path" to path, "token" to token)) + packer.packMap( + mapOf( + "repository" to repository, + "read" to read, + "write" to write, + ), + ) } } -internal class RepositorySubscribe : ValueRequest { +internal class RepositoryAccessMode : ValueRequest { constructor(value: Long) : super(value) } -internal class RepositorySetReadAccess( +internal class RepositorySetAccessMode( val repository: Long, + val accessMode: AccessMode, val password: String?, - val shareToken: String?, ) : Request() { override fun packContent(packer: MessagePacker) { packer.packMap( mapOf( "repository" to repository, + "access_mode" to accessMode, "password" to password, - "share_token" to shareToken, ), ) } } -internal class RepositorySetReadAndWriteAccess( +internal class RepositoryCredentials : ValueRequest { + constructor(value: Long) : super(value) +} + +internal class RepositorySetCredentials( val repository: Long, - val oldPassword: String?, - val newPassword: String?, - val shareToken: String?, + val credentials: ByteArray, ) : Request() { override fun packContent(packer: MessagePacker) { packer.packMap( mapOf( "repository" to repository, - "old_password" to oldPassword, - "new_password" to newPassword, - "share_token" to shareToken, + "credentials" to credentials, ), ) } @@ -206,10 +216,6 @@ internal class RepositoryCreateShareToken( ) } -internal class RepositoryAccessMode : ValueRequest { - constructor(value: Long) : super(value) -} - internal class RepositorySyncProgress : ValueRequest { constructor(value: Long) : super(value) } @@ -421,6 +427,7 @@ private fun MessagePacker.packAny(value: Any) { is Long -> packLong(value) is Short -> packShort(value) is String -> packString(value) + is AccessChange -> value.pack(this) else -> throw IllegalArgumentException("can't pack ${value::class.qualifiedName}") } } diff --git a/bindings/kotlin/lib/src/main/kotlin/org/equalitie/ouisync/Session.kt b/bindings/kotlin/lib/src/main/kotlin/org/equalitie/ouisync/Session.kt index 400e1bd5f..cfb9e6fe8 100644 --- a/bindings/kotlin/lib/src/main/kotlin/org/equalitie/ouisync/Session.kt +++ b/bindings/kotlin/lib/src/main/kotlin/org/equalitie/ouisync/Session.kt @@ -4,7 +4,6 @@ import com.sun.jna.Pointer import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.flow.map import kotlinx.coroutines.runBlocking -import java.io.Closeable /** * The entry point to the ouisync library. diff --git a/bindings/kotlin/lib/src/test/kotlin/org/equalitie/ouisync/RepositoryTest.kt b/bindings/kotlin/lib/src/test/kotlin/org/equalitie/ouisync/RepositoryTest.kt index c136780e9..6d6645b7f 100644 --- a/bindings/kotlin/lib/src/test/kotlin/org/equalitie/ouisync/RepositoryTest.kt +++ b/bindings/kotlin/lib/src/test/kotlin/org/equalitie/ouisync/RepositoryTest.kt @@ -81,9 +81,10 @@ class RepositoryTest { var repo = createRepo() try { - val token = repo.createReopenToken() + val credentials = repo.credentials() repo.close() - repo = Repository.reopen(session, repoPath, token) + repo = Repository.open(session, repoPath) + repo.setCredentials(credentials) } finally { repo.close() } @@ -95,15 +96,15 @@ class RepositoryTest { assertFalse(it.requiresLocalPasswordForReading()) assertFalse(it.requiresLocalPasswordForWriting()) - it.setReadAndWriteAccess(oldPassword = null, newPassword = "banana") + it.setAccess(read = EnableAccess("banana"), write = EnableAccess("banana")) assertTrue(it.requiresLocalPasswordForReading()) assertTrue(it.requiresLocalPasswordForWriting()) - it.setReadAccess(password = null) + it.setAccess(read = EnableAccess(null)) assertFalse(it.requiresLocalPasswordForReading()) assertTrue(it.requiresLocalPasswordForWriting()) - it.setReadAndWriteAccess(oldPassword = null, newPassword = null) + it.setAccess(write = EnableAccess(null)) assertFalse(it.requiresLocalPasswordForReading()) assertFalse(it.requiresLocalPasswordForWriting()) }