From b68d290e4f6837110c690882b6bc9355d5131fed Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Thu, 15 Oct 2026 12:05:27 +0200 Subject: [PATCH 1/3] Rename .java to .kt Signed-off-by: alperozturk96 --- ...icKeyRemoteOperation.java => GetCertificateRemoteOperation.kt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename library/src/main/java/com/owncloud/android/lib/resources/users/{GetPublicKeyRemoteOperation.java => GetCertificateRemoteOperation.kt} (100%) diff --git a/library/src/main/java/com/owncloud/android/lib/resources/users/GetPublicKeyRemoteOperation.java b/library/src/main/java/com/owncloud/android/lib/resources/users/GetCertificateRemoteOperation.kt similarity index 100% rename from library/src/main/java/com/owncloud/android/lib/resources/users/GetPublicKeyRemoteOperation.java rename to library/src/main/java/com/owncloud/android/lib/resources/users/GetCertificateRemoteOperation.kt From 9946fa6ed1d73dac1d8bb75885722a82f352ce76 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Thu, 15 Oct 2026 12:05:28 +0200 Subject: [PATCH 2/3] remove confusion between certificate and server public key and use kotlin for both Signed-off-by: alperozturk96 --- .../e2ee/SendCSRRemoteOperationIT.kt | 4 +- .../users/GetCertificateRemoteOperation.kt | 129 ++++++++---------- .../GetServerPublicKeyRemoteOperation.kt | 57 ++++---- 3 files changed, 84 insertions(+), 106 deletions(-) diff --git a/library/src/androidTest/java/com/owncloud/android/lib/resources/e2ee/SendCSRRemoteOperationIT.kt b/library/src/androidTest/java/com/owncloud/android/lib/resources/e2ee/SendCSRRemoteOperationIT.kt index fa0747b79c..27504ea4b2 100644 --- a/library/src/androidTest/java/com/owncloud/android/lib/resources/e2ee/SendCSRRemoteOperationIT.kt +++ b/library/src/androidTest/java/com/owncloud/android/lib/resources/e2ee/SendCSRRemoteOperationIT.kt @@ -9,7 +9,7 @@ package com.owncloud.android.lib.resources.e2ee import com.owncloud.android.AbstractIT import com.owncloud.android.lib.common.OwnCloudClientManagerFactory -import com.owncloud.android.lib.resources.users.GetPublicKeyRemoteOperation +import com.owncloud.android.lib.resources.users.GetCertificateRemoteOperation import com.owncloud.android.lib.resources.users.SendCSRRemoteOperation import junit.framework.TestCase import junit.framework.TestCase.assertTrue @@ -44,7 +44,7 @@ class SendCSRRemoteOperationIT : AbstractIT() { assertTrue(result.isSuccess) // verify public key - result = GetPublicKeyRemoteOperation(client.userId).execute(nextcloudClient) + result = GetCertificateRemoteOperation(client.userId).execute(nextcloudClient) assertTrue(result.isSuccess) TestCase.assertNotNull(result.resultData) } diff --git a/library/src/main/java/com/owncloud/android/lib/resources/users/GetCertificateRemoteOperation.kt b/library/src/main/java/com/owncloud/android/lib/resources/users/GetCertificateRemoteOperation.kt index eda258d89d..d61356b0b2 100644 --- a/library/src/main/java/com/owncloud/android/lib/resources/users/GetCertificateRemoteOperation.kt +++ b/library/src/main/java/com/owncloud/android/lib/resources/users/GetCertificateRemoteOperation.kt @@ -2,94 +2,73 @@ * Nextcloud Android Library * * SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2026 Alper Ozturk * SPDX-FileCopyrightText: 2017 Tobias Kaminsky * SPDX-License-Identifier: MIT */ -package com.owncloud.android.lib.resources.users; - -import com.nextcloud.common.NextcloudClient; -import com.nextcloud.operations.GetMethod; -import com.owncloud.android.lib.common.operations.RemoteOperation; -import com.owncloud.android.lib.common.operations.RemoteOperationResult; -import com.owncloud.android.lib.common.utils.Log_OC; - -import org.apache.commons.httpclient.HttpStatus; -import org.json.JSONObject; - -import java.util.HashMap; +package com.owncloud.android.lib.resources.users +import com.nextcloud.common.NextcloudClient +import com.nextcloud.operations.GetMethod +import com.owncloud.android.lib.common.operations.RemoteOperation +import com.owncloud.android.lib.common.operations.RemoteOperationResult +import com.owncloud.android.lib.common.utils.Log_OC +import org.apache.commons.httpclient.HttpStatus +import org.json.JSONObject /** - * Remote operation performing the fetch of the public key for an user + * Fetches the end-to-end encryption certificate of a user. + * + * Note: although the endpoint is `.../api/v1/public-key` and the response node is `public-keys`, + * the server returns the user's PEM-encoded X.509 **certificate** (`-----BEGIN CERTIFICATE-----`). */ - -public class GetPublicKeyRemoteOperation extends RemoteOperation { - - private static final String TAG = GetPublicKeyRemoteOperation.class.getSimpleName(); - private static final String PUBLIC_KEY_URL = "/ocs/v2.php/apps/end_to_end_encryption/api/v1/public-key"; - - // JSON node names - private static final String NODE_OCS = "ocs"; - private static final String NODE_DATA = "data"; - private static final String NODE_PUBLIC_KEYS = "public-keys"; - - private String user; - - public GetPublicKeyRemoteOperation() { - this.user = ""; - } - - public GetPublicKeyRemoteOperation(String user) { - this.user = user; - } - - /** - * @param client Client object - */ - @Override - public RemoteOperationResult run(NextcloudClient client) { - GetMethod getMethod = null; - RemoteOperationResult result; - - try { - // remote request - getMethod = new GetMethod(client.getBaseUri() + PUBLIC_KEY_URL + JSON_FORMAT, true); - - if (!user.isEmpty()) { - HashMap map = new HashMap<>(); - map.put("users", "[\"" + user + "\"]"); - getMethod.setQueryString(map); - } else { - user = client.getUserId(); +class GetCertificateRemoteOperation + @JvmOverloads + constructor( + private val user: String = "" + ) : RemoteOperation() { + @Suppress("TooGenericExceptionCaught") + override fun run(client: NextcloudClient): RemoteOperationResult { + val targetUser = user.ifEmpty { client.userId } + val getMethod = GetMethod(client.baseUri.toString() + CERTIFICATE_URL + JSON_FORMAT, true) + if (user.isNotEmpty()) { + getMethod.setQueryString(mapOf(USERS_PARAM to "[\"$user\"]")) } - int status = client.execute(getMethod); - - if (status == HttpStatus.SC_OK) { - String response = getMethod.getResponseBodyAsString(); - - // Parse the response - JSONObject respJSON = new JSONObject(response); - String key = respJSON + return try { + val status = client.execute(getMethod) + if (status != HttpStatus.SC_OK) { + return RemoteOperationResult(false, getMethod) + } + val key = + JSONObject(getMethod.getResponseBodyAsString()) .getJSONObject(NODE_OCS) .getJSONObject(NODE_DATA) .getJSONObject(NODE_PUBLIC_KEYS) - .getString(user); + .getString(targetUser) + RemoteOperationResult(true, getMethod).apply { resultData = key } + } catch (e: Exception) { + failure(e, targetUser) + } finally { + getMethod.releaseConnection() + } + } - result = new RemoteOperationResult<>(true, getMethod); - result.setResultData(key); - } else { - result = new RemoteOperationResult<>(false, getMethod); + @Suppress("DEPRECATION") + private fun failure( + e: Exception, + targetUser: String + ): RemoteOperationResult = + RemoteOperationResult(e).also { + Log_OC.e(TAG, "Fetching of public key failed for user $targetUser: ${it.logMessage}", it.exception) } - } catch (Exception e) { - result = new RemoteOperationResult<>(e); - Log_OC.e(TAG, - "Fetching of public key failed for user " + user + ": " + result.getLogMessage(), - result.getException()); - } finally { - if (getMethod != null) - getMethod.releaseConnection(); + + companion object { + private val TAG = GetCertificateRemoteOperation::class.java.simpleName + private const val CERTIFICATE_URL = "/ocs/v2.php/apps/end_to_end_encryption/api/v1/public-key" + private const val USERS_PARAM = "users" + private const val NODE_OCS = "ocs" + private const val NODE_DATA = "data" + private const val NODE_PUBLIC_KEYS = "public-keys" } - return result; } -} diff --git a/library/src/main/java/com/owncloud/android/lib/resources/users/GetServerPublicKeyRemoteOperation.kt b/library/src/main/java/com/owncloud/android/lib/resources/users/GetServerPublicKeyRemoteOperation.kt index 91abb78819..62291c134d 100644 --- a/library/src/main/java/com/owncloud/android/lib/resources/users/GetServerPublicKeyRemoteOperation.kt +++ b/library/src/main/java/com/owncloud/android/lib/resources/users/GetServerPublicKeyRemoteOperation.kt @@ -16,43 +16,42 @@ import org.apache.commons.httpclient.HttpStatus import org.json.JSONObject class GetServerPublicKeyRemoteOperation : RemoteOperation() { - companion object { - private val TAG: String = GetPublicKeyRemoteOperation::class.java.simpleName - private const val ENDPOINT = "/ocs/v2.php/apps/end_to_end_encryption/api/v1/server-key" - } - @Suppress("TooGenericExceptionCaught") override fun run(client: NextcloudClient): RemoteOperationResult { - var getMethod: GetMethod? = null - var result: RemoteOperationResult - val user: String = client.userId - - try { - getMethod = GetMethod(client.baseUri.toString() + ENDPOINT + JSON_FORMAT, true) + val getMethod = GetMethod(client.baseUri.toString() + ENDPOINT + JSON_FORMAT, true) + return try { val status = client.execute(getMethod) - - if (status == HttpStatus.SC_OK) { - val response = getMethod.getResponseBodyAsString() - val respJSON = JSONObject(response) - val serverKey = respJSON.getJSONObject("ocs").getJSONObject("data").getString("public-key") - - result = RemoteOperationResult(true, getMethod) - result.setResultData(serverKey) - } else { - result = RemoteOperationResult(false, getMethod) + if (status != HttpStatus.SC_OK) { + return RemoteOperationResult(false, getMethod) } + val serverKey = + JSONObject(getMethod.getResponseBodyAsString()) + .getJSONObject(NODE_OCS) + .getJSONObject(NODE_DATA) + .getString(NODE_PUBLIC_KEY) + RemoteOperationResult(true, getMethod).apply { resultData = serverKey } } catch (e: Exception) { - result = RemoteOperationResult(e) - Log_OC.e( - TAG, - "Fetching of server public key failed for user " + user + ": " + result.logMessage, - result.exception - ) + failure(e, client.userId) } finally { - getMethod?.releaseConnection() + getMethod.releaseConnection() + } + } + + @Suppress("DEPRECATION") + private fun failure( + e: Exception, + user: String + ): RemoteOperationResult = + RemoteOperationResult(e).also { + Log_OC.e(TAG, "Fetching of server public key failed for user $user: ${it.logMessage}", it.exception) } - return result + companion object { + private val TAG = GetServerPublicKeyRemoteOperation::class.java.simpleName + private const val ENDPOINT = "/ocs/v2.php/apps/end_to_end_encryption/api/v1/server-key" + private const val NODE_OCS = "ocs" + private const val NODE_DATA = "data" + private const val NODE_PUBLIC_KEY = "public-key" } } From 82ea28b254ad4a9447e66631c642e65b5ceb384c Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Thu, 15 Oct 2026 12:23:17 +0200 Subject: [PATCH 3/3] add tests for GetServerPublicKeyRemoteOperation Signed-off-by: alperozturk96 --- .../GetServerPublicKeyRemoteOperationIT.kt | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 library/src/androidTest/java/com/owncloud/android/lib/resources/e2ee/GetServerPublicKeyRemoteOperationIT.kt diff --git a/library/src/androidTest/java/com/owncloud/android/lib/resources/e2ee/GetServerPublicKeyRemoteOperationIT.kt b/library/src/androidTest/java/com/owncloud/android/lib/resources/e2ee/GetServerPublicKeyRemoteOperationIT.kt new file mode 100644 index 0000000000..1feb14cd06 --- /dev/null +++ b/library/src/androidTest/java/com/owncloud/android/lib/resources/e2ee/GetServerPublicKeyRemoteOperationIT.kt @@ -0,0 +1,51 @@ +/* + * Nextcloud Android Library + * + * SPDX-FileCopyrightText: 2026 Alper Ozturk + * SPDX-License-Identifier: MIT + */ +package com.owncloud.android.lib.resources.e2ee + +import com.owncloud.android.AbstractIT +import com.owncloud.android.lib.common.OwnCloudClientManagerFactory +import com.owncloud.android.lib.resources.users.GetServerPublicKeyRemoteOperation +import org.apache.commons.httpclient.HttpStatus +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test + +class GetServerPublicKeyRemoteOperationIT : AbstractIT() { + @Before + fun init() { + OwnCloudClientManagerFactory.setUserAgent(SUPPORTED_USER_AGENT) + } + + @Test + fun serverPublicKeyIsReturnedAsPem() { + val result = GetServerPublicKeyRemoteOperation().execute(nextcloudClient) + + assertTrue(result.isSuccess) + val key = result.resultData.trim() + assertTrue(key.startsWith(PEM_HEADER)) + assertTrue(key.endsWith(PEM_FOOTER)) + } + + @Test + fun unsupportedClientIsRejected() { + OwnCloudClientManagerFactory.setUserAgent(UNSUPPORTED_USER_AGENT) + + val result = GetServerPublicKeyRemoteOperation().execute(nextcloudClient) + + assertFalse(result.isSuccess) + assertEquals(HttpStatus.SC_FORBIDDEN, result.httpCode) + } + + companion object { + private const val SUPPORTED_USER_AGENT = "Mozilla/5.0 (Android) Nextcloud-android/3.13.0" + private const val UNSUPPORTED_USER_AGENT = "Mozilla/5.0 (Android) Nextcloud-android/1.0.0" + private const val PEM_HEADER = "-----BEGIN PUBLIC KEY-----" + private const val PEM_FOOTER = "-----END PUBLIC KEY-----" + } +}