From 8f6d922b05437166ba187e2f68c5db3a29bc0727 Mon Sep 17 00:00:00 2001 From: tobiasKaminsky Date: Mon, 9 Dec 2024 08:24:49 +0100 Subject: [PATCH 1/2] ToS Signed-off-by: tobiasKaminsky --- .../lib/resources/tos/TermsOfServicesIT.kt | 39 +++++++++++ .../resources/tos/GetTermsRemoteOperation.kt | 67 +++++++++++++++++++ .../resources/tos/SignTermRemoteOperation.kt | 44 ++++++++++++ .../android/lib/resources/tos/Term.kt | 17 +++++ .../android/lib/resources/tos/Terms.kt | 15 +++++ .../common/operations/ExceptionParser.java | 5 ++ .../operations/RemoteOperationResult.java | 10 ++- .../files/ExistenceCheckRemoteOperation.java | 2 +- 8 files changed, 196 insertions(+), 3 deletions(-) create mode 100644 library/src/androidTest/java/com/nextcloud/android/lib/resources/tos/TermsOfServicesIT.kt create mode 100644 library/src/main/java/com/nextcloud/android/lib/resources/tos/GetTermsRemoteOperation.kt create mode 100644 library/src/main/java/com/nextcloud/android/lib/resources/tos/SignTermRemoteOperation.kt create mode 100644 library/src/main/java/com/nextcloud/android/lib/resources/tos/Term.kt create mode 100644 library/src/main/java/com/nextcloud/android/lib/resources/tos/Terms.kt diff --git a/library/src/androidTest/java/com/nextcloud/android/lib/resources/tos/TermsOfServicesIT.kt b/library/src/androidTest/java/com/nextcloud/android/lib/resources/tos/TermsOfServicesIT.kt new file mode 100644 index 000000000..9e5d08ce5 --- /dev/null +++ b/library/src/androidTest/java/com/nextcloud/android/lib/resources/tos/TermsOfServicesIT.kt @@ -0,0 +1,39 @@ +/* + * Nextcloud Android Library + * + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2024 Tobias Kaminsky + * SPDX-License-Identifier: MIT + */ + +package com.nextcloud.android.lib.resources.tos + +import com.owncloud.android.AbstractIT +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue + +class TermsOfServicesIT : AbstractIT() { + // @Test disabled for now as no good way to test on CI + fun getAndSignTerms() { + // user 3 with ToS + var result = GetTermsRemoteOperation().execute(nextcloudClient) + assertTrue(result.isSuccess) + + var terms = result.resultData + assertTrue(terms.terms.isNotEmpty()) + assertFalse(terms.hasSigned) + + val id = terms.terms[0].id + + // sign + assertTrue(SignTermRemoteOperation(id).execute(nextcloudClient).isSuccess) + + // signed terms + result = GetTermsRemoteOperation().execute(nextcloudClient) + assertTrue(result.isSuccess) + + terms = result.resultData + assertTrue(terms.terms.isNotEmpty()) + assertTrue(terms.hasSigned) + } +} diff --git a/library/src/main/java/com/nextcloud/android/lib/resources/tos/GetTermsRemoteOperation.kt b/library/src/main/java/com/nextcloud/android/lib/resources/tos/GetTermsRemoteOperation.kt new file mode 100644 index 000000000..b5002de81 --- /dev/null +++ b/library/src/main/java/com/nextcloud/android/lib/resources/tos/GetTermsRemoteOperation.kt @@ -0,0 +1,67 @@ +/* + * Nextcloud Android Library + * + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2024 Tobias Kaminsky + * SPDX-License-Identifier: MIT + */ +package com.nextcloud.android.lib.resources.tos + +import com.google.gson.reflect.TypeToken +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.ocs.ServerResponse +import com.owncloud.android.lib.resources.OCSRemoteOperation +import org.apache.commons.httpclient.HttpStatus + +/** + * Get terms of service of an user + */ +class GetTermsRemoteOperation : OCSRemoteOperation() { + @Suppress("TooGenericExceptionCaught") + override fun run(client: NextcloudClient): RemoteOperationResult { + var result: RemoteOperationResult + var getMethod: GetMethod? = null + try { + getMethod = + GetMethod( + client.baseUri.toString() + ENDPOINT + JSON_FORMAT, + true + ) + val status = client.execute(getMethod) + if (status == HttpStatus.SC_OK) { + val terms = + getServerResponse( + getMethod, + object : TypeToken>() {} + )?.ocs?.data + + if (terms != null) { + result = RemoteOperationResult(true, getMethod) + result.setResultData(terms) + } else { + result = RemoteOperationResult(false, getMethod) + } + } else { + result = RemoteOperationResult(false, getMethod) + } + } catch (e: Exception) { + result = RemoteOperationResult(e) + Log_OC.e( + TAG, + "Get terms failed: " + result.logMessage, + result.exception + ) + } finally { + getMethod?.releaseConnection() + } + return result + } + + companion object { + private val TAG = GetTermsRemoteOperation::class.java.simpleName + private const val ENDPOINT = "/ocs/v2.php/apps/terms_of_service/terms" + } +} diff --git a/library/src/main/java/com/nextcloud/android/lib/resources/tos/SignTermRemoteOperation.kt b/library/src/main/java/com/nextcloud/android/lib/resources/tos/SignTermRemoteOperation.kt new file mode 100644 index 000000000..d4eb18e49 --- /dev/null +++ b/library/src/main/java/com/nextcloud/android/lib/resources/tos/SignTermRemoteOperation.kt @@ -0,0 +1,44 @@ +/* + * Nextcloud Android Library + * + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2024 Tobias Kaminsky + * SPDX-License-Identifier: MIT + */ +package com.nextcloud.android.lib.resources.tos + +import com.nextcloud.common.NextcloudClient +import com.nextcloud.operations.PostMethod +import com.owncloud.android.lib.common.operations.RemoteOperation +import com.owncloud.android.lib.common.operations.RemoteOperationResult +import okhttp3.MediaType.Companion.toMediaTypeOrNull +import okhttp3.RequestBody.Companion.toRequestBody +import org.apache.commons.httpclient.HttpStatus + +/** + * Sign terms of services + */ +class SignTermRemoteOperation(val id: Int) : RemoteOperation() { + @Suppress("TooGenericExceptionCaught") + override fun run(client: NextcloudClient): RemoteOperationResult { + val requestBody = hashMapOf("termId" to id) + + val json = gson.toJson(requestBody) + + val request = json.toRequestBody("application/json".toMediaTypeOrNull()) + + val postMethod = PostMethod(client.baseUri.toString() + ENDPOINT, true, request) + + val status = postMethod.execute(client) + + return if (status == HttpStatus.SC_OK) { + RemoteOperationResult(true, postMethod) + } else { + RemoteOperationResult(false, postMethod) + } + } + + companion object { + private const val ENDPOINT = "/ocs/v2.php/apps/terms_of_service/sign" + } +} diff --git a/library/src/main/java/com/nextcloud/android/lib/resources/tos/Term.kt b/library/src/main/java/com/nextcloud/android/lib/resources/tos/Term.kt new file mode 100644 index 000000000..73ee43b01 --- /dev/null +++ b/library/src/main/java/com/nextcloud/android/lib/resources/tos/Term.kt @@ -0,0 +1,17 @@ +/* + * Nextcloud Android Library + * + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2024 Tobias Kaminsky + * SPDX-License-Identifier: MIT + */ + +package com.nextcloud.android.lib.resources.tos + +data class Term( + val id: Int, + val countryCode: String, + val languageCode: String, + val body: String, + val renderedBody: String +) diff --git a/library/src/main/java/com/nextcloud/android/lib/resources/tos/Terms.kt b/library/src/main/java/com/nextcloud/android/lib/resources/tos/Terms.kt new file mode 100644 index 000000000..a8c2fa72b --- /dev/null +++ b/library/src/main/java/com/nextcloud/android/lib/resources/tos/Terms.kt @@ -0,0 +1,15 @@ +/* + * Nextcloud Android Library + * + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2024 Tobias Kaminsky + * SPDX-License-Identifier: MIT + */ + +package com.nextcloud.android.lib.resources.tos + +data class Terms( + val terms: List, + val hasSigned: Boolean, + val languages: Map +) diff --git a/library/src/main/java/com/owncloud/android/lib/common/operations/ExceptionParser.java b/library/src/main/java/com/owncloud/android/lib/common/operations/ExceptionParser.java index 623502062..9fcb55ce3 100644 --- a/library/src/main/java/com/owncloud/android/lib/common/operations/ExceptionParser.java +++ b/library/src/main/java/com/owncloud/android/lib/common/operations/ExceptionParser.java @@ -30,6 +30,7 @@ public class ExceptionParser { private static final String INVALID_PATH_EXCEPTION_STRING = "OC\\Connector\\Sabre\\Exception\\InvalidPath"; private static final String INVALID_PATH_EXCEPTION_UPLOAD_STRING = "OCP\\Files\\InvalidPathException"; private static final String VIRUS_EXCEPTION_STRING = "OCA\\DAV\\Connector\\Sabre\\Exception\\UnsupportedMediaType"; + private static final String TOS_EXCEPTION_STRING = "OCA\\TermsOfService\\TermsNotSignedException"; // No namespaces private static final String ns = null; @@ -75,6 +76,10 @@ public boolean isInvalidCharacterException() { public boolean isVirusException() { return VIRUS_EXCEPTION_STRING.equalsIgnoreCase(exception) && message.startsWith("Virus"); } + + public boolean isToSException() { + return TOS_EXCEPTION_STRING.equalsIgnoreCase(exception); + } /** * Parse OCS node diff --git a/library/src/main/java/com/owncloud/android/lib/common/operations/RemoteOperationResult.java b/library/src/main/java/com/owncloud/android/lib/common/operations/RemoteOperationResult.java index 55014c176..5f281a80d 100644 --- a/library/src/main/java/com/owncloud/android/lib/common/operations/RemoteOperationResult.java +++ b/library/src/main/java/com/owncloud/android/lib/common/operations/RemoteOperationResult.java @@ -135,7 +135,8 @@ public enum ResultCode { VIRUS_DETECTED, FOLDER_ALREADY_EXISTS, CANNOT_CREATE_FILE, - LOCKED + LOCKED, + SIGNING_TOS_NEEDED } private boolean mSuccess = false; @@ -329,7 +330,9 @@ public RemoteOperationResult(boolean success, OkHttpMethodBase httpMethod) { public RemoteOperationResult(boolean success, HttpMethod httpMethod) { this(success, httpMethod.getStatusCode(), httpMethod.getStatusText(), httpMethod.getResponseHeaders()); - if (mHttpCode == HttpStatus.SC_BAD_REQUEST || mHttpCode == HttpStatus.SC_UNSUPPORTED_MEDIA_TYPE) { + if (mHttpCode == HttpStatus.SC_BAD_REQUEST || + mHttpCode == HttpStatus.SC_UNSUPPORTED_MEDIA_TYPE || + mHttpCode == HttpStatus.SC_FORBIDDEN) { try { String bodyResponse = httpMethod.getResponseBodyAsString(); @@ -343,6 +346,9 @@ public RemoteOperationResult(boolean success, HttpMethod httpMethod) { if (xmlParser.isVirusException()) { mCode = ResultCode.VIRUS_DETECTED; } + if (xmlParser.isToSException()) { + mCode = ResultCode.SIGNING_TOS_NEEDED; + } mHttpPhrase = xmlParser.getMessage(); } diff --git a/library/src/main/java/com/owncloud/android/lib/resources/files/ExistenceCheckRemoteOperation.java b/library/src/main/java/com/owncloud/android/lib/resources/files/ExistenceCheckRemoteOperation.java index 6cf5aa62e..fe6a0664b 100644 --- a/library/src/main/java/com/owncloud/android/lib/resources/files/ExistenceCheckRemoteOperation.java +++ b/library/src/main/java/com/owncloud/android/lib/resources/files/ExistenceCheckRemoteOperation.java @@ -81,7 +81,7 @@ protected RemoteOperationResult run(OwnCloudClient client) { status = mRedirectionPath.getLastStatus(); } client.exhaustResponse(head.getResponseBodyAsStream()); - boolean success = (status == HttpStatus.SC_OK && !mSuccessIfAbsent) || + boolean success = ((status == HttpStatus.SC_OK || status == HttpStatus.SC_UNAUTHORIZED || status == HttpStatus.SC_FORBIDDEN) && !mSuccessIfAbsent) || (status == HttpStatus.SC_NOT_FOUND && mSuccessIfAbsent); result = new RemoteOperationResult( success, From f4f71d5effccaf33edcf581a4963e0de19ccb0be Mon Sep 17 00:00:00 2001 From: tobiasKaminsky Date: Wed, 5 Feb 2025 10:51:38 +0100 Subject: [PATCH 2/2] ToS Signed-off-by: tobiasKaminsky --- .../android/lib/resources/tos/SignTermRemoteOperation.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/library/src/main/java/com/nextcloud/android/lib/resources/tos/SignTermRemoteOperation.kt b/library/src/main/java/com/nextcloud/android/lib/resources/tos/SignTermRemoteOperation.kt index d4eb18e49..ffd0795f4 100644 --- a/library/src/main/java/com/nextcloud/android/lib/resources/tos/SignTermRemoteOperation.kt +++ b/library/src/main/java/com/nextcloud/android/lib/resources/tos/SignTermRemoteOperation.kt @@ -18,7 +18,9 @@ import org.apache.commons.httpclient.HttpStatus /** * Sign terms of services */ -class SignTermRemoteOperation(val id: Int) : RemoteOperation() { +class SignTermRemoteOperation( + val id: Int +) : RemoteOperation() { @Suppress("TooGenericExceptionCaught") override fun run(client: NextcloudClient): RemoteOperationResult { val requestBody = hashMapOf("termId" to id)