diff --git a/library/src/androidTest/java/com/nextcloud/android/lib/resources/users/GenerateOneTimeAppPasswordRemoteOperationIT.kt b/library/src/androidTest/java/com/nextcloud/android/lib/resources/users/GenerateOneTimeAppPasswordRemoteOperationIT.kt new file mode 100644 index 0000000000..ac28260c9b --- /dev/null +++ b/library/src/androidTest/java/com/nextcloud/android/lib/resources/users/GenerateOneTimeAppPasswordRemoteOperationIT.kt @@ -0,0 +1,34 @@ +/* + * Nextcloud Android Library + * + * SPDX-FileCopyrightText: 2020-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2020 Tobias Kaminsky + * SPDX-License-Identifier: MIT + */ +package com.nextcloud.android.lib.resources.users + +import android.text.TextUtils +import com.owncloud.android.AbstractIT +import com.owncloud.android.lib.resources.status.NextcloudVersion +import junit.framework.TestCase.assertFalse +import junit.framework.TestCase.assertTrue +import org.junit.Test + +class GenerateOneTimeAppPasswordRemoteOperationIT : AbstractIT() { + @Test + fun generateAppPassword() { + // only on NC34+ + testOnlyOnServer(NextcloudVersion.nextcloud_34) + + val sut = GenerateOneTimeAppPasswordRemoteOperation() + val result = sut.execute(nextcloudClient) + + assertTrue(result.isSuccess) + + val appPassword = result.getResultData() + assertFalse(TextUtils.isEmpty(appPassword)) + + // re-using onetime password should fail + assertFalse(GenerateOneTimeAppPasswordRemoteOperation().execute(nextcloudClient).isSuccess) + } +} diff --git a/library/src/main/java/com/nextcloud/android/lib/resources/users/GenerateOneTimeAppPasswordRemoteOperation.kt b/library/src/main/java/com/nextcloud/android/lib/resources/users/GenerateOneTimeAppPasswordRemoteOperation.kt new file mode 100644 index 0000000000..d7e0d0eecd --- /dev/null +++ b/library/src/main/java/com/nextcloud/android/lib/resources/users/GenerateOneTimeAppPasswordRemoteOperation.kt @@ -0,0 +1,76 @@ +/* + * Nextcloud Android Library + * + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2025 Tobias Kaminsky + * SPDX-License-Identifier: MIT + */ +package com.nextcloud.android.lib.resources.users + +import com.nextcloud.common.NextcloudClient +import com.nextcloud.operations.GetMethod +import com.owncloud.android.lib.common.operations.RemoteOperationResult +import com.owncloud.android.lib.common.utils.Log_OC +import com.owncloud.android.lib.resources.OCSRemoteOperation +import okio.IOException +import org.apache.commons.httpclient.HttpStatus +import org.json.JSONObject + +/** + * Generate an app password via username / login and **onetime** password. Available since Nextcloud 33 + */ +class GenerateOneTimeAppPasswordRemoteOperation : OCSRemoteOperation() { + override fun run(client: NextcloudClient): RemoteOperationResult { + var result: RemoteOperationResult + var getMethod: GetMethod? = null + + try { + getMethod = GetMethod(client.baseUri.toString() + ENDPOINT + JSON_FORMAT, true) + + // remote request + val status: Int = client.execute(getMethod) + + if (status == HttpStatus.SC_OK) { + val response = getMethod.getResponseBodyAsString() + + val respJSON = JSONObject(response) + val password = + respJSON.getJSONObject(NODE_OCS).getJSONObject(NODE_DATA).getString( + NODE_APPPASSWORD + ) + + result = RemoteOperationResult(true, getMethod) + result.resultData = password + } else { + result = RemoteOperationResult(false, getMethod) + } + } catch (e: IOException) { + result = RemoteOperationResult(e) + Log_OC.e( + TAG, + "Generate app password failed: " + result.getLogMessage(), + result.exception + ) + } catch (e: org.json.JSONException) { + result = RemoteOperationResult(e) + Log_OC.e( + TAG, + "Generate app password failed: " + result.getLogMessage(), + result.exception + ) + } finally { + getMethod?.releaseConnection() + } + return result + } + + companion object { + private val TAG: String = GenerateOneTimeAppPasswordRemoteOperation::class.java.getSimpleName() + private const val ENDPOINT = "/ocs/v2.php/core/getapppassword-onetime" + + // JSON node names + private const val NODE_OCS = "ocs" + private const val NODE_DATA = "data" + private const val NODE_APPPASSWORD = "apppassword" + } +}