From 61d86b18be7825f021575c811cf8834b562e755c Mon Sep 17 00:00:00 2001 From: Robinson Date: Sat, 14 Aug 2021 23:13:06 -0600 Subject: [PATCH] Added CAA records, added multi-page results --- LICENSE | 29 ++++- README.md | 2 +- build.gradle.kts | 54 ++++++---- src/dorkbox/kloudflareApi/Kloudflare.kt | 101 ++++++++++++------ .../kloudflareApi/api/CloudflareActions.kt | 13 +-- .../kloudflareApi/api/core/ResultInfo.kt | 3 + .../kloudflareApi/api/dns/CreateDnsRecord.kt | 6 ++ src/dorkbox/kloudflareApi/api/dns/Data.kt | 26 ----- .../kloudflareApi/api/dns/DnsRecord.kt | 8 +- .../kloudflareApi/api/dns/RecordType.kt | 2 +- test/dorkbox/kloudflareApi/KloudflareTest.kt | 26 +++-- 11 files changed, 168 insertions(+), 102 deletions(-) delete mode 100644 src/dorkbox/kloudflareApi/api/dns/Data.kt diff --git a/LICENSE b/LICENSE index fdc70aa..9cbcb68 100644 --- a/LICENSE +++ b/LICENSE @@ -1,7 +1,7 @@ - KloudflareAPI - [The Apache Software License, Version 2.0] https://git.dorkbox.com/dorkbox/KloudflareAPI - Copyright 2020 + Copyright 2021 Dorkbox LLC Cloudflare API v4 for Kotlin @@ -13,3 +13,30 @@ JetBrains s.r.o. and Kotlin Programming Language contributors Kotlin Compiler, Test Data+Libraries, and Tools repository contain third-party code, to which different licenses may apply See: https://github.com/JetBrains/kotlin/blob/master/license/README.md + + - OkHttp - Square’s meticulous HTTP client for the JVM, Android, and GraalVM + [The Apache Software License, Version 2.0] + https://github.com/square/okhttp + Copyright 2021 + Square, Inc + + - Conscrypt - An open platform for building modern web apps for Java back ends + [The Apache Software License, Version 2.0] + https://github.com/google/conscrypt + Copyright 2021 + Google Inc + The Android Open Source Project + The Netty Project + Apache Harmony + + - Retrofit - A type-safe HTTP client for Android and the JVM + [The Apache Software License, Version 2.0] + https://github.com/square/retrofit + Copyright 2021 + Square, Inc + + - Moshi - A modern JSON library for Kotlin and Java + [The Apache Software License, Version 2.0] + https://github.com/square/moshi + Copyright 2021 + Square, Inc diff --git a/README.md b/README.md index db05992..5a65dfc 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ Maven Info com.dorkbox KloudflareAPI - 1.3 + 1.4 ``` diff --git a/build.gradle.kts b/build.gradle.kts index 4e581a4..7afeb88 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,30 +1,45 @@ +/* + * Copyright 2021 dorkbox, llc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ import java.time.Instant - /////////////////////////////// ////// PUBLISH TO SONATYPE / MAVEN CENTRAL ////// TESTING : (to local maven repo) <'publish and release' - 'publishToMavenLocal'> ////// RELEASE : (to sonatype/maven central), <'publish and release' - 'publishToSonatypeAndRelease'> /////////////////////////////// +gradle.startParameter.showStacktrace = ShowStacktrace.ALWAYS // always show the stacktrace! +gradle.startParameter.warningMode = WarningMode.All + plugins { - java + id("com.dorkbox.GradleUtils") version "2.9" + id("com.dorkbox.Licensing") version "2.9.2" + id("com.dorkbox.VersionUpdate") version "2.4" + id("com.dorkbox.GradlePublish") version "1.11" - id("com.dorkbox.GradleUtils") version "1.12" - id("com.dorkbox.Licensing") version "2.5.2" - id("com.dorkbox.VersionUpdate") version "2.0.5" - id("com.dorkbox.GradlePublish") version "1.8" - - kotlin("jvm") version "1.4.21" - kotlin("kapt") version "1.4.21" + kotlin("jvm") version "1.5.21" + kotlin("kapt") version "1.5.21" } object Extras { // set for the project const val description = "Cloudflare API v4 for Kotlin" const val group = "com.dorkbox" - const val version = "1.3" + const val version = "1.4" // set as project.ext const val name = "KloudflareAPI" @@ -32,6 +47,7 @@ object Extras { const val vendor = "Dorkbox LLC" const val vendorUrl = "https://dorkbox.com" const val url = "https://git.dorkbox.com/dorkbox/KloudflareAPI" + val buildDate = Instant.now().toString() } @@ -39,8 +55,7 @@ object Extras { ///// assign 'Extras' /////////////////////////////// GradleUtils.load("$projectDir/../../gradle.properties", Extras) -GradleUtils.fixIntellijPaths() -GradleUtils.defaultResolutionStrategy() +GradleUtils.defaults() GradleUtils.compileConfiguration(JavaVersion.VERSION_11) licensing { @@ -71,12 +86,6 @@ sourceSets { } } -repositories { -// mavenLocal() // this must be first! - jcenter() -} - - tasks.jar.get().apply { manifest { // https://docs.oracle.com/javase/tutorial/deployment/jar/packageman.html @@ -98,16 +107,17 @@ dependencies { implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") implementation("org.jetbrains.kotlin:kotlin-stdlib") implementation("org.jetbrains.kotlin:kotlin-stdlib-common") + implementation("org.jetbrains.kotlin:kotlin-reflect") - val moshiVer = "1.11.0" - val okHttpVer = "4.9.0" + val moshiVer = "1.12.0" + val okHttpVer = "4.9.1" val retroVer = "2.9.0" implementation("com.squareup.okhttp3:okhttp:$okHttpVer") implementation("com.squareup.okhttp3:logging-interceptor:$okHttpVer") // Log Network Calls // better SSL library - implementation("org.conscrypt:conscrypt-openjdk-uber:2.5.1") + implementation("org.conscrypt:conscrypt-openjdk-uber:2.5.2") // For serialization. THESE ARE NOT TRANSITIVE because it screws up the kotlin version implementation("com.squareup.retrofit2:retrofit:$retroVer") @@ -116,6 +126,8 @@ dependencies { implementation ("com.squareup.moshi:moshi:$moshiVer") implementation ("com.squareup.moshi:moshi-kotlin:$moshiVer") + implementation("com.dorkbox:Updates:1.1") + // for AUTOMATIC kotlin reflective serialization of json classes kapt ("com.squareup.moshi:moshi-kotlin-codegen:$moshiVer") kaptTest ("com.squareup.moshi:moshi-kotlin-codegen:$moshiVer") diff --git a/src/dorkbox/kloudflareApi/Kloudflare.kt b/src/dorkbox/kloudflareApi/Kloudflare.kt index efd8947..f6258a4 100644 --- a/src/dorkbox/kloudflareApi/Kloudflare.kt +++ b/src/dorkbox/kloudflareApi/Kloudflare.kt @@ -35,31 +35,33 @@ import okhttp3.ResponseBody import org.conscrypt.Conscrypt import retrofit2.Call import retrofit2.Converter +import retrofit2.Response import retrofit2.Retrofit import retrofit2.converter.moshi.MoshiConverterFactory import java.io.IOException import java.security.Security -import java.util.Collections.emptyMap class Kloudflare(private val xAuthEmail: String, private val xAuthKey: String) { companion object { private const val API_BASE_URL = "https://api.cloudflare.com/client/v4/" - - init { - try { - Security.insertProviderAt(Conscrypt.newProvider(), 1); - } - catch (e: Throwable) { - e.printStackTrace(); - } - } - /** * Gets the version number. */ - const val version = "1.3" + const val version = "1.4" + + init { + // Add this project to the updates system, which verifies this class + UUID + version information + dorkbox.updates.Updates.add(Kloudflare::class.java, "16bcc9060ac6483782aafc2a5502e7b3", version) + + try { + Security.insertProviderAt(Conscrypt.newProvider(), 1) + } + catch (e: Throwable) { + e.printStackTrace() + } + } } private val errorConverter: Converter @@ -69,11 +71,11 @@ class Kloudflare(private val xAuthEmail: String, private val xAuthKey: String) { init { // JSON mapping to java classes - // val interceptor = HttpLoggingInterceptor() - // interceptor.level = HttpLoggingInterceptor.Level.BODY +// val interceptor = HttpLoggingInterceptor() +// interceptor.level = HttpLoggingInterceptor.Level.BODY client = OkHttpClient.Builder() - // .addInterceptor(interceptor) // this is the raw HTTP interceptor +// .addInterceptor(interceptor) // this is the raw HTTP interceptor .build() val moshi = Moshi.Builder() @@ -103,11 +105,10 @@ class Kloudflare(private val xAuthEmail: String, private val xAuthKey: String) { return response.body?.string()!! } - @Throws(IOException::class) private fun wrap(call: Call>): T { - val response = call.execute() + val response: Response> = call.execute() - val body = response.body() + val body: CfResponse? = response.body() if (response.isSuccessful && body != null && body.success) { return body.result!! } @@ -116,6 +117,18 @@ class Kloudflare(private val xAuthEmail: String, private val xAuthKey: String) { throw IOException("Call failed: " + errorResponse?.errors?.joinToString { error: Error -> "[${error.code} : ${error.message}]" }) } + private fun wrapOptions(call: Call>): Pair { + val response: Response> = call.execute() + + val body: CfResponse? = response.body() + if (response.isSuccessful && body != null && body.success) { + return Pair(body.resultInfo!!, body.result!!) + } + + val errorResponse = errorConverter.convert(response.errorBody()!!) + throw IOException("Call failed: " + errorResponse?.errors?.joinToString { error: Error -> "[${error.code} : ${error.message}]" }) + } + /** * Gets the User details * @@ -149,14 +162,25 @@ class Kloudflare(private val xAuthEmail: String, private val xAuthKey: String) { * https://api.cloudflare.com/#zone-properties */ fun listZones(options: Map = emptyMap()): List { - val zones = wrap(cloudflare.listZones(xAuthEmail, xAuthKey, options)) - zones.forEach { zone -> - // have to assign - zone.kloudflare = this; + val actualOptions = mutableMapOf() + actualOptions.putAll(options) + actualOptions.putIfAbsent("per_page", "20") // not too many at once! + val (info, data) = wrapOptions(cloudflare.listZones(xAuthEmail, xAuthKey, actualOptions)) + data.forEach { zone -> + // have to assign + zone.kloudflare = this } - return zones + val zones = mutableListOf() + zones.addAll(data) + + if (info.totalPages - info.page > 0) { + actualOptions["page"] = "${info.page + 1}" + zones.addAll(listZones(actualOptions)) + } + + return data } /** @@ -182,13 +206,25 @@ class Kloudflare(private val xAuthEmail: String, private val xAuthKey: String) { * * https://api.cloudflare.com/#dns-records-for-a-zone-properties */ - fun listDnsRecords(zone: Zone): List { - val wrap = - wrap(cloudflare.listDnsRecords(xAuthEmail, xAuthKey, zone.id)) - wrap.forEach { + fun listDnsRecords(zone: Zone, options: Map = emptyMap()): List { + val actualOptions = mutableMapOf() + actualOptions.putAll(options) + actualOptions.putIfAbsent("per_page", "20") // not too many at once! + + val (info, data) = wrapOptions(cloudflare.listDnsRecords(xAuthEmail, xAuthKey, zone.id, actualOptions)) + data.forEach { it.zone = zone } - return wrap + + val records = mutableListOf() + records.addAll(data) + + if (info.totalPages - info.page > 0) { + actualOptions["page"] = "${info.page + 1}" + records.addAll(listDnsRecords(zone, actualOptions)) + } + + return records } /** @@ -197,10 +233,9 @@ class Kloudflare(private val xAuthEmail: String, private val xAuthKey: String) { * https://api.cloudflare.com/#dns-records-for-a-zone-create-dns-record */ fun createDnsRecord(dnsRecord: CreateDnsRecord): DnsRecord { - val wrap = - wrap(cloudflare.createDnsRecord(xAuthEmail, xAuthKey, dnsRecord.zone.id, dnsRecord)) - wrap.zone = dnsRecord.zone - return wrap + return wrap(cloudflare.createDnsRecord(xAuthEmail, xAuthKey, dnsRecord.zone.id, dnsRecord)).apply { + zone = dnsRecord.zone + } } /** @@ -208,7 +243,7 @@ class Kloudflare(private val xAuthEmail: String, private val xAuthKey: String) { * * https://api.cloudflare.com/#dns-records-for-a-zone-update-dns-record */ - fun updateDnsRecord(updatedDnsRecord: UpdateDnsRecord): Any { + fun updateDnsRecord(updatedDnsRecord: UpdateDnsRecord): DnsRecord { return wrap(cloudflare.updateDnsRecord(xAuthEmail, xAuthKey, updatedDnsRecord.zone.id, diff --git a/src/dorkbox/kloudflareApi/api/CloudflareActions.kt b/src/dorkbox/kloudflareApi/api/CloudflareActions.kt index d06471e..ddb162d 100644 --- a/src/dorkbox/kloudflareApi/api/CloudflareActions.kt +++ b/src/dorkbox/kloudflareApi/api/CloudflareActions.kt @@ -28,15 +28,7 @@ import dorkbox.kloudflareApi.api.zone.RatePlan import dorkbox.kloudflareApi.api.zone.Zone import dorkbox.kloudflareApi.api.zone.settings.ZoneSetting import retrofit2.Call -import retrofit2.http.Body -import retrofit2.http.DELETE -import retrofit2.http.GET -import retrofit2.http.Header -import retrofit2.http.Headers -import retrofit2.http.POST -import retrofit2.http.PUT -import retrofit2.http.Path -import retrofit2.http.QueryMap +import retrofit2.http.* interface CloudflareActions { /** @@ -124,7 +116,8 @@ interface CloudflareActions { fun listDnsRecords( @Header("X-Auth-Email") email: String, @Header("X-Auth-Key") key: String, - @Path("zone_identifier") zoneIdentifier: String + @Path("zone_identifier") zoneIdentifier: String, + @QueryMap options: Map = emptyMap() ): Call>> diff --git a/src/dorkbox/kloudflareApi/api/core/ResultInfo.kt b/src/dorkbox/kloudflareApi/api/core/ResultInfo.kt index 0c46d4b..6b55705 100644 --- a/src/dorkbox/kloudflareApi/api/core/ResultInfo.kt +++ b/src/dorkbox/kloudflareApi/api/core/ResultInfo.kt @@ -26,6 +26,9 @@ class ResultInfo { @field:[Json(name = "per_page")] var perPage = 20 + @field:[Json(name = "total_pages")] + var totalPages = 1 + @field:[Json(name = "count")] var count = 1 diff --git a/src/dorkbox/kloudflareApi/api/dns/CreateDnsRecord.kt b/src/dorkbox/kloudflareApi/api/dns/CreateDnsRecord.kt index cef6fe0..12793af 100644 --- a/src/dorkbox/kloudflareApi/api/dns/CreateDnsRecord.kt +++ b/src/dorkbox/kloudflareApi/api/dns/CreateDnsRecord.kt @@ -67,4 +67,10 @@ open class CreateDnsRecord(@Transient val zone: Zone = Zone()) { */ @field:[Json(name = "proxied")] var proxied = false + + /** + * Whether the record is receiving the performance and security benefits of Cloudflare + */ + @field:[Json(name = "data")] + var data = mutableMapOf() } diff --git a/src/dorkbox/kloudflareApi/api/dns/Data.kt b/src/dorkbox/kloudflareApi/api/dns/Data.kt deleted file mode 100644 index 6110a8a..0000000 --- a/src/dorkbox/kloudflareApi/api/dns/Data.kt +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2019 dorkbox, llc - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package dorkbox.kloudflareApi.api.dns - -import com.squareup.moshi.JsonClass - -/** - * https://api.cloudflare.com/#dns-records-for-a-zone-properties - */ -@JsonClass(generateAdapter = true) -class Data { - -} diff --git a/src/dorkbox/kloudflareApi/api/dns/DnsRecord.kt b/src/dorkbox/kloudflareApi/api/dns/DnsRecord.kt index 0add1ac..68c94e4 100644 --- a/src/dorkbox/kloudflareApi/api/dns/DnsRecord.kt +++ b/src/dorkbox/kloudflareApi/api/dns/DnsRecord.kt @@ -43,7 +43,9 @@ open class DnsRecord { } } - + override fun toString(): String { + return "DnsRecord(type=$type, name='${name}, content='${content}', ttl='${ttl}')" + } /** * DNS record identifier tag @@ -53,7 +55,7 @@ open class DnsRecord { /** * Record type - * A, AAAA, CNAME, TXT, SRV, LOC, MX, NS, SPF, CERT, DNSKEY, DS, NAPTR, SMIMEA, SSHFP, TLSA, URI + * A, AAAA, CNAME, TXT, SRV, LOC, MX, NS, SPF, CERT, DNSKEY, DS, NAPTR, SMIMEA, SSHFP, TLSA, URI, CCA */ @field:[Json(name = "type")] var type = RecordType.A @@ -125,5 +127,5 @@ open class DnsRecord { * Metadata about the record */ @field:[Json(name = "data")] - var data: Data = Data() + var data = mutableMapOf() } diff --git a/src/dorkbox/kloudflareApi/api/dns/RecordType.kt b/src/dorkbox/kloudflareApi/api/dns/RecordType.kt index 4d26d46..b1b0326 100644 --- a/src/dorkbox/kloudflareApi/api/dns/RecordType.kt +++ b/src/dorkbox/kloudflareApi/api/dns/RecordType.kt @@ -16,5 +16,5 @@ package dorkbox.kloudflareApi.api.dns enum class RecordType { - A, AAAA, CNAME, TXT, SRV, LOC, MX, NS, SPF, CERT, DNSKEY, DS, NAPTR, SMIMEA, SSHFP, TLSA, URI + A, AAAA, CNAME, TXT, SRV, LOC, MX, NS, SPF, CERT, DNSKEY, DS, NAPTR, SMIMEA, SSHFP, TLSA, URI, CAA } diff --git a/test/dorkbox/kloudflareApi/KloudflareTest.kt b/test/dorkbox/kloudflareApi/KloudflareTest.kt index fe8f237..c08fc7e 100644 --- a/test/dorkbox/kloudflareApi/KloudflareTest.kt +++ b/test/dorkbox/kloudflareApi/KloudflareTest.kt @@ -53,18 +53,32 @@ object KloudflareTest { // println(kloudflare.getUserBillingHistory()) - val zones = kloudflare.listZones() - zones.forEach { - println(it) - } +// val zones = kloudflare.listZones().filter { it.name == "example.com" } +// zones.forEach { +// println(it) +// } // println(kloudflare.getZoneRatePlans("123")) // println(kloudflare.getZoneRatePlans("123")) // println(kloudflare.getZoneSettings("123")) - // println(kloudflare.listDnsRecords("123")) - println(kloudflare.listAccessRules()) +// println(kloudflare.listDnsRecords("123")) +// println(kloudflare.listAccessRules()) + val zone = kloudflare.listZones().first { it.name == "example.com" } + zone.dnsRecords.forEach { + println(it) + } + +// val newDnsRecord = CreateDnsRecord(zone) +// newDnsRecord.type = RecordType.CAA +// newDnsRecord.name = "example.com" +// newDnsRecord.data["flags"] = 0 +// newDnsRecord.data["tag"] = "issue" +// newDnsRecord.data["value"] = "letsencrypt.org" +// +// val newRecord = kloudflare.createDnsRecord(newDnsRecord) +// println("Created: ${newRecord.name} -> ${newRecord.content}") } finally {