diff --git a/bigbone-rx/src/main/kotlin/social/bigbone/rx/admin/RxAdminDomainAllowMethods.kt b/bigbone-rx/src/main/kotlin/social/bigbone/rx/admin/RxAdminDomainAllowMethods.kt new file mode 100644 index 000000000..74af79211 --- /dev/null +++ b/bigbone-rx/src/main/kotlin/social/bigbone/rx/admin/RxAdminDomainAllowMethods.kt @@ -0,0 +1,57 @@ +package social.bigbone.rx.admin + +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Single +import social.bigbone.MastodonClient +import social.bigbone.api.Pageable +import social.bigbone.api.Range +import social.bigbone.api.entity.admin.AdminDomainAllow +import social.bigbone.api.method.admin.AdminDomainAllowMethods + +/** + * Reactive implementation of [AdminDomainAllowMethods]. + * + * Allow certain domains to federate. + * @see Mastodon admin/domain_allows API methods + */ +class RxAdminDomainAllowMethods(client: MastodonClient) { + + private val adminAllowMethods = AdminDomainAllowMethods(client) + + /** + * Show information about all allowed domains. + * @param range optional Range for the pageable return value + * @see Mastodon API documentation: methods/domain_allows/#get + */ + @JvmOverloads + fun getAllAllowedDomains(range: Range = Range()): Single> = Single.fromCallable { + adminAllowMethods.getAllAllowedDomains(range = range).execute() + } + + /** + * Show information about a single allowed domain. + * @param id The ID of the DomainAllow in the database. + * @see Mastodon API documentation: methods/domain_allows/#get-one + */ + fun getAllowedDomain(id: String): Single = Single.fromCallable { + adminAllowMethods.getAllowedDomain(id = id).execute() + } + + /** + * Add a domain to the list of domains allowed to federate, to be used when the instance is in allow-list federation mode. + * @param domain The domain to allow federation with. + * @see Mastodon API documentation: admin/domain_allows/#create + */ + fun allowDomainToFederate(domain: String): Single = Single.fromCallable { + adminAllowMethods.allowDomainToFederate(domain = domain).execute() + } + + /** + * Delete a domain from the allowed domains list. + * @param id The ID of the DomainAllow in the database. + * @see Mastodon API documentation: admin/domain_allows/#delete + */ + fun removeAllowedDomain(id: String): Completable = Completable.fromAction { + adminAllowMethods.removeAllowedDomain(id = id) + } +} diff --git a/bigbone/src/main/kotlin/social/bigbone/MastodonClient.kt b/bigbone/src/main/kotlin/social/bigbone/MastodonClient.kt index 641f141f9..af62f96c1 100644 --- a/bigbone/src/main/kotlin/social/bigbone/MastodonClient.kt +++ b/bigbone/src/main/kotlin/social/bigbone/MastodonClient.kt @@ -68,6 +68,7 @@ import social.bigbone.api.method.TrendMethods import social.bigbone.api.method.admin.AdminAccountMethods import social.bigbone.api.method.admin.AdminCanonicalEmailBlockMethods import social.bigbone.api.method.admin.AdminDimensionMethods +import social.bigbone.api.method.admin.AdminDomainAllowMethods import social.bigbone.api.method.admin.AdminDomainBlockMethods import social.bigbone.api.method.admin.AdminEmailDomainBlockMethods import social.bigbone.api.method.admin.AdminIpBlockMethods @@ -130,6 +131,13 @@ private constructor( @get:JvmName("adminDimensions") val adminDimensions: AdminDimensionMethods by lazy { AdminDimensionMethods(this) } + /** + * Access API methods under the "admin/domain_allows" endpoint. + */ + @Suppress("unused") // public API + @get:JvmName("adminDomainAllow") + val adminDomainAllow: AdminDomainAllowMethods by lazy { AdminDomainAllowMethods(this) } + /** * Access API methods under the "admin/domain_blocks" endpoint. */ diff --git a/bigbone/src/main/kotlin/social/bigbone/api/entity/admin/AdminDomainAllow.kt b/bigbone/src/main/kotlin/social/bigbone/api/entity/admin/AdminDomainAllow.kt new file mode 100644 index 000000000..1dbde083c --- /dev/null +++ b/bigbone/src/main/kotlin/social/bigbone/api/entity/admin/AdminDomainAllow.kt @@ -0,0 +1,32 @@ +package social.bigbone.api.entity.admin + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import social.bigbone.DateTimeSerializer +import social.bigbone.PrecisionDateTime + +/** + * Represents a domain allowed to federate. + * @see Mastodon API Admin::DomainAllow/ + */ +@Serializable +data class AdminDomainAllow( + /** + * The ID of the DomainAllow in the database. + */ + @SerialName("id") + val id: String = "0", + + /** + * The domain that is allowed to federate. + */ + @SerialName("domain") + val domain: String = "", + + /** + * When the domain was allowed to federate. + */ + @SerialName("created_at") + @Serializable(with = DateTimeSerializer::class) + val createdAt: PrecisionDateTime = PrecisionDateTime.InvalidPrecisionDateTime.Unavailable +) diff --git a/bigbone/src/main/kotlin/social/bigbone/api/method/admin/AdminDomainAllowMethods.kt b/bigbone/src/main/kotlin/social/bigbone/api/method/admin/AdminDomainAllowMethods.kt new file mode 100644 index 000000000..956747241 --- /dev/null +++ b/bigbone/src/main/kotlin/social/bigbone/api/method/admin/AdminDomainAllowMethods.kt @@ -0,0 +1,70 @@ +package social.bigbone.api.method.admin + +import social.bigbone.MastodonClient +import social.bigbone.MastodonRequest +import social.bigbone.Parameters +import social.bigbone.api.Pageable +import social.bigbone.api.Range +import social.bigbone.api.entity.admin.AdminDomainAllow + +/** + * Allow certain domains to federate. + * @see Mastodon admin/domain_allows API methods + */ +class AdminDomainAllowMethods(private val client: MastodonClient) { + + private val adminDomainAllowEndpoint = "api/v1/admin/domain_allows" + + /** + * Show information about all allowed domains. + * @param range optional Range for the pageable return value + * @see Mastodon API documentation: methods/domain_allows/#get + */ + @JvmOverloads + fun getAllAllowedDomains(range: Range = Range()): MastodonRequest> { + return client.getPageableMastodonRequest( + endpoint = adminDomainAllowEndpoint, + method = MastodonClient.Method.GET, + parameters = range.toParameters() + ) + } + + /** + * Show information about a single allowed domain. + * @param id The ID of the DomainAllow in the database. + * @see Mastodon API documentation: methods/domain_allows/#get-one + */ + fun getAllowedDomain(id: String): MastodonRequest { + return client.getMastodonRequest( + endpoint = "$adminDomainAllowEndpoint/$id", + method = MastodonClient.Method.GET + ) + } + + /** + * Add a domain to the list of domains allowed to federate, to be used when the instance is in allow-list federation mode. + * @param domain The domain to allow federation with. + * @see Mastodon API documentation: admin/domain_allows/#create + */ + fun allowDomainToFederate(domain: String): MastodonRequest { + return client.getMastodonRequest( + endpoint = adminDomainAllowEndpoint, + method = MastodonClient.Method.POST, + parameters = Parameters().apply { + append("domain", domain) + } + ) + } + + /** + * Delete a domain from the allowed domains list. + * @param id The ID of the DomainAllow in the database. + * @see Mastodon API documentation: admin/domain_allows/#delete + */ + fun removeAllowedDomain(id: String) { + client.performAction( + endpoint = "$adminDomainAllowEndpoint/$id", + method = MastodonClient.Method.DELETE + ) + } +} diff --git a/bigbone/src/main/kotlin/social/bigbone/api/method/admin/AdminDomainBlockMethods.kt b/bigbone/src/main/kotlin/social/bigbone/api/method/admin/AdminDomainBlockMethods.kt index 1dc8f10ac..aa554a092 100644 --- a/bigbone/src/main/kotlin/social/bigbone/api/method/admin/AdminDomainBlockMethods.kt +++ b/bigbone/src/main/kotlin/social/bigbone/api/method/admin/AdminDomainBlockMethods.kt @@ -15,7 +15,7 @@ import social.bigbone.api.entity.admin.AdminDomainBlock.Severity */ class AdminDomainBlockMethods(private val client: MastodonClient) { - private val adminDomainBlocksEndpoint = "api/v1/admin/domain_blocks" + private val endpoint = "api/v1/admin/domain_blocks" /** * Show information about all blocked domains. @@ -27,7 +27,7 @@ class AdminDomainBlockMethods(private val client: MastodonClient) { @JvmOverloads fun getAllBlockedDomains(range: Range = Range()): MastodonRequest> { return client.getPageableMastodonRequest( - endpoint = adminDomainBlocksEndpoint, + endpoint = endpoint, method = MastodonClient.Method.GET, parameters = range.toParameters() ) @@ -42,7 +42,7 @@ class AdminDomainBlockMethods(private val client: MastodonClient) { */ fun getBlockedDomain(id: String): MastodonRequest { return client.getMastodonRequest( - endpoint = "$adminDomainBlocksEndpoint/$id", + endpoint = "$endpoint/$id", method = MastodonClient.Method.GET ) } @@ -71,7 +71,7 @@ class AdminDomainBlockMethods(private val client: MastodonClient) { publicComment: String? = null ): MastodonRequest { return client.getMastodonRequest( - endpoint = adminDomainBlocksEndpoint, + endpoint = endpoint, method = MastodonClient.Method.POST, parameters = Parameters().apply { append("domain", domain) @@ -113,7 +113,7 @@ class AdminDomainBlockMethods(private val client: MastodonClient) { publicComment: String? = null ): MastodonRequest { return client.getMastodonRequest( - endpoint = "$adminDomainBlocksEndpoint/$id", + endpoint = "$endpoint/$id", method = MastodonClient.Method.PUT, parameters = Parameters().apply { severity?.let { append("severity", severity.apiName) } @@ -138,7 +138,7 @@ class AdminDomainBlockMethods(private val client: MastodonClient) { */ fun removeBlockedDomain(id: String) { client.performAction( - endpoint = "$adminDomainBlocksEndpoint/$id", + endpoint = "$endpoint/$id", method = MastodonClient.Method.DELETE ) } diff --git a/bigbone/src/test/assets/admin_domain_allow.json b/bigbone/src/test/assets/admin_domain_allow.json new file mode 100644 index 000000000..94fd8ae3a --- /dev/null +++ b/bigbone/src/test/assets/admin_domain_allow.json @@ -0,0 +1,5 @@ +{ + "id": "1", + "domain": "mastodon.social", + "created_at": "2022-09-14T21:23:02.755Z" +} \ No newline at end of file diff --git a/bigbone/src/test/assets/admin_domain_allow_list.json b/bigbone/src/test/assets/admin_domain_allow_list.json new file mode 100644 index 000000000..f70c0faee --- /dev/null +++ b/bigbone/src/test/assets/admin_domain_allow_list.json @@ -0,0 +1,12 @@ +[ + { + "id": "2", + "domain": "mastodon", + "created_at": "2022-09-14T21:24:15.360Z" + }, + { + "id": "1", + "domain": "mastodon.social", + "created_at": "2022-09-14T21:23:02.755Z" + } +] \ No newline at end of file diff --git a/bigbone/src/test/kotlin/social/bigbone/api/method/admin/AdminDomainAllowMethodsTest.kt b/bigbone/src/test/kotlin/social/bigbone/api/method/admin/AdminDomainAllowMethodsTest.kt new file mode 100644 index 000000000..6deb1356f --- /dev/null +++ b/bigbone/src/test/kotlin/social/bigbone/api/method/admin/AdminDomainAllowMethodsTest.kt @@ -0,0 +1,116 @@ +package social.bigbone.api.method.admin + +import io.mockk.slot +import io.mockk.verify +import org.amshove.kluent.AnyException +import org.amshove.kluent.invoking +import org.amshove.kluent.shouldBeEqualTo +import org.amshove.kluent.shouldNotThrow +import org.amshove.kluent.shouldThrow +import org.amshove.kluent.withMessage +import org.junit.jupiter.api.Test +import social.bigbone.MastodonClient +import social.bigbone.Parameters +import social.bigbone.api.Range +import social.bigbone.api.exception.BigBoneRequestException +import social.bigbone.testtool.MockClient + +class AdminDomainAllowMethodsTest { + + private val adminDomainAllowEndpoint = "api/v1/admin/domain_allows" + + @Test + fun `Given client returning success, when getting all allowed domains, then return list of allowed domains`() { + val client = MockClient.mock("admin_domain_allow_list.json") + val adminDomainAllowMethods = AdminDomainAllowMethods(client) + val range = Range("1", "10", "1", 10) + val allowedDomains = adminDomainAllowMethods.getAllAllowedDomains(range).execute() + allowedDomains.part.size shouldBeEqualTo 2 + with(allowedDomains.part[1]) { + id shouldBeEqualTo "1" + domain shouldBeEqualTo "mastodon.social" + } + + val parametersCapturingSlot = slot() + verify { + client.get( + path = adminDomainAllowEndpoint, + query = capture(parametersCapturingSlot) + ) + } + with(parametersCapturingSlot.captured) { + toQuery() shouldBeEqualTo "max_id=${range.maxId}&min_id=${range.minId}&since_id=${range.sinceId}&limit=${range.limit}" + } + } + + @Test + fun `Given client returning success, when getting specific allowed domain, then return expected data for single domain`() { + val client = MockClient.mock("admin_domain_allow.json") + val adminDomainAllowMethods = AdminDomainAllowMethods(client) + val pathVariable = "1" + val adminDomainAllowed = adminDomainAllowMethods.getAllowedDomain(pathVariable).execute() + with(adminDomainAllowed) { + id shouldBeEqualTo pathVariable + domain shouldBeEqualTo "mastodon.social" + } + + verify { + client.get( + path = "$adminDomainAllowEndpoint/$pathVariable" + ) + } + } + + @Test + fun `Given client returning success, when posting a domain to be allowed to federate, then return expected data about the domain`() { + val client = MockClient.mock("admin_domain_allow.json") + val adminDomainAllowMethods = AdminDomainAllowMethods(client) + + invoking { + adminDomainAllowMethods.allowDomainToFederate(domain = "mastodon.social").execute() + } shouldNotThrow AnyException + + val parametersCapturingSlot = slot() + verify { + client.post( + path = adminDomainAllowEndpoint, + body = capture(parametersCapturingSlot), + addIdempotencyKey = false + ) + } + with(parametersCapturingSlot.captured) { + toQuery() shouldBeEqualTo "domain=mastodon.social" + } + } + + @Test + fun `Given client returning success, when removing specific domain from being allowd, then check data for single ip`() { + val client = MockClient.mock("admin_domain_allow.json") + val adminDomainAllowMethods = AdminDomainAllowMethods(client) + val id = "1" + + invoking { + adminDomainAllowMethods.removeAllowedDomain(id = id) + } shouldNotThrow AnyException + + verify { + client.performAction( + endpoint = "$adminDomainAllowEndpoint/$id", + method = MastodonClient.Method.DELETE + ) + } + } + + @Test + fun `Given a client returning forbidden, when getting specific allowed domain, then propagate error`() { + val client = MockClient.failWithResponse( + responseJsonAssetPath = "error_403_forbidden.json", + responseCode = 403, + message = "Forbidden" + ) + + invoking { + AdminDomainAllowMethods(client).getAllowedDomain(id = "1").execute() + } shouldThrow BigBoneRequestException::class withMessage "Forbidden" + } +} diff --git a/docs/api-coverage/admin/domain-allows.md b/docs/api-coverage/admin/domain-allows.md index 431e4586f..f093c16d7 100644 --- a/docs/api-coverage/admin/domain-allows.md +++ b/docs/api-coverage/admin/domain-allows.md @@ -11,8 +11,8 @@ nav_order: 7 https://docs.joinmastodon.org/methods/admin/domain_allows/ | Method | Description | Status | Comments | -|------------------------------------------|-----------------------------|--------|----------| -| `GET /api/v1/admin/domain_allows` | List all allowed domains | 🔴 | | -| `GET /api/v1/admin/domain_allows/:id` | Get a single allowed domain | 🔴 | | -| `POST /api/v1/admin/domain_allows` | Allow a domain to federate | 🔴 | | -| `DELETE /api/v1/admin/domain_allows/:id` | Delete an allowed domain | 🔴 | | +|------------------------------------------|-----------------------------|----|--| +| `GET /api/v1/admin/domain_allows` | List all allowed domains | | Fully supported. | +| `GET /api/v1/admin/domain_allows/:id` | Get a single allowed domain | | Fully supported. | +| `POST /api/v1/admin/domain_allows` | Allow a domain to federate | | Fully supported. | +| `DELETE /api/v1/admin/domain_allows/:id` | Delete an allowed domain | | Fully supported. |