diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/webfinger/GetOCInstanceViaWebfingerOperation.kt b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/webfinger/GetOCInstanceViaWebfingerOperation.kt new file mode 100644 index 00000000..42ee4b12 --- /dev/null +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/webfinger/GetOCInstanceViaWebfingerOperation.kt @@ -0,0 +1,108 @@ +/* ownCloud Android Library is available under MIT license + * + * Copyright (C) 2022 ownCloud GmbH. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.owncloud.android.lib.resources.webfinger + +import android.net.Uri +import com.owncloud.android.lib.common.OwnCloudClient +import com.owncloud.android.lib.common.http.HttpConstants +import com.owncloud.android.lib.common.http.methods.nonwebdav.GetMethod +import com.owncloud.android.lib.common.http.methods.nonwebdav.HttpMethod +import com.owncloud.android.lib.common.operations.RemoteOperation +import com.owncloud.android.lib.common.operations.RemoteOperationResult +import com.owncloud.android.lib.resources.webfinger.responses.WebfingerJrdResponse +import com.squareup.moshi.Moshi +import timber.log.Timber +import java.net.URL + +class GetOCInstanceViaWebfingerOperation( + private val lockupServerDomain: String, + private val rel: String, + private val resource: String, +) : RemoteOperation() { + + private fun buildRequestUri() = + Uri.parse(lockupServerDomain).buildUpon() + .path(ENDPOINT_WEBFINGER_PATH) + .appendQueryParameter("rel", rel) + .appendQueryParameter("resource", resource) + .build() + + private fun isSuccess(status: Int): Boolean = status == HttpConstants.HTTP_OK + + private fun parseResponse(response: String): WebfingerJrdResponse { + val moshi = Moshi.Builder().build() + val adapter = moshi.adapter(WebfingerJrdResponse::class.java) + return adapter.fromJson(response)!! + } + + private fun onResultUnsuccessful( + method: HttpMethod, + response: String?, + status: Int + ): RemoteOperationResult { + Timber.e("Failed requesting webfinger info") + if (response != null) { + Timber.e("*** status code: $status; response message: $response") + } else { + Timber.e("*** status code: $status") + } + return RemoteOperationResult(method) + } + + private fun onRequestSuccessful(rawResponse: String): RemoteOperationResult { + val response = parseResponse(rawResponse) + for (i in response.links) { + if (i.rel == rel) { + val operationResult = RemoteOperationResult(RemoteOperationResult.ResultCode.OK) + operationResult.data = i.href + return operationResult + } + } + Timber.e("Could not find ownCloud relevant information in webfinger response: $rawResponse") + throw java.lang.Exception("Could not find ownCloud relevant information in webfinger response") + } + + override fun run(client: OwnCloudClient): RemoteOperationResult { + val requestUri = buildRequestUri() + val getMethod = GetMethod(URL(requestUri.toString())) + return try { + val status = client.executeHttpMethod(getMethod) + val response = getMethod.getResponseBodyAsString()!! + + if (isSuccess(status)) { + onRequestSuccessful(response) + } else { + onResultUnsuccessful(getMethod, response, status) + } + } catch (e: Exception) { + Timber.e(e, "Requesting webfinger info failed") + RemoteOperationResult(e) + } + } + + companion object { + private const val ENDPOINT_WEBFINGER_PATH = "/.well-known/webfinger" + } +} diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/webfinger/responses/WebfingerResponse.kt b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/webfinger/responses/WebfingerResponse.kt new file mode 100644 index 00000000..45e85a75 --- /dev/null +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/webfinger/responses/WebfingerResponse.kt @@ -0,0 +1,39 @@ +/* ownCloud Android Library is available under MIT license + * + * Copyright (C) 2022 ownCloud GmbH. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.owncloud.android.lib.resources.webfinger.responses + +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class WebfingerJrdResponse( + val subject: String, + val links: List +) + +@JsonClass(generateAdapter = true) +data class LinkItem( + val href: String, + val rel: String +) diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/webfinger/services/WebfingerService.kt b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/webfinger/services/WebfingerService.kt new file mode 100644 index 00000000..4e057beb --- /dev/null +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/webfinger/services/WebfingerService.kt @@ -0,0 +1,29 @@ +/** + * ownCloud Android client application + * + * Copyright (C) 2022 ownCloud GmbH. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.owncloud.android.lib.resources.webfinger.services + +import com.owncloud.android.lib.common.OwnCloudClient +import com.owncloud.android.lib.common.operations.RemoteOperationResult + +interface WebfingerService { + fun getInstanceFromWebfinger( + lookupServer: String, + username: String, + client: OwnCloudClient, + ): RemoteOperationResult +} diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/webfinger/services/implementation/OCWebfingerService.kt b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/webfinger/services/implementation/OCWebfingerService.kt new file mode 100644 index 00000000..3eab83d4 --- /dev/null +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/webfinger/services/implementation/OCWebfingerService.kt @@ -0,0 +1,38 @@ +/** + * ownCloud Android client application + * + * Copyright (C) 2022 ownCloud GmbH. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.owncloud.android.lib.resources.webfinger.services.implementation + +import com.owncloud.android.lib.common.OwnCloudClient +import com.owncloud.android.lib.common.operations.RemoteOperationResult +import com.owncloud.android.lib.resources.webfinger.GetOCInstanceViaWebfingerOperation +import com.owncloud.android.lib.resources.webfinger.services.WebfingerService + +class OCWebfingerService : WebfingerService { + + override fun getInstanceFromWebfinger( + lookupServer: String, + username: String, + client: OwnCloudClient, + ): RemoteOperationResult = + GetOCInstanceViaWebfingerOperation(lookupServer, OWNCLOUD_REL, username).execute(client) + + companion object { + private const val OWNCLOUD_REL = "http://webfinger.owncloud/rel/server-instance" + } + +} diff --git a/owncloudComLibrary/src/test/java/com/owncloud/android/lib/resources/shares/responses/ShareeResponseTest.kt b/owncloudComLibrary/src/test/java/com/owncloud/android/lib/resources/shares/responses/ShareeResponseTest.kt index 3981ab5b..374429c3 100644 --- a/owncloudComLibrary/src/test/java/com/owncloud/android/lib/resources/shares/responses/ShareeResponseTest.kt +++ b/owncloudComLibrary/src/test/java/com/owncloud/android/lib/resources/shares/responses/ShareeResponseTest.kt @@ -90,4 +90,4 @@ class ShareeResponseTest { val EXAMPLE_RESPONSE_JSON = "$RESOURCES_PATH/example_sharee_response.json" val EMPTY_RESPONSE_JSON = "$RESOURCES_PATH/empty_sharee_response.json" } -} \ No newline at end of file +} diff --git a/owncloudComLibrary/src/test/java/com/owncloud/android/lib/resources/webfinger/responses/WebfingerResponseTest.kt b/owncloudComLibrary/src/test/java/com/owncloud/android/lib/resources/webfinger/responses/WebfingerResponseTest.kt new file mode 100644 index 00000000..2d0c6235 --- /dev/null +++ b/owncloudComLibrary/src/test/java/com/owncloud/android/lib/resources/webfinger/responses/WebfingerResponseTest.kt @@ -0,0 +1,49 @@ +package com.owncloud.android.lib.resources.webfinger.responses + +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.JsonDataException +import com.squareup.moshi.Moshi +import org.junit.Assert +import org.junit.Before +import org.junit.Test +import java.io.File + +class WebfingerResponseTest { + lateinit var adapter: JsonAdapter + + private fun loadResponses(fileName: String) = adapter.fromJson(File(fileName).readText()) + + @Before + fun prepare() { + val moshi = Moshi.Builder().build() + adapter = moshi.adapter(WebfingerJrdResponse::class.java) + } + + @Test + fun `check rel in too much information - ok`() { + val response = loadResponses(TOO_MUCH_INFORMATION_JSON)!! + Assert.assertEquals("https://gast.somedomain.de", response.links[0].href) + Assert.assertEquals("http://webfinger.owncloud/rel/server-instance", response.links[0].rel) + } + + @Test(expected = JsonDataException::class) + fun `check key value pairs - ko - no href key`() { + val response = loadResponses(BROKEN_JSON)!! + Assert.assertEquals("https://gast.somedomain.de", response.links[0].href) + } + + @Test(expected = JsonDataException::class) + fun `check key value pairs - ko - no rel key`() { + val response = loadResponses(BROKEN_JSON)!! + Assert.assertEquals("https://gast.somedomain.de", response.links[0].href) + } + + companion object { + private const val RESOURCES_PATH = + "src/test/responses/com.owncloud.android.lib.resources.webfinger.responses" + private const val EXAMPLE_RESPONSE_JSON = "$RESOURCES_PATH/simple_response.json" + private const val TOO_MUCH_INFORMATION_JSON = "$RESOURCES_PATH/to_much_information_response.json" + private const val BROKEN_JSON = "$RESOURCES_PATH/broken_response.json" + private const val NOT_CONTAINING_RELEVANT_INFORMATION_JSON = "$RESOURCES_PATH/not_containing_relevant_info_response.json" + } +} diff --git a/owncloudComLibrary/src/test/responses/com.owncloud.android.lib.resources.webfinger.responses/broken_response.json b/owncloudComLibrary/src/test/responses/com.owncloud.android.lib.resources.webfinger.responses/broken_response.json new file mode 100644 index 00000000..85dd2279 --- /dev/null +++ b/owncloudComLibrary/src/test/responses/com.owncloud.android.lib.resources.webfinger.responses/broken_response.json @@ -0,0 +1,15 @@ +{ + "subject": "acct:bob@example.com", + "aliases": [ + "https://www.example.com/~bob/" + ], + "properties": { + "http://example.com/ns/role": "employee" + }, + "links": [ + { + "lel": "http://webfinger.example/rel/profile-page", + "foo": "https://www.example.com/~bob/" + } + ] +} \ No newline at end of file diff --git a/owncloudComLibrary/src/test/responses/com.owncloud.android.lib.resources.webfinger.responses/not_containing_relevant_info_response.json b/owncloudComLibrary/src/test/responses/com.owncloud.android.lib.resources.webfinger.responses/not_containing_relevant_info_response.json new file mode 100644 index 00000000..712d113a --- /dev/null +++ b/owncloudComLibrary/src/test/responses/com.owncloud.android.lib.resources.webfinger.responses/not_containing_relevant_info_response.json @@ -0,0 +1,9 @@ +{ + "subject": "acct:peter.sine@gurken.xxx", + "links": [ + { + "rel": "http://webfinger.example/rel/businesscard", + "href": "https://www.example.com/~bob/bob.vcf" + } + ] +} diff --git a/owncloudComLibrary/src/test/responses/com.owncloud.android.lib.resources.webfinger.responses/simple_response.json b/owncloudComLibrary/src/test/responses/com.owncloud.android.lib.resources.webfinger.responses/simple_response.json new file mode 100644 index 00000000..bda67449 --- /dev/null +++ b/owncloudComLibrary/src/test/responses/com.owncloud.android.lib.resources.webfinger.responses/simple_response.json @@ -0,0 +1,9 @@ +{ + "links": [ + { + "href": "https://gast.somedomain.de", + "rel": "http://webfinger.owncloud/rel/server-instance" + } + ], + "subject": "acct:peter.sine@gurken.xxx" +} diff --git a/owncloudComLibrary/src/test/responses/com.owncloud.android.lib.resources.webfinger.responses/to_much_information_response.json b/owncloudComLibrary/src/test/responses/com.owncloud.android.lib.resources.webfinger.responses/to_much_information_response.json new file mode 100644 index 00000000..79c68f3c --- /dev/null +++ b/owncloudComLibrary/src/test/responses/com.owncloud.android.lib.resources.webfinger.responses/to_much_information_response.json @@ -0,0 +1,24 @@ +{ + "subject": "acct:peter.sine@gurken.xxx", + "aliases": [ + "https://www.example.com/~bob/" + ], + "properties": { + "http://example.com/ns/role": "employee" + }, + "gurken": { + "whatever": 42 + }, + "links": [ + { + "gurken": "sallat", + "href": "https://gast.somedomain.de", + "rel": "http://webfinger.owncloud/rel/server-instance" + }, + { + "gurken": "sallat", + "rel": "http://webfinger.example/rel/businesscard", + "href": "https://www.example.com/~bob/bob.vcf" + } + ] +}