All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.virgilsecurity.keyknox.client.HttpClient.kt Maven / Gradle / Ivy

Go to download

Virgil is a stack of security libraries (ECIES with Crypto Agility wrapped in Virgil Cryptogram) and all the necessary infrastructure to enable seamless, end-to-end encryption for any application, platform or device. Learn about Virgil Java/Android SDK https://virgilsecurity.com/api-docs/java-android/quickstart

The newest version!
/*
 * Copyright (c) 2015-2020, Virgil Security, Inc.
 *
 * Lead Maintainer: Virgil Security Inc. 
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *     (1) Redistributions of source code must retain the above copyright notice, this
 *     list of conditions and the following disclaimer.
 *
 *     (2) Redistributions in binary form must reproduce the above copyright notice,
 *     this list of conditions and the following disclaimer in the documentation
 *     and/or other materials provided with the distribution.
 *
 *     (3) Neither the name of virgil nor the names of its
 *     contributors may be used to endorse or promote products derived from
 *     this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package com.virgilsecurity.keyknox.client

import com.google.gson.JsonObject
import com.virgilsecurity.keyknox.exception.KeyknoxServiceException
import com.virgilsecurity.keyknox.utils.Loggable
import com.virgilsecurity.keyknox.utils.Serializer
import com.virgilsecurity.sdk.VirgilInfo
import com.virgilsecurity.sdk.common.ErrorResponse
import com.virgilsecurity.sdk.jwt.TokenContext
import com.virgilsecurity.sdk.jwt.contract.AccessTokenProvider
import com.virgilsecurity.sdk.utils.ConvertionUtils
import com.virgilsecurity.sdk.utils.OsUtils
import com.virgilsecurity.sdk.utils.StringUtils
import java.io.BufferedInputStream
import java.io.IOException
import java.net.HttpURLConnection
import java.net.URL
import java.util.logging.Level

class HttpClient : HttpClientProtocol, Loggable {

    private val virgilAgentHeader: String
    private val accessTokenProvider: AccessTokenProvider

    constructor(accessTokenProvider: AccessTokenProvider) : this(
        accessTokenProvider,
        VIRGIL_AGENT_PRODUCT,
        VirgilInfo.VERSION
    )

    constructor(accessTokenProvider: AccessTokenProvider, product: String, version: String) {
        this.accessTokenProvider = accessTokenProvider
        virgilAgentHeader = "$product;$VIRGIL_AGENT_FAMILY;${OsUtils.getOsAgentName()};$version"
    }

    override fun send(
        url: URL,
        method: Method,
        tokenContext: TokenContext,
        body: Any?,
        headers: Map?
    ): Response {
        try {
            logger().fine("${method.name} to $url")
            val accessToken = accessTokenProvider.getToken(tokenContext)
            val urlConnection = createConnection(url, method, accessToken.stringRepresentation())
            headers?.forEach { (key, value) ->
                urlConnection.setRequestProperty(key, value)
            }

            body?.let {
                val bodyStr = if (body is JsonObject) {
                    body.toString()
                } else {
                    Serializer.gson.toJson(body)
                }
                urlConnection.outputStream.write(ConvertionUtils.toBytes(bodyStr))
                urlConnection.outputStream.flush()
            }
            try {
                if (urlConnection.responseCode >= HttpURLConnection.HTTP_BAD_REQUEST) {
                    logger().fine("Error ${urlConnection.responseCode} \t ${method.name} to $url")
                    // Get error code from request
                    BufferedInputStream(urlConnection.errorStream).use { `in` ->
                        val responseBody = ConvertionUtils.toString(`in`)
                        if (!StringUtils.isBlank(responseBody)) {
                            val error = ConvertionUtils.getGson().fromJson(responseBody, ErrorResponse::class.java)
                            throw KeyknoxServiceException(urlConnection.responseCode, error.code, error.message)
                        } else {
                            throw KeyknoxServiceException(urlConnection.responseCode, -1, urlConnection.responseMessage)
                        }
                    }
                } else {
                    BufferedInputStream(urlConnection.inputStream).use { instream ->
                        val responseBody = ConvertionUtils.toString(instream)
                        val headerFields = mutableMapOf()
                        urlConnection.headerFields?.keys?.filterNotNull()?.forEach { key ->
                            val value = urlConnection.headerFields[key]
                            value?.let {
                                headerFields[key.toLowerCase()] = value.first()
                            }
                        }
                        return Response(responseBody, headerFields, accessToken)
                    }
                }
            } finally {
                logger().finest("Disconnecting...")
                urlConnection.disconnect()
            }
        } catch (e: IOException) {
            logger().log(Level.SEVERE, "Connection error", e)
            throw KeyknoxServiceException(-1, message = e.message)
        }
    }

    private fun createConnection(url: URL, method: Method, accessToken: String): HttpURLConnection {
        val urlConnection = url.openConnection() as HttpURLConnection
        urlConnection.requestMethod = method.name
        urlConnection.useCaches = false

        when (method) {
            Method.DELETE, Method.POST, Method.PUT -> urlConnection.doOutput = true
            else -> {
                urlConnection.doOutput = false
            }
        }

        if (!StringUtils.isBlank(accessToken)) {
            urlConnection.setRequestProperty("Authorization", "Virgil $accessToken")
        }
        urlConnection.setRequestProperty("Content-Type", "application/json; charset=utf-8")
        urlConnection.setRequestProperty(VIRGIL_AGENT_HEADER_KEY, virgilAgentHeader)

        return urlConnection
    }

    companion object {
        private const val VIRGIL_AGENT_HEADER_KEY = "virgil-agent"
        private const val VIRGIL_AGENT_PRODUCT = "keyknox"
        private const val VIRGIL_AGENT_FAMILY = "jvm"
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy