Added CAA records, added multi-page results

This commit is contained in:
Robinson 2021-08-14 23:13:06 -06:00
parent 25750bf1c3
commit 61d86b18be
11 changed files with 168 additions and 102 deletions

29
LICENSE
View File

@ -1,7 +1,7 @@
- KloudflareAPI - - KloudflareAPI -
[The Apache Software License, Version 2.0] [The Apache Software License, Version 2.0]
https://git.dorkbox.com/dorkbox/KloudflareAPI https://git.dorkbox.com/dorkbox/KloudflareAPI
Copyright 2020 Copyright 2021
Dorkbox LLC Dorkbox LLC
Cloudflare API v4 for Kotlin Cloudflare API v4 for Kotlin
@ -13,3 +13,30 @@
JetBrains s.r.o. and Kotlin Programming Language contributors 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 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 See: https://github.com/JetBrains/kotlin/blob/master/license/README.md
- OkHttp - Squares 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

View File

@ -60,7 +60,7 @@ Maven Info
<dependency> <dependency>
<groupId>com.dorkbox</groupId> <groupId>com.dorkbox</groupId>
<artifactId>KloudflareAPI</artifactId> <artifactId>KloudflareAPI</artifactId>
<version>1.3</version> <version>1.4</version>
</dependency> </dependency>
</dependencies> </dependencies>
``` ```

View File

@ -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 import java.time.Instant
/////////////////////////////// ///////////////////////////////
////// PUBLISH TO SONATYPE / MAVEN CENTRAL ////// PUBLISH TO SONATYPE / MAVEN CENTRAL
////// TESTING : (to local maven repo) <'publish and release' - 'publishToMavenLocal'> ////// TESTING : (to local maven repo) <'publish and release' - 'publishToMavenLocal'>
////// RELEASE : (to sonatype/maven central), <'publish and release' - 'publishToSonatypeAndRelease'> ////// RELEASE : (to sonatype/maven central), <'publish and release' - 'publishToSonatypeAndRelease'>
/////////////////////////////// ///////////////////////////////
gradle.startParameter.showStacktrace = ShowStacktrace.ALWAYS // always show the stacktrace!
gradle.startParameter.warningMode = WarningMode.All
plugins { 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" kotlin("jvm") version "1.5.21"
id("com.dorkbox.Licensing") version "2.5.2" kotlin("kapt") version "1.5.21"
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"
} }
object Extras { object Extras {
// set for the project // set for the project
const val description = "Cloudflare API v4 for Kotlin" const val description = "Cloudflare API v4 for Kotlin"
const val group = "com.dorkbox" const val group = "com.dorkbox"
const val version = "1.3" const val version = "1.4"
// set as project.ext // set as project.ext
const val name = "KloudflareAPI" const val name = "KloudflareAPI"
@ -32,6 +47,7 @@ object Extras {
const val vendor = "Dorkbox LLC" const val vendor = "Dorkbox LLC"
const val vendorUrl = "https://dorkbox.com" const val vendorUrl = "https://dorkbox.com"
const val url = "https://git.dorkbox.com/dorkbox/KloudflareAPI" const val url = "https://git.dorkbox.com/dorkbox/KloudflareAPI"
val buildDate = Instant.now().toString() val buildDate = Instant.now().toString()
} }
@ -39,8 +55,7 @@ object Extras {
///// assign 'Extras' ///// assign 'Extras'
/////////////////////////////// ///////////////////////////////
GradleUtils.load("$projectDir/../../gradle.properties", Extras) GradleUtils.load("$projectDir/../../gradle.properties", Extras)
GradleUtils.fixIntellijPaths() GradleUtils.defaults()
GradleUtils.defaultResolutionStrategy()
GradleUtils.compileConfiguration(JavaVersion.VERSION_11) GradleUtils.compileConfiguration(JavaVersion.VERSION_11)
licensing { licensing {
@ -71,12 +86,6 @@ sourceSets {
} }
} }
repositories {
// mavenLocal() // this must be first!
jcenter()
}
tasks.jar.get().apply { tasks.jar.get().apply {
manifest { manifest {
// https://docs.oracle.com/javase/tutorial/deployment/jar/packageman.html // 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-jdk8")
implementation("org.jetbrains.kotlin:kotlin-stdlib") implementation("org.jetbrains.kotlin:kotlin-stdlib")
implementation("org.jetbrains.kotlin:kotlin-stdlib-common") implementation("org.jetbrains.kotlin:kotlin-stdlib-common")
implementation("org.jetbrains.kotlin:kotlin-reflect")
val moshiVer = "1.11.0" val moshiVer = "1.12.0"
val okHttpVer = "4.9.0" val okHttpVer = "4.9.1"
val retroVer = "2.9.0" val retroVer = "2.9.0"
implementation("com.squareup.okhttp3:okhttp:$okHttpVer") implementation("com.squareup.okhttp3:okhttp:$okHttpVer")
implementation("com.squareup.okhttp3:logging-interceptor:$okHttpVer") // Log Network Calls implementation("com.squareup.okhttp3:logging-interceptor:$okHttpVer") // Log Network Calls
// better SSL library // 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 // For serialization. THESE ARE NOT TRANSITIVE because it screws up the kotlin version
implementation("com.squareup.retrofit2:retrofit:$retroVer") implementation("com.squareup.retrofit2:retrofit:$retroVer")
@ -116,6 +126,8 @@ dependencies {
implementation ("com.squareup.moshi:moshi:$moshiVer") implementation ("com.squareup.moshi:moshi:$moshiVer")
implementation ("com.squareup.moshi:moshi-kotlin:$moshiVer") implementation ("com.squareup.moshi:moshi-kotlin:$moshiVer")
implementation("com.dorkbox:Updates:1.1")
// for AUTOMATIC kotlin reflective serialization of json classes // for AUTOMATIC kotlin reflective serialization of json classes
kapt ("com.squareup.moshi:moshi-kotlin-codegen:$moshiVer") kapt ("com.squareup.moshi:moshi-kotlin-codegen:$moshiVer")
kaptTest ("com.squareup.moshi:moshi-kotlin-codegen:$moshiVer") kaptTest ("com.squareup.moshi:moshi-kotlin-codegen:$moshiVer")

View File

@ -35,31 +35,33 @@ import okhttp3.ResponseBody
import org.conscrypt.Conscrypt import org.conscrypt.Conscrypt
import retrofit2.Call import retrofit2.Call
import retrofit2.Converter import retrofit2.Converter
import retrofit2.Response
import retrofit2.Retrofit import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory import retrofit2.converter.moshi.MoshiConverterFactory
import java.io.IOException import java.io.IOException
import java.security.Security import java.security.Security
import java.util.Collections.emptyMap
class Kloudflare(private val xAuthEmail: String, private val xAuthKey: String) { class Kloudflare(private val xAuthEmail: String, private val xAuthKey: String) {
companion object { companion object {
private const val API_BASE_URL = "https://api.cloudflare.com/client/v4/" 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. * 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<ResponseBody, CfErrorResponse> private val errorConverter: Converter<ResponseBody, CfErrorResponse>
@ -69,11 +71,11 @@ class Kloudflare(private val xAuthEmail: String, private val xAuthKey: String) {
init { init {
// JSON mapping to java classes // JSON mapping to java classes
// val interceptor = HttpLoggingInterceptor() // val interceptor = HttpLoggingInterceptor()
// interceptor.level = HttpLoggingInterceptor.Level.BODY // interceptor.level = HttpLoggingInterceptor.Level.BODY
client = OkHttpClient.Builder() client = OkHttpClient.Builder()
// .addInterceptor(interceptor) // this is the raw HTTP interceptor // .addInterceptor(interceptor) // this is the raw HTTP interceptor
.build() .build()
val moshi = Moshi.Builder() val moshi = Moshi.Builder()
@ -103,11 +105,10 @@ class Kloudflare(private val xAuthEmail: String, private val xAuthKey: String) {
return response.body?.string()!! return response.body?.string()!!
} }
@Throws(IOException::class)
private fun <T> wrap(call: Call<CfResponse<T>>): T { private fun <T> wrap(call: Call<CfResponse<T>>): T {
val response = call.execute() val response: Response<CfResponse<T>> = call.execute()
val body = response.body() val body: CfResponse<T>? = response.body()
if (response.isSuccessful && body != null && body.success) { if (response.isSuccessful && body != null && body.success) {
return body.result!! 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}]" }) throw IOException("Call failed: " + errorResponse?.errors?.joinToString { error: Error -> "[${error.code} : ${error.message}]" })
} }
private fun <T> wrapOptions(call: Call<CfResponse<T>>): Pair<ResultInfo, T> {
val response: Response<CfResponse<T>> = call.execute()
val body: CfResponse<T>? = 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 * Gets the User details
* *
@ -149,14 +162,25 @@ class Kloudflare(private val xAuthEmail: String, private val xAuthKey: String) {
* https://api.cloudflare.com/#zone-properties * https://api.cloudflare.com/#zone-properties
*/ */
fun listZones(options: Map<String, String> = emptyMap()): List<Zone> { fun listZones(options: Map<String, String> = emptyMap()): List<Zone> {
val zones = wrap(cloudflare.listZones(xAuthEmail, xAuthKey, options)) val actualOptions = mutableMapOf<String, String>()
zones.forEach { zone -> actualOptions.putAll(options)
// have to assign actualOptions.putIfAbsent("per_page", "20") // not too many at once!
zone.kloudflare = this;
val (info, data) = wrapOptions(cloudflare.listZones(xAuthEmail, xAuthKey, actualOptions))
data.forEach { zone ->
// have to assign
zone.kloudflare = this
} }
return zones val zones = mutableListOf<Zone>()
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 * https://api.cloudflare.com/#dns-records-for-a-zone-properties
*/ */
fun listDnsRecords(zone: Zone): List<DnsRecord> { fun listDnsRecords(zone: Zone, options: Map<String, String> = emptyMap()): List<DnsRecord> {
val wrap = val actualOptions = mutableMapOf<String, String>()
wrap(cloudflare.listDnsRecords(xAuthEmail, xAuthKey, zone.id)) actualOptions.putAll(options)
wrap.forEach { 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 it.zone = zone
} }
return wrap
val records = mutableListOf<DnsRecord>()
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 * https://api.cloudflare.com/#dns-records-for-a-zone-create-dns-record
*/ */
fun createDnsRecord(dnsRecord: CreateDnsRecord): DnsRecord { fun createDnsRecord(dnsRecord: CreateDnsRecord): DnsRecord {
val wrap = return wrap(cloudflare.createDnsRecord(xAuthEmail, xAuthKey, dnsRecord.zone.id, dnsRecord)).apply {
wrap(cloudflare.createDnsRecord(xAuthEmail, xAuthKey, dnsRecord.zone.id, dnsRecord)) zone = dnsRecord.zone
wrap.zone = dnsRecord.zone }
return wrap
} }
/** /**
@ -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 * 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, return wrap(cloudflare.updateDnsRecord(xAuthEmail,
xAuthKey, xAuthKey,
updatedDnsRecord.zone.id, updatedDnsRecord.zone.id,

View File

@ -28,15 +28,7 @@ import dorkbox.kloudflareApi.api.zone.RatePlan
import dorkbox.kloudflareApi.api.zone.Zone import dorkbox.kloudflareApi.api.zone.Zone
import dorkbox.kloudflareApi.api.zone.settings.ZoneSetting import dorkbox.kloudflareApi.api.zone.settings.ZoneSetting
import retrofit2.Call import retrofit2.Call
import retrofit2.http.Body import retrofit2.http.*
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
interface CloudflareActions { interface CloudflareActions {
/** /**
@ -124,7 +116,8 @@ interface CloudflareActions {
fun listDnsRecords( fun listDnsRecords(
@Header("X-Auth-Email") email: String, @Header("X-Auth-Email") email: String,
@Header("X-Auth-Key") key: String, @Header("X-Auth-Key") key: String,
@Path("zone_identifier") zoneIdentifier: String @Path("zone_identifier") zoneIdentifier: String,
@QueryMap options: Map<String, String> = emptyMap()
): Call<CfResponse<List<DnsRecord>>> ): Call<CfResponse<List<DnsRecord>>>

View File

@ -26,6 +26,9 @@ class ResultInfo {
@field:[Json(name = "per_page")] @field:[Json(name = "per_page")]
var perPage = 20 var perPage = 20
@field:[Json(name = "total_pages")]
var totalPages = 1
@field:[Json(name = "count")] @field:[Json(name = "count")]
var count = 1 var count = 1

View File

@ -67,4 +67,10 @@ open class CreateDnsRecord(@Transient val zone: Zone = Zone()) {
*/ */
@field:[Json(name = "proxied")] @field:[Json(name = "proxied")]
var proxied = false var proxied = false
/**
* Whether the record is receiving the performance and security benefits of Cloudflare
*/
@field:[Json(name = "data")]
var data = mutableMapOf<String, Any>()
} }

View File

@ -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 {
}

View File

@ -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 * DNS record identifier tag
@ -53,7 +55,7 @@ open class DnsRecord {
/** /**
* Record type * 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")] @field:[Json(name = "type")]
var type = RecordType.A var type = RecordType.A
@ -125,5 +127,5 @@ open class DnsRecord {
* Metadata about the record * Metadata about the record
*/ */
@field:[Json(name = "data")] @field:[Json(name = "data")]
var data: Data = Data() var data = mutableMapOf<String, Any>()
} }

View File

@ -16,5 +16,5 @@
package dorkbox.kloudflareApi.api.dns package dorkbox.kloudflareApi.api.dns
enum class RecordType { 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
} }

View File

@ -53,18 +53,32 @@ object KloudflareTest {
// println(kloudflare.getUserBillingHistory()) // println(kloudflare.getUserBillingHistory())
val zones = kloudflare.listZones() // val zones = kloudflare.listZones().filter { it.name == "example.com" }
zones.forEach { // zones.forEach {
println(it) // println(it)
} // }
// println(kloudflare.getZoneRatePlans("123")) // println(kloudflare.getZoneRatePlans("123"))
// println(kloudflare.getZoneRatePlans("123")) // println(kloudflare.getZoneRatePlans("123"))
// println(kloudflare.getZoneSettings("123")) // println(kloudflare.getZoneSettings("123"))
// println(kloudflare.listDnsRecords("123")) // println(kloudflare.listDnsRecords("123"))
println(kloudflare.listAccessRules()) // 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 { finally {