Skip to content

Commit

Permalink
TqRestClient flag for allowing self signed certs
Browse files Browse the repository at this point in the history
It's a common thing especially in enterprise code to communicate with
servers that are self-signed. You do still trust the server in question
but you don't wish to completely ignore all certificate checking across
all of the url connections. Thus this feature was added to allow for
improved usability of restful connections without exposing security
concerns across an application.

Also upgraded various dependencies to the latest.
  • Loading branch information
sepatel committed Sep 2, 2018
1 parent f55e500 commit 655de4d
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 11 deletions.
4 changes: 2 additions & 2 deletions gradle.properties
@@ -1,6 +1,6 @@
version=0.9.3-SNAPSHOT
kotlin_version=1.2.40
jackson_version=2.9.5
kotlin_version=1.2.61
jackson_version=2.9.6
junit_version=4.12
spark_version=2.7.2

36 changes: 27 additions & 9 deletions tekniq-rest/src/main/kotlin/io/tekniq/rest/TqRestClient.kt
Expand Up @@ -6,38 +6,46 @@ import com.fasterxml.jackson.module.kotlin.KotlinModule
import java.io.IOException
import java.net.HttpURLConnection
import java.net.URL
import java.security.SecureRandom
import java.security.cert.X509Certificate
import java.util.*
import javax.net.ssl.*
import kotlin.reflect.KClass
import kotlin.system.measureTimeMillis

open class TqRestClient(val logHandler: RestLogHandler = NoOpRestLogHandler,
val mapper: ObjectMapper = ObjectMapper().registerModule(KotlinModule())
.enable(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL)
.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)) {
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES),
val allowSelfSigned: Boolean = false) {
private val ctx = SSLContext.getInstance("SSL").apply {
init(null, arrayOf(SelfSignedTrustManager), SecureRandom())
}

open fun delete(url: String, headers: Map<String, Any> = emptyMap()): TqResponse = request("DELETE", url, headers = headers)
open fun get(url: String, headers: Map<String, Any> = emptyMap()): TqResponse = request("GET", url, headers = headers)
open fun put(url: String, json: Any?, headers: Map<String, Any> = emptyMap()): TqResponse = request("PUT", url, json, headers)
open fun post(url: String, json: Any?, headers: Map<String, Any> = emptyMap()): TqResponse = request("POST", url, json, headers)

open fun <T : Any?> delete(url: String, headers: Map<String, Any> = emptyMap(), action: TqResponse.() -> T): T? {
val response = delete(url, headers)
return action.invoke(response)
return action(response)
}

open fun <T : Any?> get(url: String, headers: Map<String, Any> = emptyMap(), action: TqResponse.() -> T): T? {
val response = get(url, headers)
return action.invoke(response)
return action(response)
}

open fun <T : Any?> put(url: String, json: Any?, headers: Map<String, Any> = emptyMap(), action: TqResponse.() -> T): T? {
val response = put(url, json, headers)
return action.invoke(response)
return action(response)
}

open fun <T : Any?> post(url: String, json: Any?, headers: Map<String, Any> = emptyMap(), action: TqResponse.() -> T): T? {
val response = post(url, json, headers)
return action.invoke(response)
return action(response)
}

open fun transform(json: Any?) = when (json) {
Expand All @@ -50,6 +58,9 @@ open class TqRestClient(val logHandler: RestLogHandler = NoOpRestLogHandler,
var response: TqResponse? = null
val duration = measureTimeMillis {
val conn = (URL(url).openConnection() as HttpURLConnection).apply {
if (allowSelfSigned && this is HttpsURLConnection) {
sslSocketFactory = ctx.socketFactory
}
requestMethod = method
setRequestProperty("Content-Type", "application/json")
headers.forEach {
Expand All @@ -62,17 +73,23 @@ open class TqRestClient(val logHandler: RestLogHandler = NoOpRestLogHandler,
}
}

try {
response = try {
val responseCode = conn.responseCode
val stream = conn.errorStream ?: conn.inputStream
response = TqResponse(responseCode, stream.bufferedReader().use { it.readText() }, conn.headerFields, mapper)
TqResponse(responseCode, stream.bufferedReader().use { it.readText() }, conn.headerFields, mapper)
} catch (e: IOException) {
response = TqResponse(-1, e.message ?: "", conn.headerFields, mapper)
TqResponse(-1, e.message ?: "", conn.headerFields, mapper)
}
}
logHandler.onRestLog(RestLog(method, url, duration = duration, request = payload, status = response!!.status, response = response!!.body))
return response ?: TqResponse(-1, "", emptyMap(), mapper)
}

private object SelfSignedTrustManager : X509TrustManager {
override fun checkServerTrusted(p0: Array<out X509Certificate>?, p1: String?) = Unit
override fun getAcceptedIssuers(): Array<out X509Certificate> = emptyArray()
override fun checkClientTrusted(p0: Array<out X509Certificate>?, p1: String?) = Unit
}
}

data class TqResponse(val status: Int, val body: String, private val headers: Map<String, Any>, private val mapper: ObjectMapper) {
Expand All @@ -87,7 +104,7 @@ data class TqResponse(val status: Int, val body: String, private val headers: Ma
fun headers(): Map<String, Any> {
return headers.mapValues {
if (it is Array<*> && it.size == 1) {
return@mapValues it.get(0)!!
return@mapValues it[0]!!
}
it
}
Expand All @@ -108,3 +125,4 @@ private object NoOpRestLogHandler : RestLogHandler {
override fun onRestLog(log: RestLog) {
}
}

28 changes: 28 additions & 0 deletions tekniq-sparklin/src/test/kotlin/io/tekniq/rest/TqRestClientTest.kt
@@ -0,0 +1,28 @@
package io.tekniq.rest

import org.junit.Assert.assertEquals
import org.junit.Assert.fail
import org.junit.Test

class TqRestClientTest {
@Test
fun get() {
val client = TqRestClient()
client.get("https://www.google.com") {
assertEquals(200, status)
}
}

@Test
fun failOnInsecureSite() {
val client = TqRestClient()
client.get("https://216.58.210.164/finance") {
assertEquals(-1, status)
}
}

@Test
fun getFailsonInsecureSiteDueToPreviousCallDisablingSecurity() {
failOnInsecureSite()
}
}

0 comments on commit 655de4d

Please sign in to comment.